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