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
|