Оптимизмрован код, исправлены параметры отображения факторов экспримента

This commit is contained in:
2026-05-26 22:50:16 +05:00
parent 63b5f0a49f
commit 1ddfe20a8d
3 changed files with 75 additions and 100 deletions
+8 -8
View File
@@ -16,7 +16,7 @@ import numpy as np
# Типы расчёта шага # Типы расчёта шага
FACTOR_TYPES = { FACTOR_TYPES = {
'absolute': 'абс', # абсолютный шаг 'absolute': 'ед.', # абсолютный шаг
'relative': '%', # относительный шаг (процент от нулевого уровня) 'relative': '%', # относительный шаг (процент от нулевого уровня)
} }
@@ -33,7 +33,7 @@ def calculate_factor_levels(
Параметры: Параметры:
center_value: нулевой уровень фактора (центральная точка) center_value: нулевой уровень фактора (центральная точка)
step_value: значение шага step_value: значение шага
step_type: тип шага ("абс" - абсолютный, "%" - относительный) step_type: тип шага ("ед." - абсолютный, "%" - относительный)
base_value: базовое значение для относительного шага (если None, используется center_value) base_value: базовое значение для относительного шага (если None, используется center_value)
Возвращает: Возвращает:
@@ -42,14 +42,14 @@ def calculate_factor_levels(
Пример: Пример:
>>> calculate_factor_levels(100, 10, "%") >>> calculate_factor_levels(100, 10, "%")
(110.0, 90.0) (110.0, 90.0)
>>> calculate_factor_levels(100, 20, "абс") >>> calculate_factor_levels(100, 20, "ед.")
(120.0, 80.0) (120.0, 80.0)
""" """
# Определяем абсолютное значение шага # Определяем абсолютное значение шага
if step_type == "%": if step_type == "%":
base = base_value if base_value is not None else center_value base = base_value if base_value is not None else center_value
step_abs = center_value * step_value / 100 step_abs = center_value * step_value / 100
else: # "абс" или "absolute" else: # "ед." или "absolute"
step_abs = step_value step_abs = step_value
high_level = center_value + step_abs high_level = center_value + step_abs
@@ -79,7 +79,7 @@ def generate_factorial_design(
- center (float): нулевой уровень (0) - center (float): нулевой уровень (0)
- unit (str): единица измерения - unit (str): единица измерения
- step (float, опционально): шаг варьирования - step (float, опционально): шаг варьирования
- step_type (str, опционально): тип шага ("абс" или "%") - step_type (str, опционально): тип шага ("ед." или "%")
Минимально необходимые ключи: name, low, high, center, unit Минимально необходимые ключи: name, low, high, center, unit
@@ -297,7 +297,7 @@ def create_factor_from_reagent(
step_value = center_value * step_percent / 100 step_value = center_value * step_percent / 100
high_level, low_level = calculate_factor_levels( high_level, low_level = calculate_factor_levels(
center_value, step_value, "абс" center_value, step_value, "ед."
) )
return { return {
@@ -306,7 +306,7 @@ def create_factor_from_reagent(
'low': low_level, 'low': low_level,
'high': high_level, 'high': high_level,
'step': step_value, 'step': step_value,
'step_type': 'абс', 'step_type': 'ед.',
'unit': reagent.get('unit', volume_unit), 'unit': reagent.get('unit', volume_unit),
'percentage': reagent.get('percentage', 0), 'percentage': reagent.get('percentage', 0),
'dilution_factor': reagent.get('dilution_factor', 1.0) 'dilution_factor': reagent.get('dilution_factor', 1.0)
@@ -336,7 +336,7 @@ def create_factor_from_reagent(
step_value = center_value * step_percent / 100 step_value = center_value * step_percent / 100
high_level, low_level = calculate_factor_levels( high_level, low_level = calculate_factor_levels(
center_value, step_value, "абс" center_value, step_value, "ед."
) )
return { return {
+53 -84
View File
@@ -189,24 +189,22 @@ class MainWindow(QMainWindow):
return tab return tab
def _add_reagent_row(self): def _add_reagent_row(self, name = "", percentage = "0", unit = "мл", dilution = "1", amount = ""):
row = self.reagents_table.rowCount() row = self.reagents_table.rowCount()
self.reagents_table.insertRow(row) self.reagents_table.insertRow(row)
if name == "":
self.reagents_table.setItem(row, 0, QTableWidgetItem(f"Реагент_{row+1}")) self.reagents_table.setItem(row, 0, QTableWidgetItem(f"Реагент_{row+1}"))
self.reagents_table.setItem(row, 1, QTableWidgetItem("0")) else:
self.reagents_table.setItem(row, 0, QTableWidgetItem(name))
self.reagents_table.setItem(row, 1, QTableWidgetItem(percentage))
unit_combo = QComboBox() unit_combo = QComboBox()
unit_combo.setStyleSheet(""" unit_combo.addItems(["мг", "г", "кг", "мкг", "нг", "мл", "мкл", "л"])
QComboBox { unit_combo.setCurrentText(unit)
padding: 1px;
}
""")
unit_combo.addItems(["мг", "г", "кг", "мкг", "нг", "мл", "мкл", "л", "нл"])
unit_combo.setCurrentText("мл")
self.reagents_table.setCellWidget(row, 2, unit_combo) self.reagents_table.setCellWidget(row, 2, unit_combo)
self.reagents_table.setItem(row, 3, QTableWidgetItem("1")) self.reagents_table.setItem(row, 3, QTableWidgetItem(dilution))
self.reagents_table.setItem(row, 4, QTableWidgetItem("")) self.reagents_table.setItem(row, 4, QTableWidgetItem(amount))
def _remove_reagent_row(self): def _remove_reagent_row(self):
for row in sorted(set(i.row() for i in self.reagents_table.selectedItems()), reverse=True): for row in sorted(set(i.row() for i in self.reagents_table.selectedItems()), reverse=True):
@@ -280,13 +278,12 @@ class MainWindow(QMainWindow):
# Сохраняем результаты для передачи в DoE # Сохраняем результаты для передачи в DoE
self.last_medium_result = result self.last_medium_result = result
QMessageBox.information(self, "Успех", "Расчёт выполнен успешно!") # QMessageBox.information(self, "Успех", "Расчёт выполнен успешно!")
except Exception as e: except Exception as e:
QMessageBox.critical(self, "Ошибка", str(e)) QMessageBox.critical(self, "Ошибка", str(e))
# ========== ВКЛАДКА 2: ФАКТОРЫ ЭКСПЕРИМЕНТА ========== # ========== ВКЛАДКА 2: ФАКТОРЫ ЭКСПЕРИМЕНТА ==========
def _create_factors_tab(self): def _create_factors_tab(self):
tab = QWidget() tab = QWidget()
layout = QVBoxLayout(tab) layout = QVBoxLayout(tab)
@@ -313,7 +310,7 @@ class MainWindow(QMainWindow):
"Фактор", "%", "Разбавление", "Нулевой уровень", "Шаг", "Фактор", "%", "Разбавление", "Нулевой уровень", "Шаг",
"Тип шага", "Верхний (+1)", "Нижний (-1)", "Ед. изм." "Тип шага", "Верхний (+1)", "Нижний (-1)", "Ед. изм."
]) ])
self.factors_table.setAlternatingRowColors(True) #self.factors_table.setAlternatingRowColors(True)
factors_layout.addWidget(self.factors_table) factors_layout.addWidget(self.factors_table)
@@ -361,30 +358,38 @@ class MainWindow(QMainWindow):
return tab return tab
def _add_factor_row(self): def _add_factor_row(self, name = "", percentage = "0", dilution = "0", center = "0", step ="0", step_type = "%", high = "", low = "", unit= "г"):
row = self.factors_table.rowCount() row = self.factors_table.rowCount()
self.factors_table.insertRow(row) self.factors_table.insertRow(row)
if name == "":
self.factors_table.setItem(row, 0, QTableWidgetItem(f"Фактор_{row+1}")) self.factors_table.setItem(row, 0, QTableWidgetItem(f"Фактор_{row+1}"))
self.factors_table.setItem(row, 1, QTableWidgetItem("0")) else:
self.factors_table.setItem(row, 2, QTableWidgetItem("1")) self.factors_table.setItem(row, 0, QTableWidgetItem(name))
self.factors_table.setItem(row, 3, QTableWidgetItem("0")) self.factors_table.setItem(row, 1, QTableWidgetItem(percentage))
self.factors_table.setItem(row, 4, QTableWidgetItem("1")) self.factors_table.setItem(row, 2, QTableWidgetItem(dilution))
self.factors_table.setItem(row, 3, QTableWidgetItem(center))
self.factors_table.setItem(row, 4, QTableWidgetItem(step))
step_type_combo = QComboBox() step_type_combo = QComboBox()
step_type_combo.addItems(["абс", "%"]) step_type_combo.addItems(["%", "ед."])
step_type_combo.setCurrentText(step_type)
self.factors_table.setCellWidget(row, 5, step_type_combo) self.factors_table.setCellWidget(row, 5, step_type_combo)
high_item = QTableWidgetItem("1") high_item = QTableWidgetItem(high)
high_item.setFlags(high_item.flags() & ~Qt.ItemIsEditable) high_item.setFlags(high_item.flags() & ~Qt.ItemIsEditable)
high_item.setBackground(QColor(240, 240, 240)) high_item.setBackground(QColor(240, 240, 240))
self.factors_table.setItem(row, 6, high_item) self.factors_table.setItem(row, 6, high_item)
low_item = QTableWidgetItem("-1") low_item = QTableWidgetItem(low)
low_item.setFlags(low_item.flags() & ~Qt.ItemIsEditable) low_item.setFlags(low_item.flags() & ~Qt.ItemIsEditable)
low_item.setBackground(QColor(240, 240, 240)) low_item.setBackground(QColor(240, 240, 240))
self.factors_table.setItem(row, 7, low_item) self.factors_table.setItem(row, 7, low_item)
unit_measure = QComboBox()
self.factors_table.setItem(row, 8, QTableWidgetItem("")) unit_measure.addItems(["мкл", "мл", "л", "мг", "г", "кг"])
unit_measure.setCurrentText(unit)
self.factors_table.setCellWidget(row, 8, unit_measure)
return row
def _remove_factor_row(self): def _remove_factor_row(self):
for row in sorted(set(i.row() for i in self.factors_table.selectedItems()), reverse=True): for row in sorted(set(i.row() for i in self.factors_table.selectedItems()), reverse=True):
@@ -404,37 +409,20 @@ class MainWindow(QMainWindow):
self.factors_table.setRowCount(0) self.factors_table.setRowCount(0)
for reagent in result['reagents']: for reagent in result['reagents']:
row = self.factors_table.rowCount()
self.factors_table.insertRow(row)
name = reagent['name'] name = reagent['name']
if reagent.get('dilution_factor', 1.0) != 1.0: if reagent.get('dilution_factor', 1.0) != 1.0:
name += f" (разб. ×{reagent['dilution_factor']:.2f})" name += f" (разб. ×{reagent['dilution_factor']:.g})"
percentage = f"{reagent['percentage']:.g}"
self.factors_table.setItem(row, 0, QTableWidgetItem(name)) dilution = f"{reagent.get('dilution_factor', 1.0):g}"
self.factors_table.setItem(row, 1, QTableWidgetItem(f"{reagent['percentage']:.2f}")) center = str(reagent.get('calculated_amount'))
self.factors_table.setItem(row, 2, QTableWidgetItem(f"{reagent.get('dilution_factor', 1.0):.3f}")) step = ""
step_type = "%"
# Нулевой уровень - исходное количество (неразбавленное) high = ""
center = reagent.get('undiluted_amount', reagent['calculated_amount']) low = ""
self.factors_table.setItem(row, 3, QTableWidgetItem(self._format_number(center))) unit = reagent["unit"]
self._add_factor_row(name, percentage, dilution, center, step, step_type, high, low, unit)
# Шаг - 10% от нулевого уровня # QMessageBox.information(self, "Успех",
step = center * 0.1 # f"Импортировано {len(result['reagents'])} факторов из калькулятора сред")
self.factors_table.setItem(row, 4, QTableWidgetItem(self._format_number(step)))
# Тип шага - абсолютный
step_combo = self.factors_table.cellWidget(row, 5)
if step_combo:
step_combo.setCurrentText("абс")
# Верхний и нижний уровни
self.factors_table.setItem(row, 6, QTableWidgetItem(self._format_number(center + step)))
self.factors_table.setItem(row, 7, QTableWidgetItem(self._format_number(center - step)))
self.factors_table.setItem(row, 8, QTableWidgetItem(result['total_unit']))
QMessageBox.information(self, "Успех",
f"Импортировано {len(result['reagents'])} факторов из калькулятора сред")
def _get_factors_from_table(self) -> List[Dict]: def _get_factors_from_table(self) -> List[Dict]:
"""Собирает данные факторов из таблицы""" """Собирает данные факторов из таблицы"""
@@ -466,7 +454,7 @@ class MainWindow(QMainWindow):
'low': float(low_text), 'low': float(low_text),
'unit': unit_item.text() if unit_item else "", 'unit': unit_item.text() if unit_item else "",
'step': float(step_item.text()) if step_item and step_item.text() else 0, 'step': float(step_item.text()) if step_item and step_item.text() else 0,
'step_type': step_combo.currentText() if step_combo else "абс" 'step_type': step_combo.currentText() if step_combo else "ед."
} }
factors.append(factor) factors.append(factor)
except (ValueError, AttributeError) as e: except (ValueError, AttributeError) as e:
@@ -750,7 +738,7 @@ class MainWindow(QMainWindow):
try: try:
project = ProjectData.load_from_file(filename) project = ProjectData.load_from_file(filename)
self._apply_project_data(project) self._apply_project_data(project)
QMessageBox.information(self, "Успех", f"Проект загружен из {filename}") # QMessageBox.information(self, "Успех", f"Проект загружен из {filename}")
except FileNotFoundError: except FileNotFoundError:
QMessageBox.critical(self, "Ошибка", f"Файл не найден: {filename}") QMessageBox.critical(self, "Ошибка", f"Файл не найден: {filename}")
except Exception as e: except Exception as e:
@@ -788,7 +776,7 @@ class MainWindow(QMainWindow):
low_item = self.factors_table.item(row, 7) low_item = self.factors_table.item(row, 7)
high_item = self.factors_table.item(row, 6) high_item = self.factors_table.item(row, 6)
step_item = self.factors_table.item(row, 4) step_item = self.factors_table.item(row, 4)
unit_item = self.factors_table.item(row, 8) unit_item = self.factors_table.cellWidget(row, 8)
step_combo = self.factors_table.cellWidget(row, 5) step_combo = self.factors_table.cellWidget(row, 5)
percent_item = self.factors_table.item(row, 1) percent_item = self.factors_table.item(row, 1)
dilution_item = self.factors_table.item(row, 2) dilution_item = self.factors_table.item(row, 2)
@@ -801,8 +789,8 @@ class MainWindow(QMainWindow):
low=float(low_item.text()) if low_item and low_item.text() else 0, low=float(low_item.text()) if low_item and low_item.text() else 0,
high=float(high_item.text()) if high_item and high_item.text() else 0, high=float(high_item.text()) if high_item and high_item.text() else 0,
step=float(step_item.text()) if step_item and step_item.text() else 0, step=float(step_item.text()) if step_item and step_item.text() else 0,
step_type=step_combo.currentText() if step_combo else "абс", step_type=step_combo.currentText() if step_combo else "ед.",
unit=unit_item.text() if unit_item else "", unit=unit_item.currentText() if unit_item else "",
percentage=float(percent_item.text()) if percent_item and percent_item.text() else None, percentage=float(percent_item.text()) if percent_item and percent_item.text() else None,
dilution_factor=float(dilution_item.text()) if dilution_item and dilution_item.text() else None dilution_factor=float(dilution_item.text()) if dilution_item and dilution_item.text() else None
) )
@@ -874,32 +862,13 @@ class MainWindow(QMainWindow):
# Применяем данные факторов эксперимента # Применяем данные факторов эксперимента
self.factors_table.setRowCount(0) self.factors_table.setRowCount(0)
for factor in project.experiment_factors: for factor in project.experiment_factors:
row = self.factors_table.rowCount()
self.factors_table.insertRow(row)
self.factors_table.setItem(row, 0, QTableWidgetItem(factor.name))
if factor.percentage is not None: if factor.percentage is not None:
self.factors_table.setItem(row, 1, QTableWidgetItem(str(factor.percentage))) percentage = str(factor.percentage)
else:
self.factors_table.setItem(row, 1, QTableWidgetItem("0"))
if factor.dilution_factor is not None: if factor.dilution_factor is not None:
self.factors_table.setItem(row, 2, QTableWidgetItem(str(factor.dilution_factor))) dilution = str(factor.dilution_factor)
else: self._add_factor_row(factor.name, percentage, dilution, str(factor.center),
self.factors_table.setItem(row, 2, QTableWidgetItem("1")) str(factor.step), factor.step_type, str(factor.high),
str(factor.low), factor.unit)
self.factors_table.setItem(row, 3, QTableWidgetItem(str(factor.center)))
self.factors_table.setItem(row, 4, QTableWidgetItem(str(factor.step)))
step_combo = QComboBox()
step_combo.addItems(["абс", "%"])
step_combo.setCurrentText(factor.step_type)
self.factors_table.setCellWidget(row, 5, step_combo)
self.factors_table.setItem(row, 6, QTableWidgetItem(str(factor.high)))
self.factors_table.setItem(row, 7, QTableWidgetItem(str(factor.low)))
self.factors_table.setItem(row, 8, QTableWidgetItem(factor.unit))
# Применяем настройки эксперимента # Применяем настройки эксперимента
self.center_points_spin.setValue(project.experiment_center_points) self.center_points_spin.setValue(project.experiment_center_points)
self.randomize_check.setChecked(project.experiment_randomize) self.randomize_check.setChecked(project.experiment_randomize)
@@ -916,9 +885,9 @@ class MainWindow(QMainWindow):
if i < self.results_table.rowCount() and row_results: if i < self.results_table.rowCount() and row_results:
self.results_table.setItem(i, 1, QTableWidgetItem(str(row_results[0]))) self.results_table.setItem(i, 1, QTableWidgetItem(str(row_results[0])))
# Переключаемся на вкладку с факторами # Переключаемся на вкладку с экспериментом
if self.tab_widget: if self.tab_widget:
self.tab_widget.setCurrentIndex(1) self.tab_widget.setCurrentIndex(0)
def _refresh_design_matrix(self): def _refresh_design_matrix(self):
"""Обновляет отображение матрицы планирования""" """Обновляет отображение матрицы планирования"""
+12 -6
View File
@@ -12,8 +12,8 @@ class Colors:
"""Цветовая палитра приложения""" """Цветовая палитра приложения"""
# Основные цвета (Primary) # Основные цвета (Primary)
PRIMARY = "#3498db" # Синий - основной акцент PRIMARY = "#999999" # Синий - основной акцент
PRIMARY_DARK = "#2980b9" # Тёмно-синий (наведение) PRIMARY_DARK = "#777777" # Тёмно-синий (наведение)
PRIMARY_LIGHT = "#5dade2" # Светло-синий PRIMARY_LIGHT = "#5dade2" # Светло-синий
PRIMARY_BG = "#ebf5fb" # Фоновый для primary элементов PRIMARY_BG = "#ebf5fb" # Фоновый для primary элементов
@@ -216,10 +216,10 @@ class ButtonStyles:
{ButtonStyles._base_style()} {ButtonStyles._base_style()}
}} }}
QPushButton:hover {{ QPushButton:hover {{
background-color: {Colors.PRIMARY_DARK}; background-color: {Colors.PRIMARY_LIGHT};
}} }}
QPushButton:pressed {{ QPushButton:pressed {{
background-color: {Colors.PRIMARY_LIGHT}; background-color: {Colors.PRIMARY_DARK};
}} }}
QPushButton:disabled {{ QPushButton:disabled {{
background-color: {Colors.GRAY_400}; background-color: {Colors.GRAY_400};
@@ -326,7 +326,14 @@ class InputStyles:
@staticmethod @staticmethod
def default(): def default():
return f""" return f"""
QLineEdit, QDoubleSpinBox, QSpinBox, QComboBox {{ QComboBox {{
border: 1px solid {Colors.BORDER_DEFAULT};
border-radius: {Spacing.BORDER_RADIUS_SM}px;
padding: 1px;
background-color: {Colors.WHITE};
min-height: {Spacing.INPUT_HEIGHT - 16}px;
}}
QLineEdit, QDoubleSpinBox, QSpinBox {{
border: 1px solid {Colors.BORDER_DEFAULT}; border: 1px solid {Colors.BORDER_DEFAULT};
border-radius: {Spacing.BORDER_RADIUS_SM}px; border-radius: {Spacing.BORDER_RADIUS_SM}px;
padding: {Spacing.SM}px; padding: {Spacing.SM}px;
@@ -342,7 +349,6 @@ class InputStyles:
color: {Colors.GRAY_600}; color: {Colors.GRAY_600};
}} }}
""" """
@staticmethod @staticmethod
def error(): def error():
return f""" return f"""