From 40cbdf36c7d9ebce4585a946b39367407efc6085 Mon Sep 17 00:00:00 2001 From: kit8nino Date: Wed, 18 Feb 2026 12:25:14 +0300 Subject: [PATCH] expression 6 ub begin --- main.py | 882 ++++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 633 insertions(+), 249 deletions(-) diff --git a/main.py b/main.py index 3df2ccd..4e62f94 100644 --- a/main.py +++ b/main.py @@ -1,304 +1,688 @@ import numpy as np import matplotlib.pyplot as plt -from typing import Dict, List, Callable, Any, Optional -import networkx as nx -from functools import lru_cache -import sympy as sp -from collections import defaultdict +from typing import Callable, List, Tuple, Optional, Dict, Any, Union +import dataclasses +from enum import Enum +import inspect +# ====================== ТИПЫ ЗАВИСИМОСТЕЙ ====================== + +class DependencyType(Enum): + """Типы зависимостей между параметрами""" + INDEPENDENT = "independent" # Независимый параметр + DEPENDENT = "dependent" # Зависимый параметр + EXPRESSION = "expression" # Выражение, использующее другие параметры + +# ====================== КЛАССЫ ДЛЯ ПАРАМЕТРОВ ====================== + +@dataclasses.dataclass class Parameter: - """Класс для представления параметра с его формулой и зависимостями""" + """Базовый класс для параметра""" + name: str + param_type: DependencyType + description: str = "" - def __init__(self, name: str, formula: Callable, dependencies: List[str] = None, - description: str = "", units: str = ""): - self.name = name - self.formula = formula # Функция для вычисления параметра - self.dependencies = dependencies or [] # Список зависимых параметров - self.description = description - self.units = units - self.cache = {} # Кэш для мемоизации + 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] - def calculate(self, x_values: np.ndarray, param_values: Dict[str, np.ndarray]) -> np.ndarray: - """Вычисление значений параметра для заданных x""" - # Проверка кэша - cache_key = (tuple(x_values), tuple(sorted(param_values.items()))) - if cache_key in self.cache: - return self.cache[cache_key] + # Вычисляем выражение + 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 - # Вычисление - if self.dependencies: - # Подготовка аргументов для формулы - args = [x_values] + [param_values[dep] for dep in self.dependencies] - result = self.formula(*args) + # Отслеживаем зависимости + 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: - result = self.formula(x_values) - - # Сохранение в кэш - self.cache[cache_key] = result - return result + raise ValueError(f"Не удалось получить значение для {param}") - def clear_cache(self): - """Очистка кэша параметра""" - self.cache.clear() + def __repr__(self): + return f"Harmonic(A={self.amplitude}, f={self.frequency}, φ={self.phase})" +# ====================== ОСНОВНОЙ КЛАСС МОДЕЛИ ====================== -class FormulaModel: - """Основной класс для управления моделью с параметрами""" +class MathematicalModel: + """ + Модель с поддержкой зависимых параметров + """ def __init__(self): self.parameters: Dict[str, Parameter] = {} + self.harmonics: List[HarmonicOscillation] = [] self.main_formula: Optional[Callable] = None - self.dependency_graph = nx.DiGraph() - - def add_parameter(self, parameter: Parameter): - """Добавление параметра в модель""" - self.parameters[parameter.name] = parameter - - # Обновление графа зависимостей - self.dependency_graph.add_node(parameter.name) - for dep in parameter.dependencies: - self.dependency_graph.add_edge(dep, parameter.name) - - # Проверка на циклические зависимости - if not nx.is_directed_acyclic_graph(self.dependency_graph): - raise ValueError(f"Циклическая зависимость обнаружена при добавлении параметра {parameter.name}") + self.evaluation_context: Dict[str, Any] = {} - def set_main_formula(self, formula: Callable, dependencies: List[str]): - """Установка основной формулы модели""" + 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 - self.main_dependencies = dependencies - def get_calculation_order(self) -> List[str]: - """Получение порядка вычисления параметров с учетом зависимостей""" - try: - return list(nx.topological_sort(self.dependency_graph)) - except nx.NetworkXError: - raise ValueError("Невозможно определить порядок вычисления из-за циклических зависимостей") + 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 calculate_all_parameters(self, x_values: np.ndarray) -> Dict[str, np.ndarray]: - """Вычисление всех параметров в правильном порядке""" - param_values = {} - calculation_order = self.get_calculation_order() + 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} - for param_name in calculation_order: - if param_name in self.parameters: + # Функция для топологической сортировки + 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] - param_values[param_name] = param.calculate(x_values, param_values) + 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 - return param_values + # Вычисляем параметры в правильном порядке + eval_order = topological_sort() + + for param_name in eval_order: + param = self.parameters[param_name] + results[param_name] = param.evaluate(results) + + return results - def calculate_main_formula(self, x_values: np.ndarray) -> np.ndarray: - """Вычисление основной формулы""" + 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("Основная формула не установлена") - param_values = self.calculate_all_parameters(x_values) + # Вычисляем все параметры + context = self.evaluate_parameters(x) + context.update(kwargs) - # Подготовка аргументов для основной формулы - args = [x_values] + [param_values[dep] for dep in self.main_dependencies] - return self.main_formula(*args) - - def clear_all_cache(self): - """Очистка всех кэшей""" - for param in self.parameters.values(): - param.clear_cache() + # Добавляем сумму гармоник в контекст + 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) +# ====================== УЛУЧШЕННЫЙ ВИЗУАЛИЗАТОР ====================== -class Plotter: - """Класс для построения графиков""" +class ModelVisualizer: + """Класс для визуализации модели с зависимостями""" - def __init__(self, model: FormulaModel): + def __init__(self, model: MathematicalModel): self.model = model - - def plot_parameter(self, param_name: str, x_range: tuple = (-10, 10), - num_points: int = 1000, title: str = None): - """График отдельного параметра""" + self.figures = {} + + def plot_parameter(self, param_name: str, + x_range: Optional[Tuple[float, float]] = None, + figsize: Tuple[int, int] = (10, 6), + title: Optional[str] = None, + show_dependencies: bool = True) -> plt.Figure: + """ + Строит график для параметра с учетом его зависимостей + """ if param_name not in self.model.parameters: - raise ValueError(f"Параметр {param_name} не найден") - - x_values = np.linspace(x_range[0], x_range[1], num_points) - param_values = self.model.calculate_all_parameters(x_values) - y_values = param_values[param_name] - - plt.figure(figsize=(10, 6)) - plt.plot(x_values, y_values, linewidth=2) - plt.grid(True, alpha=0.3) + raise ValueError(f"Параметр '{param_name}' не найден") param = self.model.parameters[param_name] - plt.title(title or f"Параметр {param_name}: {param.description}") - plt.xlabel("x") - plt.ylabel(f"{param_name} ({param.units})" if param.units else param_name) - plt.show() - - def plot_all_parameters(self, x_range: tuple = (-10, 10), num_points: int = 1000): - """График всех параметров на одном рисунке""" - x_values = np.linspace(x_range[0], x_range[1], num_points) - param_values = self.model.calculate_all_parameters(x_values) + # Определяем диапазон x + if x_range is None: + if isinstance(param, IndependentParameter): + x_range = param.x_range + else: + # Для зависимых параметров нужно определить разумный диапазон + x_range = (0, 10) # По умолчанию - plt.figure(figsize=(14, 8)) + x = np.linspace(x_range[0], x_range[1], 1000) - for param_name, y_values in param_values.items(): - plt.plot(x_values, y_values, label=param_name, linewidth=2) + # Вычисляем значение параметра + context = self.model.evaluate_parameters(x) + y = context[param_name] - plt.grid(True, alpha=0.3) - plt.title("Все параметры модели") - plt.xlabel("x") - plt.ylabel("Значения параметров") - plt.legend() - plt.show() - - def plot_main_formula(self, x_range: tuple = (-10, 10), num_points: int = 1000, - title: str = "Основная формула"): - """График основной формулы""" - x_values = np.linspace(x_range[0], x_range[1], num_points) - y_values = self.model.calculate_main_formula(x_values) + # Создаем фигуру + fig, ax = plt.subplots(figsize=figsize) - plt.figure(figsize=(10, 6)) - plt.plot(x_values, y_values, 'r-', linewidth=2, label='Основная формула') - plt.grid(True, alpha=0.3) - plt.title(title) - plt.xlabel("x") - plt.ylabel("f(x)") - plt.legend() - plt.show() - - def plot_dependency_graph(self): - """Визуализация графа зависимостей""" - plt.figure(figsize=(12, 8)) - pos = nx.spring_layout(self.model.dependency_graph, k=2, iterations=50) + # Строим основной график + ax.plot(x, y, color=param.color, linestyle=param.line_style, + linewidth=2, label=param_name) - nx.draw(self.model.dependency_graph, pos, - with_labels=True, node_color='lightblue', - node_size=2000, font_size=10, font_weight='bold', - arrows=True, arrowsize=20, edge_color='gray') + # Если нужно показать зависимости + if show_dependencies and isinstance(param, DependentParameter): + for dep_name in param.dependencies: + if dep_name in context: + dep_y = context[dep_name] + # Нормализуем для отображения на том же графике + dep_y_normalized = (dep_y - np.min(dep_y)) / (np.max(dep_y) - np.min(dep_y) + 1e-10) + ax.plot(x, dep_y_normalized, '--', alpha=0.5, + label=f"{dep_name} (норм.)") - plt.title("Граф зависимостей параметров") - plt.show() - - -# Пример использования и заготовки функций - -def create_example_model(): - """Создание примера модели для демонстрации""" - model = FormulaModel() + ax.grid(True, alpha=0.3) + ax.set_title(title or f"График параметра: {param_name}\n{param.description}", fontsize=14) + ax.set_xlabel("x", fontsize=12) + ax.set_ylabel("Значение", fontsize=12) + ax.legend(loc='best') + + self.figures[f"param_{param_name}"] = fig + return fig - # Независимые параметры - param_a = Parameter( - name="A", - formula=lambda x: np.sin(x) + 2, - description="Синусоидальная функция", - units="м/с" - ) + def plot_parameter_with_dependencies(self, param_name: str, + figsize: Tuple[int, int] = (14, 10)) -> plt.Figure: + """ + Строит подробный график параметра и всех его зависимостей + """ + if param_name not in self.model.parameters: + raise ValueError(f"Параметр '{param_name}' не найден") + + param = self.model.parameters[param_name] + + # Собираем все зависимости (рекурсивно) + def get_all_deps(name, deps_set): + deps = self.model.get_parameter_dependencies(name) + for dep in deps: + if dep not in deps_set: + deps_set.add(dep) + get_all_deps(dep, deps_set) + return deps_set + + all_deps = list(get_all_deps(param_name, set())) + + # Определяем диапазон x + x_range = (0, 10) + if isinstance(param, IndependentParameter): + x_range = param.x_range + elif all_deps: + # Пытаемся найти независимый параметр среди зависимостей + for dep in all_deps: + p = self.model.parameters[dep] + if isinstance(p, IndependentParameter): + x_range = p.x_range + break + + x = np.linspace(x_range[0], x_range[1], 1000) + context = self.model.evaluate_parameters(x) + + # Создаем подграфики + n_plots = len(all_deps) + 1 + cols = min(3, n_plots) + rows = (n_plots + cols - 1) // cols + + fig, axes = plt.subplots(rows, cols, figsize=figsize) + axes = axes.flatten() if n_plots > 1 else [axes] + + # График целевого параметра + axes[0].plot(x, context[param_name], color=param.color, + linestyle=param.line_style, linewidth=2.5) + axes[0].grid(True, alpha=0.3) + axes[0].set_title(f"{param_name} (целевой)", fontsize=12) + axes[0].set_xlabel("x") + + # Графики зависимостей + for i, dep_name in enumerate(all_deps, 1): + if i < len(axes): + dep_param = self.model.parameters[dep_name] + axes[i].plot(x, context[dep_name], color=dep_param.color, + linestyle=dep_param.line_style, linewidth=2) + axes[i].grid(True, alpha=0.3) + axes[i].set_title(f"{dep_name}\n{getattr(dep_param, 'description', '')}", fontsize=10) + axes[i].set_xlabel("x") + + # Скрываем лишние подграфики + for j in range(len(all_deps) + 1, len(axes)): + axes[j].set_visible(False) + + plt.tight_layout() + self.figures[f"param_{param_name}_with_deps"] = fig + return fig - # Зависимый параметр (зависит от A) - param_b = Parameter( - name="B", - formula=lambda x, a: np.cos(x) * a, - dependencies=["A"], - description="Функция, зависящая от A", - units="м/с²" - ) + def plot_harmonics(self, t_range: Tuple[float, float] = (0, 10), + figsize: Tuple[int, int] = (12, 8), + x_for_dependencies: Optional[np.ndarray] = None) -> plt.Figure: + """ + Строит графики гармоник с учетом возможных зависимостей от параметров + """ + if not self.model.harmonics: + raise ValueError("Нет гармонических колебаний") + + t = np.linspace(t_range[0], t_range[1], 1000) + + # Подготавливаем контекст для зависимых гармоник + if x_for_dependencies is not None: + context = self.model.evaluate_parameters(x_for_dependencies) + else: + # Если не задан x, используем значения по умолчанию + context = {} + for name, param in self.model.parameters.items(): + if isinstance(param, IndependentParameter): + x_default = np.linspace(param.x_range[0], param.x_range[1], 1) + context[name] = param.evaluate({'x': x_default})[0] + + n_plots = len(self.model.harmonics) + 1 + fig, axes = plt.subplots(n_plots, 1, figsize=(figsize[0], figsize[1] * n_plots/3)) + + # Индивидуальные гармоники + for i, h in enumerate(self.model.harmonics): + harmonic_values = h.evaluate(t, context) + axes[i].plot(t, harmonic_values, linewidth=1.5) + axes[i].grid(True, alpha=0.3) + + # Показываем зависимости в заголовке + deps = h.get_dependencies() + deps_str = f" (зависит от: {', '.join(deps)})" if deps else "" + axes[i].set_title(f"Гармоника {i+1}: {h}{deps_str}") + axes[i].set_ylabel("Амплитуда") + + # Сумма гармоник + total = np.zeros_like(t) + for h in self.model.harmonics: + total += h.evaluate(t, context) + + axes[-1].plot(t, total, 'r-', linewidth=2) + axes[-1].grid(True, alpha=0.3) + axes[-1].set_title("Сумма всех гармоник") + axes[-1].set_xlabel("Время t") + axes[-1].set_ylabel("Амплитуда") + + plt.tight_layout() + self.figures["harmonics"] = fig + return fig - # Параметр, зависящий от B - param_c = Parameter( - name="C", - formula=lambda x, b: np.exp(-x/10) * b, - dependencies=["B"], - description="Экспоненциальная функция от B", - units="Н" - ) - - # Параметр, зависящий от A и C - param_d = Parameter( - name="D", - formula=lambda x, a, c: (a**2 + c) / (1 + x**2), - dependencies=["A", "C"], - description="Комбинированная функция", - units="Дж" - ) - - # Независимый параметр - param_e = Parameter( - name="E", - formula=lambda x: np.log(1 + np.abs(x)), - description="Логарифмическая функция", - units="К" - ) - - # Добавление параметров в модель - for param in [param_a, param_b, param_c, param_d, param_e]: - model.add_parameter(param) - - # Основная формула, использующая несколько параметров - model.set_main_formula( - formula=lambda x, a, c, e: a * c + e**2, - dependencies=["A", "C", "E"] - ) - - return model - - -# Утилитарные функции - -def analyze_model_complexity(model: FormulaModel): - """Анализ сложности модели""" - print("=== Анализ модели ===") - print(f"Количество параметров: {len(model.parameters)}") - - # Анализ зависимостей - max_depth = 0 - for param_name in model.parameters: + def plot_dependency_graph(self, figsize: Tuple[int, int] = (12, 8)) -> plt.Figure: + """ + Визуализирует граф зависимостей между параметрами + """ try: - depth = nx.shortest_path_length(model.dependency_graph, param_name) - max_depth = max(max_depth, max(depth.values()) if depth else 0) - except: - pass - - print(f"Максимальная глубина зависимостей: {max_depth}") - print(f"Порядок вычисления: {model.get_calculation_order()}") - - return max_depth + import networkx as nx + except ImportError: + print("Для визуализации графа зависимостей установите networkx: pip install networkx") + return None + + G = nx.DiGraph() + + # Добавляем узлы и ребра + for name, param in self.model.parameters.items(): + node_attrs = { + 'color': 'lightblue' if isinstance(param, IndependentParameter) else 'lightgreen' + } + G.add_node(name, **node_attrs) + + if isinstance(param, DependentParameter): + for dep in param.dependencies: + G.add_edge(dep, name) + + # Добавляем гармоники как узлы, если у них есть зависимости + for i, h in enumerate(self.model.harmonics): + deps = h.get_dependencies() + if deps: + harmonic_name = f"Harmonic_{i+1}" + G.add_node(harmonic_name, color='lightcoral') + for dep in deps: + G.add_edge(dep, harmonic_name) + + fig, ax = plt.subplots(figsize=figsize) + + # Раскладка графа + pos = nx.spring_layout(G, k=2, iterations=50) + + # Рисуем граф + node_colors = [G.nodes[node].get('color', 'lightgray') for node in G.nodes] + nx.draw(G, pos, with_labels=True, node_color=node_colors, + node_size=2000, font_size=10, font_weight='bold', + arrows=True, arrowsize=20, ax=ax) + + ax.set_title("Граф зависимостей параметров", fontsize=16) + + self.figures["dependency_graph"] = fig + return fig +# ====================== ПРИМЕР ИСПОЛЬЗОВАНИЯ ====================== -def benchmark_model(model: FormulaModel, x_range: tuple = (-10, 10), - num_points: int = 10000): - """Бенчмарк производительности модели""" - import time - - x_values = np.linspace(x_range[0], x_range[1], num_points) - - start_time = time.time() - param_values = model.calculate_all_parameters(x_values) - end_time = time.time() - - print(f"Время вычисления всех параметров: {end_time - start_time:.4f} сек") - print(f"Точек данных: {num_points}") - print(f"Скорость: {num_points / (end_time - start_time):.0f} точек/сек") - - -# Основная функция для демонстрации def main(): - """Основная функция для демонстрации работы скрипта""" - # Создание модели - model = create_example_model() - plotter = Plotter(model) + """Пример использования с зависимыми параметрами""" - # Анализ модели - analyze_model_complexity(model) + # Создаем модель + model = MathematicalModel() + # Константы + U0 = 1.0 + h = 1.0 + pi = np.pi + h_br = h / 2 / pi - # Построение графиков - plotter.plot_dependency_graph() - plotter.plot_all_parameters() - plotter.plot_main_formula() + # Добавляем независимые параметры (базовые) + model.add_independent_parameter( + name="n", + formula=lambda x: 1.0 * np.ones_like(x), # Константа + x_range=(0, 10), + color='gray', + description="Для дисперсионного уравнения" + ) - # Графики отдельных параметров - for param_name in ["A", "B", "C"]: - plotter.plot_parameter(param_name) + model.add_independent_parameter( + name="x0_br", + formula=lambda x: 1.0 * np.ones_like(x), # Константа + x_range=(0, 10), + color='blue', + description="Порог" + ) - # Бенчмарк - benchmark_model(model) - + 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="Kn", + expression=lambda A0, time: A0 * np.exp(-0.1 * time), # Затухающая амплитуда + dependencies=["n", "x0_br"], + 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()