Теперь подсветка факторов эксперимента подсвечиваются соответственно значениям: + зелёным - красным, 0 (факторы с шагом в 0) - светлосерым. Убран лишний столбец в матрице факторного эксперимента
This commit is contained in:
+10
-95
@@ -84,56 +84,11 @@ def generate_factorial_design(
|
|||||||
randomize: bool = True
|
randomize: bool = True
|
||||||
) -> List[Dict]:
|
) -> List[Dict]:
|
||||||
"""
|
"""
|
||||||
ГЕНЕРИРУЕТ ПОЛНОФАКТОРНЫЙ ПЛАН ЭКСПЕРИМЕНТА
|
Генерирует полнофакторный план 2^k с правильным порядком изменения факторов:
|
||||||
|
- фактор 1 меняется через 1 эксперимент (2^0)
|
||||||
Создаёт матрицу планирования для 2^k полнофакторного эксперимента
|
- фактор 2 меняется через 2 эксперимента (2^1)
|
||||||
с добавлением центральных точек.
|
- фактор 3 меняется через 4 эксперимента (2^2)
|
||||||
|
- и т.д.
|
||||||
ПАРАМЕТРЫ:
|
|
||||||
----------
|
|
||||||
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)
|
k = len(factors)
|
||||||
@@ -143,15 +98,14 @@ def generate_factorial_design(
|
|||||||
n_factorial = 2 ** k
|
n_factorial = 2 ** k
|
||||||
design = []
|
design = []
|
||||||
|
|
||||||
# Генерация факторных точек (все комбинации уровней)
|
# Генерация факторных точек в правильном порядке (двоичный счётчик)
|
||||||
for i in range(n_factorial):
|
for i in range(n_factorial):
|
||||||
experiment = {}
|
experiment = {}
|
||||||
for j in range(k):
|
for j in range(k):
|
||||||
# Кодированный уровень: -1 для 0, +1 для 1 в бите
|
# Правильный порядок битов: младший бит - первый фактор
|
||||||
# (k-1-j) для правильного порядка факторов
|
# (j) - для прямого порядка: фактор 1 меняется чаще всего
|
||||||
coded_level = -1 if (i >> (k - 1 - j)) & 1 == 0 else 1
|
coded_level = -1 if (i >> j) & 1 == 0 else 1
|
||||||
|
|
||||||
# Натуральное значение
|
|
||||||
natural_value = factors[j]['low'] if coded_level == -1 else factors[j]['high']
|
natural_value = factors[j]['low'] if coded_level == -1 else factors[j]['high']
|
||||||
|
|
||||||
experiment[f"Фактор_{j+1}"] = {
|
experiment[f"Фактор_{j+1}"] = {
|
||||||
@@ -165,7 +119,6 @@ def generate_factorial_design(
|
|||||||
# Добавление центральных точек
|
# Добавление центральных точек
|
||||||
for i in range(center_points):
|
for i in range(center_points):
|
||||||
center_experiment = {}
|
center_experiment = {}
|
||||||
|
|
||||||
for j in range(k):
|
for j in range(k):
|
||||||
center_experiment[f"Фактор_{j+1}"] = {
|
center_experiment[f"Фактор_{j+1}"] = {
|
||||||
'coded': 0,
|
'coded': 0,
|
||||||
@@ -177,7 +130,7 @@ def generate_factorial_design(
|
|||||||
center_experiment['center_num'] = i + 1
|
center_experiment['center_num'] = i + 1
|
||||||
design.append(center_experiment)
|
design.append(center_experiment)
|
||||||
|
|
||||||
# Перемешивание порядка
|
# Перемешивание порядка (опционально)
|
||||||
if randomize:
|
if randomize:
|
||||||
random.shuffle(design)
|
random.shuffle(design)
|
||||||
|
|
||||||
@@ -294,44 +247,6 @@ def analyze_experiment(
|
|||||||
return analysis
|
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(
|
def create_factor_from_reagent(
|
||||||
reagent: Dict,
|
reagent: Dict,
|
||||||
|
|||||||
@@ -553,10 +553,9 @@ class MainWindow(QMainWindow):
|
|||||||
"""Генерирует план эксперимента"""
|
"""Генерирует план эксперимента"""
|
||||||
all_factors = self._get_factors_from_table()
|
all_factors = self._get_factors_from_table()
|
||||||
factors = get_active_factors(all_factors)
|
factors = get_active_factors(all_factors)
|
||||||
# Выбираем только те факторы, шаг которых не равен нулю
|
|
||||||
i_factors = get_inactive_factors(all_factors)
|
i_factors = get_inactive_factors(all_factors)
|
||||||
# Отдельно сохраняем неактивные факторы
|
|
||||||
if len(factors) == 0:
|
if not factors:
|
||||||
QMessageBox.warning(self, "Предупреждение", "Добавьте хотя бы один фактор!")
|
QMessageBox.warning(self, "Предупреждение", "Добавьте хотя бы один фактор!")
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -567,75 +566,14 @@ class MainWindow(QMainWindow):
|
|||||||
randomize=self.randomize_check.isChecked()
|
randomize=self.randomize_check.isChecked()
|
||||||
)
|
)
|
||||||
self.generated_design = design
|
self.generated_design = design
|
||||||
|
self._fill_design_matrix(design, factors, i_factors)
|
||||||
n_exp = len(design)
|
|
||||||
n_factors = len(factors)
|
|
||||||
n_i_factors = len(i_factors)
|
|
||||||
solvent_name = self.exp_solvent.text()
|
|
||||||
total_volume = self.exp_total_volume.value()
|
|
||||||
solvent_unit = self.exp_volume_unit.currentText()
|
|
||||||
self.design_matrix.setRowCount(n_exp)
|
|
||||||
self.design_matrix.setColumnCount(n_factors + 4)
|
|
||||||
headers = [f['name'] for f in factors] + [f['name'] for f in i_factors] + [solvent_name] +["Тип"] +["Отклик"]
|
|
||||||
self.design_matrix.setHorizontalHeaderLabels(headers)
|
|
||||||
|
|
||||||
for exp_idx, exp in enumerate(design):
|
|
||||||
solvent = convert_units(total_volume,solvent_unit)
|
|
||||||
for f_idx in range(n_factors):
|
|
||||||
key = f"Фактор_{f_idx+1}"
|
|
||||||
if key not in exp:
|
|
||||||
continue
|
|
||||||
value = exp[key]['natural']
|
|
||||||
unit = factors[f_idx]['unit']
|
|
||||||
solvent -= convert_units(value,unit)
|
|
||||||
display = self._format_number(value)
|
|
||||||
if unit:
|
|
||||||
display += f" {unit}"
|
|
||||||
|
|
||||||
item = QTableWidgetItem(display)
|
|
||||||
if exp.get('is_center', False):
|
|
||||||
item.setBackground(QColor(255, 255, 200))
|
|
||||||
self.design_matrix.setItem(exp_idx, f_idx, item)
|
|
||||||
if n_i_factors>0:
|
|
||||||
for f_idx in range(n_i_factors):
|
|
||||||
value = i_factors[f_idx]['center']
|
|
||||||
unit = i_factors[f_idx]['unit']
|
|
||||||
display = self._format_number(value)
|
|
||||||
if unit:
|
|
||||||
display += f" {unit}"
|
|
||||||
item = QTableWidgetItem(display)
|
|
||||||
|
|
||||||
item.setBackground(QColor(255, 255, 200))
|
|
||||||
self.design_matrix.setItem(exp_idx, n_factors + f_idx, item)
|
|
||||||
solvent = convert_units(solvent, "мкл", solvent_unit)
|
|
||||||
display = self._format_number(solvent)
|
|
||||||
if solvent_unit:
|
|
||||||
display += f" {solvent_unit}"
|
|
||||||
item = QTableWidgetItem(display)
|
|
||||||
self.design_matrix.setItem(exp_idx, n_factors+n_i_factors, item)
|
|
||||||
if exp.get('is_center', False):
|
|
||||||
type_item = QTableWidgetItem(f"Центр #{exp['center_num']}")
|
|
||||||
type_item.setBackground(QColor(255, 255, 200))
|
|
||||||
else:
|
|
||||||
type_item = QTableWidgetItem("Факторная")
|
|
||||||
self.design_matrix.setItem(exp_idx, n_factors+n_i_factors + 1, type_item)
|
|
||||||
|
|
||||||
self.design_matrix.resizeColumnsToContents()
|
|
||||||
|
|
||||||
n_factorial = 2 ** n_factors
|
|
||||||
n_center = self.center_points_spin.value()
|
|
||||||
self.design_info.setText(
|
|
||||||
f"📊 Факторных точек: {n_factorial}, Центральных: {n_center}, Всего: {n_exp}"
|
|
||||||
)
|
|
||||||
|
|
||||||
self.export_csv_btn.setEnabled(True)
|
self.export_csv_btn.setEnabled(True)
|
||||||
self._setup_results_table(n_exp)
|
QMessageBox.information(self, "Успех", f"Сгенерирован план для {len(factors)} факторов ({len(design)} опытов)")
|
||||||
|
|
||||||
QMessageBox.information(self, "Успех",
|
|
||||||
f"Сгенерирован план для {n_factors} факторов ({n_exp} опытов)")
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
QMessageBox.critical(self, "Ошибка", f"Ошибка генерации плана: {str(e)}")
|
QMessageBox.critical(self, "Ошибка", f"Ошибка генерации плана: {str(e)}")
|
||||||
|
|
||||||
|
|
||||||
def _setup_results_table(self, n_experiments: int):
|
def _setup_results_table(self, n_experiments: int):
|
||||||
"""Настраивает таблицу результатов для ввода данных"""
|
"""Настраивает таблицу результатов для ввода данных"""
|
||||||
# Получаем отклики (для простоты используем один отклик)
|
# Получаем отклики (для простоты используем один отклик)
|
||||||
@@ -979,43 +917,15 @@ class MainWindow(QMainWindow):
|
|||||||
if not self.generated_design:
|
if not self.generated_design:
|
||||||
return
|
return
|
||||||
|
|
||||||
factors = self._get_factors_from_table()
|
all_factors = self._get_factors_from_table()
|
||||||
|
factors = get_active_factors(all_factors)
|
||||||
|
i_factors = get_inactive_factors(all_factors)
|
||||||
|
|
||||||
|
self._fill_design_matrix(self.generated_design, factors, i_factors)
|
||||||
|
|
||||||
n_exp = len(self.generated_design)
|
n_exp = len(self.generated_design)
|
||||||
n_factors = len(factors)
|
n_factors = len(factors)
|
||||||
|
|
||||||
self.design_matrix.setRowCount(n_exp)
|
|
||||||
self.design_matrix.setColumnCount(n_factors + 2)
|
|
||||||
headers = ["№"] + [f['name'] for f in factors] + ["Тип"]
|
|
||||||
self.design_matrix.setHorizontalHeaderLabels(headers)
|
|
||||||
|
|
||||||
for exp_idx, exp in enumerate(self.generated_design):
|
|
||||||
self.design_matrix.setItem(exp_idx, 0, QTableWidgetItem(str(exp_idx + 1)))
|
|
||||||
|
|
||||||
for f_idx in range(n_factors):
|
|
||||||
key = f"Фактор_{f_idx + 1}"
|
|
||||||
if key not in exp:
|
|
||||||
continue
|
|
||||||
value = exp[key]['natural']
|
|
||||||
unit = factors[f_idx]['unit']
|
|
||||||
|
|
||||||
display = self._format_number(value)
|
|
||||||
if unit:
|
|
||||||
display += f" {unit}"
|
|
||||||
|
|
||||||
item = QTableWidgetItem(display)
|
|
||||||
if exp.get('is_center', False):
|
|
||||||
item.setBackground(QColor(255, 255, 200))
|
|
||||||
self.design_matrix.setItem(exp_idx, f_idx + 1, item)
|
|
||||||
|
|
||||||
if exp.get('is_center', False):
|
|
||||||
type_item = QTableWidgetItem(f"Центр #{exp['center_num']}")
|
|
||||||
type_item.setBackground(QColor(255, 255, 200))
|
|
||||||
else:
|
|
||||||
type_item = QTableWidgetItem("Факторная")
|
|
||||||
self.design_matrix.setItem(exp_idx, n_factors + 1, type_item)
|
|
||||||
|
|
||||||
self.design_matrix.resizeColumnsToContents()
|
|
||||||
|
|
||||||
if hasattr(self, 'export_csv_btn'):
|
if hasattr(self, 'export_csv_btn'):
|
||||||
self.export_csv_btn.setEnabled(True)
|
self.export_csv_btn.setEnabled(True)
|
||||||
|
|
||||||
@@ -1028,6 +938,74 @@ class MainWindow(QMainWindow):
|
|||||||
|
|
||||||
# ========== ВСПОМОГАТЕЛЬНЫЕ МЕТОДЫ ==========
|
# ========== ВСПОМОГАТЕЛЬНЫЕ МЕТОДЫ ==========
|
||||||
|
|
||||||
|
def _fill_design_matrix(self, design, factors, i_factors):
|
||||||
|
"""Заполняет матрицу планирования (общая логика)"""
|
||||||
|
n_exp = len(design)
|
||||||
|
n_factors = len(factors)
|
||||||
|
n_i_factors = len(i_factors)
|
||||||
|
solvent_name = self.exp_solvent.text()
|
||||||
|
total_volume = self.exp_total_volume.value()
|
||||||
|
solvent_unit = self.exp_volume_unit.currentText()
|
||||||
|
|
||||||
|
self.design_matrix.setRowCount(n_exp)
|
||||||
|
self.design_matrix.setColumnCount(n_factors + n_i_factors + 3)
|
||||||
|
headers = [f['name'] for f in factors] + [f['name'] for f in i_factors] + [solvent_name] + ["Тип"] + ["Отклик"]
|
||||||
|
self.design_matrix.setHorizontalHeaderLabels(headers)
|
||||||
|
|
||||||
|
for exp_idx, exp in enumerate(design):
|
||||||
|
remaining = convert_units(total_volume, solvent_unit)
|
||||||
|
|
||||||
|
# Активные факторы
|
||||||
|
for f_idx in range(n_factors):
|
||||||
|
value = exp[f"Фактор_{f_idx+1}"]['natural']
|
||||||
|
unit = factors[f_idx]['unit']
|
||||||
|
remaining -= convert_units(value, unit)
|
||||||
|
item = self._create_item(value, unit, exp.get('is_center', False))
|
||||||
|
if value == factors[f_idx]['high']:
|
||||||
|
item.setBackground(QColor(200, 250, 200))
|
||||||
|
elif value ==factors[f_idx]['low']:
|
||||||
|
item.setBackground(QColor(250, 200, 200))
|
||||||
|
else:
|
||||||
|
item.setBackground(QColor(230, 230, 230))
|
||||||
|
self.design_matrix.setItem(exp_idx, f_idx, item)
|
||||||
|
|
||||||
|
# Неактивные факторы
|
||||||
|
for f_idx in range(n_i_factors):
|
||||||
|
value = i_factors[f_idx]['center']
|
||||||
|
unit = i_factors[f_idx]['unit']
|
||||||
|
remaining -= convert_units(value, unit)
|
||||||
|
item = self._create_item(value, unit, True)
|
||||||
|
item.setBackground(QColor(230, 230, 230))
|
||||||
|
self.design_matrix.setItem(exp_idx, n_factors + f_idx, item)
|
||||||
|
|
||||||
|
# Растворитель
|
||||||
|
remaining = convert_units(remaining, 'мкл', solvent_unit)
|
||||||
|
item = self._create_item(remaining, solvent_unit, False)
|
||||||
|
item.setBackground(QColor(200, 200, 255))
|
||||||
|
self.design_matrix.setItem(exp_idx, n_factors + n_i_factors, item)
|
||||||
|
|
||||||
|
# Тип опыта
|
||||||
|
type_item = QTableWidgetItem(f"Центр #{exp['center_num']}" if exp.get('is_center') else "Факторная")
|
||||||
|
if exp.get('is_center'):
|
||||||
|
type_item.setBackground(QColor(200, 200, 200))
|
||||||
|
self.design_matrix.setItem(exp_idx, n_factors + n_i_factors + 1, type_item)
|
||||||
|
|
||||||
|
# Отклик
|
||||||
|
self.design_matrix.setItem(exp_idx, n_factors + n_i_factors + 2, QTableWidgetItem(""))
|
||||||
|
|
||||||
|
self.design_matrix.resizeColumnsToContents()
|
||||||
|
|
||||||
|
def _create_item(self, value, unit, is_center):
|
||||||
|
"""Создаёт QTableWidgetItem с форматированием"""
|
||||||
|
display = self._format_number(value)
|
||||||
|
if unit:
|
||||||
|
display += f" {unit}"
|
||||||
|
item = QTableWidgetItem(display)
|
||||||
|
if is_center:
|
||||||
|
item.setBackground(QColor(255, 255, 200))
|
||||||
|
return item
|
||||||
|
|
||||||
|
|
||||||
def _format_number(self, value: float) -> str:
|
def _format_number(self, value: float) -> str:
|
||||||
"""Форматирует число для отображения"""
|
"""Форматирует число для отображения"""
|
||||||
if value == int(value):
|
if value == int(value):
|
||||||
|
|||||||
+12
@@ -0,0 +1,12 @@
|
|||||||
|
Меласса (разб. ×5.00),Глицерин,Этанол,Лимонная кислота,Вода,Тип,Отклик
|
||||||
|
2.25 мл,0.45 г,0.3 мл,0.06 г,26.94 мл,Факторная,
|
||||||
|
5.25 мл,0.45 г,0.3 мл,0.06 г,23.94 мл,Факторная,
|
||||||
|
2.25 мл,0.75 г,0.3 мл,0.06 г,26.64 мл,Факторная,
|
||||||
|
5.25 мл,0.75 г,0.3 мл,0.06 г,23.64 мл,Факторная,
|
||||||
|
2.25 мл,0.45 г,0.6 мл,0.06 г,26.64 мл,Факторная,
|
||||||
|
5.25 мл,0.45 г,0.6 мл,0.06 г,23.64 мл,Факторная,
|
||||||
|
2.25 мл,0.75 г,0.6 мл,0.06 г,26.34 мл,Факторная,
|
||||||
|
5.25 мл,0.75 г,0.6 мл,0.06 г,23.34 мл,Факторная,
|
||||||
|
3.75 мл,0.6 г,0.45 мл,0.06 г,25.14 мл,Центр #1,
|
||||||
|
3.75 мл,0.6 г,0.45 мл,0.06 г,25.14 мл,Центр #2,
|
||||||
|
3.75 мл,0.6 г,0.45 мл,0.06 г,25.14 мл,Центр #3,
|
||||||
|
Reference in New Issue
Block a user