/** * Parameter panel with Material-UI - input fields for simulation configuration */ import { useState } from 'react'; import { Box, TextField, Typography, Divider, Button, IconButton, Paper, Alert, Stack, ToggleButtonGroup, ToggleButton, Select, MenuItem, type SelectChangeEvent, } from '@mui/material'; import { Add as AddIcon, Delete as DeleteIcon, CheckCircle as CheckCircleIcon, Error as ErrorIcon, } from '@mui/icons-material'; import { useSimulationStore } from '../../store/simulationStore'; import TimeConverter from '../tools/TimeConverter'; type SimulationTimeUnit = 'ms' | 's' | 'min' | 'h'; export default function ParameterPanel() { const { config, updateConfig, addServer, removeServer, updateServer, isRunning, timeUnit, setTimeUnit, } = useSimulationStore(); const [simTimeUnit, setSimTimeUnit] = useState('ms'); if (!config) { return ( Aucune configuration chargée ); } // Conversion helpers const convertToRate = (meanTime: number): number => { if (meanTime === 0) return 0; const timeInMs = timeUnit === 's' ? meanTime * 1000 : meanTime; return 1 / timeInMs; }; const convertFromRate = (rate: number): number => { if (rate === 0) return 0; const timeInMs = 1 / rate; return timeUnit === 's' ? timeInMs / 1000 : timeInMs; }; const handleTimeUnitChange = (_: React.MouseEvent, newUnit: 'ms' | 's' | null) => { if (newUnit !== null) { setTimeUnit(newUnit); } }; const handleArrivalRateChange = (e: React.ChangeEvent) => { const rateInput = parseFloat(e.target.value); if (isNaN(rateInput)) { updateConfig({ arrival_rate: 0 }); return; } // Convert to req/ms (internal unit) const rateInMs = timeUnit === 's' ? rateInput / 1000 : rateInput; updateConfig({ arrival_rate: rateInMs }); }; const getDisplayedArrivalRate = (): string => { // Convert from req/ms to selected unit const displayValue = timeUnit === 's' ? config.arrival_rate * 1000 : config.arrival_rate; return displayValue.toFixed(timeUnit === 's' ? 1 : 3); }; const handleCoordinatorServiceRateChange = (e: React.ChangeEvent) => { const meanTime = parseFloat(e.target.value) || 0; updateConfig({ coordinator_service_rate: convertToRate(meanTime) }); }; const handleExitProbabilityChange = (e: React.ChangeEvent) => { updateConfig({ coordinator_exit_probability: parseFloat(e.target.value) || 0 }); }; const handleSimulationTimeChange = (e: React.ChangeEvent) => { const timeInput = parseFloat(e.target.value) || 0; // Convert to ms (internal unit) let timeInMs = timeInput; if (simTimeUnit === 's') timeInMs = timeInput * 1000; else if (simTimeUnit === 'min') timeInMs = timeInput * 60 * 1000; else if (simTimeUnit === 'h') timeInMs = timeInput * 60 * 60 * 1000; updateConfig({ simulation_time: timeInMs }); }; const handleWarmupTimeChange = (e: React.ChangeEvent) => { const timeInput = parseFloat(e.target.value) || 0; // Convert to ms (internal unit) let timeInMs = timeInput; if (simTimeUnit === 's') timeInMs = timeInput * 1000; else if (simTimeUnit === 'min') timeInMs = timeInput * 60 * 1000; else if (simTimeUnit === 'h') timeInMs = timeInput * 60 * 60 * 1000; // Ensure warmup < simulation time const simTime = config.simulation_time || 100000; if (timeInMs >= simTime) { // Cap at 50% of simulation time timeInMs = Math.floor(simTime * 0.5); } updateConfig({ warmup_time: timeInMs }); }; const handleSimTimeUnitChange = (e: SelectChangeEvent) => { setSimTimeUnit(e.target.value as SimulationTimeUnit); }; const getDisplayedSimulationTime = (): number => { const simTime = config.simulation_time || 100000; if (simTimeUnit === 's') return simTime / 1000; if (simTimeUnit === 'min') return simTime / (60 * 1000); if (simTimeUnit === 'h') return simTime / (60 * 60 * 1000); return simTime; // ms }; const getDisplayedWarmupTime = (): number => { const warmup = config.warmup_time || 0; if (simTimeUnit === 's') return warmup / 1000; if (simTimeUnit === 'min') return warmup / (60 * 1000); if (simTimeUnit === 'h') return warmup / (60 * 60 * 1000); return warmup; // ms }; const getSimTimeStep = (): number => { if (simTimeUnit === 'ms') return 10000; if (simTimeUnit === 's') return 10; if (simTimeUnit === 'min') return 1; return 0.1; // h }; const getExpectedRequests = (): number => { const simTime = config.simulation_time || 100000; return Math.round(config.arrival_rate * simTime); }; const handleServerServiceRateChange = (serverId: string, value: string) => { const meanTime = parseFloat(value) || 0; updateServer(serverId, { service_rate: convertToRate(meanTime) }); }; const handleServerRoutingProbChange = (serverId: string, value: string) => { updateServer(serverId, { routing_probability: parseFloat(value) || 0 }); }; // Calculate total routing probability const totalRoutingProb = config.coordinator_exit_probability + config.servers.reduce((sum, s) => sum + s.routing_probability, 0); const isProbabilityValid = Math.abs(totalRoutingProb - 1.0) < 0.001; return ( Paramètres du réseau ms s {/* External arrival rate */} {timeUnit === 'ms' ? 'req/ms' : 'req/s'} }} /> {/* Simulation time */} {/* Warmup time */} = config.simulation_time} helperText={ config.warmup_time >= config.simulation_time ? `Le warmup doit être < ${getDisplayedSimulationTime()} ${simTimeUnit}` : `Période initiale ignorée pour calcul des statistiques (max: ${(getDisplayedSimulationTime() * 0.9).toFixed(0)} ${simTimeUnit})` } /> {/* Warning alert if warmup >= simulation time */} {config.warmup_time >= config.simulation_time && ( Le temps de préchauffage ({getDisplayedWarmupTime().toFixed(0)} {simTimeUnit}) ne peut pas être supérieur ou égal au temps de simulation ({getDisplayedSimulationTime().toFixed(0)} {simTimeUnit}). )} {/* Coordinator parameters */} Coordinateur {timeUnit} }} /> {/* Servers */} Serveurs {config.servers.map((server, index) => ( Serveur {index + 1} {config.servers.length > 1 && ( removeServer(server.id)} disabled={isRunning} color="error" > )} handleServerServiceRateChange(server.id, e.target.value)} disabled={isRunning} fullWidth size="small" helperText={`En ${timeUnit === 'ms' ? 'millisecondes' : 'secondes'}`} InputProps={{ endAdornment: {timeUnit} }} /> handleServerRoutingProbChange(server.id, e.target.value)} disabled={isRunning} fullWidth size="small" /> ))} {/* Probability validation */} : } > Conservation des probabilités p + Σq = {totalRoutingProb.toFixed(3)} {isProbabilityValid ? ' = 1.0 ✓' : ' ≠ 1.0 ✗'} {/* Time unit converter */} ); }