import { describe, expect, it } from 'vitest'; import { buildSignalPlan } from '@/domain/plan/signalPlan'; import { createStandardIntersectionProject } from '@/domain/model/factory'; import { TrafficSimulation, type SimulationSnapshot } from '@/domain/simulation/simulation'; /** * Die Simulation muss in Schritten von `timeStep` rechnen, gleich in welchen * Teilstuecken die Zeit uebergeben wird (Befund 27, behoben in Fassung 5.9.0). * * Was der Anwender sonst erlebt: Die Ansicht uebergibt je Einzelbild die * verstrichene Zeit mal Ablaufgeschwindigkeit. Bei "Echtzeit" und 60 Bildern je * Sekunde rechnete das Modell deshalb mit 0,017 s statt der vorgegebenen 0,5 s. * Dieselbe Anlage lieferte je nach Ablaufgeschwindigkeit und Bildwiederholrate * andere Werte fuer Abgeflossen, Rueckstau und mittlere Wartezeit - waehrend der * Hinweis am Ankreuzfeld "dieselbe Einstellung liefert dasselbe Ergebnis" * zusagt. */ const FESTES_DATUM = new Date('2026-01-01T00:00:00Z'); function laufen(teilstueck: number, gesamt: number, stochastisch = false): SimulationSnapshot { const project = createStandardIntersectionProject('Zeitschritt', FESTES_DATUM); const plan = buildSignalPlan(project); const sim = new TrafficSimulation(plan, project, { timeStep: 0.5, stochastic: stochastisch, }); for (let i = 0; i < Math.round(gesamt / teilstueck); i += 1) sim.advance(teilstueck); return sim.snapshot(); } /** Die Groessen, die in der Ansicht abgelesen werden. */ function kennzahlen(snapshot: SimulationSnapshot): unknown { return { time: snapshot.time, overallDelay: snapshot.overallDelay, totalArrived: snapshot.totalArrived, totalDeparted: snapshot.totalDeparted, totalQueue: snapshot.totalQueue, groups: snapshot.groups.map((g) => ({ name: g.name, queue: g.queue, maxQueue: g.maxQueue, arrived: g.arrived, departed: g.departed, averageDelay: g.averageDelay, grade: g.serviceLevel.grade, })), }; } describe('Zeitschritt der Simulation', () => { // Teilstuecke, wie sie die Bildschleife bei 64 Bildern je Sekunde und // Echtzeit, 8-fach, 16-fach und 32-fach uebergibt. it.each([1 / 64, 0.125, 0.25, 0.5])( 'liefert bei %s s je Aufruf dasselbe Ergebnis wie in Bloecken zu 60 s', (teilstueck) => { expect(kennzahlen(laufen(teilstueck, 600))).toEqual(kennzahlen(laufen(60, 600))); }, ); it('haengt auch mit Ankunftsschwankungen nicht an der Groesse der Teilstuecke', () => { // Bei gesetztem Haken zog das Modell je Zeitschritt einmal; mit feinen // Schritten kam so ein Vielfaches der erfassten Verkehrsstaerke an. const inBloecken = laufen(60, 600, true); const inEinzelbildern = laufen(0.125, 600, true); expect(kennzahlen(inEinzelbildern)).toEqual(kennzahlen(inBloecken)); }); it('rechnet keinen Bruchteil eines Zeitschritts vorzeitig ab', () => { // Vier Aufrufe zu 0,125 s ergeben genau einen Schritt zu 0,5 s - vorher // rechnete jeder Aufruf einen eigenen, kleineren Schritt. const project = createStandardIntersectionProject('Zeitschritt', FESTES_DATUM); const plan = buildSignalPlan(project); const sim = new TrafficSimulation(plan, project, { timeStep: 0.5 }); sim.advance(0.125); expect(sim.snapshot().time).toBe(0); sim.advance(0.125); sim.advance(0.125); expect(sim.snapshot().time).toBe(0); sim.advance(0.125); expect(sim.snapshot().time).toBe(0.5); }); });