Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 40cbdf36c7 | |||
| ea55212306 |
255
main.py
255
main.py
|
|
@ -4,7 +4,6 @@ from typing import Callable, List, Tuple, Optional, Dict, Any, Union
|
|||
import dataclasses
|
||||
from enum import Enum
|
||||
import inspect
|
||||
from visualizer import *
|
||||
|
||||
# ====================== ТИПЫ ЗАВИСИМОСТЕЙ ======================
|
||||
|
||||
|
|
@ -293,27 +292,265 @@ class MathematicalModel:
|
|||
|
||||
return self.main_formula(**call_args)
|
||||
|
||||
# ====================== УЛУЧШЕННЫЙ ВИЗУАЛИЗАТОР ======================
|
||||
|
||||
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
|
||||
|
||||
# ====================== ПРИМЕР ИСПОЛЬЗОВАНИЯ ======================
|
||||
|
||||
def main():
|
||||
"""Пример использования с зависимыми параметрами"""
|
||||
|
||||
# Создаем модель
|
||||
model = MathematicalModel()
|
||||
# Константы
|
||||
U0 = 1.0
|
||||
h = 1.0
|
||||
pi = np.pi
|
||||
h_br = h / 2 / pi
|
||||
|
||||
# Добавляем независимые параметры (базовые)
|
||||
model.add_independent_parameter(
|
||||
name="time",
|
||||
formula=lambda x: x, # Просто время
|
||||
name="n",
|
||||
formula=lambda x: 1.0 * np.ones_like(x), # Константа
|
||||
x_range=(0, 10),
|
||||
color='gray',
|
||||
description="Базовый параметр времени"
|
||||
description="Для дисперсионного уравнения"
|
||||
)
|
||||
|
||||
model.add_independent_parameter(
|
||||
name="A0",
|
||||
formula=lambda x: 2.0 * np.ones_like(x), # Константа
|
||||
name="x0_br",
|
||||
formula=lambda x: 1.0 * np.ones_like(x), # Константа
|
||||
x_range=(0, 10),
|
||||
color='blue',
|
||||
description="Базовая амплитуда"
|
||||
description="Порог"
|
||||
)
|
||||
|
||||
model.add_independent_parameter(
|
||||
|
|
@ -326,9 +563,9 @@ def main():
|
|||
|
||||
# Добавляем зависимые параметры (выражаются через другие)
|
||||
model.add_dependent_parameter(
|
||||
name="A_effective",
|
||||
name="Kn",
|
||||
expression=lambda A0, time: A0 * np.exp(-0.1 * time), # Затухающая амплитуда
|
||||
dependencies=["A0", "time"],
|
||||
dependencies=["n", "x0_br"],
|
||||
color='green',
|
||||
description="Эффективная амплитуда (зависит от времени)"
|
||||
)
|
||||
|
|
|
|||
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