paper_quant_2/visualizer.py
2026-02-18 11:37:45 +03:00

229 lines
10 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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