# plan.py """ Планирование эксперимента. Позволяет задать шаги для факторов, сгенерировать план различных типов, показать матрицу плана, экспериментальные смеси и сохранить дизайн в проект. """ from core.experiment import ExperimentDesign from core.fs import save_project_partial from core.doe import ze, ccp, fractional_factorial, plackett_burman, box_behnken from core.table_io import handle_table_output def plan_menu(mixture, design): if design is None: design = ExperimentDesign() while True: print("\n" + "=" * 50) print("ПЛАНИРОВАНИЕ ЭКСПЕРИМЕНТА") print("1. Задать шаги для факторов (ингредиентов, кроме растворителя)") print("2. Сгенерировать план (выбор типа)") print("3. Показать матрицу плана") print("4. Показать экспериментальные смеси (абсолютные значения)") print("5. Сохранить дизайн в проект") print("(Enter - выход в главное меню)") print("=" * 50) choice = input("Выберите пункт: ").strip() if choice == "": break if choice == "1": num_factors = len(mixture.ings) - 1 if num_factors <= 0: print("В смеси нет изменяемых ингредиентов (только растворитель).") continue print(f"Введите шаги для {num_factors} факторов (в %). Введите 0, если фактор не используется:") steps = [] for i in range(1, len(mixture.ings)): name = mixture.ings[i].name while True: val = input(f"Шаг для {name} (текущий % = {mixture.ings[i].value:.1f}): ") try: s = float(val) if s < 0: print("Шаг не может быть отрицательным. Введите 0 или положительное число.") continue steps.append(s) break except ValueError: print("Введите число.") design.set_steps(steps) active = design._active_steps() print(f"Шаги установлены. Активных факторов (шаг > 0): {len(active)}") if len(active) < 2: print("Предупреждение: для генерации плана необходимо минимум 2 активных фактора.") elif choice == "2": active_steps = design._active_steps() if len(active_steps) < 2: print(f"Недостаточно активных факторов (шаг > 0): {len(active_steps)}. Требуется минимум 2.") continue k = len(active_steps) print("\nВыберите тип плана:") print(" 1. Полный факторный план 2^k") print(" 2. Центральный композиционный план (ЦКП)") print(" 3. Дробный факторный план 2^(k-p)") print(" 4. План Плакетта-Бермана") print(" 5. План Бокса-Бенкена") plan_type = input("Ваш выбор (по умолчанию 1): ").strip() if plan_type == "": plan_type = "1" if plan_type == "1": print(f"Активных факторов: {k}. Будет сгенерирован план 2^{k} = {2**k} опытов.") try: design.generate_plan() print(f"Основной план сгенерирован: {len(design.plan)} опытов.") except ValueError as e: print(f"Ошибка: {e}") continue try: num_center = input("Введите количество дополнительных центральных точек (0, если не нужно): ").strip() if num_center == "": num_center = "0" num_center = int(num_center) if num_center < 0: print("Количество не может быть отрицательным.") elif num_center > 0: center_rows = ze(k, num_center) if center_rows: design.plan.extend(center_rows) print(f"Добавлено {num_center} центральных точек. Всего опытов: {len(design.plan)}") else: print("Ошибка генерации центральных точек.") except ValueError: print("Ошибка: введите целое число.") elif plan_type == "2": print("Центральный композиционный план (ЦКП)") try: alpha_input = input("Введите значение альфа (звёздное плечо, по умолчанию 1.0): ").strip() alpha = float(alpha_input) if alpha_input else 1.0 except ValueError: print("Ошибка ввода, используется alpha=1.0") alpha = 1.0 try: center_input = input("Введите количество центральных точек (по умолчанию 1): ").strip() center_points = int(center_input) if center_input else 1 if center_points < 0: print("Количество не может быть отрицательным, установлено 1.") center_points = 1 except ValueError: print("Ошибка ввода, используется 1 центральная точка.") center_points = 1 use_fractional = input("Использовать дробное ядро? (y/n, по умолчанию n): ").strip().lower() fractional = None if use_fractional == 'y': try: p = int(input("Введите степень дробления p (1 или больше): ").strip()) if 0 < p < k: if k - p < 2: print(f"Ошибка: при k={k} и p={p} ядро будет содержать {k-p} факторов, что меньше 2. Используйте p <= {k-2}.") continue fractional = p print(f"Будет использовано дробное ядро 2^{k-p}.") else: print("Некорректное p, используем полное ядро.") except ValueError: print("Ошибка ввода, используем полное ядро.") try: design.plan = ccp(k, alpha, center_points, fractional) if fractional: print(f"ЦКП с дробным ядром 2^{k-fractional} сгенерирован. Опытов: {len(design.plan)}") else: print(f"ЦКП с полным ядром сгенерирован. Опытов: {len(design.plan)}") except Exception as e: print(f"Ошибка генерации ЦКП: {e}") elif plan_type == "3": print("Дробный факторный план 2^(k-p)") try: p_input = input("Введите степень дробления p (по умолчанию 1): ").strip() p = int(p_input) if p_input else 1 if p <= 0 or p >= k: print("p должно быть от 1 до k-1. Используем p=1.") p = 1 if k - p < 2: print(f"Ошибка: при k={k} и p={p} ядро будет содержать {k-p} факторов, что меньше 2. Используйте p <= {k-2}.") continue design.plan = fractional_factorial(k, p) print(f"Дробный факторный план 2^{k-p} сгенерирован. Опытов: {len(design.plan)}") except Exception as e: print(f"Ошибка: {e}") elif plan_type == "4": print("План Плакетта-Бермана") try: design.plan = plackett_burman(k) print(f"План Плакетта-Бермана сгенерирован. Опытов: {len(design.plan)}") except Exception as e: print(f"Ошибка: {e}") elif plan_type == "5": print("План Бокса-Бенкена") try: if k < 3: print("План Бокса-Бенкена требует минимум 3 фактора.") continue design.plan = box_behnken(k) print(f"План Бокса-Бенкена сгенерирован. Опытов: {len(design.plan)}") except Exception as e: print(f"Ошибка: {e}") else: print("Неверный тип плана. План не сгенерирован.") elif choice == "3": if not design.plan: print("План не сгенерирован.") continue active_indices = design._active_indices() headers = ["Опыт"] + [mixture.ings[idx+1].name for idx in active_indices] rows = [] for i, row in enumerate(design.plan, start=1): rows.append([str(i)] + [str(v) for v in row]) handle_table_output(headers, rows, title="Матрица плана") elif choice == "4": if not design.plan or not design.steps: print("План или шаги не заданы.") continue try: exp_mixtures = design.get_all_mixtures(mixture) headers = ["Опыт"] + [f"{info.name} ({info.unit})" for info in mixture.ings] rows = [] for i, exp_mix in enumerate(exp_mixtures, start=1): abs_ings = exp_mix.get_absolute_ingredients() rows.append([str(i)] + [f"{ing.value:.2f}" for ing in abs_ings]) handle_table_output(headers, rows, title="Экспериментальные смеси (абсолютные значения)") except Exception as e: print(f"Ошибка: {e}") elif choice == "5": save_project_partial(design=design) print("Дизайн сохранён в проект.") else: print("Неверный пункт.") return design