Files
help_lab/calculations/doe.py
T

353 lines
14 KiB
Python
Raw 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.
"""
Планирование эксперимента (DoE)
Модуль для генерации полнофакторных планов экспериментов
и анализа результатов.
Основные функции:
- generate_factorial_design() - генерация плана
- analyze_experiment() - статистический анализ
- calculate_factor_levels() - расчёт уровней факторов
"""
from typing import List, Dict, Tuple, Optional
import random
import numpy as np
# Типы расчёта шага
FACTOR_TYPES = {
'absolute': 'абс', # абсолютный шаг
'relative': '%', # относительный шаг (процент от нулевого уровня)
}
def calculate_factor_levels(
center_value: float,
step_value: float,
step_type: str,
base_value: float = None
) -> Tuple[float, float]:
"""
РАССЧИТЫВАЕТ ВЕРХНИЙ И НИЖНИЙ УРОВНИ ФАКТОРА
Параметры:
center_value: нулевой уровень фактора (центральная точка)
step_value: значение шага
step_type: тип шага ("абс" - абсолютный, "%" - относительный)
base_value: базовое значение для относительного шага (если None, используется center_value)
Возвращает:
(high_level, low_level): верхний и нижний уровни
Пример:
>>> calculate_factor_levels(100, 10, "%")
(110.0, 90.0)
>>> calculate_factor_levels(100, 20, "абс")
(120.0, 80.0)
"""
# Определяем абсолютное значение шага
if step_type == "%":
base = base_value if base_value is not None else center_value
step_abs = center_value * step_value / 100
else: # "абс" или "absolute"
step_abs = step_value
high_level = center_value + step_abs
low_level = center_value - step_abs
return high_level, low_level
def generate_factorial_design(
factors: List[Dict],
center_points: int = 3,
randomize: bool = True
) -> List[Dict]:
"""
ГЕНЕРИРУЕТ ПОЛНОФАКТОРНЫЙ ПЛАН ЭКСПЕРИМЕНТА
Создаёт матрицу планирования для 2^k полнофакторного эксперимента
с добавлением центральных точек.
ПАРАМЕТРЫ:
----------
factors : List[Dict]
Список факторов. Каждый фактор - словарь с ключами:
- name (str): название фактора
- low (float): нижний уровень (-1)
- high (float): верхний уровень (+1)
- center (float): нулевой уровень (0)
- unit (str): единица измерения
- step (float, опционально): шаг варьирования
- step_type (str, опционально): тип шага ("абс" или "%")
Минимально необходимые ключи: name, low, high, center, unit
center_points : int
Количество центральных точек (повторений в центре плана)
По умолчанию 3
randomize : bool
Перемешивать ли порядок опытов случайным образом
По умолчанию True
ВОЗВРАЩАЕТ:
-----------
List[Dict]
Список экспериментов. Каждый эксперимент - словарь:
- для каждого фактора: "Фактор_N" с полями:
- coded: кодированное значение (-1, 0, +1)
- natural: натуральное значение
- name: название фактора
- unit: единица измерения
- is_center (bool): является ли точка центральной
- center_num (int): номер центральной точки (если is_center)
ПРИМЕР ИСПОЛЬЗОВАНИЯ:
---------------------
>>> factors = [
... {'name': 'Температура', 'low': 25, 'high': 37, 'center': 31, 'unit': '°C'},
... {'name': 'pH', 'low': 6.5, 'high': 7.5, 'center': 7.0, 'unit': ''}
... ]
>>> design = generate_factorial_design(factors, center_points=2)
>>> print(len(design)) # 2^2 + 2 = 6
6
>>> design[0]['Фактор_1']['coded'] # первый фактор в первом опыте
-1
"""
k = len(factors)
if k == 0:
return []
n_factorial = 2 ** k
design = []
# Генерация факторных точек (все комбинации уровней)
for i in range(n_factorial):
experiment = {}
for j in range(k):
# Кодированный уровень: -1 для 0, +1 для 1 в бите
# (k-1-j) для правильного порядка факторов
coded_level = -1 if (i >> (k - 1 - j)) & 1 == 0 else 1
# Натуральное значение
natural_value = factors[j]['low'] if coded_level == -1 else factors[j]['high']
experiment[f"Фактор_{j+1}"] = {
'coded': coded_level,
'natural': natural_value,
'name': factors[j]['name'],
'unit': factors[j].get('unit', '')
}
design.append(experiment)
# Добавление центральных точек
for i in range(center_points):
center_experiment = {}
for j in range(k):
center_experiment[f"Фактор_{j+1}"] = {
'coded': 0,
'natural': factors[j]['center'],
'name': factors[j]['name'],
'unit': factors[j].get('unit', '')
}
center_experiment['is_center'] = True
center_experiment['center_num'] = i + 1
design.append(center_experiment)
# Перемешивание порядка
if randomize:
random.shuffle(design)
return design
def analyze_experiment(
results: List[List[float]],
design: List[Dict],
responses: List[Dict]
) -> Dict:
"""
ПРОВОДИТ СТАТИСТИЧЕСКИЙ АНАЛИЗ РЕЗУЛЬТАТОВ ЭКСПЕРИМЕНТА
ПАРАМЕТРЫ:
----------
results : List[List[float]]
Матрица результатов. Каждая строка - эксперимент,
каждый столбец - отклик.
Размер: [n_experiments, n_responses]
design : List[Dict]
План эксперимента, возвращённый generate_factorial_design()
responses : List[Dict]
Список откликов. Каждый отклик - словарь с ключами:
- name (str): название отклика
- unit (str): единица измерения
ВОЗВРАЩАЕТ:
-----------
Dict
Словарь с анализом для каждого отклика:
{
'Название отклика': {
'mean': float, # среднее значение всех опытов
'variance': float, # общая дисперсия
'std_dev': float, # стандартное отклонение
'cv': float, # коэффициент вариации (%)
'factorial_values': list, # значения в факторных точках
'center_values': list, # значения в центральных точках
'center_variance': float, # дисперсия воспроизводимости
'n_factorial': int, # количество факторных точек
'n_center': int, # количество центральных точек
'fisher_ratio': float, # критерий Фишера (если применимо)
'model_adequate': bool|None # адекватность модели
}
}
ПРИМЕР ИСПОЛЬЗОВАНИЯ:
---------------------
>>> factors = [{'name': 'X', 'low': 0, 'high': 10, 'center': 5, 'unit': ''}]
>>> design = generate_factorial_design(factors, center_points=2)
>>> results = [[2.0], [8.0], [5.1], [4.9]] # 2 факторные + 2 центральные
>>> responses = [{'name': 'Выход', 'unit': '%'}]
>>> analysis = analyze_experiment(results, design, responses)
>>> print(analysis['Выход']['mean'])
5.0
"""
analysis = {}
for resp_idx, response in enumerate(responses):
resp_name = response.get('name', f'Отклик_{resp_idx+1}')
# Собираем все значения для этого отклика
y_values = [results[i][resp_idx] for i in range(len(results))]
# Базовые статистики
mean_y = np.mean(y_values)
variance = np.var(y_values, ddof=1) if len(y_values) > 1 else 0
std_dev = np.std(y_values, ddof=1) if len(y_values) > 1 else 0
cv = (std_dev / mean_y) * 100 if mean_y != 0 else 0
# Разделяем факторные и центральные точки
factorial_y = []
center_y = []
for i, exp in enumerate(design):
if exp.get('is_center', False):
center_y.append(y_values[i])
else:
factorial_y.append(y_values[i])
# Анализ воспроизводимости
center_variance = np.var(center_y, ddof=1) if len(center_y) > 1 else 0
# Критерий Фишера для проверки адекватности модели
fisher_ratio = None
model_adequate = None
if len(center_y) > 1 and len(factorial_y) > 1:
factorial_variance = np.var(factorial_y, ddof=1)
if factorial_variance > 0 and center_variance > 0:
fisher_ratio = max(factorial_variance, center_variance) / min(factorial_variance, center_variance)
# Критическое значение F (приблизительное, для p=0.05)
# Более точное требует знания степеней свободы
model_adequate = fisher_ratio < 4.0
analysis[resp_name] = {
'mean': mean_y,
'variance': variance,
'std_dev': std_dev,
'cv': cv,
'factorial_values': factorial_y,
'center_values': center_y,
'center_variance': center_variance,
'n_factorial': len(factorial_y),
'n_center': len(center_y),
'fisher_ratio': fisher_ratio,
'model_adequate': model_adequate
}
return analysis
def create_factor_from_reagent(
reagent: Dict,
total_volume: float,
volume_unit: str,
step_percent: float = 10.0
) -> Dict:
"""
СОЗДАЁТ ФАКТОР ИЗ РЕАГЕНТА (для интеграции калькулятора и DoE)
Преобразует рассчитанный реагент в фактор для планирования эксперимента.
Параметры:
reagent: рассчитанный реагент (из calculate_medium_composition)
total_volume: общий объём среды
volume_unit: единица объёма
step_percent: шаг варьирования в процентах от нулевого уровня
Возвращает:
Dict: фактор для использования в generate_factorial_design()
"""
center_value = reagent.get('undiluted_amount', reagent.get('calculated_amount', 0))
step_value = center_value * step_percent / 100
high_level, low_level = calculate_factor_levels(
center_value, step_value, "абс"
)
return {
'name': reagent['name'],
'center': center_value,
'low': low_level,
'high': high_level,
'step': step_value,
'step_type': 'абс',
'unit': reagent.get('unit', volume_unit),
'percentage': reagent.get('percentage', 0),
'dilution_factor': reagent.get('dilution_factor', 1.0)
}
def create_factor_from_reagent(
reagent: Dict,
total_volume: float,
volume_unit: str,
step_percent: float = 10.0
) -> Dict:
"""
СОЗДАЁТ ФАКТОР ИЗ РЕАГЕНТА (для интеграции калькулятора и DoE)
Преобразует рассчитанный реагент в фактор для планирования эксперимента.
Параметры:
reagent: рассчитанный реагент (из calculate_medium_composition)
total_volume: общий объём среды
volume_unit: единица объёма
step_percent: шаг варьирования в процентах от нулевого уровня
Возвращает:
Dict: фактор для использования в generate_factorial_design()
"""
center_value = reagent.get('undiluted_amount', reagent.get('calculated_amount', 0))
step_value = center_value * step_percent / 100
high_level, low_level = calculate_factor_levels(
center_value, step_value, "абс"
)
return {
'name': reagent['name'],
'center': center_value,
'low': low_level,
'high': high_level,
'step': step_value,
'step_type': 'абс',
'unit': reagent.get('unit', volume_unit),
'percentage': reagent.get('percentage', 0),
'dilution_factor': reagent.get('dilution_factor', 1.0)
}