template
This commit is contained in:
parent
a1a898521a
commit
ea55212306
681
main.py
681
main.py
|
|
@ -1,451 +1,304 @@
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import matplotlib.pyplot as plt
|
import matplotlib.pyplot as plt
|
||||||
from typing import Callable, List, Tuple, Optional, Dict, Any, Union
|
from typing import Dict, List, Callable, Any, Optional
|
||||||
import dataclasses
|
import networkx as nx
|
||||||
from enum import Enum
|
from functools import lru_cache
|
||||||
import inspect
|
import sympy as sp
|
||||||
from visualizer import *
|
from collections import defaultdict
|
||||||
|
|
||||||
# ====================== ТИПЫ ЗАВИСИМОСТЕЙ ======================
|
|
||||||
|
|
||||||
class DependencyType(Enum):
|
|
||||||
"""Типы зависимостей между параметрами"""
|
|
||||||
INDEPENDENT = "independent" # Независимый параметр
|
|
||||||
DEPENDENT = "dependent" # Зависимый параметр
|
|
||||||
EXPRESSION = "expression" # Выражение, использующее другие параметры
|
|
||||||
|
|
||||||
# ====================== КЛАССЫ ДЛЯ ПАРАМЕТРОВ ======================
|
|
||||||
|
|
||||||
@dataclasses.dataclass
|
|
||||||
class Parameter:
|
class Parameter:
|
||||||
"""Базовый класс для параметра"""
|
"""Класс для представления параметра с его формулой и зависимостями"""
|
||||||
name: str
|
|
||||||
param_type: DependencyType
|
|
||||||
description: str = ""
|
|
||||||
|
|
||||||
def evaluate(self, context: Dict[str, Any]) -> np.ndarray:
|
def __init__(self, name: str, formula: Callable, dependencies: List[str] = None,
|
||||||
"""Вычисляет значение параметра в заданном контексте"""
|
description: str = "", units: str = ""):
|
||||||
raise NotImplementedError
|
self.name = name
|
||||||
|
self.formula = formula # Функция для вычисления параметра
|
||||||
@dataclasses.dataclass
|
self.dependencies = dependencies or [] # Список зависимых параметров
|
||||||
class IndependentParameter(Parameter):
|
self.description = description
|
||||||
"""Независимый параметр (базовый)"""
|
self.units = units
|
||||||
formula: Callable
|
self.cache = {} # Кэш для мемоизации
|
||||||
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:
|
||||||
return self.expression(**dep_values)
|
"""Вычисление значений параметра для заданных x"""
|
||||||
|
# Проверка кэша
|
||||||
# ====================== КЛАСС ДЛЯ ГАРМОНИК ======================
|
cache_key = (tuple(x_values), tuple(sorted(param_values.items())))
|
||||||
|
if cache_key in self.cache:
|
||||||
class HarmonicOscillation:
|
return self.cache[cache_key]
|
||||||
"""Класс для описания гармонического колебания"""
|
|
||||||
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 []
|
if self.dependencies:
|
||||||
self.frequency_depends_on = frequency_depends_on or []
|
# Подготовка аргументов для формулы
|
||||||
self.phase_depends_on = phase_depends_on or []
|
args = [x_values] + [param_values[dep] for dep in self.dependencies]
|
||||||
|
result = self.formula(*args)
|
||||||
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:
|
else:
|
||||||
raise ValueError(f"Не удалось получить значение для {param}")
|
result = self.formula(x_values)
|
||||||
|
|
||||||
|
# Сохранение в кэш
|
||||||
|
self.cache[cache_key] = result
|
||||||
|
return result
|
||||||
|
|
||||||
def __repr__(self):
|
def clear_cache(self):
|
||||||
return f"Harmonic(A={self.amplitude}, f={self.frequency}, φ={self.phase})"
|
"""Очистка кэша параметра"""
|
||||||
|
self.cache.clear()
|
||||||
|
|
||||||
# ====================== ОСНОВНОЙ КЛАСС МОДЕЛИ ======================
|
|
||||||
|
|
||||||
class MathematicalModel:
|
class FormulaModel:
|
||||||
"""
|
"""Основной класс для управления моделью с параметрами"""
|
||||||
Модель с поддержкой зависимых параметров
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.parameters: Dict[str, Parameter] = {}
|
self.parameters: Dict[str, Parameter] = {}
|
||||||
self.harmonics: List[HarmonicOscillation] = []
|
|
||||||
self.main_formula: Optional[Callable] = None
|
self.main_formula: Optional[Callable] = None
|
||||||
self.evaluation_context: Dict[str, Any] = {}
|
self.dependency_graph = nx.DiGraph()
|
||||||
|
|
||||||
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(
|
def add_parameter(self, parameter: Parameter):
|
||||||
name=name,
|
"""Добавление параметра в модель"""
|
||||||
expression=expression,
|
self.parameters[parameter.name] = parameter
|
||||||
dependencies=dependencies,
|
|
||||||
color=color,
|
# Обновление графа зависимостей
|
||||||
line_style=line_style,
|
self.dependency_graph.add_node(parameter.name)
|
||||||
description=description
|
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}")
|
||||||
|
|
||||||
def add_harmonic(self, amplitude: Union[float, str],
|
def set_main_formula(self, formula: Callable, dependencies: List[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_formula = formula
|
||||||
|
self.main_dependencies = dependencies
|
||||||
|
|
||||||
def get_parameter_dependencies(self, param_name: str) -> List[str]:
|
def get_calculation_order(self) -> List[str]:
|
||||||
"""Возвращает список зависимостей для параметра"""
|
"""Получение порядка вычисления параметров с учетом зависимостей"""
|
||||||
param = self.parameters.get(param_name)
|
try:
|
||||||
if isinstance(param, DependentParameter):
|
return list(nx.topological_sort(self.dependency_graph))
|
||||||
return param.dependencies
|
except nx.NetworkXError:
|
||||||
return []
|
raise ValueError("Невозможно определить порядок вычисления из-за циклических зависимостей")
|
||||||
|
|
||||||
def get_all_dependencies(self) -> Dict[str, List[str]]:
|
def calculate_all_parameters(self, x_values: np.ndarray) -> Dict[str, np.ndarray]:
|
||||||
"""Возвращает словарь всех зависимостей"""
|
"""Вычисление всех параметров в правильном порядке"""
|
||||||
dependencies = {}
|
param_values = {}
|
||||||
for name, param in self.parameters.items():
|
calculation_order = self.get_calculation_order()
|
||||||
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:
|
||||||
def topological_sort():
|
if param_name in self.parameters:
|
||||||
visited = set()
|
|
||||||
order = []
|
|
||||||
|
|
||||||
def dfs(param_name):
|
|
||||||
if param_name in visited:
|
|
||||||
return
|
|
||||||
visited.add(param_name)
|
|
||||||
|
|
||||||
param = self.parameters[param_name]
|
param = self.parameters[param_name]
|
||||||
if isinstance(param, DependentParameter):
|
param_values[param_name] = param.calculate(x_values, param_values)
|
||||||
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 sum_harmonics(self, t: np.ndarray, context: Dict[str, Any]) -> np.ndarray:
|
def calculate_main_formula(self, x_values: np.ndarray) -> 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:
|
if self.main_formula is None:
|
||||||
raise ValueError("Основная формула не установлена")
|
raise ValueError("Основная формула не установлена")
|
||||||
|
|
||||||
# Вычисляем все параметры
|
param_values = self.calculate_all_parameters(x_values)
|
||||||
context = self.evaluate_parameters(x)
|
|
||||||
context.update(kwargs)
|
|
||||||
|
|
||||||
# Добавляем сумму гармоник в контекст
|
# Подготовка аргументов для основной формулы
|
||||||
context['harmonic_sum'] = lambda t: self.sum_harmonics(t, context)
|
args = [x_values] + [param_values[dep] for dep in self.main_dependencies]
|
||||||
|
return self.main_formula(*args)
|
||||||
# Получаем аргументы функции
|
|
||||||
sig = inspect.signature(self.main_formula)
|
def clear_all_cache(self):
|
||||||
|
"""Очистка всех кэшей"""
|
||||||
# Подготавливаем аргументы для вызова
|
for param in self.parameters.values():
|
||||||
call_args = {}
|
param.clear_cache()
|
||||||
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:
|
||||||
|
"""Класс для построения графиков"""
|
||||||
|
|
||||||
|
def __init__(self, model: FormulaModel):
|
||||||
|
self.model = model
|
||||||
|
|
||||||
|
def plot_parameter(self, param_name: str, x_range: tuple = (-10, 10),
|
||||||
|
num_points: int = 1000, title: str = None):
|
||||||
|
"""График отдельного параметра"""
|
||||||
|
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)
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
plt.figure(figsize=(14, 8))
|
||||||
|
|
||||||
|
for param_name, y_values in param_values.items():
|
||||||
|
plt.plot(x_values, y_values, label=param_name, linewidth=2)
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
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')
|
||||||
|
|
||||||
|
plt.title("Граф зависимостей параметров")
|
||||||
|
plt.show()
|
||||||
|
|
||||||
|
|
||||||
|
# Пример использования и заготовки функций
|
||||||
|
|
||||||
|
def create_example_model():
|
||||||
|
"""Создание примера модели для демонстрации"""
|
||||||
|
model = FormulaModel()
|
||||||
|
|
||||||
|
# Независимые параметры
|
||||||
|
param_a = Parameter(
|
||||||
|
name="A",
|
||||||
|
formula=lambda x: np.sin(x) + 2,
|
||||||
|
description="Синусоидальная функция",
|
||||||
|
units="м/с"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Зависимый параметр (зависит от A)
|
||||||
|
param_b = Parameter(
|
||||||
|
name="B",
|
||||||
|
formula=lambda x, a: np.cos(x) * a,
|
||||||
|
dependencies=["A"],
|
||||||
|
description="Функция, зависящая от A",
|
||||||
|
units="м/с²"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Параметр, зависящий от 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:
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
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():
|
def main():
|
||||||
"""Пример использования с зависимыми параметрами"""
|
"""Основная функция для демонстрации работы скрипта"""
|
||||||
|
# Создание модели
|
||||||
|
model = create_example_model()
|
||||||
|
plotter = Plotter(model)
|
||||||
|
|
||||||
# Создаем модель
|
# Анализ модели
|
||||||
model = MathematicalModel()
|
analyze_model_complexity(model)
|
||||||
|
|
||||||
# Добавляем независимые параметры (базовые)
|
# Построение графиков
|
||||||
model.add_independent_parameter(
|
plotter.plot_dependency_graph()
|
||||||
name="time",
|
plotter.plot_all_parameters()
|
||||||
formula=lambda x: x, # Просто время
|
plotter.plot_main_formula()
|
||||||
x_range=(0, 10),
|
|
||||||
color='gray',
|
|
||||||
description="Базовый параметр времени"
|
|
||||||
)
|
|
||||||
|
|
||||||
model.add_independent_parameter(
|
# Графики отдельных параметров
|
||||||
name="A0",
|
for param_name in ["A", "B", "C"]:
|
||||||
formula=lambda x: 2.0 * np.ones_like(x), # Константа
|
plotter.plot_parameter(param_name)
|
||||||
x_range=(0, 10),
|
|
||||||
color='blue',
|
|
||||||
description="Базовая амплитуда"
|
|
||||||
)
|
|
||||||
|
|
||||||
model.add_independent_parameter(
|
# Бенчмарк
|
||||||
name="f0",
|
benchmark_model(model)
|
||||||
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__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|
|
||||||
228
visualizer.py
228
visualizer.py
|
|
@ -1,228 +0,0 @@
|
||||||
class ModelVisualizer:
|
|
||||||
"""Класс для визуализации модели с зависимостями"""
|
|
||||||
|
|
||||||
def __init__(self, model: MathematicalModel):
|
|
||||||
self.model = model
|
|
||||||
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}' не найден")
|
|
||||||
|
|
||||||
param = self.model.parameters[param_name]
|
|
||||||
|
|
||||||
# Определяем диапазон x
|
|
||||||
if x_range is None:
|
|
||||||
if isinstance(param, IndependentParameter):
|
|
||||||
x_range = param.x_range
|
|
||||||
else:
|
|
||||||
# Для зависимых параметров нужно определить разумный диапазон
|
|
||||||
x_range = (0, 10) # По умолчанию
|
|
||||||
|
|
||||||
x = np.linspace(x_range[0], x_range[1], 1000)
|
|
||||||
|
|
||||||
# Вычисляем значение параметра
|
|
||||||
context = self.model.evaluate_parameters(x)
|
|
||||||
y = context[param_name]
|
|
||||||
|
|
||||||
# Создаем фигуру
|
|
||||||
fig, ax = plt.subplots(figsize=figsize)
|
|
||||||
|
|
||||||
# Строим основной график
|
|
||||||
ax.plot(x, y, color=param.color, linestyle=param.line_style,
|
|
||||||
linewidth=2, label=param_name)
|
|
||||||
|
|
||||||
# Если нужно показать зависимости
|
|
||||||
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} (норм.)")
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
def plot_dependency_graph(self, figsize: Tuple[int, int] = (12, 8)) -> plt.Figure:
|
|
||||||
"""
|
|
||||||
Визуализирует граф зависимостей между параметрами
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
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
|
|
||||||
Loading…
Reference in New Issue
Block a user