lsa-planer

LSA-Planer Professional – Planungssoftware für Lichtsignalanlagen nach RiLSA 2015 und § 45 StVO. EUPL-1.2.

/ tests domain simulation.test.ts

4,4 KB Rohdatei
tests/domain/simulation.test.ts — 114 Zeilen
1 import { describe, expect, it } from 'vitest';
2 import { TrafficSimulation } from '@/domain/simulation/simulation';
3 import { buildSignalPlan } from '@/domain/plan/signalPlan';
4 import { isGreenAt } from '@/domain/plan/aspects';
5 import { createStandardIntersectionProject } from '@/domain/model/factory';
6
7 const FIXED_DATE = new Date('2026-01-01T00:00:00Z');
8
9 function setup(): { simulation: TrafficSimulation; plan: ReturnType<typeof buildSignalPlan> } {
10 const project = createStandardIntersectionProject('Simulation', FIXED_DATE);
11 const plan = buildSignalPlan(project);
12 return { simulation: new TrafficSimulation(plan, project, { timeStep: 0.5 }), plan };
13 }
14
15 describe('Verkehrsablaufsimulation', () => {
16 it('laesst sich mit den Projektdaten starten', () => {
17 const { simulation } = setup();
18 expect(simulation.runnable).toBe(true);
19 expect(simulation.snapshot().groups.length).toBeGreaterThan(0);
20 });
21
22 it('haelt die Fahrzeugbilanz geschlossen', () => {
23 const { simulation } = setup();
24 simulation.advance(60);
25 simulation.advance(60);
26 simulation.advance(60);
27
28 for (const group of simulation.snapshot().groups) {
29 // Der Altbestand liess Fahrzeuge verschwinden, weil der Abfluss nicht
30 // gegen die Warteschlangenlaenge begrenzt war.
31 expect(group.arrived).toBe(group.departed + group.queue);
32 expect(group.queue).toBeGreaterThanOrEqual(0);
33 }
34 });
35
36 it('laesst nur bei Freigabe Fahrzeuge abfliessen', () => {
37 const { simulation, plan } = setup();
38 let lastDeparted = new Map<string, number>();
39
40 for (let i = 0; i < 400; i += 1) {
41 const before = simulation.snapshot();
42 simulation.advance(0.5);
43 const after = simulation.snapshot();
44
45 for (const group of after.groups) {
46 const previous = lastDeparted.get(group.groupId) ?? 0;
47 if (group.departed > previous) {
48 const planned = plan.groups.find((g) => g.groupId === group.groupId)!;
49 // Innerhalb des Schritts muss die Gruppe Freigabe gehabt haben.
50 const hadGreen =
51 isGreenAt(planned, before.cyclePosition, plan.cycleTime) ||
52 isGreenAt(planned, after.cyclePosition, plan.cycleTime);
53 expect(hadGreen, `${group.name} floss ohne Freigabe ab`).toBe(true);
54 }
55 }
56 lastDeparted = new Map(after.groups.map((g) => [g.groupId, g.departed]));
57 }
58 });
59
60 it('liefert bei gleichem Startwert identische Ergebnisse', () => {
61 const a = setup().simulation;
62 const b = setup().simulation;
63 a.advance(300);
64 b.advance(300);
65 expect(a.snapshot().groups.map((g) => g.arrived)).toEqual(
66 b.snapshot().groups.map((g) => g.arrived),
67 );
68 expect(a.snapshot().overallDelay).toBe(b.snapshot().overallDelay);
69 });
70
71 it('setzt vollstaendig zurueck', () => {
72 const { simulation } = setup();
73 simulation.advance(300);
74 simulation.reset();
75 const snapshot = simulation.snapshot();
76 expect(snapshot.time).toBe(0);
77 expect(snapshot.totalArrived).toBe(0);
78 expect(snapshot.totalQueue).toBe(0);
79 for (const group of snapshot.groups) {
80 expect(group.maxQueue).toBe(0);
81 expect(group.averageDelay).toBe(0);
82 }
83 });
84
85 it('bildet die Ankunftsrate ueber laengere Zeit richtig ab', () => {
86 const { simulation } = setup();
87 simulation.advance(60);
88 for (let i = 0; i < 59; i += 1) simulation.advance(60);
89
90 const snapshot = simulation.snapshot();
91 for (const group of snapshot.groups) {
92 // Nach einer Stunde muessen etwa so viele Fahrzeuge angekommen sein wie
93 // die Verkehrsstaerke angibt.
94 expect(group.arrived).toBeGreaterThan(group.demand * 0.97);
95 expect(group.arrived).toBeLessThanOrEqual(group.demand + 1);
96 }
97 });
98
99 it('begrenzt einen ueberlangen Zeitsprung', () => {
100 const { simulation } = setup();
101 simulation.advance(100000);
102 // Hoechstens 60 s werden nachgerechnet, damit die Oberflaeche nicht einfriert.
103 expect(simulation.snapshot().time).toBeLessThanOrEqual(60);
104 });
105
106 it('meldet sich als nicht lauffaehig, wenn keine Verkehrsstaerken erfasst sind', () => {
107 const project = createStandardIntersectionProject('Ohne Daten', FIXED_DATE);
108 const plan = buildSignalPlan({ ...project, demands: [] });
109 const simulation = new TrafficSimulation(plan, { ...project, demands: [] });
110 expect(simulation.runnable).toBe(false);
111 simulation.advance(60);
112 expect(simulation.snapshot().totalArrived).toBe(0);
113 });
114 });