Переработан интерфейс программы, расчёт занчений в процессе исправления.
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
"""Библиотека расчётов для биохимика"""
|
||||
|
||||
from .medium import (
|
||||
calculate_medium_composition,
|
||||
convert_units,
|
||||
VOLUME_UNITS,
|
||||
MASS_UNITS
|
||||
)
|
||||
|
||||
from .doe import (
|
||||
generate_factorial_design,
|
||||
analyze_experiment,
|
||||
calculate_factor_levels,
|
||||
create_factor_from_reagent, # Добавлено
|
||||
FACTOR_TYPES
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
'calculate_medium_composition',
|
||||
'convert_units',
|
||||
'VOLUME_UNITS',
|
||||
'MASS_UNITS',
|
||||
'generate_factorial_design',
|
||||
'analyze_experiment',
|
||||
'calculate_factor_levels',
|
||||
'create_factor_from_reagent',
|
||||
'FACTOR_TYPES',
|
||||
]
|
||||
@@ -0,0 +1,352 @@
|
||||
"""
|
||||
Планирование эксперимента (DoE)
|
||||
|
||||
Модуль для генерации полнофакторных планов экспериментов
|
||||
и анализа результатов.
|
||||
|
||||
Основные функции:
|
||||
- generate_factorial_design() - генерация плана
|
||||
- analyze_experiment() - статистический анализ
|
||||
- calculate_factor_levels() - расчёт уровней факторов
|
||||
"""
|
||||
|
||||
from typing import List, Dict, Tuple, Optional
|
||||
import random
|
||||
import numpy as np
|
||||
|
||||
# Типы расчёта шага
|
||||
FACTOR_TYPES = {
|
||||
'absolute': 'абс', # абсолютный шаг
|
||||
'relative': '%', # относительный шаг (процент от нулевого уровня)
|
||||
}
|
||||
|
||||
|
||||
def calculate_factor_levels(
|
||||
center_value: float,
|
||||
step_value: float,
|
||||
step_type: str,
|
||||
base_value: float = None
|
||||
) -> Tuple[float, float]:
|
||||
"""
|
||||
РАССЧИТЫВАЕТ ВЕРХНИЙ И НИЖНИЙ УРОВНИ ФАКТОРА
|
||||
|
||||
Параметры:
|
||||
center_value: нулевой уровень фактора (центральная точка)
|
||||
step_value: значение шага
|
||||
step_type: тип шага ("абс" - абсолютный, "%" - относительный)
|
||||
base_value: базовое значение для относительного шага (если None, используется center_value)
|
||||
|
||||
Возвращает:
|
||||
(high_level, low_level): верхний и нижний уровни
|
||||
|
||||
Пример:
|
||||
>>> calculate_factor_levels(100, 10, "%")
|
||||
(110.0, 90.0)
|
||||
>>> calculate_factor_levels(100, 20, "абс")
|
||||
(120.0, 80.0)
|
||||
"""
|
||||
# Определяем абсолютное значение шага
|
||||
if step_type == "%":
|
||||
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
|
||||
low_level = center_value - step_abs
|
||||
|
||||
return high_level, low_level
|
||||
|
||||
|
||||
def generate_factorial_design(
|
||||
factors: List[Dict],
|
||||
center_points: int = 3,
|
||||
randomize: bool = True
|
||||
) -> List[Dict]:
|
||||
"""
|
||||
ГЕНЕРИРУЕТ ПОЛНОФАКТОРНЫЙ ПЛАН ЭКСПЕРИМЕНТА
|
||||
|
||||
Создаёт матрицу планирования для 2^k полнофакторного эксперимента
|
||||
с добавлением центральных точек.
|
||||
|
||||
ПАРАМЕТРЫ:
|
||||
----------
|
||||
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)
|
||||
if k == 0:
|
||||
return []
|
||||
|
||||
n_factorial = 2 ** k
|
||||
design = []
|
||||
|
||||
# Генерация факторных точек (все комбинации уровней)
|
||||
for i in range(n_factorial):
|
||||
experiment = {}
|
||||
for j in range(k):
|
||||
# Кодированный уровень: -1 для 0, +1 для 1 в бите
|
||||
# (k-1-j) для правильного порядка факторов
|
||||
coded_level = -1 if (i >> (k - 1 - j)) & 1 == 0 else 1
|
||||
|
||||
# Натуральное значение
|
||||
natural_value = factors[j]['low'] if coded_level == -1 else factors[j]['high']
|
||||
|
||||
experiment[f"Фактор_{j+1}"] = {
|
||||
'coded': coded_level,
|
||||
'natural': natural_value,
|
||||
'name': factors[j]['name'],
|
||||
'unit': factors[j].get('unit', '')
|
||||
}
|
||||
design.append(experiment)
|
||||
|
||||
# Добавление центральных точек
|
||||
for i in range(center_points):
|
||||
center_experiment = {}
|
||||
for j in range(k):
|
||||
center_experiment[f"Фактор_{j+1}"] = {
|
||||
'coded': 0,
|
||||
'natural': factors[j]['center'],
|
||||
'name': factors[j]['name'],
|
||||
'unit': factors[j].get('unit', '')
|
||||
}
|
||||
center_experiment['is_center'] = True
|
||||
center_experiment['center_num'] = i + 1
|
||||
design.append(center_experiment)
|
||||
|
||||
# Перемешивание порядка
|
||||
if randomize:
|
||||
random.shuffle(design)
|
||||
|
||||
return design
|
||||
|
||||
|
||||
def analyze_experiment(
|
||||
results: List[List[float]],
|
||||
design: List[Dict],
|
||||
responses: List[Dict]
|
||||
) -> Dict:
|
||||
"""
|
||||
ПРОВОДИТ СТАТИСТИЧЕСКИЙ АНАЛИЗ РЕЗУЛЬТАТОВ ЭКСПЕРИМЕНТА
|
||||
|
||||
ПАРАМЕТРЫ:
|
||||
----------
|
||||
results : List[List[float]]
|
||||
Матрица результатов. Каждая строка - эксперимент,
|
||||
каждый столбец - отклик.
|
||||
Размер: [n_experiments, n_responses]
|
||||
|
||||
design : List[Dict]
|
||||
План эксперимента, возвращённый generate_factorial_design()
|
||||
|
||||
responses : List[Dict]
|
||||
Список откликов. Каждый отклик - словарь с ключами:
|
||||
- name (str): название отклика
|
||||
- unit (str): единица измерения
|
||||
|
||||
ВОЗВРАЩАЕТ:
|
||||
-----------
|
||||
Dict
|
||||
Словарь с анализом для каждого отклика:
|
||||
{
|
||||
'Название отклика': {
|
||||
'mean': float, # среднее значение всех опытов
|
||||
'variance': float, # общая дисперсия
|
||||
'std_dev': float, # стандартное отклонение
|
||||
'cv': float, # коэффициент вариации (%)
|
||||
'factorial_values': list, # значения в факторных точках
|
||||
'center_values': list, # значения в центральных точках
|
||||
'center_variance': float, # дисперсия воспроизводимости
|
||||
'n_factorial': int, # количество факторных точек
|
||||
'n_center': int, # количество центральных точек
|
||||
'fisher_ratio': float, # критерий Фишера (если применимо)
|
||||
'model_adequate': bool|None # адекватность модели
|
||||
}
|
||||
}
|
||||
|
||||
ПРИМЕР ИСПОЛЬЗОВАНИЯ:
|
||||
---------------------
|
||||
>>> factors = [{'name': 'X', 'low': 0, 'high': 10, 'center': 5, 'unit': ''}]
|
||||
>>> design = generate_factorial_design(factors, center_points=2)
|
||||
>>> results = [[2.0], [8.0], [5.1], [4.9]] # 2 факторные + 2 центральные
|
||||
>>> responses = [{'name': 'Выход', 'unit': '%'}]
|
||||
>>> analysis = analyze_experiment(results, design, responses)
|
||||
>>> print(analysis['Выход']['mean'])
|
||||
5.0
|
||||
"""
|
||||
|
||||
analysis = {}
|
||||
|
||||
for resp_idx, response in enumerate(responses):
|
||||
resp_name = response.get('name', f'Отклик_{resp_idx+1}')
|
||||
|
||||
# Собираем все значения для этого отклика
|
||||
y_values = [results[i][resp_idx] for i in range(len(results))]
|
||||
|
||||
# Базовые статистики
|
||||
mean_y = np.mean(y_values)
|
||||
variance = np.var(y_values, ddof=1) if len(y_values) > 1 else 0
|
||||
std_dev = np.std(y_values, ddof=1) if len(y_values) > 1 else 0
|
||||
cv = (std_dev / mean_y) * 100 if mean_y != 0 else 0
|
||||
|
||||
# Разделяем факторные и центральные точки
|
||||
factorial_y = []
|
||||
center_y = []
|
||||
|
||||
for i, exp in enumerate(design):
|
||||
if exp.get('is_center', False):
|
||||
center_y.append(y_values[i])
|
||||
else:
|
||||
factorial_y.append(y_values[i])
|
||||
|
||||
# Анализ воспроизводимости
|
||||
center_variance = np.var(center_y, ddof=1) if len(center_y) > 1 else 0
|
||||
|
||||
# Критерий Фишера для проверки адекватности модели
|
||||
fisher_ratio = None
|
||||
model_adequate = None
|
||||
|
||||
if len(center_y) > 1 and len(factorial_y) > 1:
|
||||
factorial_variance = np.var(factorial_y, ddof=1)
|
||||
if factorial_variance > 0 and center_variance > 0:
|
||||
fisher_ratio = max(factorial_variance, center_variance) / min(factorial_variance, center_variance)
|
||||
# Критическое значение F (приблизительное, для p=0.05)
|
||||
# Более точное требует знания степеней свободы
|
||||
model_adequate = fisher_ratio < 4.0
|
||||
|
||||
analysis[resp_name] = {
|
||||
'mean': mean_y,
|
||||
'variance': variance,
|
||||
'std_dev': std_dev,
|
||||
'cv': cv,
|
||||
'factorial_values': factorial_y,
|
||||
'center_values': center_y,
|
||||
'center_variance': center_variance,
|
||||
'n_factorial': len(factorial_y),
|
||||
'n_center': len(center_y),
|
||||
'fisher_ratio': fisher_ratio,
|
||||
'model_adequate': model_adequate
|
||||
}
|
||||
|
||||
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(
|
||||
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)
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
"""
|
||||
Расчёт питательных сред
|
||||
|
||||
Этот модуль содержит функции для расчёта состава питательных сред
|
||||
на основе процентов, коэффициентов конверсии и разбавления.
|
||||
|
||||
Основная функция: calculate_medium_composition()
|
||||
"""
|
||||
|
||||
from typing import List, Dict, Tuple
|
||||
|
||||
# Константы для конверсии единиц измерения
|
||||
# Базовые единицы: мкл для объёма, мг для массы
|
||||
VOLUME_UNITS = {
|
||||
'нл': 0.001, # нанолитры -> микролитры
|
||||
'мкл': 1.0, # микролитры (база)
|
||||
'мл': 1000.0, # миллилитры -> микролитры
|
||||
'л': 1000000.0, # литры -> микролитры
|
||||
}
|
||||
|
||||
MASS_UNITS = {
|
||||
'нг': 0.000001, # нанограммы -> миллиграммы
|
||||
'мкг': 0.001, # микрограммы -> миллиграммы
|
||||
'мг': 1.0, # миллиграммы (база)
|
||||
'г': 1000.0, # граммы -> миллиграммы
|
||||
'кг': 1000000.0, # килограммы -> миллиграммы
|
||||
}
|
||||
|
||||
|
||||
def convert_units(value: float, from_unit: str, to_unit: str = None) -> float:
|
||||
"""
|
||||
Конвертирует значение между единицами объёма или массы
|
||||
|
||||
Параметры:
|
||||
value: числовое значение
|
||||
from_unit: исходная единица (например "мл" или "мг")
|
||||
to_unit: целевая единица (если None, конвертирует в базовую)
|
||||
|
||||
Возвращает:
|
||||
float: сконвертированное значение
|
||||
|
||||
Пример:
|
||||
>>> convert_units(100, 'мл') # конвертирует в базовую (мкл)
|
||||
100000.0
|
||||
>>> convert_units(500, 'мкл', 'мл')
|
||||
0.5
|
||||
"""
|
||||
# Определяем тип единицы (объём или масса)
|
||||
if from_unit in VOLUME_UNITS:
|
||||
units_map = VOLUME_UNITS
|
||||
elif from_unit in MASS_UNITS:
|
||||
units_map = MASS_UNITS
|
||||
else:
|
||||
raise ValueError(f"Неизвестная единица измерения: {from_unit}")
|
||||
|
||||
# Конвертируем в базовую единицу (мкл для объёма, мг для массы)
|
||||
value_in_base = value * units_map[from_unit]
|
||||
|
||||
# Если нужна конвертация в другую единицу
|
||||
if to_unit and to_unit in units_map:
|
||||
return value_in_base / units_map[to_unit]
|
||||
|
||||
return value_in_base
|
||||
|
||||
|
||||
def calculate_medium_composition(
|
||||
total_volume: float,
|
||||
volume_unit: str,
|
||||
reagents: List[Dict],
|
||||
solvent_name: str = "Вода"
|
||||
) -> Dict:
|
||||
"""
|
||||
РАССЧИТЫВАЕТ СОСТАВ ПИТАТЕЛЬНОЙ СРЕДЫ
|
||||
|
||||
Эта функция является основной для расчёта питательных сред.
|
||||
|
||||
ВХОДНЫЕ ПАРАМЕТРЫ:
|
||||
---------------
|
||||
total_volume : float
|
||||
Общий объём/количество среды (например 1000)
|
||||
|
||||
volume_unit : str
|
||||
Единица измерения общего объёма: "нл", "мкл", "мл", "л"
|
||||
|
||||
reagents : List[Dict]
|
||||
Список реагентов. Каждый реагент - словарь с ключами:
|
||||
- name (str): название реагента
|
||||
- percentage (float): процентное содержание в среде (0-100)
|
||||
- unit (str): единица измерения реагента (нг, мкг, мг, г, кг, нл, мкл, мл, л)
|
||||
- dilution_factor (float): фактор разбавления (необяз., по умолч. 1.0)
|
||||
|
||||
Пример реагента:
|
||||
{
|
||||
'name': 'Глюкоза',
|
||||
'percentage': 2.5,
|
||||
'unit': 'г',
|
||||
'dilution_factor': 1.0
|
||||
}
|
||||
|
||||
solvent_name : str, optional
|
||||
Название растворителя (по умолчанию "Вода")
|
||||
|
||||
ВОЗВРАЩАЕМЫЙ СЛОВАРЬ:
|
||||
--------------------
|
||||
{
|
||||
'total_volume': float, # Исходный объём
|
||||
'total_unit': str, # Единица измерения
|
||||
'solvent_name': str, # Название растворителя
|
||||
'solvent_volume': float, # Объём растворителя
|
||||
'solvent_percentage': float, # Процент растворителя
|
||||
'reagents': List[Dict] # Список реагентов с рассчитанными количествами
|
||||
}
|
||||
|
||||
Каждый реагент в возвращаемом списке содержит:
|
||||
- все исходные поля
|
||||
- calculated_amount (float): рассчитанное количество реагента
|
||||
- undiluted_amount (float): количество до разбавления
|
||||
- amount_unit (str): единица измерения (скопирована из unit)
|
||||
|
||||
ПРИМЕР ИСПОЛЬЗОВАНИЯ (в CLI):
|
||||
-----------------------------
|
||||
>>> reagents = [
|
||||
... {'name': 'Глюкоза', 'percentage': 2.0, 'unit': 'г', 'conversion_factor': 1.0},
|
||||
... {'name': 'Пептон', 'percentage': 1.0, 'unit': 'г', 'conversion_factor': 1.0}
|
||||
... ]
|
||||
>>> result = calculate_medium_composition(1000, 'мл', reagents)
|
||||
>>> print(f"Растворитель: {result['solvent_volume']} мл")
|
||||
Растворитель: 970.0 мл
|
||||
>>> for r in result['reagents']:
|
||||
... print(f"{r['name']}: {r['calculated_amount']} {r['unit']}")
|
||||
Глюкоза: 20.0 г
|
||||
Пептон: 10.0 г
|
||||
"""
|
||||
|
||||
# Проверка входных данных
|
||||
|
||||
if total_volume <= 0:
|
||||
raise ValueError(f"Общий объём должен быть положительным: {total_volume}")
|
||||
|
||||
if volume_unit not in VOLUME_UNITS:
|
||||
raise ValueError(f"Неизвестная единица объёма: {volume_unit}")
|
||||
|
||||
# Суммируем проценты всех реагентов
|
||||
total_percentage = sum(r.get('percentage', 0) for r in reagents)
|
||||
|
||||
if total_percentage > 100:
|
||||
raise ValueError(
|
||||
f"Сумма процентов ({total_percentage:.2f}%) превышает 100%"
|
||||
)
|
||||
|
||||
# Конвертируем общий объём в базовую единицу (мкл)
|
||||
total_base = convert_units(total_volume, volume_unit)
|
||||
|
||||
results = []
|
||||
total_diluted_volume_base = 0 # объём разбавленных реагентов в мкл
|
||||
|
||||
for reagent in reagents:
|
||||
# Извлекаем параметры с значениями по умолчанию
|
||||
percentage = reagent.get('percentage', 0)
|
||||
unit = reagent.get('unit', 'мг')
|
||||
# print ("unit = ",unit)
|
||||
# conversion_factor = reagent.get('conversion_factor', 1.0)
|
||||
dilution_factor = reagent.get('dilution_factor', 1.0)
|
||||
|
||||
# Проверяем, является ли реагент жидкостью (объём) или твёрдым веществом (масса)
|
||||
is_volume = unit in VOLUME_UNITS
|
||||
|
||||
# 1. Объём реагента в среде (исходя из процента)
|
||||
amount_in_base = (percentage / 100) * total_base
|
||||
# print ("amount_in_base = ",amount_in_base)
|
||||
# 2. Применяем коэффициент конверсии
|
||||
# adjusted_amount_base = amount_in_base * conversion_factor
|
||||
|
||||
# 3. Конвертируем в нужную единицу (без учёта разбавления)
|
||||
# undiluted_amount = convert_units(adjusted_amount_base, volume_unit, unit)
|
||||
undiluted_amount = convert_units(amount_in_base, 'мкл', unit)
|
||||
# print ("volume_unit = ",volume_unit)
|
||||
|
||||
# 4. Применяем разбавление
|
||||
if dilution_factor <= 0:
|
||||
dilution_factor = 1.0
|
||||
diluted_amount = undiluted_amount * dilution_factor
|
||||
# print ("diluted_amount = ", diluted_amount)
|
||||
# 5. Для объёмных реагентов учитываем в расчёте растворителя
|
||||
if is_volume:
|
||||
reagent_volume_base = convert_units(diluted_amount, unit)
|
||||
total_diluted_volume_base += reagent_volume_base
|
||||
|
||||
# Сохраняем результат
|
||||
reagent_result = reagent.copy()
|
||||
reagent_result['calculated_amount'] = diluted_amount
|
||||
reagent_result['undiluted_amount'] = undiluted_amount
|
||||
reagent_result['amount_unit'] = unit
|
||||
results.append(reagent_result)
|
||||
|
||||
# Рассчитываем объём растворителя
|
||||
solvent_volume_base = total_base - total_diluted_volume_base
|
||||
if solvent_volume_base < 0:
|
||||
solvent_volume_base = 0
|
||||
|
||||
solvent_volume = convert_units(solvent_volume_base, 'мкл', volume_unit)
|
||||
solvent_percentage = 100 - total_percentage
|
||||
|
||||
return {
|
||||
'total_volume': total_volume,
|
||||
'total_unit': volume_unit,
|
||||
'solvent_name': solvent_name,
|
||||
'solvent_volume': solvent_volume,
|
||||
'solvent_percentage': solvent_percentage,
|
||||
'reagents': results
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
"""
|
||||
Модель данных проекта для сохранения/загрузки в JSON
|
||||
"""
|
||||
|
||||
from typing import List, Dict, Optional
|
||||
from dataclasses import dataclass, asdict
|
||||
from datetime import datetime
|
||||
import json
|
||||
|
||||
|
||||
@dataclass
|
||||
class ReagentData:
|
||||
"""Данные реагента"""
|
||||
name: str
|
||||
percentage: float
|
||||
unit: str
|
||||
conversion_factor: float
|
||||
dilution_factor: float
|
||||
|
||||
def to_dict(self) -> Dict:
|
||||
return asdict(self)
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: Dict) -> 'ReagentData':
|
||||
return cls(
|
||||
name=data['name'],
|
||||
percentage=data['percentage'],
|
||||
unit=data['unit'],
|
||||
conversion_factor=data.get('conversion_factor', 1.0),
|
||||
dilution_factor=data.get('dilution_factor', 1.0)
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class FactorData:
|
||||
"""Данные фактора эксперимента"""
|
||||
name: str
|
||||
center: float
|
||||
low: float
|
||||
high: float
|
||||
step: float
|
||||
step_type: str
|
||||
unit: str
|
||||
percentage: Optional[float] = None
|
||||
dilution_factor: Optional[float] = None
|
||||
|
||||
def to_dict(self) -> Dict:
|
||||
d = asdict(self)
|
||||
# Удаляем None значения
|
||||
return {k: v for k, v in d.items() if v is not None}
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: Dict) -> 'FactorData':
|
||||
return cls(
|
||||
name=data['name'],
|
||||
center=data['center'],
|
||||
low=data['low'],
|
||||
high=data['high'],
|
||||
step=data.get('step', 0),
|
||||
step_type=data.get('step_type', 'абс'),
|
||||
unit=data.get('unit', ''),
|
||||
percentage=data.get('percentage'),
|
||||
dilution_factor=data.get('dilution_factor')
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class ResponseData:
|
||||
"""Данные отклика эксперимента"""
|
||||
name: str
|
||||
unit: str
|
||||
|
||||
def to_dict(self) -> Dict:
|
||||
return asdict(self)
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: Dict) -> 'ResponseData':
|
||||
return cls(
|
||||
name=data['name'],
|
||||
unit=data.get('unit', '')
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class ExperimentResultsData:
|
||||
"""Результаты эксперимента"""
|
||||
design: List[Dict] # План эксперимента
|
||||
results: List[List[float]] # Результаты измерений
|
||||
responses: List[ResponseData] # Отклики
|
||||
|
||||
def to_dict(self) -> Dict:
|
||||
return {
|
||||
'design': self.design,
|
||||
'results': self.results,
|
||||
'responses': [r.to_dict() for r in self.responses]
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: Dict) -> 'ExperimentResultsData':
|
||||
return cls(
|
||||
design=data['design'],
|
||||
results=data['results'],
|
||||
responses=[ResponseData.from_dict(r) for r in data['responses']]
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class ProjectData:
|
||||
"""Полные данные проекта"""
|
||||
# Информация о проекте
|
||||
project_name: str
|
||||
created_at: str
|
||||
modified_at: str
|
||||
version: str = "1.0"
|
||||
|
||||
# Данные калькулятора сред
|
||||
medium_total_volume: float = 1000.0
|
||||
medium_volume_unit: str = "мл"
|
||||
medium_solvent: str = "Вода"
|
||||
medium_reagents: List[ReagentData] = None
|
||||
|
||||
# Данные эксперимента
|
||||
experiment_factors: List[FactorData] = None
|
||||
experiment_responses: List[ResponseData] = None
|
||||
experiment_center_points: int = 3
|
||||
experiment_randomize: bool = True
|
||||
experiment_results: Optional[ExperimentResultsData] = None
|
||||
|
||||
def __post_init__(self):
|
||||
if self.medium_reagents is None:
|
||||
self.medium_reagents = []
|
||||
if self.experiment_factors is None:
|
||||
self.experiment_factors = []
|
||||
if self.experiment_responses is None:
|
||||
self.experiment_responses = []
|
||||
|
||||
def to_dict(self) -> Dict:
|
||||
"""Конвертирует в словарь для JSON"""
|
||||
return {
|
||||
'project_info': {
|
||||
'name': self.project_name,
|
||||
'created_at': self.created_at,
|
||||
'modified_at': self.modified_at,
|
||||
'version': self.version
|
||||
},
|
||||
'medium_calculator': {
|
||||
'total_volume': self.medium_total_volume,
|
||||
'volume_unit': self.medium_volume_unit,
|
||||
'solvent': self.medium_solvent,
|
||||
'reagents': [r.to_dict() for r in self.medium_reagents]
|
||||
},
|
||||
'experiment': {
|
||||
'factors': [f.to_dict() for f in self.experiment_factors],
|
||||
'responses': [r.to_dict() for r in self.experiment_responses],
|
||||
'center_points': self.experiment_center_points,
|
||||
'randomize': self.experiment_randomize,
|
||||
'results': self.experiment_results.to_dict() if self.experiment_results else None
|
||||
}
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: Dict) -> 'ProjectData':
|
||||
"""Создаёт объект из словаря"""
|
||||
project_info = data.get('project_info', {})
|
||||
medium = data.get('medium_calculator', {})
|
||||
experiment = data.get('experiment', {})
|
||||
|
||||
# Создаём объект
|
||||
obj = cls(
|
||||
project_name=project_info.get('name', 'Новый проект'),
|
||||
created_at=project_info.get('created_at', datetime.now().isoformat()),
|
||||
modified_at=project_info.get('modified_at', datetime.now().isoformat()),
|
||||
version=project_info.get('version', '1.0'),
|
||||
medium_total_volume=medium.get('total_volume', 1000.0),
|
||||
medium_volume_unit=medium.get('volume_unit', 'мл'),
|
||||
medium_solvent=medium.get('solvent', 'Вода'),
|
||||
medium_reagents=[ReagentData.from_dict(r) for r in medium.get('reagents', [])],
|
||||
experiment_factors=[FactorData.from_dict(f) for f in experiment.get('factors', [])],
|
||||
experiment_responses=[ResponseData.from_dict(r) for r in experiment.get('responses', [])],
|
||||
experiment_center_points=experiment.get('center_points', 3),
|
||||
experiment_randomize=experiment.get('randomize', True),
|
||||
experiment_results=ExperimentResultsData.from_dict(experiment['results'])
|
||||
if experiment.get('results') else None
|
||||
)
|
||||
return obj
|
||||
|
||||
def save_to_file(self, filename: str):
|
||||
"""Сохраняет проект в JSON файл"""
|
||||
self.modified_at = datetime.now().isoformat()
|
||||
with open(filename, 'w', encoding='utf-8') as f:
|
||||
json.dump(self.to_dict(), f, ensure_ascii=False, indent=2)
|
||||
|
||||
@classmethod
|
||||
def load_from_file(cls, filename: str) -> 'ProjectData':
|
||||
"""Загружает проект из JSON файла"""
|
||||
with open(filename, 'r', encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
return cls.from_dict(data)
|
||||
|
||||
|
||||
def create_new_project(name: str = "Новый проект") -> ProjectData:
|
||||
"""Создаёт новый проект с текущей датой"""
|
||||
now = datetime.now().isoformat()
|
||||
return ProjectData(
|
||||
project_name=name,
|
||||
created_at=now,
|
||||
modified_at=now
|
||||
)
|
||||
Reference in New Issue
Block a user