229 lines
10 KiB
Python
229 lines
10 KiB
Python
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
|