lsa-planer

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

/ tests domain abflussfenster.test.ts

3,5 KB Rohdatei
tests/domain/abflussfenster.test.ts — 76 Zeilen
1 import { describe, expect, it } from 'vitest';
2 import { buildSignalPlan } from '@/domain/plan/signalPlan';
3 import { createStandardIntersectionProject } from '@/domain/model/factory';
4 import { TrafficSimulation } from '@/domain/simulation/simulation';
5 import type { Project } from '@/domain/model/project';
6
7 /**
8 * Das Abflussfenster der Simulation muss dieselbe Abflusszeit tA tragen, mit
9 * der die Leistungsfaehigkeit desselben Plans rechnet (Befund 26, behoben in
10 * Fassung 5.9.0).
11 *
12 * Was der Anwender sonst erlebt: Der Signalzeitenplan weist eine Kapazitaet
13 * C = qS * tA / tU aus und meldet keine Ueberlastung, waehrend die Simulation
14 * daneben einen unbegrenzt wachsenden Rueckstau und eine nicht konvergierende
15 * mittlere Wartezeit zeigt. Die Simulation liess je Freigabefenster nur
16 * tF minus Anfahrverlustzeit abfliessen - bei HBS 3 s je Fenster weniger als
17 * die Bemessung - und verwarf am Freigabezeitende zusaetzlich den
18 * aufgelaufenen Bruchteil eines Fahrzeugs.
19 */
20
21 const FESTES_DATUM = new Date('2026-01-01T00:00:00Z');
22
23 /** Knotenpunkt mit fester Umlaufzeit 90 s und dauerhaft gefuellter Warteschlange. */
24 function ueberlasteterKnotenpunkt(verfahren: 'HBS' | 'HCM' = 'HBS'): Project {
25 const basis = createStandardIntersectionProject('Abflussfenster', FESTES_DATUM);
26 return {
27 ...basis,
28 settings: { ...basis.settings, serviceLevelScale: verfahren },
29 program: { ...basis.program, method: 'manuell', manualCycleTime: 90 },
30 demands: basis.demands.map((d) => ({ ...d, volume: 1500 })),
31 };
32 }
33
34 describe('Abflussfenster der Simulation', () => {
35 it.each(['HBS', 'HCM'] as const)(
36 'laesst je Umlauf so viele Fahrzeuge abfliessen, wie die Kapazitaet des Plans hergibt (%s)',
37 (verfahren) => {
38 const project = ueberlasteterKnotenpunkt(verfahren);
39 const plan = buildSignalPlan(project);
40 expect(plan.cycleTime).toBe(90);
41 const sim = new TrafficSimulation(plan, project, { timeStep: 0.5 });
42
43 // Vier Umlaeufe einlaufen lassen, danach genau 20 Umlaeufe messen.
44 for (let i = 0; i < 6; i += 1) sim.advance(60);
45 const vorher = new Map(sim.snapshot().groups.map((g) => [g.groupId, g.departed]));
46 for (let i = 0; i < 30; i += 1) sim.advance(60);
47
48 for (const group of sim.snapshot().groups) {
49 const planned = plan.groups.find((p) => p.groupId === group.groupId);
50 const kapazitaet = planned?.capacity;
51 if (!kapazitaet) continue;
52 // Die Warteschlange darf in keinem Umlauf leergelaufen sein, sonst
53 // misst der Vergleich den Zufluss statt des Abflussfensters.
54 expect(group.queue).toBeGreaterThan(0);
55 const abgeflossen = group.departed - (vorher.get(group.groupId) ?? 0);
56 const erwartet = (kapazitaet.saturationFlow / 3600) * kapazitaet.abflusszeit * 20;
57 // Vor der Behebung flossen bei HBS 340 statt 383 Fahrzeuge ab.
58 expect(
59 Math.abs(abgeflossen - erwartet),
60 `${group.name}: ${abgeflossen} statt ${erwartet.toFixed(1)}`,
61 ).toBeLessThanOrEqual(1.5);
62 }
63 },
64 );
65
66 it('haelt die Fahrzeugbilanz auch bei Ueberlastung geschlossen', () => {
67 const project = ueberlasteterKnotenpunkt();
68 const plan = buildSignalPlan(project);
69 const sim = new TrafficSimulation(plan, project, { timeStep: 0.5 });
70 for (let i = 0; i < 30; i += 1) sim.advance(60);
71 for (const group of sim.snapshot().groups) {
72 expect(group.arrived).toBe(group.departed + group.queue);
73 expect(group.queue).toBeGreaterThanOrEqual(0);
74 }
75 });
76 });