paper_quant_2/visualizer.py

229 lines
10 KiB
Python
Raw Normal View History

2026-02-18 08:37:45 +00:00
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