Исправлен расчёт +1 и -1 факторов эксперимента
This commit is contained in:
+45
-31
@@ -13,7 +13,13 @@
|
|||||||
from typing import List, Dict, Tuple, Optional
|
from typing import List, Dict, Tuple, Optional
|
||||||
import random
|
import random
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
from calculations.medium import (
|
||||||
|
calculate_medium_composition,
|
||||||
|
convert_units,
|
||||||
|
VOLUME_UNITS,
|
||||||
|
MASS_UNITS
|
||||||
|
)
|
||||||
|
from calculations.models.project_data import FactorData
|
||||||
# Типы расчёта шага
|
# Типы расчёта шага
|
||||||
FACTOR_TYPES = {
|
FACTOR_TYPES = {
|
||||||
'absolute': 'ед.', # абсолютный шаг
|
'absolute': 'ед.', # абсолютный шаг
|
||||||
@@ -22,41 +28,49 @@ FACTOR_TYPES = {
|
|||||||
|
|
||||||
|
|
||||||
def calculate_factor_levels(
|
def calculate_factor_levels(
|
||||||
center_value: float,
|
factor: 'FactorData',
|
||||||
step_value: float,
|
total_volume: float = 1000,
|
||||||
step_type: str,
|
volume_unit: str = "мл"
|
||||||
base_value: float = None
|
) -> 'FactorData':
|
||||||
) -> Tuple[float, float]:
|
"""Рассчитывает high/low уровни фактора через калькулятор сред"""
|
||||||
"""
|
|
||||||
РАССЧИТЫВАЕТ ВЕРХНИЙ И НИЖНИЙ УРОВНИ ФАКТОРА
|
|
||||||
|
|
||||||
Параметры:
|
# Функция для расчёта количества реагента при заданном проценте
|
||||||
center_value: нулевой уровень фактора (центральная точка)
|
def calc_amount(percentage: float) -> float:
|
||||||
step_value: значение шага
|
reagents = [{
|
||||||
step_type: тип шага ("ед." - абсолютный, "%" - относительный)
|
'name': factor.name,
|
||||||
base_value: базовое значение для относительного шага (если None, используется center_value)
|
'percentage': max(0, percentage),
|
||||||
|
'unit': factor.unit,
|
||||||
|
'dilution_factor': factor.dilution_factor or 1.0
|
||||||
|
}]
|
||||||
|
result = calculate_medium_composition(total_volume, volume_unit, reagents)
|
||||||
|
return result['reagents'][0]['calculated_amount']
|
||||||
|
|
||||||
Возвращает:
|
|
||||||
(high_level, low_level): верхний и нижний уровни
|
|
||||||
|
|
||||||
Пример:
|
# Определяем проценты для верхнего и нижнего уровней
|
||||||
>>> calculate_factor_levels(100, 10, "%")
|
if factor.step_type == "%":
|
||||||
(110.0, 90.0)
|
low = calc_amount(factor.percentage - factor.step) # low - нижний уровень
|
||||||
>>> calculate_factor_levels(100, 20, "ед.")
|
high = calc_amount(factor.percentage + factor.step) # high - верхний уровень
|
||||||
(120.0, 80.0)
|
else: # "ед."
|
||||||
"""
|
# Конвертируем абсолютный шаг в проценты
|
||||||
# Определяем абсолютное значение шага
|
low = calc_amount(factor.percentage) - factor.step
|
||||||
if step_type == "%":
|
high = calc_amount(factor.percentage) + factor.step
|
||||||
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
|
# ВОЗВРАЩАЕМ НОВЫЙ ОБЪЕКТ FactorData
|
||||||
low_level = center_value - step_abs
|
return FactorData(
|
||||||
|
name=factor.name,
|
||||||
return high_level, low_level
|
center=factor.center,
|
||||||
|
low=low, # low - нижний уровень
|
||||||
|
high=high, # high - верхний уровень
|
||||||
|
step=factor.step,
|
||||||
|
step_type=factor.step_type,
|
||||||
|
unit=factor.unit,
|
||||||
|
percentage=factor.percentage or factor.center,
|
||||||
|
dilution_factor=factor.dilution_factor
|
||||||
|
)
|
||||||
|
|
||||||
|
def calculate_all_factors_levels(factors: List['FactorData'], **kwargs) -> List['FactorData']:
|
||||||
|
"""Рассчитывает уровни для всех факторов"""
|
||||||
|
return [calculate_factor_levels(f, **kwargs) for f in factors]
|
||||||
|
|
||||||
def generate_factorial_design(
|
def generate_factorial_design(
|
||||||
factors: List[Dict],
|
factors: List[Dict],
|
||||||
|
|||||||
@@ -52,11 +52,8 @@ def convert_units(value: float, from_unit: str, to_unit: str = None) -> float:
|
|||||||
units_map = MASS_UNITS
|
units_map = MASS_UNITS
|
||||||
else:
|
else:
|
||||||
raise ValueError(f"Неизвестная единица измерения: {from_unit}")
|
raise ValueError(f"Неизвестная единица измерения: {from_unit}")
|
||||||
print("units_map = ",units_map)
|
|
||||||
# Конвертируем в базовую единицу (мкл для объёма, мг для массы)
|
# Конвертируем в базовую единицу (мкл для объёма, мг для массы)
|
||||||
value_in_base = value * units_map[from_unit]
|
value_in_base = value * units_map[from_unit]
|
||||||
print ("units_map[$from_unit]",units_map[from_unit])
|
|
||||||
print ("value_in_base = ",value_in_base)
|
|
||||||
# Если нужна конвертация в другую единицу
|
# Если нужна конвертация в другую единицу
|
||||||
if from_unit and to_unit in units_map:
|
if from_unit and to_unit in units_map:
|
||||||
return value_in_base / units_map[to_unit]
|
return value_in_base / units_map[to_unit]
|
||||||
@@ -165,7 +162,6 @@ def calculate_medium_composition(
|
|||||||
base_unit = "мг"
|
base_unit = "мг"
|
||||||
else:
|
else:
|
||||||
raise ValueError(f"Неизвестная единица измерения: {from_unit}")
|
raise ValueError(f"Неизвестная единица измерения: {from_unit}")
|
||||||
print ("unit = ",unit)
|
|
||||||
# conversion_factor = reagent.get('conversion_factor', 1.0)
|
# conversion_factor = reagent.get('conversion_factor', 1.0)
|
||||||
dilution_factor = reagent.get('dilution_factor', 1.0)
|
dilution_factor = reagent.get('dilution_factor', 1.0)
|
||||||
|
|
||||||
@@ -174,19 +170,16 @@ def calculate_medium_composition(
|
|||||||
|
|
||||||
# 1. Объём реагента в среде (исходя из процента)
|
# 1. Объём реагента в среде (исходя из процента)
|
||||||
amount_in_base = (percentage / 100) * total_base
|
amount_in_base = (percentage / 100) * total_base
|
||||||
print ("amount_in_base = ",amount_in_base)
|
|
||||||
# 2. Применяем коэффициент конверсии
|
# 2. Применяем коэффициент конверсии
|
||||||
# adjusted_amount_base = amount_in_base * conversion_factor
|
# adjusted_amount_base = amount_in_base * conversion_factor
|
||||||
|
|
||||||
# 3. Конвертируем в нужную единицу (без учёта разбавления)
|
# 3. Конвертируем в нужную единицу (без учёта разбавления)
|
||||||
# undiluted_amount = convert_units(adjusted_amount_base, volume_unit, unit)
|
# undiluted_amount = convert_units(adjusted_amount_base, volume_unit, unit)
|
||||||
undiluted_amount = convert_units(amount_in_base, base_unit, unit)
|
undiluted_amount = convert_units(amount_in_base, base_unit, unit)
|
||||||
print ("undiluted_amount = ",undiluted_amount)
|
|
||||||
# 4. Применяем разбавление
|
# 4. Применяем разбавление
|
||||||
if dilution_factor <= 0:
|
if dilution_factor <= 0:
|
||||||
dilution_factor = 1.0
|
dilution_factor = 1.0
|
||||||
diluted_amount = undiluted_amount * dilution_factor
|
diluted_amount = undiluted_amount * dilution_factor
|
||||||
print ("diluted_amount = ", diluted_amount)
|
|
||||||
# 5. Для объёмных реагентов учитываем в расчёте растворителя
|
# 5. Для объёмных реагентов учитываем в расчёте растворителя
|
||||||
if is_volume:
|
if is_volume:
|
||||||
reagent_volume_base = convert_units(diluted_amount, unit)
|
reagent_volume_base = convert_units(diluted_amount, unit)
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ from calculations.doe import (
|
|||||||
calculate_factor_levels,
|
calculate_factor_levels,
|
||||||
FACTOR_TYPES
|
FACTOR_TYPES
|
||||||
)
|
)
|
||||||
|
|
||||||
# Импорт моделей для JSON
|
# Импорт моделей для JSON
|
||||||
try:
|
try:
|
||||||
from calculations.models.project_data import (
|
from calculations.models.project_data import (
|
||||||
@@ -355,7 +354,8 @@ class MainWindow(QMainWindow):
|
|||||||
|
|
||||||
# Добавляем начальный фактор
|
# Добавляем начальный фактор
|
||||||
self._add_factor_row()
|
self._add_factor_row()
|
||||||
|
# ПОДКЛЮЧАЕМ СИГНАЛ ИЗМЕНЕНИЯ ЯЧЕЕК
|
||||||
|
self.factors_table.cellChanged.connect(self._on_factor_changed)
|
||||||
return tab
|
return tab
|
||||||
|
|
||||||
def _add_factor_row(self, name = "", percentage = "0", dilution = "0", center = "0", step ="0", step_type = "%", high = "", low = "", unit= "г"):
|
def _add_factor_row(self, name = "", percentage = "0", dilution = "0", center = "0", step ="0", step_type = "%", high = "", low = "", unit= "г"):
|
||||||
@@ -374,6 +374,7 @@ class MainWindow(QMainWindow):
|
|||||||
step_type_combo = QComboBox()
|
step_type_combo = QComboBox()
|
||||||
step_type_combo.addItems(["%", "ед."])
|
step_type_combo.addItems(["%", "ед."])
|
||||||
step_type_combo.setCurrentText(step_type)
|
step_type_combo.setCurrentText(step_type)
|
||||||
|
step_type_combo.currentTextChanged.connect(lambda: self._on_factor_changed(row, 5))
|
||||||
self.factors_table.setCellWidget(row, 5, step_type_combo)
|
self.factors_table.setCellWidget(row, 5, step_type_combo)
|
||||||
|
|
||||||
high_item = QTableWidgetItem(high)
|
high_item = QTableWidgetItem(high)
|
||||||
@@ -388,7 +389,11 @@ class MainWindow(QMainWindow):
|
|||||||
unit_measure = QComboBox()
|
unit_measure = QComboBox()
|
||||||
unit_measure.addItems(["мкл", "мл", "л", "мг", "г", "кг"])
|
unit_measure.addItems(["мкл", "мл", "л", "мг", "г", "кг"])
|
||||||
unit_measure.setCurrentText(unit)
|
unit_measure.setCurrentText(unit)
|
||||||
|
unit_measure.currentTextChanged.connect(lambda: self._on_factor_changed(row, 8))
|
||||||
|
|
||||||
self.factors_table.setCellWidget(row, 8, unit_measure)
|
self.factors_table.setCellWidget(row, 8, unit_measure)
|
||||||
|
|
||||||
|
|
||||||
return row
|
return row
|
||||||
|
|
||||||
def _remove_factor_row(self):
|
def _remove_factor_row(self):
|
||||||
@@ -411,8 +416,8 @@ class MainWindow(QMainWindow):
|
|||||||
for reagent in result['reagents']:
|
for reagent in result['reagents']:
|
||||||
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']:.g})"
|
name += f"(разб. ×{reagent['dilution_factor']:g})"
|
||||||
percentage = f"{reagent['percentage']:.g}"
|
percentage = f"{reagent['percentage']:g}"
|
||||||
dilution = f"{reagent.get('dilution_factor', 1.0):g}"
|
dilution = f"{reagent.get('dilution_factor', 1.0):g}"
|
||||||
center = str(reagent.get('calculated_amount'))
|
center = str(reagent.get('calculated_amount'))
|
||||||
step = ""
|
step = ""
|
||||||
@@ -462,6 +467,32 @@ class MainWindow(QMainWindow):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
return factors
|
return factors
|
||||||
|
def _on_factor_changed(self, row, column):
|
||||||
|
"""При изменении ячейки пересчитываем уровни"""
|
||||||
|
if getattr(self, '_loading_data', False) or column not in [3, 4, 5, 8]:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Получаем данные
|
||||||
|
percentage = float(self.factors_table.item(row, 1).text())
|
||||||
|
dilution = float(self.factors_table.item(row, 2).text())
|
||||||
|
center = float(self.factors_table.item(row, 3).text())
|
||||||
|
step = float(self.factors_table.item(row, 4).text())
|
||||||
|
step_type = self.factors_table.cellWidget(row, 5).currentText()
|
||||||
|
unit = self.factors_table.cellWidget(row, 8).currentText()
|
||||||
|
name = self.factors_table.item(row, 0).text()
|
||||||
|
|
||||||
|
# Рассчитываем
|
||||||
|
factor = FactorData(percentage=percentage, dilution_factor=dilution, name=name, center=center, low=0, high=0, step=step, step_type=step_type, unit=unit)
|
||||||
|
result = calculate_factor_levels(factor, self.total_volume_spin.value(), self.volume_unit_combo.currentText())
|
||||||
|
if result:
|
||||||
|
self.factors_table.blockSignals(True)
|
||||||
|
self.factors_table.item(row, 6).setText(self._format_number(result.high))
|
||||||
|
self.factors_table.item(row, 7).setText(self._format_number(result.low))
|
||||||
|
self.factors_table.blockSignals(False)
|
||||||
|
|
||||||
|
except (ValueError, AttributeError):
|
||||||
|
pass
|
||||||
|
|
||||||
# ========== ВКЛАДКА 3: МАТРИЦА ПЛАНИРОВАНИЯ ==========
|
# ========== ВКЛАДКА 3: МАТРИЦА ПЛАНИРОВАНИЯ ==========
|
||||||
|
|
||||||
@@ -833,61 +864,66 @@ class MainWindow(QMainWindow):
|
|||||||
|
|
||||||
def _apply_project_data(self, project):
|
def _apply_project_data(self, project):
|
||||||
"""Применяет загруженные данные к GUI"""
|
"""Применяет загруженные данные к GUI"""
|
||||||
# Применяем данные калькулятора сред
|
self._loading_data = True # <- ОТКЛЮЧАЕМ ОБРАБОТЧИК
|
||||||
self.total_volume_spin.setValue(project.medium_total_volume)
|
try:
|
||||||
|
|
||||||
index = self.volume_unit_combo.findText(project.medium_volume_unit)
|
# Применяем данные калькулятора сред
|
||||||
if index >= 0:
|
self.total_volume_spin.setValue(project.medium_total_volume)
|
||||||
self.volume_unit_combo.setCurrentIndex(index)
|
|
||||||
|
|
||||||
self.solvent_input.setText(project.medium_solvent)
|
index = self.volume_unit_combo.findText(project.medium_volume_unit)
|
||||||
|
if index >= 0:
|
||||||
|
self.volume_unit_combo.setCurrentIndex(index)
|
||||||
|
|
||||||
# Очищаем и заполняем таблицу реагентов
|
self.solvent_input.setText(project.medium_solvent)
|
||||||
self.reagents_table.setRowCount(0)
|
|
||||||
for reagent in project.medium_reagents:
|
|
||||||
row = self.reagents_table.rowCount()
|
|
||||||
self.reagents_table.insertRow(row)
|
|
||||||
self.reagents_table.setItem(row, 0, QTableWidgetItem(reagent.name))
|
|
||||||
self.reagents_table.setItem(row, 1, QTableWidgetItem(str(reagent.percentage)))
|
|
||||||
|
|
||||||
unit_combo = QComboBox()
|
# Очищаем и заполняем таблицу реагентов
|
||||||
unit_combo.addItems(["мг", "г", "кг", "мкг", "нг", "мл", "мкл", "л", "нл"])
|
self.reagents_table.setRowCount(0)
|
||||||
unit_combo.setCurrentText(reagent.unit)
|
for reagent in project.medium_reagents:
|
||||||
self.reagents_table.setCellWidget(row, 2, unit_combo)
|
row = self.reagents_table.rowCount()
|
||||||
|
self.reagents_table.insertRow(row)
|
||||||
|
self.reagents_table.setItem(row, 0, QTableWidgetItem(reagent.name))
|
||||||
|
self.reagents_table.setItem(row, 1, QTableWidgetItem(str(reagent.percentage)))
|
||||||
|
|
||||||
self.reagents_table.setItem(row, 3, QTableWidgetItem(str(reagent.conversion_factor)))
|
unit_combo = QComboBox()
|
||||||
self.reagents_table.setItem(row, 4, QTableWidgetItem(str(reagent.dilution_factor)))
|
unit_combo.addItems(["мг", "г", "кг", "мкг", "нг", "мл", "мкл", "л", "нл"])
|
||||||
self.reagents_table.setItem(row, 5, QTableWidgetItem(""))
|
unit_combo.setCurrentText(reagent.unit)
|
||||||
|
self.reagents_table.setCellWidget(row, 2, unit_combo)
|
||||||
|
|
||||||
# Применяем данные факторов эксперимента
|
self.reagents_table.setItem(row, 3, QTableWidgetItem(str(reagent.conversion_factor)))
|
||||||
self.factors_table.setRowCount(0)
|
self.reagents_table.setItem(row, 4, QTableWidgetItem(str(reagent.dilution_factor)))
|
||||||
for factor in project.experiment_factors:
|
self.reagents_table.setItem(row, 5, QTableWidgetItem(""))
|
||||||
if factor.percentage is not None:
|
|
||||||
percentage = str(factor.percentage)
|
|
||||||
if factor.dilution_factor is not None:
|
|
||||||
dilution = str(factor.dilution_factor)
|
|
||||||
self._add_factor_row(factor.name, percentage, dilution, str(factor.center),
|
|
||||||
str(factor.step), factor.step_type, str(factor.high),
|
|
||||||
str(factor.low), factor.unit)
|
|
||||||
# Применяем настройки эксперимента
|
|
||||||
self.center_points_spin.setValue(project.experiment_center_points)
|
|
||||||
self.randomize_check.setChecked(project.experiment_randomize)
|
|
||||||
|
|
||||||
# Если есть результаты, загружаем их
|
# Применяем данные факторов эксперимента
|
||||||
if project.experiment_results and project.experiment_results.design:
|
self.factors_table.setRowCount(0)
|
||||||
self.generated_design = project.experiment_results.design
|
for factor in project.experiment_factors:
|
||||||
# Обновляем отображение матрицы
|
if factor.percentage is not None:
|
||||||
self._refresh_design_matrix()
|
percentage = str(factor.percentage)
|
||||||
|
if factor.dilution_factor is not None:
|
||||||
|
dilution = str(factor.dilution_factor)
|
||||||
|
self._add_factor_row(factor.name, percentage, dilution, str(factor.center),
|
||||||
|
str(factor.step), factor.step_type, str(factor.high),
|
||||||
|
str(factor.low), factor.unit)
|
||||||
|
# Применяем настройки эксперимента
|
||||||
|
self.center_points_spin.setValue(project.experiment_center_points)
|
||||||
|
self.randomize_check.setChecked(project.experiment_randomize)
|
||||||
|
|
||||||
# Загружаем результаты в таблицу
|
# Если есть результаты, загружаем их
|
||||||
if project.experiment_results.results:
|
if project.experiment_results and project.experiment_results.design:
|
||||||
for i, row_results in enumerate(project.experiment_results.results):
|
self.generated_design = project.experiment_results.design
|
||||||
if i < self.results_table.rowCount() and row_results:
|
# Обновляем отображение матрицы
|
||||||
self.results_table.setItem(i, 1, QTableWidgetItem(str(row_results[0])))
|
self._refresh_design_matrix()
|
||||||
|
|
||||||
# Переключаемся на вкладку с экспериментом
|
# Загружаем результаты в таблицу
|
||||||
if self.tab_widget:
|
if project.experiment_results.results:
|
||||||
self.tab_widget.setCurrentIndex(0)
|
for i, row_results in enumerate(project.experiment_results.results):
|
||||||
|
if i < self.results_table.rowCount() and row_results:
|
||||||
|
self.results_table.setItem(i, 1, QTableWidgetItem(str(row_results[0])))
|
||||||
|
|
||||||
|
# Переключаемся на вкладку с экспериментом
|
||||||
|
if self.tab_widget:
|
||||||
|
self.tab_widget.setCurrentIndex(0)
|
||||||
|
finally:
|
||||||
|
self._loading_data = False # <- ВКЛЮЧАЕМ ОБРАТНО
|
||||||
|
|
||||||
def _refresh_design_matrix(self):
|
def _refresh_design_matrix(self):
|
||||||
"""Обновляет отображение матрицы планирования"""
|
"""Обновляет отображение матрицы планирования"""
|
||||||
|
|||||||
Reference in New Issue
Block a user