Files
mixture_optimizer/plan.py
T

216 lines
12 KiB
Python

# 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