from PyQt5.QtWidgets import (QMainWindow, QVBoxLayout, QHBoxLayout, QPushButton, QLabel, QWidget, QMessageBox, QTableWidget, QTableWidgetItem, QGroupBox, QSpinBox, QDoubleSpinBox, QComboBox, QLineEdit, QTextEdit, QTabWidget, QFormLayout) from PyQt5.QtCore import Qt from PyQt5.QtGui import QFont class ExperimentDesignWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("Планирование эксперимента - Цифровой помощник биохимика") self.setGeometry(200, 100, 1000, 750) self.setStyleSheet(""" QMainWindow { background-color: #f5f5f5; } QGroupBox { font-weight: bold; border: 2px solid #ccc; border-radius: 5px; margin-top: 10px; padding-top: 10px; } QGroupBox::title { subcontrol-origin: margin; left: 10px; padding: 0 5px 0 5px; } QPushButton { background-color: #4CAF50; color: white; border: none; padding: 8px; border-radius: 4px; font-weight: bold; } QPushButton:hover { background-color: #45a049; } QTableWidget { gridline-color: #ddd; } """) self._init_ui() def _init_ui(self): central_widget = QWidget() self.setCentralWidget(central_widget) layout = QVBoxLayout(central_widget) # Заголовок title_label = QLabel("📈 Планирование полнофакторного эксперимента (DoE)") title_font = QFont() title_font.setPointSize(18) title_font.setBold(True) title_label.setFont(title_font) title_label.setAlignment(Qt.AlignCenter) title_label.setStyleSheet("color: #2E7D32;") layout.addWidget(title_label) # Вкладки tabs = QTabWidget() # Вкладка 1: Параметры эксперимента params_tab = QWidget() params_layout = QVBoxLayout(params_tab) # Группа факторов factors_group = QGroupBox("Факторы эксперимента (независимые переменные)") factors_layout = QVBoxLayout() # Информация info_label = QLabel("Определите факторы, которые влияют на ваш эксперимент:") info_label.setStyleSheet("color: #555; font-weight: normal;") factors_layout.addWidget(info_label) self.factors_table = QTableWidget() self.factors_table.setColumnCount(4) self.factors_table.setHorizontalHeaderLabels(["Фактор", "Нижний уровень (-1)", "Верхний уровень (+1)", "Единица измерения"]) self.factors_table.setRowCount(3) # Пример данных sample_factors = [ ["Температура", "25", "37", "°C"], ["pH", "6.5", "7.5", ""], ["Концентрация глюкозы", "5", "20", "г/л"] ] for i, factor in enumerate(sample_factors): for j, value in enumerate(factor): self.factors_table.setItem(i, j, QTableWidgetItem(value)) factors_layout.addWidget(self.factors_table) # Кнопки для управления факторами factor_buttons = QHBoxLayout() add_factor_btn = QPushButton("+ Добавить фактор") add_factor_btn.clicked.connect(self.add_factor_row) remove_factor_btn = QPushButton("- Удалить последний") remove_factor_btn.clicked.connect(self.remove_factor_row) factor_buttons.addWidget(add_factor_btn) factor_buttons.addWidget(remove_factor_btn) factor_buttons.addStretch() factors_layout.addLayout(factor_buttons) factors_group.setLayout(factors_layout) params_layout.addWidget(factors_group) # Группа откликов responses_group = QGroupBox("Отклики (зависимые переменные)") responses_layout = QVBoxLayout() self.responses_table = QTableWidget() self.responses_table.setColumnCount(2) self.responses_table.setHorizontalHeaderLabels(["Отклик", "Единица измерения"]) self.responses_table.setRowCount(2) sample_responses = [ ["Оптическая плотность (OD600)", ""], ["Концентрация целевого продукта", "мг/мл"] ] for i, response in enumerate(sample_responses): for j, value in enumerate(response): self.responses_table.setItem(i, j, QTableWidgetItem(value)) responses_layout.addWidget(self.responses_table) # Кнопки для управления откликами response_buttons = QHBoxLayout() add_response_btn = QPushButton("+ Добавить отклик") add_response_btn.clicked.connect(self.add_response_row) remove_response_btn = QPushButton("- Удалить последний") remove_response_btn.clicked.connect(self.remove_response_row) response_buttons.addWidget(add_response_btn) response_buttons.addWidget(remove_response_btn) response_buttons.addStretch() responses_layout.addLayout(response_buttons) responses_group.setLayout(responses_layout) params_layout.addWidget(responses_group) tabs.addTab(params_tab, "📝 Параметры эксперимента") # Вкладка 2: Матрица планирования plan_tab = QWidget() plan_layout = QVBoxLayout(plan_tab) plan_info = QLabel("Полнофакторный план эксперимента (Full Factorial Design)") plan_info.setAlignment(Qt.AlignCenter) plan_info.setStyleSheet("font-weight: bold; font-size: 14px; padding: 10px;") plan_layout.addWidget(plan_info) self.design_matrix = QTableWidget() plan_layout.addWidget(self.design_matrix) generate_btn = QPushButton("🔄 Сгенерировать план эксперимента") generate_btn.clicked.connect(self.generate_design_matrix) plan_layout.addWidget(generate_btn) tabs.addTab(plan_tab, "📊 Матрица планирования") # Вкладка 3: Анализ результатов analysis_tab = QWidget() analysis_layout = QVBoxLayout(analysis_tab) analysis_info = QLabel("Регрессионный анализ и визуализация результатов") analysis_info.setAlignment(Qt.AlignCenter) analysis_info.setStyleSheet("font-weight: bold; font-size: 14px; padding: 10px;") analysis_layout.addWidget(analysis_info) # Заглушка для анализа placeholder = QLabel("Здесь будет:\n\n" "• Множественная линейная регрессия\n" "• Анализ взаимодействий факторов\n" "• ANOVA (дисперсионный анализ)\n" "• Построение поверхностей отклика\n" "• Графики главных эффектов\n" "• Оптимизация параметров") placeholder.setAlignment(Qt.AlignCenter) placeholder.setStyleSheet("color: #666; font-size: 12px; border: 1px dashed #ccc; padding: 20px;") analysis_layout.addWidget(placeholder) analyze_btn = QPushButton("📈 Провести анализ (в разработке)") analyze_btn.clicked.connect(self.show_placeholder_message) analysis_layout.addWidget(analyze_btn) tabs.addTab(analysis_tab, "📐 Анализ результатов") # Вкладка 4: Визуализация viz_tab = QWidget() viz_layout = QVBoxLayout(viz_tab) viz_info = QLabel("Интерактивная визуализация данных") viz_info.setAlignment(Qt.AlignCenter) viz_info.setStyleSheet("font-weight: bold; font-size: 14px; padding: 10px;") viz_layout.addWidget(viz_info) viz_placeholder = QLabel("Здесь будут:\n\n" "• 2D и 3D графики поверхностей отклика\n" "• Контурные графики\n" "• Диаграммы Парето\n" "• Графики нормальной вероятности") viz_placeholder.setAlignment(Qt.AlignCenter) viz_placeholder.setStyleSheet("color: #666; font-size: 12px; border: 1px dashed #ccc; padding: 20px;") viz_layout.addWidget(viz_placeholder) tabs.addTab(viz_tab, "📈 Визуализация") layout.addWidget(tabs) # Кнопки управления btn_layout = QHBoxLayout() export_btn = QPushButton("💾 Экспорт отчёта (PDF/Excel)") export_btn.clicked.connect(self.show_placeholder_message) btn_layout.addWidget(export_btn) save_btn = QPushButton("💿 Сохранить проект") save_btn.clicked.connect(self.show_placeholder_message) btn_layout.addWidget(save_btn) load_btn = QPushButton("📂 Загрузить проект") load_btn.clicked.connect(self.show_placeholder_message) btn_layout.addWidget(load_btn) btn_layout.addStretch() close_btn = QPushButton("❌ Закрыть") close_btn.clicked.connect(self.close) btn_layout.addWidget(close_btn) layout.addLayout(btn_layout) def add_factor_row(self): """Добавляет строку для нового фактора""" row = self.factors_table.rowCount() self.factors_table.insertRow(row) self.factors_table.setItem(row, 0, QTableWidgetItem(f"Фактор_{row+1}")) self.factors_table.setItem(row, 1, QTableWidgetItem("0")) self.factors_table.setItem(row, 2, QTableWidgetItem("1")) self.factors_table.setItem(row, 3, QTableWidgetItem("")) def remove_factor_row(self): """Удаляет последнюю строку факторов""" if self.factors_table.rowCount() > 1: self.factors_table.removeRow(self.factors_table.rowCount() - 1) def add_response_row(self): """Добавляет строку для нового отклика""" row = self.responses_table.rowCount() self.responses_table.insertRow(row) self.responses_table.setItem(row, 0, QTableWidgetItem(f"Отклик_{row+1}")) self.responses_table.setItem(row, 1, QTableWidgetItem("")) def remove_response_row(self): """Удаляет последнюю строку откликов""" if self.responses_table.rowCount() > 1: self.responses_table.removeRow(self.responses_table.rowCount() - 1) def generate_design_matrix(self): """Генерирует матрицу планирования (заглушка)""" n_factors = self.factors_table.rowCount() if n_factors == 0: QMessageBox.warning(self, "Предупреждение", "Добавьте хотя бы один фактор!") return # Полнофакторный план: 2^k опытов n_experiments = 2 ** n_factors self.design_matrix.setRowCount(n_experiments) self.design_matrix.setColumnCount(n_factors + 1) # Заголовки headers = ["Опыт №"] + [self.factors_table.item(i, 0).text() if self.factors_table.item(i, 0) else f"Фактор_{i+1}" for i in range(n_factors)] self.design_matrix.setHorizontalHeaderLabels(headers) # Заполняем матрицу (простой 2^k план) for exp in range(n_experiments): # Номер опыта self.design_matrix.setItem(exp, 0, QTableWidgetItem(str(exp + 1))) # Кодированные уровни факторов (-1 или +1) for factor in range(n_factors): level = -1 if (exp // (2 ** factor)) % 2 == 0 else 1 self.design_matrix.setItem(exp, factor + 1, QTableWidgetItem(str(level))) self.design_matrix.resizeColumnsToContents() QMessageBox.information(self, "Успех", f"Сгенерирован план для {n_factors} факторов\n" f"Общее количество опытов: {n_experiments}") def show_placeholder_message(self): """Показывает сообщение о том, что функция в разработке""" QMessageBox.information( self, "В разработке", "🧪 Биотехнологические инструменты в стадии активной разработки!\n\n" "В ближайшее время здесь появится:\n\n" "✅ Полнофакторный план (2^k факторный дизайн)\n" "✅ Регрессионный анализ и ANOVA\n" "✅ Построение поверхностей отклика\n" "✅ Оптимизация параметров\n" "✅ Экспорт в Excel и PDF\n" "✅ Визуализация 2D/3D графиков\n\n" "Следите за обновлениями!" )