Files

100 lines
4.0 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# core/experiment.py
"""
Класс ExperimentDesign для хранения шагов и матрицы плана,
а также генерации экспериментальных смесей.
"""
from core.classes import Mixture, Ingredient, IngredientInfo
class ExperimentDesign:
def __init__(self, steps=None, plan=None):
self.steps = steps if steps is not None else []
self.plan = plan if plan is not None else []
def set_steps(self, steps):
"""
Устанавливает шаги для всех факторов (ингредиентов кроме растворителя).
Каждый шаг должен быть >= 0. Нулевые шаги означают, что данный ингредиент не является фактором.
"""
for s in steps:
if s < 0:
raise ValueError("Step cannot be negative")
self.steps = list(steps)
def _active_indices(self):
"""Возвращает список индексов (в self.steps) с шагом > 0."""
return [i for i, s in enumerate(self.steps) if s > 0]
def _active_steps(self):
"""Возвращает список шагов > 0."""
return [s for s in self.steps if s > 0]
def generate_plan(self, k=None):
"""
Генерирует полный факторный план 2^k, где k - число активных факторов (шагов > 0).
Если k не указано, вычисляется автоматически.
Если активных факторов < 2, выбрасывает ValueError.
"""
active = self._active_steps()
if k is None:
k = len(active)
if k < 2:
raise ValueError(f"Need at least 2 active factors (steps > 0), but got {k}")
from core.doe import ffe
self.plan = ffe(k)
def get_experiment_mixture(self, mixture, exp_index):
"""
Возвращает Mixture для опыта с индексом exp_index (0-based).
Изменяются только те ингредиенты, у которых шаг > 0.
"""
if not self.plan or not self.steps:
raise ValueError("Plan or steps not set")
if exp_index >= len(self.plan):
raise IndexError("Experiment index out of range")
active_indices = self._active_indices()
active_steps = self._active_steps()
if len(active_indices) != len(self.plan[0]):
raise ValueError("Number of active steps does not match plan columns")
base_percents = [info.value for info in mixture.ings]
new_percents = base_percents.copy()
for col, idx_in_steps in enumerate(active_indices):
factor = self.plan[exp_index][col]
ingredient_idx = idx_in_steps + 1
delta = active_steps[col] * factor
new_percents[ingredient_idx] = base_percents[ingredient_idx] + delta
new_ings = []
for i, info in enumerate(mixture.ings):
new_ings.append(
IngredientInfo(
ingredient=Ingredient(name=info.name, unit=info.unit, value=new_percents[i]),
dilution=info.dilution,
density=info.density
)
)
temp_mixture = Mixture(
solvent=new_ings[0],
ings=new_ings[1:],
total_amount=mixture.total_amount,
total_unit=mixture.total_unit
)
return temp_mixture
def get_all_mixtures(self, mixture):
"""Возвращает список Mixture для всех опытов."""
return [self.get_experiment_mixture(mixture, i) for i in range(len(self.plan))]
def to_dict(self):
return {
"steps": self.steps,
"plan": self.plan
}
@classmethod
def from_dict(cls, data):
return cls(steps=data.get("steps", []), plan=data.get("plan", []))