using OOP

This commit is contained in:
kit8nino 2026-02-18 11:37:45 +03:00
commit a1a898521a
6 changed files with 679 additions and 0 deletions

BIN
1.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

BIN
2.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

BIN
3.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

BIN
4.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

451
main.py Normal file
View File

@ -0,0 +1,451 @@
import numpy as np
import matplotlib.pyplot as plt
from typing import Callable, List, Tuple, Optional, Dict, Any, Union
import dataclasses
from enum import Enum
import inspect
from visualizer import *
# ====================== ТИПЫ ЗАВИСИМОСТЕЙ ======================
class DependencyType(Enum):
"""Типы зависимостей между параметрами"""
INDEPENDENT = "independent" # Независимый параметр
DEPENDENT = "dependent" # Зависимый параметр
EXPRESSION = "expression" # Выражение, использующее другие параметры
# ====================== КЛАССЫ ДЛЯ ПАРАМЕТРОВ ======================
@dataclasses.dataclass
class Parameter:
"""Базовый класс для параметра"""
name: str
param_type: DependencyType
description: str = ""
def evaluate(self, context: Dict[str, Any]) -> np.ndarray:
"""Вычисляет значение параметра в заданном контексте"""
raise NotImplementedError
@dataclasses.dataclass
class IndependentParameter(Parameter):
"""Независимый параметр (базовый)"""
formula: Callable
x_range: Tuple[float, float]
num_points: int = 1000
color: str = 'blue'
line_style: str = '-'
def __post_init__(self):
self.param_type = DependencyType.INDEPENDENT
def evaluate(self, context: Dict[str, Any]) -> np.ndarray:
"""Вычисляет значение независимого параметра"""
x = context.get('x', np.linspace(self.x_range[0], self.x_range[1], self.num_points))
return self.formula(x)
@dataclasses.dataclass
class DependentParameter(Parameter):
"""Зависимый параметр (выражается через другие параметры)"""
expression: Callable # Функция, которая использует другие параметры
dependencies: List[str] # Имена параметров, от которых зависит
color: str = 'green'
line_style: str = '--'
def __post_init__(self):
self.param_type = DependencyType.DEPENDENT
def evaluate(self, context: Dict[str, Any]) -> np.ndarray:
"""Вычисляет значение зависимого параметра"""
# Собираем значения зависимостей из контекста
dep_values = {}
for dep_name in self.dependencies:
if dep_name not in context:
raise ValueError(f"Зависимость '{dep_name}' не найдена в контексте")
dep_values[dep_name] = context[dep_name]
# Вычисляем выражение
return self.expression(**dep_values)
# ====================== КЛАСС ДЛЯ ГАРМОНИК ======================
class HarmonicOscillation:
"""Класс для описания гармонического колебания"""
def __init__(self, amplitude: Union[float, str],
frequency: Union[float, str],
phase: Union[float, str] = 0,
amplitude_depends_on: Optional[List[str]] = None,
frequency_depends_on: Optional[List[str]] = None,
phase_depends_on: Optional[List[str]] = None):
"""
Параметры гармоники могут быть как числами, так и именами параметров модели
"""
self.amplitude = amplitude
self.frequency = frequency
self.phase = phase
# Отслеживаем зависимости
self.amplitude_depends_on = amplitude_depends_on or []
self.frequency_depends_on = frequency_depends_on or []
self.phase_depends_on = phase_depends_on or []
def get_dependencies(self) -> List[str]:
"""Возвращает все зависимости гармоники"""
return (self.amplitude_depends_on +
self.frequency_depends_on +
self.phase_depends_on)
def evaluate(self, t: np.ndarray, context: Dict[str, Any]) -> np.ndarray:
"""Вычисляет значение гармонического колебания с учетом зависимостей"""
# Получаем значения амплитуды, частоты и фазы
amp = self._get_value(self.amplitude, context)
freq = self._get_value(self.frequency, context)
phase = self._get_value(self.phase, context)
return amp * np.sin(2 * np.pi * freq * t + phase)
def _get_value(self, param: Any, context: Dict[str, Any]) -> float:
"""Получает значение параметра (число или из контекста)"""
if isinstance(param, (int, float)):
return param
elif isinstance(param, str) and param in context:
# Если параметр - строка, берем соответствующее значение из контекста
val = context[param]
# Если это массив, берем среднее или первое значение
if isinstance(val, np.ndarray):
return np.mean(val) # или val[0] в зависимости от логики
return float(val)
else:
raise ValueError(f"Не удалось получить значение для {param}")
def __repr__(self):
return f"Harmonic(A={self.amplitude}, f={self.frequency}, φ={self.phase})"
# ====================== ОСНОВНОЙ КЛАСС МОДЕЛИ ======================
class MathematicalModel:
"""
Модель с поддержкой зависимых параметров
"""
def __init__(self):
self.parameters: Dict[str, Parameter] = {}
self.harmonics: List[HarmonicOscillation] = []
self.main_formula: Optional[Callable] = None
self.evaluation_context: Dict[str, Any] = {}
def add_independent_parameter(self, name: str, formula: Callable,
x_range: Tuple[float, float],
num_points: int = 1000,
color: str = 'blue',
line_style: str = '-',
description: str = "") -> None:
"""Добавляет независимый параметр"""
self.parameters[name] = IndependentParameter(
name=name,
formula=formula,
x_range=x_range,
num_points=num_points,
color=color,
line_style=line_style,
description=description
)
def add_dependent_parameter(self, name: str, expression: Callable,
dependencies: List[str],
color: str = 'green',
line_style: str = '--',
description: str = "") -> None:
"""Добавляет зависимый параметр"""
# Проверяем, что все зависимости существуют
for dep in dependencies:
if dep not in self.parameters:
raise ValueError(f"Зависимость '{dep}' не найдена среди параметров")
self.parameters[name] = DependentParameter(
name=name,
expression=expression,
dependencies=dependencies,
color=color,
line_style=line_style,
description=description
)
def add_harmonic(self, amplitude: Union[float, str],
frequency: Union[float, str],
phase: Union[float, str] = 0,
amplitude_depends_on: Optional[List[str]] = None,
frequency_depends_on: Optional[List[str]] = None,
phase_depends_on: Optional[List[str]] = None) -> None:
"""Добавляет гармоническое колебание с возможными зависимостями"""
harmonic = HarmonicOscillation(
amplitude=amplitude,
frequency=frequency,
phase=phase,
amplitude_depends_on=amplitude_depends_on,
frequency_depends_on=frequency_depends_on,
phase_depends_on=phase_depends_on
)
# Проверяем, что все зависимости существуют
for dep in harmonic.get_dependencies():
if dep not in self.parameters:
raise ValueError(f"Зависимость '{dep}' для гармоники не найдена среди параметров")
self.harmonics.append(harmonic)
def set_main_formula(self, formula: Callable) -> None:
"""Устанавливает основную формулу"""
self.main_formula = formula
def get_parameter_dependencies(self, param_name: str) -> List[str]:
"""Возвращает список зависимостей для параметра"""
param = self.parameters.get(param_name)
if isinstance(param, DependentParameter):
return param.dependencies
return []
def get_all_dependencies(self) -> Dict[str, List[str]]:
"""Возвращает словарь всех зависимостей"""
dependencies = {}
for name, param in self.parameters.items():
if isinstance(param, DependentParameter):
dependencies[name] = param.dependencies
return dependencies
def evaluate_parameters(self, x: np.ndarray) -> Dict[str, np.ndarray]:
"""
Вычисляет все параметры с учетом зависимостей.
Использует топологическую сортировку для правильного порядка вычисления.
"""
results = {'x': x}
# Функция для топологической сортировки
def topological_sort():
visited = set()
order = []
def dfs(param_name):
if param_name in visited:
return
visited.add(param_name)
param = self.parameters[param_name]
if isinstance(param, DependentParameter):
for dep in param.dependencies:
if dep in self.parameters:
dfs(dep)
order.append(param_name)
for name in self.parameters:
if name not in visited:
dfs(name)
return order
# Вычисляем параметры в правильном порядке
eval_order = topological_sort()
for param_name in eval_order:
param = self.parameters[param_name]
results[param_name] = param.evaluate(results)
return results
def sum_harmonics(self, t: np.ndarray, context: Dict[str, Any]) -> np.ndarray:
"""Вычисляет сумму всех гармонических колебаний с учетом зависимостей"""
if not self.harmonics:
return np.zeros_like(t)
result = np.zeros_like(t)
for h in self.harmonics:
result += h.evaluate(t, context)
return result
def evaluate_main(self, x: np.ndarray, **kwargs) -> np.ndarray:
"""
Вычисляет основную формулу с заданным x и дополнительными параметрами
"""
if self.main_formula is None:
raise ValueError("Основная формула не установлена")
# Вычисляем все параметры
context = self.evaluate_parameters(x)
context.update(kwargs)
# Добавляем сумму гармоник в контекст
context['harmonic_sum'] = lambda t: self.sum_harmonics(t, context)
# Получаем аргументы функции
sig = inspect.signature(self.main_formula)
# Подготавливаем аргументы для вызова
call_args = {}
for param_name in sig.parameters:
if param_name in context:
call_args[param_name] = context[param_name]
elif param_name == 'x':
call_args['x'] = x
return self.main_formula(**call_args)
def main():
"""Пример использования с зависимыми параметрами"""
# Создаем модель
model = MathematicalModel()
# Добавляем независимые параметры (базовые)
model.add_independent_parameter(
name="time",
formula=lambda x: x, # Просто время
x_range=(0, 10),
color='gray',
description="Базовый параметр времени"
)
model.add_independent_parameter(
name="A0",
formula=lambda x: 2.0 * np.ones_like(x), # Константа
x_range=(0, 10),
color='blue',
description="Базовая амплитуда"
)
model.add_independent_parameter(
name="f0",
formula=lambda x: 1.0 * np.ones_like(x), # Константа
x_range=(0, 10),
color='red',
description="Базовая частота"
)
# Добавляем зависимые параметры (выражаются через другие)
model.add_dependent_parameter(
name="A_effective",
expression=lambda A0, time: A0 * np.exp(-0.1 * time), # Затухающая амплитуда
dependencies=["A0", "time"],
color='green',
description="Эффективная амплитуда (зависит от времени)"
)
model.add_dependent_parameter(
name="f_effective",
expression=lambda f0, time: f0 * (1 + 0.05 * np.sin(time)), # Модулированная частота
dependencies=["f0", "time"],
color='orange',
description="Эффективная частота (модулирована)"
)
model.add_dependent_parameter(
name="modulation_index",
expression=lambda A_effective, f_effective: A_effective * f_effective / 2,
dependencies=["A_effective", "f_effective"],
color='purple',
description="Индекс модуляции (произведение)"
)
# Добавляем гармоники, которые могут зависеть от параметров
model.add_harmonic(
amplitude="A_effective", # Использует параметр A_effective
frequency="f_effective", # Использует параметр f_effective
phase=0,
amplitude_depends_on=["A_effective"],
frequency_depends_on=["f_effective"]
)
model.add_harmonic(
amplitude=0.5,
frequency=2.0,
phase=np.pi/4
)
model.add_harmonic(
amplitude="modulation_index",
frequency=3.0,
phase=0,
amplitude_depends_on=["modulation_index"]
)
# Устанавливаем основную формулу
def main_formula(x, A_effective, f_effective, modulation_index, harmonic_sum):
"""
Основная формула: комбинация параметров и гармоник
"""
# Параметрическая часть
parametric_part = A_effective * np.sin(2 * np.pi * f_effective * x)
# Модуляционная часть
modulation_part = modulation_index * np.cos(2 * np.pi * x)
# Гармоническая часть
harmonic_part = harmonic_sum(x)
return parametric_part + modulation_part + harmonic_part
model.set_main_formula(main_formula)
# Создаем визуализатор
viz = ModelVisualizer(model)
# Строим граф зависимостей
print("Визуализация графа зависимостей...")
viz.plot_dependency_graph()
# Строим графики параметров с зависимостями
print("\nГрафики параметров с зависимостями...")
viz.plot_parameter("A_effective", show_dependencies=True)
viz.plot_parameter("f_effective", show_dependencies=True)
viz.plot_parameter_with_dependencies("modulation_index")
# Строим графики всех параметров
print("\nВсе параметры...")
fig, axes = plt.subplots(2, 3, figsize=(15, 10))
axes = axes.flatten()
x_test = np.linspace(0, 10, 1000)
context = model.evaluate_parameters(x_test)
for i, (name, param) in enumerate(model.parameters.items()):
if i < len(axes):
axes[i].plot(x_test, context[name], color=param.color,
linestyle=param.line_style, linewidth=2)
axes[i].grid(True, alpha=0.3)
axes[i].set_title(f"{name}\n{param.description}")
axes[i].set_xlabel("x")
# Скрываем лишний подграфик
for j in range(len(model.parameters), len(axes)):
axes[j].set_visible(False)
plt.tight_layout()
# Строим графики гармоник с зависимостями
print("\nГармонические колебания...")
viz.plot_harmonics(t_range=(0, 5), x_for_dependencies=x_test)
# Строим график основной формулы
print("\nОсновная формула...")
fig, ax = plt.subplots(figsize=(12, 6))
y_main = model.evaluate_main(x_test)
ax.plot(x_test, y_main, 'b-', linewidth=2)
ax.grid(True, alpha=0.3)
ax.set_title("Результат работы основной формулы", fontsize=14)
ax.set_xlabel("x", fontsize=12)
ax.set_ylabel("F(x)", fontsize=12)
# Показываем все графики
plt.show()
# Выводим информацию о зависимостях
print("\nСтруктура зависимостей:")
deps = model.get_all_dependencies()
for param, dependencies in deps.items():
print(f" {param} зависит от: {', '.join(dependencies)}")
if __name__ == "__main__":
main()

228
visualizer.py Normal file
View File

@ -0,0 +1,228 @@
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