lsa-planer
LSA-Planer Professional – Planungssoftware für Lichtsignalanlagen nach RiLSA 2015 und § 45 StVO. EUPL-1.2.
/ tests domain grundwartezeit.test.ts
| 1 | import { describe, expect, it } from 'vitest'; |
| 2 | import { computeDelay, sperrzeitenAusFenstern } from '@/domain/rilsa/delay'; |
| 3 | |
| 4 | /* |
| 5 | * Fassung 5.10.0 - die Grundwartezeit. |
| 6 | * |
| 7 | * `computeDelay` bildet den Zaehler der Grundwartezeit seit Befund 26 aus |
| 8 | * Summe(r_j^2) / tU ueber die Sperrzeiten des Umlaufs |
| 9 | * (`summeSperrzeitenQuadrate` in src/domain/rilsa/delay.ts); ein Nachtrag hat |
| 10 | * die Uebergabe der Sperrzeiten aus dem Planaufbau nachgezogen, seitdem kommt |
| 11 | * die allgemeine Form auch im laufenden Programm an. |
| 12 | * |
| 13 | * Die Unterlagen fuehrten die Groesse weiter ausschliesslich in der |
| 14 | * geschlossenen Einfensterform tW,G = tU * (1 - fA)^2 / (2 * (1 - min(1; x) * fA)). |
| 15 | * Wer damit nachrechnet, was das Programm bei mehreren Freigabezeitfenstern |
| 16 | * ausweist, kommt auf das Doppelte und haelt die Zahl des Programms fuer |
| 17 | * falsch. Die Faelle hier messen an `computeDelay` nach, dass die allgemeine |
| 18 | * Form gilt - und dass die Einfensterform genau der Sonderfall ist. |
| 19 | * |
| 20 | * NACHTRAG: Die r_j der Formel sind nicht die Rot-Abschnitte, |
| 21 | * die `sperrzeitenAusFenstern` liefert - deren Summe ist tU - tF. Erst nachdem |
| 22 | * `summeSperrzeitenQuadrate` je Freigabezeitfenster den Zeitzuschlag der |
| 23 | * Abflusszeit abgezogen hat, gilt Summe(r_j) = tU - tA. Der letzte Fall misst |
| 24 | * diesen Unterschied. |
| 25 | * |
| 26 | * Die beiden Rechenfaelle sind der Beleg, dass die allgemeine Form stimmt, und |
| 27 | * werden rot, sobald jemand die Rechnung wieder auf die Einfensterform |
| 28 | * zurueckdreht. |
| 29 | */ |
| 30 | |
| 31 | /** |
| 32 | * Umlauf mit zwei gleich langen Sperrzeiten. |
| 33 | * |
| 34 | * tU = 100 s, tA = 20 s: Die wirksame Sperrzeit betraegt 80 s. Uebergeben |
| 35 | * werden 45 s + 45 s - die Sperrzeiten aus den Freigabezeitfenstern tragen den |
| 36 | * Zeitzuschlag der Abflusszeit noch; `summeSperrzeitenQuadrate` zieht ihn je |
| 37 | * Fenster ab und rechnet mit 40 s + 40 s. |
| 38 | * |
| 39 | * Zaehler allgemein: (40² + 40²) / 100 = 32 s. Zaehler geschlossen: 100 · (1 − |
| 40 | * 0,2)² = 64 s - genau das Doppelte. |
| 41 | */ |
| 42 | const ZWEI_FENSTER = { |
| 43 | cycleTime: 100, |
| 44 | greenTime: 19, |
| 45 | abflusszeit: 20, |
| 46 | capacity: 1000, |
| 47 | demand: 500, |
| 48 | verfahren: 'HBS', |
| 49 | mode: 'kfz', |
| 50 | } as const; |
| 51 | |
| 52 | describe('Die Grundwartezeit rechnet in der allgemeinen Form', () => { |
| 53 | it('rechnet bei zwei Fenstern nach der allgemeinen Form', () => { |
| 54 | const zwei = computeDelay({ ...ZWEI_FENSTER, sperrzeiten: [45, 45] }); |
| 55 | |
| 56 | // 32 s / (2 · (1 − 0,5 · 0,2)) = 32 / 1,8 = 17,78 s |
| 57 | expect(zwei.rechenweg.abflussanteil).toBeCloseTo(0.2, 6); |
| 58 | expect(zwei.degreeOfSaturation).toBeCloseTo(0.5, 6); |
| 59 | expect(zwei.uniformDelay).toBeCloseTo(32 / 1.8, 1); |
| 60 | }); |
| 61 | |
| 62 | it('fällt ohne brauchbare Sperrzeiten auf die Einfensterform zurück', () => { |
| 63 | // Genau der Sonderfall der Einfensterform: 64 s / 1,8 = 35,56 s, |
| 64 | // das Doppelte des Wertes darueber. |
| 65 | const ohne = computeDelay(ZWEI_FENSTER); |
| 66 | const unbrauchbar = computeDelay({ ...ZWEI_FENSTER, sperrzeiten: [0, Number.NaN] }); |
| 67 | |
| 68 | expect(ohne.uniformDelay).toBeCloseTo(64 / 1.8, 1); |
| 69 | expect(unbrauchbar.uniformDelay).toBe(ohne.uniformDelay); |
| 70 | // Ein Fenster: Die allgemeine Form ergibt denselben Wert wie der Rueckfall. |
| 71 | expect(computeDelay({ ...ZWEI_FENSTER, sperrzeiten: [81] }).uniformDelay).toBeCloseTo( |
| 72 | ohne.uniformDelay, |
| 73 | 1, |
| 74 | ); |
| 75 | }); |
| 76 | |
| 77 | it('unterscheidet in der Rechnung die Rot-Abschnitte von den wirksamen r_j', () => { |
| 78 | // Zwei Freigabezeitfenster von je 5 s im 100-s-Umlauf: tF = 10 s. |
| 79 | const rotAbschnitte = sperrzeitenAusFenstern( |
| 80 | [ |
| 81 | { start: 0, duration: 5 }, |
| 82 | { start: 50, duration: 5 }, |
| 83 | ], |
| 84 | 100, |
| 85 | ); |
| 86 | expect(rotAbschnitte).toEqual([45, 45]); |
| 87 | // Die Summe der gelieferten Abschnitte ist tU - tF, nicht tU - tA. |
| 88 | const summeRot = rotAbschnitte.reduce((wert, r) => wert + r, 0); |
| 89 | expect(summeRot).toBeCloseTo(100 - 10, 6); |
| 90 | expect(summeRot).not.toBeCloseTo(100 - 12, 6); |
| 91 | |
| 92 | // tA = tF + 1 s je Fenster = 12 s. Erst die wirksamen Sperrzeiten |
| 93 | // 44 s + 44 s summieren sich zu tU - tA = 88 s. |
| 94 | const ergebnis = computeDelay({ ...ZWEI_FENSTER, abflusszeit: 12, sperrzeiten: rotAbschnitte }); |
| 95 | expect(ergebnis.rechenweg.abflussanteil).toBeCloseTo(0.12, 6); |
| 96 | // Zaehler (44² + 44²) / 100 = 38,72 s, Nenner 2 · (1 − 0,5 · 0,12) = 1,88. |
| 97 | expect(ergebnis.uniformDelay).toBeCloseTo(38.72 / 1.88, 1); |
| 98 | // Mit den Rot-Abschnitten selbst waeren es (45² + 45²) / 100 = 40,5 s - |
| 99 | // genau der Nachrechenfehler, den die alte Gleichsetzung ausloeste. |
| 100 | expect(ergebnis.uniformDelay).not.toBeCloseTo(40.5 / 1.88, 1); |
| 101 | }); |
| 102 | }); |