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