Newer
Older
* 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,
} 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 TimeUnit = 'ms' | 's';
export default function ParameterPanel() {
const {
config,
updateConfig,
addServer,
removeServer,
updateServer,
isRunning,
} = useSimulationStore();
const [timeUnit, setTimeUnit] = useState<TimeUnit>('ms');
if (!config) {
return (
<Typography variant="body2" color="text.secondary" textAlign="center" py={3}>
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<HTMLElement>, newUnit: TimeUnit | null) => {
if (newUnit !== null) {
setTimeUnit(newUnit);
}
};
const handleArrivalRateChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const meanTime = parseFloat(e.target.value) || 0;
updateConfig({ arrival_rate: convertToRate(meanTime) });
};
const handleCoordinatorServiceRateChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const meanTime = parseFloat(e.target.value) || 0;
updateConfig({ coordinator_service_rate: convertToRate(meanTime) });
};
const handleExitProbabilityChange = (e: React.ChangeEvent<HTMLInputElement>) => {
updateConfig({ coordinator_exit_probability: parseFloat(e.target.value) || 0 });
};
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 (
<Box>
<Divider sx={{ my: 2 }} />
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 2 }}>
<Typography variant="subtitle2" fontWeight={600}>
Paramètres du réseau
</Typography>
<ToggleButtonGroup
value={timeUnit}
exclusive
onChange={handleTimeUnitChange}
size="small"
disabled={isRunning}
>
<ToggleButton value="ms">ms</ToggleButton>
<ToggleButton value="s">s</ToggleButton>
</ToggleButtonGroup>
</Box>
<Stack spacing={2}>
{/* External arrival rate */}
label={`Temps moyen entre arrivées (1/λ)`}
inputProps={{ step: timeUnit === 's' ? 0.001 : 1, min: 0 }}
value={convertFromRate(config.arrival_rate).toFixed(timeUnit === 's' ? 3 : 1)}
onChange={handleArrivalRateChange}
disabled={isRunning}
fullWidth
size="small"
helperText={`En ${timeUnit === 'ms' ? 'millisecondes' : 'secondes'}`}
InputProps={{
endAdornment: <Typography variant="caption" sx={{ ml: 1 }}>{timeUnit}</Typography>
}}
{/* Coordinator parameters */}
<Box>
<Typography variant="caption" fontWeight={600} color="text.secondary" sx={{ textTransform: 'uppercase' }}>
Coordinateur
</Typography>
<Stack spacing={1.5} sx={{ mt: 1 }}>
<TextField
label="Temps moyen de service (1/μc)"
inputProps={{ step: timeUnit === 's' ? 0.001 : 1, min: 0 }}
value={convertFromRate(config.coordinator_service_rate).toFixed(timeUnit === 's' ? 3 : 1)}
onChange={handleCoordinatorServiceRateChange}
disabled={isRunning}
fullWidth
size="small"
helperText={`En ${timeUnit === 'ms' ? 'millisecondes' : 'secondes'}`}
InputProps={{
endAdornment: <Typography variant="caption" sx={{ ml: 1 }}>{timeUnit}</Typography>
}}
/>
<TextField
label="Probabilité de sortie (p)"
type="number"
inputProps={{ step: 0.01, min: 0, max: 1 }}
value={config.coordinator_exit_probability}
onChange={handleExitProbabilityChange}
disabled={isRunning}
fullWidth
size="small"
/>
</Stack>
</Box>
{/* Servers */}
<Box>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 1 }}>
<Typography variant="caption" fontWeight={600} color="text.secondary" sx={{ textTransform: 'uppercase' }}>
</Typography>
<Button
size="small"
startIcon={<AddIcon />}
onClick={addServer}
disabled={isRunning}
>
Ajouter
</Button>
</Box>
<Stack spacing={1.5}>
{config.servers.map((server, index) => (
<Paper key={server.id} variant="outlined" sx={{ p: 1.5 }}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 1 }}>
<Typography variant="caption" fontWeight={600}>
Serveur {index + 1}
{config.servers.length > 1 && (
<IconButton
size="small"
onClick={() => removeServer(server.id)}
disabled={isRunning}
<DeleteIcon fontSize="small" />
</IconButton>
</Box>
<Stack spacing={1}>
<TextField
label={`Temps moyen de service (1/μ${index + 1})`}
inputProps={{ step: timeUnit === 's' ? 0.001 : 1, min: 0 }}
value={convertFromRate(server.service_rate).toFixed(timeUnit === 's' ? 3 : 1)}
onChange={(e) => handleServerServiceRateChange(server.id, e.target.value)}
disabled={isRunning}
fullWidth
size="small"
helperText={`En ${timeUnit === 'ms' ? 'millisecondes' : 'secondes'}`}
InputProps={{
endAdornment: <Typography variant="caption" sx={{ ml: 1 }}>{timeUnit}</Typography>
}}
/>
<TextField
label={`Probabilité de routage (q${index + 1})`}
type="number"
inputProps={{ step: 0.01, min: 0, max: 1 }}
value={server.routing_probability}
onChange={(e) => handleServerRoutingProbChange(server.id, e.target.value)}
disabled={isRunning}
fullWidth
size="small"
/>
</Stack>
</Paper>
</Stack>
</Box>
{/* Probability validation */}
<Alert
severity={isProbabilityValid ? 'success' : 'error'}
icon={isProbabilityValid ? <CheckCircleIcon /> : <ErrorIcon />}
<Typography variant="caption" fontWeight={600}>
Conservation des probabilités
</Typography>
<Typography variant="caption" component="div">
p + Σq = {totalRoutingProb.toFixed(3)}
{isProbabilityValid ? ' = 1.0 ✓' : ' ≠ 1.0 ✗'}
</Typography>
</Alert>
{/* Time unit converter */}
<TimeConverter />
</Stack>
</Box>