lsa-planer

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

/ tests domain performance.test.ts

6,1 KB Rohdatei
tests/domain/performance.test.ts — 154 Zeilen
1 import { describe, expect, it } from 'vitest';
2 import { computeCapacity, criticalFlowRatio, saturationFlow } from '@/domain/rilsa/capacity';
3 import { computeDelay, serviceLevelFor } from '@/domain/rilsa/delay';
4 import { computeCoordination, travelTime } from '@/domain/rilsa/coordination';
5
6 describe('Kapazitaet', () => {
7 it('mindert die Saettigungsverkehrsstaerke abbiegender Stroeme', () => {
8 expect(saturationFlow('geradeaus', 1)).toBe(2000);
9 expect(saturationFlow('geradeaus', 2)).toBe(4000);
10 expect(saturationFlow('links', 1)).toBeCloseTo(1800, 6);
11 });
12
13 it('rechnet c = qS * tA / tU mit der Abflusszeit tA = tF + 1 s (HBS)', () => {
14 const result = computeCapacity(29, 90, 1800);
15 expect(result.abflusszeit).toBe(30);
16 expect(result.greenRatio).toBeCloseTo(1 / 3, 4);
17 expect(result.capacity).toBe(600);
18 expect(result.verfahren).toBe('HBS');
19 });
20
21 it('rechnet nach HCM mit der Freigabezeit selbst', () => {
22 const result = computeCapacity(30, 90, 1800, undefined, { verfahren: 'HCM' });
23 expect(result.abflusszeit).toBe(30);
24 expect(result.capacity).toBe(600);
25 });
26
27 it('rechnet den Auslastungsgrad tatsaechlich aus der Verkehrsstaerke', () => {
28 // Der Altbestand gab hier konstant 0 aus.
29 const result = computeCapacity(29, 90, 1800, 540);
30 expect(result.degreeOfSaturation).toBeCloseTo(0.9, 4);
31 expect(result.notes.some((n) => n.code === 'auslastung-kritisch')).toBe(false);
32 });
33
34 it('meldet eine Nachfrage ueber der Kapazitaet', () => {
35 const result = computeCapacity(29, 90, 1800, 900);
36 expect(result.degreeOfSaturation).toBeCloseTo(1.5, 4);
37 expect(result.notes.some((n) => n.code === 'auslastung-ueber-100')).toBe(true);
38 });
39
40 it('meldet eine Freigabezeit laenger als die Umlaufzeit', () => {
41 const result = computeCapacity(100, 90, 1800);
42 expect(result.notes.some((n) => n.code === 'freigabezeit-ueber-umlaufzeit')).toBe(true);
43 });
44
45 it('liefert bei fehlender Saettigung keinen NaN-Wert', () => {
46 expect(criticalFlowRatio(500, 0)).toBe(Number.POSITIVE_INFINITY);
47 expect(criticalFlowRatio(Number.NaN, 1800)).toBe(0);
48 });
49 });
50
51 describe('Wartezeit und Qualitaetsstufe', () => {
52 const wartezeit = (demand: number, verfahren: 'HBS' | 'HCM' = 'HBS') =>
53 computeDelay({ cycleTime: 90, greenTime: 30, capacity: 600, demand, verfahren, mode: 'kfz' });
54
55 it('setzt sich aus Grundwartezeit und Reststauanteil zusammen', () => {
56 for (const verfahren of ['HBS', 'HCM'] as const) {
57 const result = wartezeit(400, verfahren);
58 expect(result.uniformDelay).toBeGreaterThan(0);
59 expect(result.incrementalDelay).toBeGreaterThanOrEqual(0);
60 expect(result.averageDelay).toBeCloseTo(result.uniformDelay + result.incrementalDelay, 0);
61 }
62 });
63
64 it('waechst mit steigender Auslastung deutlich an', () => {
65 for (const verfahren of ['HBS', 'HCM'] as const) {
66 const low = wartezeit(200, verfahren);
67 const high = wartezeit(580, verfahren);
68 // Der Altbestand rechnete mit fest eingetragenem x = 0,85 und lieferte
69 // deshalb fuer beide Faelle denselben Wert.
70 expect(high.averageDelay).toBeGreaterThan(low.averageDelay * 1.5);
71 }
72 });
73
74 it('meldet Ueberlastung und vergibt dafuer die Stufe F', () => {
75 const result = wartezeit(700);
76 expect(result.degreeOfSaturation).toBeGreaterThan(1);
77 expect(result.notes.some((n) => n.code === 'ueberlastet')).toBe(true);
78 expect(result.serviceLevel?.grade).toBe('F');
79 expect(result.serviceLevel?.kriterium).toBe('ueberlastung');
80 });
81
82 it('bewertet nach dem gewaehlten Verfahren und weist es aus', () => {
83 expect(serviceLevelFor(15, 'HBS')?.grade).toBe('A');
84 expect(serviceLevelFor(15, 'HCM')?.grade).toBe('B');
85 expect(serviceLevelFor(15, 'HBS')?.scale).toBe('HBS');
86 // Nach HBS 2015 ist E nach oben offen; F gibt es nur bei Ueberlastung.
87 expect(serviceLevelFor(500, 'HBS')?.grade).toBe('E');
88 expect(serviceLevelFor(500, 'HBS', { degreeOfSaturation: 1.2 })?.grade).toBe('F');
89 expect(serviceLevelFor(500, 'HCM')?.grade).toBe('F');
90 });
91
92 it('faengt eine Kapazitaet von 0 ab', () => {
93 const result = computeDelay({
94 cycleTime: 90,
95 greenTime: 0,
96 capacity: 0,
97 demand: 400,
98 verfahren: 'HBS',
99 mode: 'kfz',
100 });
101 expect(result.notes.some((n) => n.code === 'kapazitaet-null')).toBe(true);
102 expect(Number.isNaN(result.averageDelay)).toBe(false);
103 // Nachfrage ohne Kapazitaet ist Ueberlastung.
104 expect(result.serviceLevel?.grade).toBe('F');
105 });
106 });
107
108 describe('Koordinierung', () => {
109 it('rechnet die Reisezeit aus Abstand und Geschwindigkeit', () => {
110 // 500 m bei 50 km/h = 13,889 m/s -> 36 s
111 expect(travelTime(500, 50)).toBeCloseTo(36, 1);
112 });
113
114 it('ermittelt Versatzzeiten und Bandbreite aus den Freigabefenstern', () => {
115 const result = computeCoordination(
116 [
117 { id: 'a', name: 'Knoten A', distanceToPrevious: 0, greenStart: 0, greenDuration: 40 },
118 { id: 'b', name: 'Knoten B', distanceToPrevious: 500, greenStart: 36, greenDuration: 40 },
119 ],
120 90,
121 50,
122 );
123
124 expect(result.intersections[1]!.travelTime).toBeCloseTo(36, 1);
125 expect(result.intersections[1]!.offset).toBeCloseTo(36, 1);
126 // Beide Fenster decken sich nach Verschiebung vollstaendig.
127 expect(result.bandwidth).toBeCloseTo(40, 1);
128 expect(result.quality).toBe('gut');
129 });
130
131 it('erkennt unpassende Versaetze', () => {
132 // Der Altbestand meldete hier "kleinste Freigabezeit minus 5 s" und
133 // beruecksichtigte den Versatz gar nicht.
134 const result = computeCoordination(
135 [
136 { id: 'a', name: 'Knoten A', distanceToPrevious: 0, greenStart: 0, greenDuration: 30 },
137 { id: 'b', name: 'Knoten B', distanceToPrevious: 500, greenStart: 0, greenDuration: 30 },
138 ],
139 90,
140 50,
141 );
142
143 expect(result.bandwidth).toBe(0);
144 expect(result.notes.some((n) => n.code === 'keine-bandbreite')).toBe(true);
145 });
146
147 it('verlangt mindestens zwei Knotenpunkte', () => {
148 const result = computeCoordination(
149 [{ id: 'a', name: 'A', distanceToPrevious: 0, greenStart: 0, greenDuration: 30 }],
150 90,
151 );
152 expect(result.notes.some((n) => n.code === 'zu-wenige-knoten')).toBe(true);
153 });
154 });