lsa-planer

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

/ tests domain mehrereFreigabefenster.test.ts

6,9 KB Rohdatei
tests/domain/mehrereFreigabefenster.test.ts — 185 Zeilen
1 import { describe, expect, it } from 'vitest';
2 import { computeCapacity } from '@/domain/rilsa/capacity';
3 import { computeDelay, sperrzeitenAusFenstern } from '@/domain/rilsa/delay';
4
5 /*
6 * Befund 26, behoben in Fassung 5.10.0.
7 *
8 * Gl. 2-8 des HBS 2015 ist die geschlossene Form fuer EIN Freigabezeitfenster
9 * je Umlauf. Erhaelt eine Signalgruppe zwei getrennte Fenster, bildete
10 * capacity.ts die Abflusszeit richtig als Summe (tA = tF + n * 1 s), und
11 * computeDelay setzte genau diese Summe als ein einziges Fenster in Gl. 2-8
12 * ein. Der gleichmaessige Anteil ist aber Summe(r_j^2) / (2 * tU * (1 - x*fA))
13 * ueber die Sperrzeiten r_j des Umlaufs; die Einfensterform setzt dafuer
14 * (Summe r_j)^2 an und faellt bei jeder Aufteilung zu gross aus - bei zwei
15 * gleich langen Sperrzeiten genau um das Doppelte.
16 *
17 * Nachgerechnet (tU 90 s, zwei Fenster zu je 15 s bei 0 s und 45 s,
18 * qS 2000 Fz/h, q 300 Fz/h): tA = 32 s, C = 711 Fz/h, x = 0,4219,
19 * fA = 0,3556; Sperrzeiten je 30 s, wirksam je 29 s.
20 * Einfensterform: 90 * 0,64444^2 / 1,69995 = 21,99 s
21 * Summe(r_j^2): (29^2 + 29^2) / 90 / 1,69995 = 10,99 s
22 * Mit tW,R = 2,2 s druckte das Programm tW = 24,2 s (Stufe B) statt 13,2 s
23 * (Stufe A).
24 *
25 * Dieselbe Verallgemeinerung rechnet computeFussRadDelay fuer Fussgaenger und
26 * Radverkehr seit Befund B4 bereits (Grenzfall x * fA -> 0).
27 */
28
29 const ZWEI_FENSTER = [
30 { start: 0, duration: 15 },
31 { start: 45, duration: 15 },
32 ] as const;
33
34 describe('Grundwartezeit bei mehreren Freigabezeitfenstern (Befund 26)', () => {
35 it('wertet Gl. 2-8 ueber die Sperrzeiten des Umlaufs aus, nicht ueber ihre Summe', () => {
36 const sperrzeiten = sperrzeitenAusFenstern(ZWEI_FENSTER, 90);
37 expect(sperrzeiten).toEqual([30, 30]);
38
39 const kapazitaet = computeCapacity(30, 90, 2000, 300, {
40 verfahren: 'HBS',
41 freigabefenster: 2,
42 });
43 expect(kapazitaet.abflusszeit).toBe(32);
44 expect(kapazitaet.capacity).toBe(711);
45
46 const gemeinsam = {
47 cycleTime: 90,
48 greenTime: 30,
49 capacity: kapazitaet.capacity,
50 demand: 300,
51 verfahren: 'HBS' as const,
52 mode: 'kfz' as const,
53 abflusszeit: kapazitaet.abflusszeit,
54 };
55
56 const zweiFenster = computeDelay({ ...gemeinsam, sperrzeiten });
57 expect(zweiFenster.rechenweg.abflusszeit).toBe(32);
58 expect(zweiFenster.rechenweg.abflussanteil).toBeCloseTo(0.3556, 4);
59 expect(zweiFenster.degreeOfSaturation).toBeCloseTo(0.4219, 4);
60 expect(zweiFenster.uniformDelay).toBe(11);
61 expect(zweiFenster.averageDelay).toBe(13.2);
62 expect(zweiFenster.serviceLevel?.grade).toBe('A');
63
64 // Ohne Sperrzeiten bleibt es bei der Einfensterform - das war der Fehler:
65 // dieselbe Signalgruppe, doppelte Grundwartezeit, eine Stufe schlechter.
66 const einFenster = computeDelay(gemeinsam);
67 expect(einFenster.uniformDelay).toBe(22);
68 expect(einFenster.averageDelay).toBe(24.2);
69 expect(einFenster.serviceLevel?.grade).toBe('B');
70 });
71
72 it('rechnet ungleich verteilte Fenster ueber ihre einzelnen Sperrzeiten', () => {
73 // Fenster bei 0 s und 30 s: Sperrzeiten 15 s und 45 s, wirksam 14 s und
74 // 44 s. (14^2 + 44^2) / 90 / 1,69995 = 13,94 s - zwischen der gleichmaessig
75 // aufgeteilten Lage (10,99 s) und der Einfensterform (21,99 s).
76 const sperrzeiten = sperrzeitenAusFenstern(
77 [
78 { start: 0, duration: 15 },
79 { start: 30, duration: 15 },
80 ],
81 90,
82 );
83 expect(sperrzeiten).toEqual([15, 45]);
84
85 const ungleich = computeDelay({
86 cycleTime: 90,
87 greenTime: 30,
88 capacity: 711,
89 demand: 300,
90 verfahren: 'HBS',
91 mode: 'kfz',
92 abflusszeit: 32,
93 sperrzeiten,
94 });
95 expect(ungleich.uniformDelay).toBe(13.9);
96 expect(ungleich.averageDelay).toBe(16.1);
97 });
98
99 it('laesst den Einfensterfall unveraendert - die Sperrzeit deckt den ganzen Umlauf ab', () => {
100 // K5 des LISA+-Ausdrucks (tF 10 s, tA 11 s, C 243 Fz/h, q 167 Fz/h): ein
101 // Fenster, Sperrzeit 80 s, wirksam 79 s. 79^2 / 90 ist identisch mit
102 // 90 * (1 - 11/90)^2 - die Verallgemeinerung darf hier nichts verschieben.
103 const gemeinsam = {
104 cycleTime: 90,
105 greenTime: 10,
106 capacity: 243,
107 demand: 167,
108 verfahren: 'HBS' as const,
109 mode: 'kfz' as const,
110 abflusszeit: 11,
111 };
112 const mit = computeDelay({
113 ...gemeinsam,
114 sperrzeiten: sperrzeitenAusFenstern([{ start: 20, duration: 10 }], 90),
115 });
116 const ohne = computeDelay(gemeinsam);
117 expect(mit.uniformDelay).toBe(ohne.uniformDelay);
118 expect(mit.averageDelay).toBe(ohne.averageDelay);
119 expect(ohne.uniformDelay).toBe(37.9);
120 });
121
122 it('ueberschaetzt nie: Summe(r_j^2) bleibt unter (Summe r_j)^2', () => {
123 for (const anzahl of [2, 3, 4, 5]) {
124 const dauer = 60 / anzahl / 2;
125 const fenster = Array.from({ length: anzahl }, (_, i) => ({
126 start: (i * 120) / anzahl,
127 duration: dauer,
128 }));
129 const sperrzeiten = sperrzeitenAusFenstern(fenster, 120);
130 expect(sperrzeiten).toHaveLength(anzahl);
131 const gemeinsam = {
132 cycleTime: 120,
133 greenTime: anzahl * dauer,
134 capacity: 800,
135 demand: 400,
136 verfahren: 'HBS' as const,
137 mode: 'kfz' as const,
138 abflusszeit: anzahl * dauer + anzahl,
139 };
140 const geteilt = computeDelay({ ...gemeinsam, sperrzeiten });
141 const einFenster = computeDelay(gemeinsam);
142 expect(geteilt.uniformDelay).toBeLessThan(einFenster.uniformDelay);
143 // Bei gleich langen Sperrzeiten ist der Faktor genau die Fensterzahl.
144 expect(geteilt.uniformDelay * anzahl).toBeCloseTo(einFenster.uniformDelay, 0);
145 }
146 });
147
148 it('verallgemeinert auch den gleichmaessigen Anteil d1 des HCM-Verfahrens', () => {
149 // d1 des HCM ist dieselbe Webster-Form; der Fehler war dort derselbe.
150 const sperrzeiten = sperrzeitenAusFenstern(ZWEI_FENSTER, 90);
151 const gemeinsam = {
152 cycleTime: 90,
153 greenTime: 30,
154 capacity: 667,
155 demand: 300,
156 verfahren: 'HCM' as const,
157 mode: 'kfz' as const,
158 abflusszeit: 30,
159 };
160 const zweiFenster = computeDelay({ ...gemeinsam, sperrzeiten });
161 const einFenster = computeDelay(gemeinsam);
162 expect(zweiFenster.uniformDelay * 2).toBeCloseTo(einFenster.uniformDelay, 0);
163 });
164
165 it('faellt auf die Einfensterform zurueck, wenn die Sperrzeiten nicht zur Abflusszeit passen', () => {
166 const gemeinsam = {
167 cycleTime: 90,
168 greenTime: 30,
169 capacity: 711,
170 demand: 300,
171 verfahren: 'HBS' as const,
172 mode: 'kfz' as const,
173 abflusszeit: 32,
174 };
175 // Dauerfreigabe: keine Sperrzeit im Umlauf.
176 expect(computeDelay({ ...gemeinsam, sperrzeiten: [] }).uniformDelay).toBe(
177 computeDelay(gemeinsam).uniformDelay,
178 );
179 // Sperrzeiten, deren Summe kleiner ist als tU - tA: kein Zeitzuschlag zu
180 // verteilen, also nicht auswertbar.
181 expect(computeDelay({ ...gemeinsam, sperrzeiten: [10, 10] }).uniformDelay).toBe(
182 computeDelay(gemeinsam).uniformDelay,
183 );
184 });
185 });