353 lines
14 KiB
Python
353 lines
14 KiB
Python
"""
|
||
Планирование эксперимента (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)
|
||
}
|