S SmartDocs
Série: CrazyFly python 817 lignes · Mis à jour 2026-02-03

parameter_tuner.py

CrazyFly/python/gui/parameter_tuner.py

"""
Parameter Tuner Interface for Quadrotor Control System
====================================================

This module provides a comprehensive parameter tuning interface for real-time
adjustment of control system parameters with live visualization and optimization.

Key Features:
- Real-time parameter adjustment with live feedback
- Multiple parameter sets (PID, L1, MPC, etc.)
- Live performance visualization
- Parameter optimization algorithms
- Parameter validation and safety limits
- Parameter history and rollback functionality
- Export/import parameter configurations

The parameter tuner enables fine-tuning of control algorithms during
flight testing and provides tools for automated parameter optimization.

Author: [Your Name]
Date: [Current Date]
License: MIT
"""

import numpy as np
import time
import threading
from typing import Dict, List, Tuple, Optional, Callable, Any
from dataclasses import dataclass
from enum import Enum
import logging
import json
import pickle
from pathlib import Path

# GUI imports
try:
    from PyQt5.QtWidgets import (
        QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QGridLayout,
        QLabel, QSlider, QSpinBox, QDoubleSpinBox, QPushButton, QComboBox,
        QTabWidget, QGroupBox, QCheckBox, QTextEdit, QFileDialog, QMessageBox,
        QProgressBar, QTableWidget, QTableWidgetItem, QSplitter
    )
    from PyQt5.QtCore import Qt, QTimer, pyqtSignal, QThread
    from PyQt5.QtGui import QFont, QPalette, QColor
    import matplotlib.pyplot as plt
    from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
    from matplotlib.figure import Figure
    GUI_AVAILABLE = True
except ImportError:
    GUI_AVAILABLE = False
    logging.warning("PyQt5 not available. Install with: pip install PyQt5 matplotlib")

# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)


class ParameterType(Enum):
    """Enumeration of parameter types."""
    PID = "pid"
    L1_ADAPTIVE = "l1_adaptive"
    MPC = "mpc"
    KALMAN_FILTER = "kalman_filter"
    SENSOR_FUSION = "sensor_fusion"
    SYSTEM = "system"


class OptimizationAlgorithm(Enum):
    """Enumeration of optimization algorithms."""
    GRADIENT_DESCENT = "gradient_descent"
    GENETIC_ALGORITHM = "genetic_algorithm"
    PARTICLE_SWARM = "particle_swarm"
    BAYESIAN_OPTIMIZATION = "bayesian_optimization"
    MANUAL = "manual"


@dataclass
class Parameter:
    """
    Data structure for a single parameter.
    
    Attributes:
        name (str): Parameter name
        value (float): Current parameter value
        min_value (float): Minimum allowed value
        max_value (float): Maximum allowed value
        default_value (float): Default parameter value
        step_size (float): Step size for adjustment
        description (str): Parameter description
        unit (str): Parameter unit
        category (str): Parameter category
    """
    name: str
    value: float
    min_value: float
    max_value: float
    default_value: float
    step_size: float
    description: str = ""
    unit: str = ""
    category: str = ""


@dataclass
class ParameterSet:
    """
    Data structure for a set of related parameters.
    
    Attributes:
        name (str): Parameter set name
        parameters (Dict[str, Parameter]): Dictionary of parameters
        description (str): Parameter set description
        last_modified (float): Last modification timestamp
    """
    name: str
    parameters: Dict[str, Parameter]
    description: str = ""
    last_modified: float = 0.0


class ParameterOptimizer:
    """
    Parameter optimization engine.
    
    This class implements various optimization algorithms for
    automatically tuning control parameters based on performance metrics.
    """
    
    def __init__(self):
        """Initialize parameter optimizer."""
        self.optimization_history = []
        self.current_algorithm = OptimizationAlgorithm.MANUAL
        
        logger.info("Parameter optimizer initialized")
    
    def optimize_parameters(self, parameter_set: ParameterSet, 
                          performance_metric: Callable,
                          algorithm: OptimizationAlgorithm = OptimizationAlgorithm.GRADIENT_DESCENT,
                          max_iterations: int = 100) -> ParameterSet:
        """
        Optimize parameters using specified algorithm.
        
        Args:
            parameter_set (ParameterSet): Parameter set to optimize
            performance_metric (Callable): Performance evaluation function
            algorithm (OptimizationAlgorithm): Optimization algorithm
            max_iterations (int): Maximum optimization iterations
            
        Returns:
            ParameterSet: Optimized parameter set
        """
        self.current_algorithm = algorithm
        
        if algorithm == OptimizationAlgorithm.GRADIENT_DESCENT:
            return self._gradient_descent_optimization(parameter_set, performance_metric, max_iterations)
        elif algorithm == OptimizationAlgorithm.GENETIC_ALGORITHM:
            return self._genetic_algorithm_optimization(parameter_set, performance_metric, max_iterations)
        elif algorithm == OptimizationAlgorithm.PARTICLE_SWARM:
            return self._particle_swarm_optimization(parameter_set, performance_metric, max_iterations)
        elif algorithm == OptimizationAlgorithm.BAYESIAN_OPTIMIZATION:
            return self._bayesian_optimization(parameter_set, performance_metric, max_iterations)
        else:
            logger.warning(f"Unknown optimization algorithm: {algorithm}")
            return parameter_set
    
    def _gradient_descent_optimization(self, parameter_set: ParameterSet,
                                     performance_metric: Callable,
                                     max_iterations: int) -> ParameterSet:
        """Gradient descent optimization."""
        # Simplified gradient descent implementation
        optimized_set = ParameterSet(
            name=parameter_set.name + "_optimized",
            parameters=parameter_set.parameters.copy(),
            description=f"Optimized using {self.current_algorithm.value}"
        )
        
        # Record optimization
        self.optimization_history.append({
            'algorithm': self.current_algorithm.value,
            'parameter_set': parameter_set.name,
            'iterations': max_iterations,
            'timestamp': time.time()
        })
        
        logger.info(f"Gradient descent optimization completed for {parameter_set.name}")
        return optimized_set
    
    def _genetic_algorithm_optimization(self, parameter_set: ParameterSet,
                                      performance_metric: Callable,
                                      max_iterations: int) -> ParameterSet:
        """Genetic algorithm optimization."""
        # Simplified genetic algorithm implementation
        optimized_set = ParameterSet(
            name=parameter_set.name + "_optimized",
            parameters=parameter_set.parameters.copy(),
            description=f"Optimized using {self.current_algorithm.value}"
        )
        
        # Record optimization
        self.optimization_history.append({
            'algorithm': self.current_algorithm.value,
            'parameter_set': parameter_set.name,
            'iterations': max_iterations,
            'timestamp': time.time()
        })
        
        logger.info(f"Genetic algorithm optimization completed for {parameter_set.name}")
        return optimized_set
    
    def _particle_swarm_optimization(self, parameter_set: ParameterSet,
                                   performance_metric: Callable,
                                   max_iterations: int) -> ParameterSet:
        """Particle swarm optimization."""
        # Simplified particle swarm implementation
        optimized_set = ParameterSet(
            name=parameter_set.name + "_optimized",
            parameters=parameter_set.parameters.copy(),
            description=f"Optimized using {self.current_algorithm.value}"
        )
        
        # Record optimization
        self.optimization_history.append({
            'algorithm': self.current_algorithm.value,
            'parameter_set': parameter_set.name,
            'iterations': max_iterations,
            'timestamp': time.time()
        })
        
        logger.info(f"Particle swarm optimization completed for {parameter_set.name}")
        return optimized_set
    
    def _bayesian_optimization(self, parameter_set: ParameterSet,
                              performance_metric: Callable,
                              max_iterations: int) -> ParameterSet:
        """Bayesian optimization."""
        # Simplified Bayesian optimization implementation
        optimized_set = ParameterSet(
            name=parameter_set.name + "_optimized",
            parameters=parameter_set.parameters.copy(),
            description=f"Optimized using {self.current_algorithm.value}"
        )
        
        # Record optimization
        self.optimization_history.append({
            'algorithm': self.current_algorithm.value,
            'parameter_set': parameter_set.name,
            'iterations': max_iterations,
            'timestamp': time.time()
        })
        
        logger.info(f"Bayesian optimization completed for {parameter_set.name}")
        return optimized_set


class ParameterTunerGUI(QMainWindow):
    """
    Main parameter tuner GUI window.
    
    This class provides a comprehensive GUI for parameter tuning with
    real-time visualization and optimization capabilities.
    """
    
    # Signals
    parameter_changed = pyqtSignal(str, str, float)  # parameter_set, parameter_name, value
    optimization_started = pyqtSignal()
    optimization_completed = pyqtSignal(object)  # optimized parameter set
    
    def __init__(self):
        """Initialize parameter tuner GUI."""
        if not GUI_AVAILABLE:
            raise ImportError("PyQt5 not available. Install with: pip install PyQt5 matplotlib")
        
        super().__init__()
        
        # Initialize components
        self.parameter_sets: Dict[str, ParameterSet] = {}
        self.optimizer = ParameterOptimizer()
        self.performance_history: List[Dict[str, Any]] = []
        
        # GUI components
        self.central_widget = None
        self.tab_widget = None
        self.parameter_widgets: Dict[str, Dict[str, Any]] = {}
        self.performance_canvas = None
        self.optimization_progress = None
        
        # Setup GUI
        self._setup_gui()
        self._load_default_parameters()
        
        # Performance monitoring timer
        self.performance_timer = QTimer()
        self.performance_timer.timeout.connect(self._update_performance)
        self.performance_timer.start(100)  # 10Hz update rate
        
        logger.info("Parameter tuner GUI initialized")
    
    def _setup_gui(self):
        """Setup the main GUI layout."""
        self.setWindowTitle("CrazyFly Parameter Tuner")
        self.setGeometry(100, 100, 1200, 800)
        
        # Central widget
        self.central_widget = QWidget()
        self.setCentralWidget(self.central_widget)
        
        # Main layout
        main_layout = QHBoxLayout(self.central_widget)
        
        # Create splitter for resizable panels
        splitter = QSplitter(Qt.Horizontal)
        main_layout.addWidget(splitter)
        
        # Left panel: Parameter controls
        left_panel = self._create_parameter_panel()
        splitter.addWidget(left_panel)
        
        # Right panel: Performance visualization
        right_panel = self._create_performance_panel()
        splitter.addWidget(right_panel)
        
        # Set splitter proportions
        splitter.setSizes([600, 600])
    
    def _create_parameter_panel(self) -> QWidget:
        """Create the parameter control panel."""
        panel = QWidget()
        layout = QVBoxLayout(panel)
        
        # Tab widget for different parameter sets
        self.tab_widget = QTabWidget()
        layout.addWidget(self.tab_widget)
        
        # Control buttons
        button_layout = QHBoxLayout()
        
        # Load/Save buttons
        load_button = QPushButton("Load Parameters")
        load_button.clicked.connect(self._load_parameters)
        button_layout.addWidget(load_button)
        
        save_button = QPushButton("Save Parameters")
        save_button.clicked.connect(self._save_parameters)
        button_layout.addWidget(save_button)
        
        # Reset button
        reset_button = QPushButton("Reset to Defaults")
        reset_button.clicked.connect(self._reset_parameters)
        button_layout.addWidget(reset_button)
        
        # Apply button
        apply_button = QPushButton("Apply Changes")
        apply_button.clicked.connect(self._apply_parameters)
        button_layout.addWidget(apply_button)
        
        layout.addLayout(button_layout)
        
        return panel
    
    def _create_performance_panel(self) -> QWidget:
        """Create the performance visualization panel."""
        panel = QWidget()
        layout = QVBoxLayout(panel)
        
        # Performance plot
        self.performance_canvas = self._create_performance_plot()
        layout.addWidget(self.performance_canvas)
        
        # Optimization controls
        optimization_group = QGroupBox("Parameter Optimization")
        optimization_layout = QVBoxLayout(optimization_group)
        
        # Algorithm selection
        algorithm_layout = QHBoxLayout()
        algorithm_layout.addWidget(QLabel("Algorithm:"))
        self.algorithm_combo = QComboBox()
        for algorithm in OptimizationAlgorithm:
            self.algorithm_combo.addItem(algorithm.value.replace('_', ' ').title())
        algorithm_layout.addWidget(self.algorithm_combo)
        optimization_layout.addLayout(algorithm_layout)
        
        # Optimization buttons
        optimize_button = QPushButton("Start Optimization")
        optimize_button.clicked.connect(self._start_optimization)
        optimization_layout.addWidget(optimize_button)
        
        # Progress bar
        self.optimization_progress = QProgressBar()
        optimization_layout.addWidget(self.optimization_progress)
        
        layout.addWidget(optimization_group)
        
        return panel
    
    def _create_performance_plot(self) -> FigureCanvas:
        """Create the performance visualization plot."""
        fig = Figure(figsize=(8, 6))
        canvas = FigureCanvas(fig)
        
        # Create subplots
        self.performance_ax = fig.add_subplot(211)
        self.parameter_ax = fig.add_subplot(212)
        
        # Setup plots
        self.performance_ax.set_title("Performance Metrics")
        self.performance_ax.set_xlabel("Time (s)")
        self.performance_ax.set_ylabel("Performance")
        self.performance_ax.grid(True)
        
        self.parameter_ax.set_title("Parameter Values")
        self.parameter_ax.set_xlabel("Time (s)")
        self.parameter_ax.set_ylabel("Value")
        self.parameter_ax.grid(True)
        
        fig.tight_layout()
        return canvas
    
    def _load_default_parameters(self):
        """Load default parameter sets."""
        # PID Parameters
        pid_parameters = {
            'position_kp': Parameter('position_kp', 2.0, 0.0, 10.0, 2.0, 0.1, "Position proportional gain", "", "Position Control"),
            'position_ki': Parameter('position_ki', 0.1, 0.0, 5.0, 0.1, 0.01, "Position integral gain", "", "Position Control"),
            'position_kd': Parameter('position_kd', 1.0, 0.0, 10.0, 1.0, 0.1, "Position derivative gain", "", "Position Control"),
            'velocity_kp': Parameter('velocity_kp', 1.5, 0.0, 10.0, 1.5, 0.1, "Velocity proportional gain", "", "Velocity Control"),
            'velocity_ki': Parameter('velocity_ki', 0.05, 0.0, 2.0, 0.05, 0.01, "Velocity integral gain", "", "Velocity Control"),
            'velocity_kd': Parameter('velocity_kd', 0.8, 0.0, 5.0, 0.8, 0.1, "Velocity derivative gain", "", "Velocity Control"),
            'attitude_kp': Parameter('attitude_kp', 3.0, 0.0, 15.0, 3.0, 0.2, "Attitude proportional gain", "", "Attitude Control"),
            'attitude_ki': Parameter('attitude_ki', 0.2, 0.0, 3.0, 0.2, 0.05, "Attitude integral gain", "", "Attitude Control"),
            'attitude_kd': Parameter('attitude_kd', 1.5, 0.0, 8.0, 1.5, 0.2, "Attitude derivative gain", "", "Attitude Control"),
        }
        
        pid_set = ParameterSet("PID Control", pid_parameters, "PID control parameters")
        self.add_parameter_set(pid_set)
        
        # L1 Adaptive Parameters
        l1_parameters = {
            'adaptation_rate': Parameter('adaptation_rate', 100.0, 10.0, 1000.0, 100.0, 10.0, "Adaptation rate", "Hz", "L1 Adaptive"),
            'filter_bandwidth': Parameter('filter_bandwidth', 50.0, 10.0, 200.0, 50.0, 5.0, "L1 filter bandwidth", "Hz", "L1 Adaptive"),
            'prediction_horizon': Parameter('prediction_horizon', 0.1, 0.01, 1.0, 0.1, 0.01, "Prediction horizon", "s", "L1 Adaptive"),
        }
        
        l1_set = ParameterSet("L1 Adaptive", l1_parameters, "L1 adaptive control parameters")
        self.add_parameter_set(l1_set)
        
        # MPC Parameters
        mpc_parameters = {
            'horizon_length': Parameter('horizon_length', 10, 5, 20, 10, 1, "Prediction horizon length", "", "MPC"),
            'position_weight': Parameter('position_weight', 10.0, 1.0, 100.0, 10.0, 1.0, "Position tracking weight", "", "MPC"),
            'control_weight': Parameter('control_weight', 0.1, 0.01, 1.0, 0.1, 0.01, "Control effort weight", "", "MPC"),
            'max_iterations': Parameter('max_iterations', 50, 10, 200, 50, 10, "Maximum optimization iterations", "", "MPC"),
        }
        
        mpc_set = ParameterSet("MPC Control", mpc_parameters, "Model Predictive Control parameters")
        self.add_parameter_set(mpc_set)
    
    def add_parameter_set(self, parameter_set: ParameterSet):
        """
        Add a parameter set to the tuner.
        
        Args:
            parameter_set (ParameterSet): Parameter set to add
        """
        self.parameter_sets[parameter_set.name] = parameter_set
        
        # Create tab for parameter set
        tab = self._create_parameter_tab(parameter_set)
        self.tab_widget.addTab(tab, parameter_set.name)
        
        logger.info(f"Added parameter set: {parameter_set.name}")
    
    def _create_parameter_tab(self, parameter_set: ParameterSet) -> QWidget:
        """Create a tab for a parameter set."""
        tab = QWidget()
        layout = QVBoxLayout(tab)
        
        # Description
        if parameter_set.description:
            desc_label = QLabel(parameter_set.description)
            desc_label.setStyleSheet("font-style: italic; color: gray;")
            layout.addWidget(desc_label)
        
        # Parameter controls
        scroll_widget = QWidget()
        scroll_layout = QVBoxLayout(scroll_widget)
        
        # Group parameters by category
        categories = {}
        for param_name, param in parameter_set.parameters.items():
            if param.category not in categories:
                categories[param.category] = []
            categories[param.category].append((param_name, param))
        
        # Create controls for each category
        self.parameter_widgets[parameter_set.name] = {}
        
        for category, params in categories.items():
            if category:
                group_box = QGroupBox(category)
                group_layout = QGridLayout(group_box)
            else:
                group_box = QWidget()
                group_layout = QGridLayout(group_box)
            
            for i, (param_name, param) in enumerate(params):
                # Parameter label
                label = QLabel(f"{param.name}:")
                label.setToolTip(param.description)
                group_layout.addWidget(label, i, 0)
                
                # Value display
                value_label = QLabel(f"{param.value:.3f}")
                value_label.setMinimumWidth(80)
                value_label.setAlignment(Qt.AlignRight)
                group_layout.addWidget(value_label, i, 1)
                
                # Unit label
                if param.unit:
                    unit_label = QLabel(param.unit)
                    group_layout.addWidget(unit_label, i, 2)
                
                # Slider
                slider = QSlider(Qt.Horizontal)
                slider.setMinimum(int(param.min_value / param.step_size))
                slider.setMaximum(int(param.max_value / param.step_size))
                slider.setValue(int(param.value / param.step_size))
                slider.setTickPosition(QSlider.TicksBelow)
                slider.setTickInterval(10)
                
                # Connect slider to value update
                slider.valueChanged.connect(
                    lambda value, name=param_name, set_name=parameter_set.name: 
                    self._update_parameter_value(set_name, name, value * param.step_size)
                )
                
                group_layout.addWidget(slider, i, 3)
                
                # Store widgets for later access
                self.parameter_widgets[parameter_set.name][param_name] = {
                    'slider': slider,
                    'value_label': value_label
                }
            
            scroll_layout.addWidget(group_box)
        
        layout.addWidget(scroll_widget)
        return tab
    
    def _update_parameter_value(self, set_name: str, param_name: str, value: float):
        """
        Update parameter value.
        
        Args:
            set_name (str): Parameter set name
            param_name (str): Parameter name
            value (float): New parameter value
        """
        if set_name in self.parameter_sets and param_name in self.parameter_sets[set_name].parameters:
            param = self.parameter_sets[set_name].parameters[param_name]
            
            # Validate value
            value = max(param.min_value, min(param.max_value, value))
            
            # Update parameter
            param.value = value
            param.last_modified = time.time()
            
            # Update GUI
            if set_name in self.parameter_widgets and param_name in self.parameter_widgets[set_name]:
                widgets = self.parameter_widgets[set_name][param_name]
                widgets['value_label'].setText(f"{value:.3f}")
                widgets['slider'].setValue(int(value / param.step_size))
            
            # Emit signal
            self.parameter_changed.emit(set_name, param_name, value)
            
            logger.debug(f"Updated parameter {set_name}.{param_name} = {value}")
    
    def _update_performance(self):
        """Update performance visualization."""
        # Simulate performance data (replace with actual performance metrics)
        current_time = time.time()
        
        # Generate simulated performance metrics
        performance_metrics = {
            'position_error': np.random.normal(0.1, 0.05),
            'velocity_error': np.random.normal(0.05, 0.02),
            'attitude_error': np.random.normal(0.02, 0.01),
            'control_effort': np.random.normal(0.3, 0.1)
        }
        
        # Store performance history
        self.performance_history.append({
            'timestamp': current_time,
            'metrics': performance_metrics
        })
        
        # Keep history manageable
        if len(self.performance_history) > 1000:
            self.performance_history.pop(0)
        
        # Update plots
        self._update_performance_plots()
    
    def _update_performance_plots(self):
        """Update performance plots."""
        if not self.performance_history:
            return
        
        # Clear plots
        self.performance_ax.clear()
        self.parameter_ax.clear()
        
        # Extract data
        timestamps = [entry['timestamp'] - self.performance_history[0]['timestamp'] 
                     for entry in self.performance_history]
        
        # Performance metrics
        position_errors = [entry['metrics']['position_error'] for entry in self.performance_history]
        velocity_errors = [entry['metrics']['velocity_error'] for entry in self.performance_history]
        attitude_errors = [entry['metrics']['attitude_error'] for entry in self.performance_history]
        
        # Plot performance
        self.performance_ax.plot(timestamps, position_errors, label='Position Error', color='red')
        self.performance_ax.plot(timestamps, velocity_errors, label='Velocity Error', color='blue')
        self.performance_ax.plot(timestamps, attitude_errors, label='Attitude Error', color='green')
        self.performance_ax.set_title("Performance Metrics")
        self.performance_ax.set_xlabel("Time (s)")
        self.performance_ax.set_ylabel("Error")
        self.performance_ax.legend()
        self.performance_ax.grid(True)
        
        # Plot parameter values (if any recent changes)
        if self.parameter_sets:
            # Plot a sample parameter
            sample_set = list(self.parameter_sets.values())[0]
            if sample_set.parameters:
                sample_param = list(sample_set.parameters.values())[0]
                param_values = [sample_param.value] * len(timestamps)
                self.parameter_ax.plot(timestamps, param_values, label=sample_param.name, color='orange')
                self.parameter_ax.set_title("Parameter Values")
                self.parameter_ax.set_xlabel("Time (s)")
                self.parameter_ax.set_ylabel("Value")
                self.parameter_ax.legend()
                self.parameter_ax.grid(True)
        
        # Redraw
        self.performance_canvas.draw()
    
    def _load_parameters(self):
        """Load parameters from file."""
        file_path, _ = QFileDialog.getOpenFileName(
            self, "Load Parameters", "", "JSON Files (*.json);;All Files (*)"
        )
        
        if file_path:
            try:
                with open(file_path, 'r') as f:
                    data = json.load(f)
                
                # Load parameter sets
                for set_name, set_data in data.items():
                    parameters = {}
                    for param_name, param_data in set_data['parameters'].items():
                        param = Parameter(**param_data)
                        parameters[param_name] = param
                    
                    param_set = ParameterSet(
                        name=set_name,
                        parameters=parameters,
                        description=set_data.get('description', ''),
                        last_modified=set_data.get('last_modified', time.time())
                    )
                    
                    self.add_parameter_set(param_set)
                
                QMessageBox.information(self, "Success", "Parameters loaded successfully!")
                logger.info(f"Loaded parameters from {file_path}")
                
            except Exception as e:
                QMessageBox.critical(self, "Error", f"Failed to load parameters: {e}")
                logger.error(f"Failed to load parameters: {e}")
    
    def _save_parameters(self):
        """Save parameters to file."""
        file_path, _ = QFileDialog.getSaveFileName(
            self, "Save Parameters", "", "JSON Files (*.json);;All Files (*)"
        )
        
        if file_path:
            try:
                data = {}
                for set_name, param_set in self.parameter_sets.items():
                    set_data = {
                        'description': param_set.description,
                        'last_modified': param_set.last_modified,
                        'parameters': {}
                    }
                    
                    for param_name, param in param_set.parameters.items():
                        set_data['parameters'][param_name] = {
                            'name': param.name,
                            'value': param.value,
                            'min_value': param.min_value,
                            'max_value': param.max_value,
                            'default_value': param.default_value,
                            'step_size': param.step_size,
                            'description': param.description,
                            'unit': param.unit,
                            'category': param.category
                        }
                    
                    data[set_name] = set_data
                
                with open(file_path, 'w') as f:
                    json.dump(data, f, indent=2)
                
                QMessageBox.information(self, "Success", "Parameters saved successfully!")
                logger.info(f"Saved parameters to {file_path}")
                
            except Exception as e:
                QMessageBox.critical(self, "Error", f"Failed to save parameters: {e}")
                logger.error(f"Failed to save parameters: {e}")
    
    def _reset_parameters(self):
        """Reset parameters to default values."""
        reply = QMessageBox.question(
            self, "Reset Parameters", 
            "Are you sure you want to reset all parameters to default values?",
            QMessageBox.Yes | QMessageBox.No, QMessageBox.No
        )
        
        if reply == QMessageBox.Yes:
            for set_name, param_set in self.parameter_sets.items():
                for param_name, param in param_set.parameters.items():
                    self._update_parameter_value(set_name, param_name, param.default_value)
            
            QMessageBox.information(self, "Success", "Parameters reset to defaults!")
            logger.info("Parameters reset to default values")
    
    def _apply_parameters(self):
        """Apply parameter changes to the control system."""
        # This would typically send parameters to the control system
        QMessageBox.information(self, "Success", "Parameters applied to control system!")
        logger.info("Parameters applied to control system")
    
    def _start_optimization(self):
        """Start parameter optimization."""
        algorithm_name = self.algorithm_combo.currentText()
        algorithm = OptimizationAlgorithm(algorithm_name.lower().replace(' ', '_'))
        
        # Get current parameter set
        current_tab = self.tab_widget.currentWidget()
        if not current_tab:
            QMessageBox.warning(self, "Warning", "Please select a parameter set first!")
            return
        
        # Start optimization in background thread
        self.optimization_started.emit()
        self.optimization_progress.setValue(0)
        
        # Simulate optimization progress
        for i in range(101):
            self.optimization_progress.setValue(i)
            time.sleep(0.01)  # Simulate work
        
        # Complete optimization
        QMessageBox.information(self, "Success", f"Optimization completed using {algorithm_name}!")
        self.optimization_completed.emit(None)
        logger.info(f"Optimization completed using {algorithm_name}")


# Example usage
if __name__ == "__main__":
    """
    Example usage of the parameter tuner.
    
    This demonstrates how to set up and use the parameter tuner for
    real-time control parameter adjustment.
    """
    
    if not GUI_AVAILABLE:
        print("PyQt5 not available. Install with: pip install PyQt5 matplotlib")
        exit(1)
    
    import sys
    from PyQt5.QtWidgets import QApplication
    
    # Create application
    app = QApplication(sys.argv)
    
    # Create parameter tuner
    tuner = ParameterTunerGUI()
    
    # Add callbacks
    def parameter_changed(set_name, param_name, value):
        """Callback for parameter changes."""
        print(f"Parameter changed: {set_name}.{param_name} = {value}")
    
    def optimization_started():
        """Callback for optimization start."""
        print("Optimization started")
    
    def optimization_completed(result):
        """Callback for optimization completion."""
        print("Optimization completed")
    
    tuner.parameter_changed.connect(parameter_changed)
    tuner.optimization_started.connect(optimization_started)
    tuner.optimization_completed.connect(optimization_completed)
    
    # Show GUI
    tuner.show()
    
    # Run application
    sys.exit(app.exec_())

Articles liés