2026-02-18 08:37:45 +00:00
|
|
|
|
import numpy as np
|
|
|
|
|
|
import matplotlib.pyplot as plt
|
2026-02-18 08:50:20 +00:00
|
|
|
|
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
|
2026-02-18 08:37:45 +00:00
|
|
|
|
|
|
|
|
|
|
class Parameter:
|
2026-02-18 08:50:20 +00:00
|
|
|
|
"""Класс для представления параметра с его формулой и зависимостями"""
|
|
|
|
|
|
|
|
|
|
|
|
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 = {} # Кэш для мемоизации
|
2026-02-18 08:37:45 +00:00
|
|
|
|
|
2026-02-18 08:50:20 +00:00
|
|
|
|
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]
|
2026-02-18 08:37:45 +00:00
|
|
|
|
|
2026-02-18 08:50:20 +00:00
|
|
|
|
# Вычисление
|
|
|
|
|
|
if self.dependencies:
|
|
|
|
|
|
# Подготовка аргументов для формулы
|
|
|
|
|
|
args = [x_values] + [param_values[dep] for dep in self.dependencies]
|
|
|
|
|
|
result = self.formula(*args)
|
2026-02-18 08:37:45 +00:00
|
|
|
|
else:
|
2026-02-18 08:50:20 +00:00
|
|
|
|
result = self.formula(x_values)
|
|
|
|
|
|
|
|
|
|
|
|
# Сохранение в кэш
|
|
|
|
|
|
self.cache[cache_key] = result
|
|
|
|
|
|
return result
|
2026-02-18 08:37:45 +00:00
|
|
|
|
|
2026-02-18 08:50:20 +00:00
|
|
|
|
def clear_cache(self):
|
|
|
|
|
|
"""Очистка кэша параметра"""
|
|
|
|
|
|
self.cache.clear()
|
2026-02-18 08:37:45 +00:00
|
|
|
|
|
|
|
|
|
|
|
2026-02-18 08:50:20 +00:00
|
|
|
|
class FormulaModel:
|
|
|
|
|
|
"""Основной класс для управления моделью с параметрами"""
|
2026-02-18 08:37:45 +00:00
|
|
|
|
|
|
|
|
|
|
def __init__(self):
|
|
|
|
|
|
self.parameters: Dict[str, Parameter] = {}
|
|
|
|
|
|
self.main_formula: Optional[Callable] = None
|
2026-02-18 08:50:20 +00:00
|
|
|
|
self.dependency_graph = nx.DiGraph()
|
2026-02-18 08:37:45 +00:00
|
|
|
|
|
2026-02-18 08:50:20 +00:00
|
|
|
|
def add_parameter(self, parameter: Parameter):
|
|
|
|
|
|
"""Добавление параметра в модель"""
|
|
|
|
|
|
self.parameters[parameter.name] = parameter
|
2026-02-18 08:37:45 +00:00
|
|
|
|
|
2026-02-18 08:50:20 +00:00
|
|
|
|
# Обновление графа зависимостей
|
|
|
|
|
|
self.dependency_graph.add_node(parameter.name)
|
|
|
|
|
|
for dep in parameter.dependencies:
|
|
|
|
|
|
self.dependency_graph.add_edge(dep, parameter.name)
|
2026-02-18 08:37:45 +00:00
|
|
|
|
|
2026-02-18 08:50:20 +00:00
|
|
|
|
# Проверка на циклические зависимости
|
|
|
|
|
|
if not nx.is_directed_acyclic_graph(self.dependency_graph):
|
|
|
|
|
|
raise ValueError(f"Циклическая зависимость обнаружена при добавлении параметра {parameter.name}")
|
2026-02-18 08:37:45 +00:00
|
|
|
|
|
2026-02-18 08:50:20 +00:00
|
|
|
|
def set_main_formula(self, formula: Callable, dependencies: List[str]):
|
|
|
|
|
|
"""Установка основной формулы модели"""
|
2026-02-18 08:37:45 +00:00
|
|
|
|
self.main_formula = formula
|
2026-02-18 08:50:20 +00:00
|
|
|
|
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()
|
2026-02-18 08:37:45 +00:00
|
|
|
|
|
2026-02-18 08:50:20 +00:00
|
|
|
|
for param_name in calculation_order:
|
|
|
|
|
|
if param_name in self.parameters:
|
2026-02-18 08:37:45 +00:00
|
|
|
|
param = self.parameters[param_name]
|
2026-02-18 08:50:20 +00:00
|
|
|
|
param_values[param_name] = param.calculate(x_values, param_values)
|
2026-02-18 08:37:45 +00:00
|
|
|
|
|
2026-02-18 08:50:20 +00:00
|
|
|
|
return param_values
|
|
|
|
|
|
|
|
|
|
|
|
def calculate_main_formula(self, x_values: np.ndarray) -> np.ndarray:
|
|
|
|
|
|
"""Вычисление основной формулы"""
|
|
|
|
|
|
if self.main_formula is None:
|
|
|
|
|
|
raise ValueError("Основная формула не установлена")
|
2026-02-18 08:37:45 +00:00
|
|
|
|
|
2026-02-18 08:50:20 +00:00
|
|
|
|
param_values = self.calculate_all_parameters(x_values)
|
2026-02-18 08:37:45 +00:00
|
|
|
|
|
2026-02-18 08:50:20 +00:00
|
|
|
|
# Подготовка аргументов для основной формулы
|
|
|
|
|
|
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:
|
|
|
|
|
|
"""Класс для построения графиков"""
|
2026-02-18 08:37:45 +00:00
|
|
|
|
|
2026-02-18 08:50:20 +00:00
|
|
|
|
def __init__(self, model: FormulaModel):
|
|
|
|
|
|
self.model = model
|
2026-02-18 08:37:45 +00:00
|
|
|
|
|
2026-02-18 08:50:20 +00:00
|
|
|
|
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} не найден")
|
2026-02-18 08:37:45 +00:00
|
|
|
|
|
2026-02-18 08:50:20 +00:00
|
|
|
|
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()
|
2026-02-18 08:37:45 +00:00
|
|
|
|
|
2026-02-18 08:50:20 +00:00
|
|
|
|
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))
|
2026-02-18 08:37:45 +00:00
|
|
|
|
|
2026-02-18 08:50:20 +00:00
|
|
|
|
for param_name, y_values in param_values.items():
|
|
|
|
|
|
plt.plot(x_values, y_values, label=param_name, linewidth=2)
|
2026-02-18 08:37:45 +00:00
|
|
|
|
|
2026-02-18 08:50:20 +00:00
|
|
|
|
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)
|
2026-02-18 08:37:45 +00:00
|
|
|
|
|
2026-02-18 08:50:20 +00:00
|
|
|
|
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)
|
2026-02-18 08:37:45 +00:00
|
|
|
|
|
2026-02-18 08:50:20 +00:00
|
|
|
|
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')
|
2026-02-18 08:37:45 +00:00
|
|
|
|
|
2026-02-18 08:50:20 +00:00
|
|
|
|
plt.title("Граф зависимостей параметров")
|
|
|
|
|
|
plt.show()
|
2026-02-18 08:37:45 +00:00
|
|
|
|
|
2026-02-18 08:50:20 +00:00
|
|
|
|
|
|
|
|
|
|
# Пример использования и заготовки функций
|
|
|
|
|
|
|
|
|
|
|
|
def create_example_model():
|
|
|
|
|
|
"""Создание примера модели для демонстрации"""
|
|
|
|
|
|
model = FormulaModel()
|
|
|
|
|
|
|
|
|
|
|
|
# Независимые параметры
|
|
|
|
|
|
param_a = Parameter(
|
|
|
|
|
|
name="A",
|
|
|
|
|
|
formula=lambda x: np.sin(x) + 2,
|
|
|
|
|
|
description="Синусоидальная функция",
|
|
|
|
|
|
units="м/с"
|
2026-02-18 08:37:45 +00:00
|
|
|
|
)
|
|
|
|
|
|
|
2026-02-18 08:50:20 +00:00
|
|
|
|
# Зависимый параметр (зависит от A)
|
|
|
|
|
|
param_b = Parameter(
|
|
|
|
|
|
name="B",
|
|
|
|
|
|
formula=lambda x, a: np.cos(x) * a,
|
|
|
|
|
|
dependencies=["A"],
|
|
|
|
|
|
description="Функция, зависящая от A",
|
|
|
|
|
|
units="м/с²"
|
2026-02-18 08:37:45 +00:00
|
|
|
|
)
|
|
|
|
|
|
|
2026-02-18 08:50:20 +00:00
|
|
|
|
# Параметр, зависящий от B
|
|
|
|
|
|
param_c = Parameter(
|
|
|
|
|
|
name="C",
|
|
|
|
|
|
formula=lambda x, b: np.exp(-x/10) * b,
|
|
|
|
|
|
dependencies=["B"],
|
|
|
|
|
|
description="Экспоненциальная функция от B",
|
|
|
|
|
|
units="Н"
|
2026-02-18 08:37:45 +00:00
|
|
|
|
)
|
|
|
|
|
|
|
2026-02-18 08:50:20 +00:00
|
|
|
|
# Параметр, зависящий от A и C
|
|
|
|
|
|
param_d = Parameter(
|
|
|
|
|
|
name="D",
|
|
|
|
|
|
formula=lambda x, a, c: (a**2 + c) / (1 + x**2),
|
|
|
|
|
|
dependencies=["A", "C"],
|
|
|
|
|
|
description="Комбинированная функция",
|
|
|
|
|
|
units="Дж"
|
2026-02-18 08:37:45 +00:00
|
|
|
|
)
|
|
|
|
|
|
|
2026-02-18 08:50:20 +00:00
|
|
|
|
# Независимый параметр
|
|
|
|
|
|
param_e = Parameter(
|
|
|
|
|
|
name="E",
|
|
|
|
|
|
formula=lambda x: np.log(1 + np.abs(x)),
|
|
|
|
|
|
description="Логарифмическая функция",
|
|
|
|
|
|
units="К"
|
2026-02-18 08:37:45 +00:00
|
|
|
|
)
|
|
|
|
|
|
|
2026-02-18 08:50:20 +00:00
|
|
|
|
# Добавление параметров в модель
|
|
|
|
|
|
for param in [param_a, param_b, param_c, param_d, param_e]:
|
|
|
|
|
|
model.add_parameter(param)
|
2026-02-18 08:37:45 +00:00
|
|
|
|
|
2026-02-18 08:50:20 +00:00
|
|
|
|
# Основная формула, использующая несколько параметров
|
|
|
|
|
|
model.set_main_formula(
|
|
|
|
|
|
formula=lambda x, a, c, e: a * c + e**2,
|
|
|
|
|
|
dependencies=["A", "C", "E"]
|
2026-02-18 08:37:45 +00:00
|
|
|
|
)
|
|
|
|
|
|
|
2026-02-18 08:50:20 +00:00
|
|
|
|
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
|
2026-02-18 08:37:45 +00:00
|
|
|
|
|
2026-02-18 08:50:20 +00:00
|
|
|
|
x_values = np.linspace(x_range[0], x_range[1], num_points)
|
2026-02-18 08:37:45 +00:00
|
|
|
|
|
2026-02-18 08:50:20 +00:00
|
|
|
|
start_time = time.time()
|
|
|
|
|
|
param_values = model.calculate_all_parameters(x_values)
|
|
|
|
|
|
end_time = time.time()
|
2026-02-18 08:37:45 +00:00
|
|
|
|
|
2026-02-18 08:50:20 +00:00
|
|
|
|
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)
|
2026-02-18 08:37:45 +00:00
|
|
|
|
|
2026-02-18 08:50:20 +00:00
|
|
|
|
# Анализ модели
|
|
|
|
|
|
analyze_model_complexity(model)
|
2026-02-18 08:37:45 +00:00
|
|
|
|
|
2026-02-18 08:50:20 +00:00
|
|
|
|
# Построение графиков
|
|
|
|
|
|
plotter.plot_dependency_graph()
|
|
|
|
|
|
plotter.plot_all_parameters()
|
|
|
|
|
|
plotter.plot_main_formula()
|
2026-02-18 08:37:45 +00:00
|
|
|
|
|
2026-02-18 08:50:20 +00:00
|
|
|
|
# Графики отдельных параметров
|
|
|
|
|
|
for param_name in ["A", "B", "C"]:
|
|
|
|
|
|
plotter.plot_parameter(param_name)
|
2026-02-18 08:37:45 +00:00
|
|
|
|
|
2026-02-18 08:50:20 +00:00
|
|
|
|
# Бенчмарк
|
|
|
|
|
|
benchmark_model(model)
|
|
|
|
|
|
|
2026-02-18 08:37:45 +00:00
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
|
main()
|