import numpy as np import matplotlib.pyplot as plt from typing import Callable, List, Tuple, Optional, Dict, Any, Union import dataclasses from enum import Enum import inspect from visualizer import * # ====================== ТИПЫ ЗАВИСИМОСТЕЙ ====================== class DependencyType(Enum): """Типы зависимостей между параметрами""" INDEPENDENT = "independent" # Независимый параметр DEPENDENT = "dependent" # Зависимый параметр EXPRESSION = "expression" # Выражение, использующее другие параметры # ====================== КЛАССЫ ДЛЯ ПАРАМЕТРОВ ====================== @dataclasses.dataclass class Parameter: """Базовый класс для параметра""" name: str param_type: DependencyType description: str = "" def evaluate(self, context: Dict[str, Any]) -> np.ndarray: """Вычисляет значение параметра в заданном контексте""" raise NotImplementedError @dataclasses.dataclass class IndependentParameter(Parameter): """Независимый параметр (базовый)""" formula: Callable x_range: Tuple[float, float] num_points: int = 1000 color: str = 'blue' line_style: str = '-' def __post_init__(self): self.param_type = DependencyType.INDEPENDENT def evaluate(self, context: Dict[str, Any]) -> np.ndarray: """Вычисляет значение независимого параметра""" x = context.get('x', np.linspace(self.x_range[0], self.x_range[1], self.num_points)) return self.formula(x) @dataclasses.dataclass class DependentParameter(Parameter): """Зависимый параметр (выражается через другие параметры)""" expression: Callable # Функция, которая использует другие параметры dependencies: List[str] # Имена параметров, от которых зависит color: str = 'green' line_style: str = '--' def __post_init__(self): self.param_type = DependencyType.DEPENDENT def evaluate(self, context: Dict[str, Any]) -> np.ndarray: """Вычисляет значение зависимого параметра""" # Собираем значения зависимостей из контекста dep_values = {} for dep_name in self.dependencies: if dep_name not in context: raise ValueError(f"Зависимость '{dep_name}' не найдена в контексте") dep_values[dep_name] = context[dep_name] # Вычисляем выражение return self.expression(**dep_values) # ====================== КЛАСС ДЛЯ ГАРМОНИК ====================== class HarmonicOscillation: """Класс для описания гармонического колебания""" def __init__(self, amplitude: Union[float, str], frequency: Union[float, str], phase: Union[float, str] = 0, amplitude_depends_on: Optional[List[str]] = None, frequency_depends_on: Optional[List[str]] = None, phase_depends_on: Optional[List[str]] = None): """ Параметры гармоники могут быть как числами, так и именами параметров модели """ self.amplitude = amplitude self.frequency = frequency self.phase = phase # Отслеживаем зависимости self.amplitude_depends_on = amplitude_depends_on or [] self.frequency_depends_on = frequency_depends_on or [] self.phase_depends_on = phase_depends_on or [] def get_dependencies(self) -> List[str]: """Возвращает все зависимости гармоники""" return (self.amplitude_depends_on + self.frequency_depends_on + self.phase_depends_on) def evaluate(self, t: np.ndarray, context: Dict[str, Any]) -> np.ndarray: """Вычисляет значение гармонического колебания с учетом зависимостей""" # Получаем значения амплитуды, частоты и фазы amp = self._get_value(self.amplitude, context) freq = self._get_value(self.frequency, context) phase = self._get_value(self.phase, context) return amp * np.sin(2 * np.pi * freq * t + phase) def _get_value(self, param: Any, context: Dict[str, Any]) -> float: """Получает значение параметра (число или из контекста)""" if isinstance(param, (int, float)): return param elif isinstance(param, str) and param in context: # Если параметр - строка, берем соответствующее значение из контекста val = context[param] # Если это массив, берем среднее или первое значение if isinstance(val, np.ndarray): return np.mean(val) # или val[0] в зависимости от логики return float(val) else: raise ValueError(f"Не удалось получить значение для {param}") def __repr__(self): return f"Harmonic(A={self.amplitude}, f={self.frequency}, φ={self.phase})" # ====================== ОСНОВНОЙ КЛАСС МОДЕЛИ ====================== class MathematicalModel: """ Модель с поддержкой зависимых параметров """ def __init__(self): self.parameters: Dict[str, Parameter] = {} self.harmonics: List[HarmonicOscillation] = [] self.main_formula: Optional[Callable] = None self.evaluation_context: Dict[str, Any] = {} def add_independent_parameter(self, name: str, formula: Callable, x_range: Tuple[float, float], num_points: int = 1000, color: str = 'blue', line_style: str = '-', description: str = "") -> None: """Добавляет независимый параметр""" self.parameters[name] = IndependentParameter( name=name, formula=formula, x_range=x_range, num_points=num_points, color=color, line_style=line_style, description=description ) def add_dependent_parameter(self, name: str, expression: Callable, dependencies: List[str], color: str = 'green', line_style: str = '--', description: str = "") -> None: """Добавляет зависимый параметр""" # Проверяем, что все зависимости существуют for dep in dependencies: if dep not in self.parameters: raise ValueError(f"Зависимость '{dep}' не найдена среди параметров") self.parameters[name] = DependentParameter( name=name, expression=expression, dependencies=dependencies, color=color, line_style=line_style, description=description ) def add_harmonic(self, amplitude: Union[float, str], frequency: Union[float, str], phase: Union[float, str] = 0, amplitude_depends_on: Optional[List[str]] = None, frequency_depends_on: Optional[List[str]] = None, phase_depends_on: Optional[List[str]] = None) -> None: """Добавляет гармоническое колебание с возможными зависимостями""" harmonic = HarmonicOscillation( amplitude=amplitude, frequency=frequency, phase=phase, amplitude_depends_on=amplitude_depends_on, frequency_depends_on=frequency_depends_on, phase_depends_on=phase_depends_on ) # Проверяем, что все зависимости существуют for dep in harmonic.get_dependencies(): if dep not in self.parameters: raise ValueError(f"Зависимость '{dep}' для гармоники не найдена среди параметров") self.harmonics.append(harmonic) def set_main_formula(self, formula: Callable) -> None: """Устанавливает основную формулу""" self.main_formula = formula def get_parameter_dependencies(self, param_name: str) -> List[str]: """Возвращает список зависимостей для параметра""" param = self.parameters.get(param_name) if isinstance(param, DependentParameter): return param.dependencies return [] def get_all_dependencies(self) -> Dict[str, List[str]]: """Возвращает словарь всех зависимостей""" dependencies = {} for name, param in self.parameters.items(): if isinstance(param, DependentParameter): dependencies[name] = param.dependencies return dependencies def evaluate_parameters(self, x: np.ndarray) -> Dict[str, np.ndarray]: """ Вычисляет все параметры с учетом зависимостей. Использует топологическую сортировку для правильного порядка вычисления. """ results = {'x': x} # Функция для топологической сортировки def topological_sort(): visited = set() order = [] def dfs(param_name): if param_name in visited: return visited.add(param_name) param = self.parameters[param_name] if isinstance(param, DependentParameter): for dep in param.dependencies: if dep in self.parameters: dfs(dep) order.append(param_name) for name in self.parameters: if name not in visited: dfs(name) return order # Вычисляем параметры в правильном порядке eval_order = topological_sort() for param_name in eval_order: param = self.parameters[param_name] results[param_name] = param.evaluate(results) return results def sum_harmonics(self, t: np.ndarray, context: Dict[str, Any]) -> np.ndarray: """Вычисляет сумму всех гармонических колебаний с учетом зависимостей""" if not self.harmonics: return np.zeros_like(t) result = np.zeros_like(t) for h in self.harmonics: result += h.evaluate(t, context) return result def evaluate_main(self, x: np.ndarray, **kwargs) -> np.ndarray: """ Вычисляет основную формулу с заданным x и дополнительными параметрами """ if self.main_formula is None: raise ValueError("Основная формула не установлена") # Вычисляем все параметры context = self.evaluate_parameters(x) context.update(kwargs) # Добавляем сумму гармоник в контекст context['harmonic_sum'] = lambda t: self.sum_harmonics(t, context) # Получаем аргументы функции sig = inspect.signature(self.main_formula) # Подготавливаем аргументы для вызова call_args = {} for param_name in sig.parameters: if param_name in context: call_args[param_name] = context[param_name] elif param_name == 'x': call_args['x'] = x return self.main_formula(**call_args) def main(): """Пример использования с зависимыми параметрами""" # Создаем модель model = MathematicalModel() # Добавляем независимые параметры (базовые) model.add_independent_parameter( name="time", formula=lambda x: x, # Просто время x_range=(0, 10), color='gray', description="Базовый параметр времени" ) model.add_independent_parameter( name="A0", formula=lambda x: 2.0 * np.ones_like(x), # Константа x_range=(0, 10), color='blue', description="Базовая амплитуда" ) model.add_independent_parameter( name="f0", formula=lambda x: 1.0 * np.ones_like(x), # Константа x_range=(0, 10), color='red', description="Базовая частота" ) # Добавляем зависимые параметры (выражаются через другие) model.add_dependent_parameter( name="A_effective", expression=lambda A0, time: A0 * np.exp(-0.1 * time), # Затухающая амплитуда dependencies=["A0", "time"], color='green', description="Эффективная амплитуда (зависит от времени)" ) model.add_dependent_parameter( name="f_effective", expression=lambda f0, time: f0 * (1 + 0.05 * np.sin(time)), # Модулированная частота dependencies=["f0", "time"], color='orange', description="Эффективная частота (модулирована)" ) model.add_dependent_parameter( name="modulation_index", expression=lambda A_effective, f_effective: A_effective * f_effective / 2, dependencies=["A_effective", "f_effective"], color='purple', description="Индекс модуляции (произведение)" ) # Добавляем гармоники, которые могут зависеть от параметров model.add_harmonic( amplitude="A_effective", # Использует параметр A_effective frequency="f_effective", # Использует параметр f_effective phase=0, amplitude_depends_on=["A_effective"], frequency_depends_on=["f_effective"] ) model.add_harmonic( amplitude=0.5, frequency=2.0, phase=np.pi/4 ) model.add_harmonic( amplitude="modulation_index", frequency=3.0, phase=0, amplitude_depends_on=["modulation_index"] ) # Устанавливаем основную формулу def main_formula(x, A_effective, f_effective, modulation_index, harmonic_sum): """ Основная формула: комбинация параметров и гармоник """ # Параметрическая часть parametric_part = A_effective * np.sin(2 * np.pi * f_effective * x) # Модуляционная часть modulation_part = modulation_index * np.cos(2 * np.pi * x) # Гармоническая часть harmonic_part = harmonic_sum(x) return parametric_part + modulation_part + harmonic_part model.set_main_formula(main_formula) # Создаем визуализатор viz = ModelVisualizer(model) # Строим граф зависимостей print("Визуализация графа зависимостей...") viz.plot_dependency_graph() # Строим графики параметров с зависимостями print("\nГрафики параметров с зависимостями...") viz.plot_parameter("A_effective", show_dependencies=True) viz.plot_parameter("f_effective", show_dependencies=True) viz.plot_parameter_with_dependencies("modulation_index") # Строим графики всех параметров print("\nВсе параметры...") fig, axes = plt.subplots(2, 3, figsize=(15, 10)) axes = axes.flatten() x_test = np.linspace(0, 10, 1000) context = model.evaluate_parameters(x_test) for i, (name, param) in enumerate(model.parameters.items()): if i < len(axes): axes[i].plot(x_test, context[name], color=param.color, linestyle=param.line_style, linewidth=2) axes[i].grid(True, alpha=0.3) axes[i].set_title(f"{name}\n{param.description}") axes[i].set_xlabel("x") # Скрываем лишний подграфик for j in range(len(model.parameters), len(axes)): axes[j].set_visible(False) plt.tight_layout() # Строим графики гармоник с зависимостями print("\nГармонические колебания...") viz.plot_harmonics(t_range=(0, 5), x_for_dependencies=x_test) # Строим график основной формулы print("\nОсновная формула...") fig, ax = plt.subplots(figsize=(12, 6)) y_main = model.evaluate_main(x_test) ax.plot(x_test, y_main, 'b-', linewidth=2) ax.grid(True, alpha=0.3) ax.set_title("Результат работы основной формулы", fontsize=14) ax.set_xlabel("x", fontsize=12) ax.set_ylabel("F(x)", fontsize=12) # Показываем все графики plt.show() # Выводим информацию о зависимостях print("\nСтруктура зависимостей:") deps = model.get_all_dependencies() for param, dependencies in deps.items(): print(f" {param} зависит от: {', '.join(dependencies)}") if __name__ == "__main__": main()