Source code for GUIBRUSHR.GUI.Input_Output_Panels.Input_Panels.TabPanels.FrameGenerationHRData.MathematicalFunctions

import numpy as np
import matplotlib.pyplot as plt

[docs] class MathematicalFunctions: """ A class containing 24 mathematical functions with various characteristics including continuous, discontinuous, descending, and constant segment behaviors. Domain: [-100, 100], Codomain: discrete values from [-1.0, 0.05, 0.1, 0.25, 0.5, 1.0, 1.5, 2.5, 3.5, 5.0, 7.5, 10.0, 20.0, 30.0] """
[docs] def __init__(self): """Initialize the class with all 24 mathematical functions""" # Define the allowed discrete codomain values self.allowed_values = np.array([-1.0, 0.05, 0.1, 0.25, 0.5, 1.0, 1.5, 2.5, 3.5, 5.0, 7.5, 10.0, 20.0, 30.0]) self.functions = { 'linear_ascending': self._linear_ascending, 'linear_descending': self._linear_descending, 'parabolic_upward': self._parabolic_upward, 'parabolic_downward': self._parabolic_downward, 'cubic_ascending': self._cubic_ascending, 'cubic_descending': self._cubic_descending, 'exponential_growth': self._exponential_growth, 'exponential_decay': self._exponential_decay, 'logarithmic_growth': self._logarithmic_growth, 'logarithmic_decay': self._logarithmic_decay, 'sine_wave': self._sine_wave, 'cosine_wave': self._cosine_wave, 'step_function_ascending': self._step_function_ascending, 'step_function_descending': self._step_function_descending, 'piecewise_mixed': self._piecewise_mixed, 'sigmoidal_curve': self._sigmoidal_curve, 'diurnal_cycle': self._diurnal_cycle, 'weather_front': self._weather_front, 'seasonal_trend': self._seasonal_trend, 'humidity_buildup': self._humidity_buildup, 'pressure_gradient': self._pressure_gradient, 'humidity_buildup_descending': self._humidity_buildup_descending, 'pressure_gradient_descending': self._pressure_gradient_descending, 'specular_seasonal_trending': self._specular_seasonal_trending } self.function_names = list(self.functions.keys())
def _map_to_allowed_values(self, y_values): """ Map continuous y values to the nearest allowed discrete values. Parameters: ----------- y_values : array-like Continuous y values to be mapped Returns: -------- numpy.ndarray : Array of values mapped to allowed discrete values """ y_values = np.asarray(y_values) mapped_values = np.zeros_like(y_values) for i, y in enumerate(y_values): # Find the closest allowed value distances = np.abs(self.allowed_values - y) closest_index = np.argmin(distances) mapped_values[i] = self.allowed_values[closest_index] return mapped_values def _linear_ascending(self, x): """Linear function - straight line ascending from allowed minimum to maximum""" continuous_y = 30 * (x + 100) / 200 return self._map_to_allowed_values(continuous_y) def _linear_descending(self, x): """Linear function - straight line descending from allowed maximum to minimum""" continuous_y = 30 - 30 * (x + 100) / 200 return self._map_to_allowed_values(continuous_y) def _parabolic_upward(self, x): """Quadratic function - parabola opening upward mapped to allowed values""" continuous_y = 30 * (x / 100) ** 2 return self._map_to_allowed_values(continuous_y) def _parabolic_downward(self, x): """Quadratic function - inverted parabola opening downward mapped to allowed values""" continuous_y = 30 * (1 - (x / 100) ** 2) return self._map_to_allowed_values(continuous_y) def _cubic_ascending(self, x): """Cubic function - S-shaped curve ascending mapped to allowed values""" continuous_y = 15 + 15 * (x / 100) ** 3 return self._map_to_allowed_values(continuous_y) def _cubic_descending(self, x): """Cubic function - S-shaped curve descending mapped to allowed values""" continuous_y = 15 - 15 * (x / 100) ** 3 return self._map_to_allowed_values(continuous_y) def _exponential_growth(self, x): """Exponential function - exponential growth mapped to allowed values""" continuous_y = 30 * (1 - np.exp(-(x + 100) / 50)) return self._map_to_allowed_values(continuous_y) def _exponential_decay(self, x): """Exponential function - exponential decay mapped to allowed values""" continuous_y = 30 * np.exp(-(x + 100) / 50) return self._map_to_allowed_values(continuous_y) def _logarithmic_growth(self, x): """Logarithmic function - logarithmic growth mapped to allowed values""" continuous_y = 30 * np.log(x + 101) / np.log(201) return self._map_to_allowed_values(continuous_y) def _logarithmic_decay(self, x): """Logarithmic function - logarithmic decay mapped to allowed values""" continuous_y = 30 * (1 - np.log(x + 101) / np.log(201)) return self._map_to_allowed_values(continuous_y) def _sine_wave(self, x): """Trigonometric function - sine wave oscillating between allowed values""" continuous_y = 15 + 15 * np.sin(4 * np.pi * x / 200) return self._map_to_allowed_values(continuous_y) def _cosine_wave(self, x): """Trigonometric function - cosine wave oscillating between allowed values""" continuous_y = 15 + 15 * np.cos(4 * np.pi * x / 200) return self._map_to_allowed_values(continuous_y) def _step_function_ascending(self, x): """Step function - ascending discrete levels using only allowed values""" # Directly use allowed values for clean steps return np.where(x < -60, 0.05, np.where(x < -20, 7.5, np.where(x < 20, 10.0, np.where(x < 60, 20.0, 30.0)))) def _step_function_descending(self, x): """Step function - descending discrete levels using only allowed values""" # Directly use allowed values for clean steps return np.where(x < -60, 30.0, np.where(x < -20, 20.0, np.where(x < 20, 10.0, np.where(x < 60, 7.5, 0.05)))) def _piecewise_mixed(self, x): """Piecewise function - mix of ascending, constant, and descending segments using allowed values""" continuous_y = np.where(x < -60, 0.15 * (x + 100), # ascending from 0 np.where(x < -20, 6, # constant np.where(x < 20, 18, # constant higher np.where(x < 60, 30 - 0.3 * (x - 20), 18)))) # descending return self._map_to_allowed_values(continuous_y) def _sigmoidal_curve(self, x): """Sigmoidal curve - S-shaped transition mapped to allowed values""" continuous_y = 30 / (1 + np.exp(-x / 25)) return self._map_to_allowed_values(continuous_y) def _diurnal_cycle(self, x): """Diurnal cycle - realistic daily water vapor variation mapped to allowed values""" daily_cycle = np.sin(2 * np.pi * (x + 100) / 100 + np.pi / 4) continuous_y = 12 + 8 * daily_cycle + 3 * np.sin(4 * np.pi * (x + 100) / 100) return self._map_to_allowed_values(continuous_y) def _weather_front(self, x): """Weather front passage - gradual increase then gradual decrease mapped to allowed values""" front_center = 0 width = 60 distance_from_center = np.abs(x - front_center) front_effect = 25 * np.exp(-(distance_from_center / width) ** 2) continuous_y = 5 + front_effect return self._map_to_allowed_values(continuous_y) def _seasonal_trend(self, x): """Seasonal trend - long-term gradual changes mapped to allowed values""" seasonal_base = 15 + 10 * np.sin(np.pi * (x + 100) / 200) variability = 3 * np.sin(3 * np.pi * (x + 100) / 200) continuous_y = seasonal_base + variability return self._map_to_allowed_values(continuous_y) def _humidity_buildup(self, x): """Humidity buildup - gradual accumulation with occasional releases mapped to allowed values""" buildup = 8 + 0.15 * (x + 100) release_cycles = 5 * np.sin(8 * np.pi * (x + 100) / 200) * np.exp(-((x + 50) / 80) ** 2) continuous_y = buildup - release_cycles continuous_y = np.clip(continuous_y, 0, 30) return self._map_to_allowed_values(continuous_y) def _pressure_gradient(self, x): """Pressure gradient effect - smooth transitions between pressure zones mapped to allowed values""" zone1 = 8 + 3 * np.tanh((x + 60) / 20) zone2 = 18 + 5 * np.tanh((x - 20) / 25) weight = 0.5 + 0.5 * np.tanh(x / 40) continuous_y = zone1 * (1 - weight) + zone2 * weight return self._map_to_allowed_values(continuous_y) def _humidity_buildup_descending(self, x): """Humidity buildup descending - gradual decrease with occasional buildups mapped to allowed values""" # Start high and decrease buildup = 30 - 8 - 0.15 * (x + 100) # Add occasional buildup cycles (opposite of releases) buildup_cycles = 5 * np.sin(8 * np.pi * (x + 100) / 200) * np.exp(-((x + 50) / 80) ** 2) continuous_y = buildup + buildup_cycles continuous_y = np.clip(continuous_y, 0, 30) return self._map_to_allowed_values(continuous_y) def _pressure_gradient_descending(self, x): """Pressure gradient descending - smooth transitions from high to low pressure zones mapped to allowed values""" # Inverted pressure gradient - start high, end low zone1 = 22 - 3 * np.tanh((x + 60) / 20) # High pressure zone zone2 = 12 - 5 * np.tanh((x - 20) / 25) # Lower pressure zone weight = 0.5 + 0.5 * np.tanh(x / 40) continuous_y = zone1 * (1 - weight) + zone2 * weight return self._map_to_allowed_values(continuous_y) def _specular_seasonal_trending(self, x): """Specular seasonal trending - mirror-like seasonal patterns with reflective symmetry mapped to allowed values""" # Create a complex pattern with multiple harmonics and reflective properties base_trend = 15 + 8 * np.cos(2 * np.pi * (x + 100) / 200) # Main seasonal cycle # Add specular (mirror-like) components specular_component = 5 * np.sin(np.pi * (x + 100) / 100) * np.cos(3 * np.pi * (x + 100) / 200) # Add reflection pattern around center reflection_pattern = 3 * np.exp(-((x - 0) / 40) ** 2) * np.sin(6 * np.pi * x / 200) # Combine all components continuous_y = base_trend + specular_component + reflection_pattern continuous_y = np.clip(continuous_y, 0, 30) return self._map_to_allowed_values(continuous_y)
[docs] def get(self, function_name, x_start, x_end, length): """ Get y values from a specific function over a range of x values. Parameters: ----------- function_name : str Name of the function to use (from self.function_names) x_start : float Starting x value x_end: Ending x value length : int Number of y values to return (determines x range) Returns: -------- dict : Dictionary containing: - 'x_values': array of x values from x_start to x_start + length - 1 - 'y_values': corresponding y values from the chosen function - 'function_used': name of the function used Raises: ------- ValueError: If function_name is not valid or x values exceed domain bounds """ # Validate function name if function_name not in self.functions: available_functions = ", ".join(self.function_names) raise ValueError(f"Function '{function_name}' not found. Available functions: {available_functions}") # Create x values array x_values = np.linspace(x_start, x_end, length, dtype=float) # Check domain bounds if np.any(x_values < -100) or np.any(x_values > 100): raise ValueError(f"X values must be within domain [-100, 100]. " f"Requested range: [{x_start}, {x_start + length - 1}]") # Get the function and calculate y values selected_function = self.functions[function_name] y_values = selected_function(x_values) return { 'x_values': x_values, 'y_values': y_values, 'function_used': function_name }
[docs] def list_functions(self): """Return a list of all available function names""" return self.function_names.copy()