305 lines
12 KiB
Python
305 lines
12 KiB
Python
import numpy as np
|
||
import matplotlib.pyplot as plt
|
||
from typing import Dict, List, Callable, Any, Optional
|
||
import networkx as nx
|
||
from functools import lru_cache
|
||
import sympy as sp
|
||
from collections import defaultdict
|
||
|
||
class Parameter:
|
||
"""Класс для представления параметра с его формулой и зависимостями"""
|
||
|
||
def __init__(self, name: str, formula: Callable, dependencies: List[str] = None,
|
||
description: str = "", units: str = ""):
|
||
self.name = name
|
||
self.formula = formula # Функция для вычисления параметра
|
||
self.dependencies = dependencies or [] # Список зависимых параметров
|
||
self.description = description
|
||
self.units = units
|
||
self.cache = {} # Кэш для мемоизации
|
||
|
||
def calculate(self, x_values: np.ndarray, param_values: Dict[str, np.ndarray]) -> np.ndarray:
|
||
"""Вычисление значений параметра для заданных x"""
|
||
# Проверка кэша
|
||
cache_key = (tuple(x_values), tuple(sorted(param_values.items())))
|
||
if cache_key in self.cache:
|
||
return self.cache[cache_key]
|
||
|
||
# Вычисление
|
||
if self.dependencies:
|
||
# Подготовка аргументов для формулы
|
||
args = [x_values] + [param_values[dep] for dep in self.dependencies]
|
||
result = self.formula(*args)
|
||
else:
|
||
result = self.formula(x_values)
|
||
|
||
# Сохранение в кэш
|
||
self.cache[cache_key] = result
|
||
return result
|
||
|
||
def clear_cache(self):
|
||
"""Очистка кэша параметра"""
|
||
self.cache.clear()
|
||
|
||
|
||
class FormulaModel:
|
||
"""Основной класс для управления моделью с параметрами"""
|
||
|
||
def __init__(self):
|
||
self.parameters: Dict[str, Parameter] = {}
|
||
self.main_formula: Optional[Callable] = None
|
||
self.dependency_graph = nx.DiGraph()
|
||
|
||
def add_parameter(self, parameter: Parameter):
|
||
"""Добавление параметра в модель"""
|
||
self.parameters[parameter.name] = parameter
|
||
|
||
# Обновление графа зависимостей
|
||
self.dependency_graph.add_node(parameter.name)
|
||
for dep in parameter.dependencies:
|
||
self.dependency_graph.add_edge(dep, parameter.name)
|
||
|
||
# Проверка на циклические зависимости
|
||
if not nx.is_directed_acyclic_graph(self.dependency_graph):
|
||
raise ValueError(f"Циклическая зависимость обнаружена при добавлении параметра {parameter.name}")
|
||
|
||
def set_main_formula(self, formula: Callable, dependencies: List[str]):
|
||
"""Установка основной формулы модели"""
|
||
self.main_formula = formula
|
||
self.main_dependencies = dependencies
|
||
|
||
def get_calculation_order(self) -> List[str]:
|
||
"""Получение порядка вычисления параметров с учетом зависимостей"""
|
||
try:
|
||
return list(nx.topological_sort(self.dependency_graph))
|
||
except nx.NetworkXError:
|
||
raise ValueError("Невозможно определить порядок вычисления из-за циклических зависимостей")
|
||
|
||
def calculate_all_parameters(self, x_values: np.ndarray) -> Dict[str, np.ndarray]:
|
||
"""Вычисление всех параметров в правильном порядке"""
|
||
param_values = {}
|
||
calculation_order = self.get_calculation_order()
|
||
|
||
for param_name in calculation_order:
|
||
if param_name in self.parameters:
|
||
param = self.parameters[param_name]
|
||
param_values[param_name] = param.calculate(x_values, param_values)
|
||
|
||
return param_values
|
||
|
||
def calculate_main_formula(self, x_values: np.ndarray) -> np.ndarray:
|
||
"""Вычисление основной формулы"""
|
||
if self.main_formula is None:
|
||
raise ValueError("Основная формула не установлена")
|
||
|
||
param_values = self.calculate_all_parameters(x_values)
|
||
|
||
# Подготовка аргументов для основной формулы
|
||
args = [x_values] + [param_values[dep] for dep in self.main_dependencies]
|
||
return self.main_formula(*args)
|
||
|
||
def clear_all_cache(self):
|
||
"""Очистка всех кэшей"""
|
||
for param in self.parameters.values():
|
||
param.clear_cache()
|
||
|
||
|
||
class Plotter:
|
||
"""Класс для построения графиков"""
|
||
|
||
def __init__(self, model: FormulaModel):
|
||
self.model = model
|
||
|
||
def plot_parameter(self, param_name: str, x_range: tuple = (-10, 10),
|
||
num_points: int = 1000, title: str = None):
|
||
"""График отдельного параметра"""
|
||
if param_name not in self.model.parameters:
|
||
raise ValueError(f"Параметр {param_name} не найден")
|
||
|
||
x_values = np.linspace(x_range[0], x_range[1], num_points)
|
||
param_values = self.model.calculate_all_parameters(x_values)
|
||
y_values = param_values[param_name]
|
||
|
||
plt.figure(figsize=(10, 6))
|
||
plt.plot(x_values, y_values, linewidth=2)
|
||
plt.grid(True, alpha=0.3)
|
||
|
||
param = self.model.parameters[param_name]
|
||
plt.title(title or f"Параметр {param_name}: {param.description}")
|
||
plt.xlabel("x")
|
||
plt.ylabel(f"{param_name} ({param.units})" if param.units else param_name)
|
||
|
||
plt.show()
|
||
|
||
def plot_all_parameters(self, x_range: tuple = (-10, 10), num_points: int = 1000):
|
||
"""График всех параметров на одном рисунке"""
|
||
x_values = np.linspace(x_range[0], x_range[1], num_points)
|
||
param_values = self.model.calculate_all_parameters(x_values)
|
||
|
||
plt.figure(figsize=(14, 8))
|
||
|
||
for param_name, y_values in param_values.items():
|
||
plt.plot(x_values, y_values, label=param_name, linewidth=2)
|
||
|
||
plt.grid(True, alpha=0.3)
|
||
plt.title("Все параметры модели")
|
||
plt.xlabel("x")
|
||
plt.ylabel("Значения параметров")
|
||
plt.legend()
|
||
plt.show()
|
||
|
||
def plot_main_formula(self, x_range: tuple = (-10, 10), num_points: int = 1000,
|
||
title: str = "Основная формула"):
|
||
"""График основной формулы"""
|
||
x_values = np.linspace(x_range[0], x_range[1], num_points)
|
||
y_values = self.model.calculate_main_formula(x_values)
|
||
|
||
plt.figure(figsize=(10, 6))
|
||
plt.plot(x_values, y_values, 'r-', linewidth=2, label='Основная формула')
|
||
plt.grid(True, alpha=0.3)
|
||
plt.title(title)
|
||
plt.xlabel("x")
|
||
plt.ylabel("f(x)")
|
||
plt.legend()
|
||
plt.show()
|
||
|
||
def plot_dependency_graph(self):
|
||
"""Визуализация графа зависимостей"""
|
||
plt.figure(figsize=(12, 8))
|
||
pos = nx.spring_layout(self.model.dependency_graph, k=2, iterations=50)
|
||
|
||
nx.draw(self.model.dependency_graph, pos,
|
||
with_labels=True, node_color='lightblue',
|
||
node_size=2000, font_size=10, font_weight='bold',
|
||
arrows=True, arrowsize=20, edge_color='gray')
|
||
|
||
plt.title("Граф зависимостей параметров")
|
||
plt.show()
|
||
|
||
|
||
# Пример использования и заготовки функций
|
||
|
||
def create_example_model():
|
||
"""Создание примера модели для демонстрации"""
|
||
model = FormulaModel()
|
||
|
||
# Независимые параметры
|
||
param_a = Parameter(
|
||
name="A",
|
||
formula=lambda x: np.sin(x) + 2,
|
||
description="Синусоидальная функция",
|
||
units="м/с"
|
||
)
|
||
|
||
# Зависимый параметр (зависит от A)
|
||
param_b = Parameter(
|
||
name="B",
|
||
formula=lambda x, a: np.cos(x) * a,
|
||
dependencies=["A"],
|
||
description="Функция, зависящая от A",
|
||
units="м/с²"
|
||
)
|
||
|
||
# Параметр, зависящий от B
|
||
param_c = Parameter(
|
||
name="C",
|
||
formula=lambda x, b: np.exp(-x/10) * b,
|
||
dependencies=["B"],
|
||
description="Экспоненциальная функция от B",
|
||
units="Н"
|
||
)
|
||
|
||
# Параметр, зависящий от A и C
|
||
param_d = Parameter(
|
||
name="D",
|
||
formula=lambda x, a, c: (a**2 + c) / (1 + x**2),
|
||
dependencies=["A", "C"],
|
||
description="Комбинированная функция",
|
||
units="Дж"
|
||
)
|
||
|
||
# Независимый параметр
|
||
param_e = Parameter(
|
||
name="E",
|
||
formula=lambda x: np.log(1 + np.abs(x)),
|
||
description="Логарифмическая функция",
|
||
units="К"
|
||
)
|
||
|
||
# Добавление параметров в модель
|
||
for param in [param_a, param_b, param_c, param_d, param_e]:
|
||
model.add_parameter(param)
|
||
|
||
# Основная формула, использующая несколько параметров
|
||
model.set_main_formula(
|
||
formula=lambda x, a, c, e: a * c + e**2,
|
||
dependencies=["A", "C", "E"]
|
||
)
|
||
|
||
return model
|
||
|
||
|
||
# Утилитарные функции
|
||
|
||
def analyze_model_complexity(model: FormulaModel):
|
||
"""Анализ сложности модели"""
|
||
print("=== Анализ модели ===")
|
||
print(f"Количество параметров: {len(model.parameters)}")
|
||
|
||
# Анализ зависимостей
|
||
max_depth = 0
|
||
for param_name in model.parameters:
|
||
try:
|
||
depth = nx.shortest_path_length(model.dependency_graph, param_name)
|
||
max_depth = max(max_depth, max(depth.values()) if depth else 0)
|
||
except:
|
||
pass
|
||
|
||
print(f"Максимальная глубина зависимостей: {max_depth}")
|
||
print(f"Порядок вычисления: {model.get_calculation_order()}")
|
||
|
||
return max_depth
|
||
|
||
|
||
def benchmark_model(model: FormulaModel, x_range: tuple = (-10, 10),
|
||
num_points: int = 10000):
|
||
"""Бенчмарк производительности модели"""
|
||
import time
|
||
|
||
x_values = np.linspace(x_range[0], x_range[1], num_points)
|
||
|
||
start_time = time.time()
|
||
param_values = model.calculate_all_parameters(x_values)
|
||
end_time = time.time()
|
||
|
||
print(f"Время вычисления всех параметров: {end_time - start_time:.4f} сек")
|
||
print(f"Точек данных: {num_points}")
|
||
print(f"Скорость: {num_points / (end_time - start_time):.0f} точек/сек")
|
||
|
||
|
||
# Основная функция для демонстрации
|
||
def main():
|
||
"""Основная функция для демонстрации работы скрипта"""
|
||
# Создание модели
|
||
model = create_example_model()
|
||
plotter = Plotter(model)
|
||
|
||
# Анализ модели
|
||
analyze_model_complexity(model)
|
||
|
||
# Построение графиков
|
||
plotter.plot_dependency_graph()
|
||
plotter.plot_all_parameters()
|
||
plotter.plot_main_formula()
|
||
|
||
# Графики отдельных параметров
|
||
for param_name in ["A", "B", "C"]:
|
||
plotter.plot_parameter(param_name)
|
||
|
||
# Бенчмарк
|
||
benchmark_model(model)
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|