// Named Constants for Convenience constant int f := 1 // sample rate in [Hz] constant int N := 7200 * f // # of samples in 2h constant float Dt := 1 / f // sample period in [s] // WARNING: some parts assume a sample frequency of 1 Hz // WARNING: some parts assume diesel fuel ////////////////////////////////////////////////////////// // Test Parameters // ////////////////////////////////////////////////////////// input float v // vehicle speed in [km/h] input float altitude // above see level in [m] input float temperature // ambient temperature in [K] // we only do this exemplary for NOx and CO2 input float nox_ppm // in [ppm] input float co2_ppm // in [ppm] input float exhaust_mass_flow // in [kg/s] ////////////////////////////////////////////////////////// // Auxiliary Streams // ////////////////////////////////////////////////////////// output bool is_stop := v < 1 // 6.8 output bool is_urban := v <= 60 // 6.3 output bool is_rural := 60 < v <= 90 // 6.4 output bool is_motorway := 90 < v // 6.5 output float Dd := Dt * v / 3.6 // distance in [m] // total duration output float duration := duration[-1,0] + Dt // maximal suffix distance output float d := sum(Dd[-N:0]) // in [m] // distance of the respective segments output float d_u := sum(Dd[-N:0 | is_urban]) output float d_r := sum(Dd[-N:0 | is_rural]) output float d_m := sum(Dd[-N:0 | is_motorway]) // maximal suffix duration output float t := sum(Dt[-N:0]) // in [s] // duration of the respective segments output float t_u := sum(Dt[-N:0 | is_urban]) output float t_m := sum(Dt[-N:0 | is_motorway]) // urban specific auxiliary streams output float u_avg_v := avg(v[-N:0 | is_urban]) output float u_stop_t := sum(Dt[-N:0 | is_stop]) output float u_stop_pc := ite(t_u > 0, stop_t / t_u, 0) // count long stops output float stop_period := ite(is_stop, stop_period[-1,0] + Dt, 0) output int indicate_long_stop := ite(stop_period > 10 & stop_period[-1,0] <= 10, 1, 0) output int long_stops := sum(indicate_long_stop[-N:0]) // motorway specific auxiliary streams output bool is_v_exceeded := v > 145 output bool is_v_above_100 := v > 100 output bool is_v_above_110 := v > 110 output float v_exceeded_t := sum(Dt[-N:0 | is_v_exceeded]) output float v_exceeded_pc := ite(t_m > 0, v_exceeded_t / t_m, 0) output float v_above_100_t := sum(Dt[-N:0 | is_v_above_100]) output float v_max := max(v[-N:0]) output bool reached_110 := any(is_v_above_110[-N:0]) // assume several means at least 5 constant int SEVERAL_STOPS := 5 ////////////////////////////////////////////////////////// // Real Driving Emissions (RDE) Specification // // EU Regulation 2017/1151 // ////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////// // ANNEX IIIA, Section 6, Trip Requirements // ////////////////////////////////////////////////////////// output bool satisfies_trip_requirements := ( 90 * 60 <= t <= 120 * 60 & // 6.10 0.29 <= ite(d > 0, d_u / d, 0) <= 0.44 & // 6.6 0.23 <= ite(d > 0, d_r / d, 0) <= 0.43 & // 6.6 0.23 <= ite(d > 0, d_m / d, 0) <= 0.43 & // 6.6 15 <= u_avg_v <= 40 & // 6.8 0.06 <= u_stop_pc <= 0.30 & // 6.8 v_max <= 160 & // 6.7 v_exceeded_pc <= 0.03 & // 6.7 v_above_100_t >= 5 * 60 & // 6.9 long_stops >= SEVERAL_STOPS & //6.8 d_u >= 16 * 1000 & // 6.12 d_r >= 16 * 1000 & // 6.12 d_m >= 16 * 1000 & // 6.12 abs(fist(altitude[-N:0],0) - altitude) <= 100 // 6.11 ) ////////////////////////////////////////////////////////// // ANNEX IIIA, Section 5.2, Ambient Conditions // ////////////////////////////////////////////////////////// output bool is_ambient_normal := ( (altitude <= 700) & // 5.2.2 (273 <= temperature <= 303) // 5.2.4 ) output bool is_ambient_extended := ( (700 < altitude <= 1300) & // 5.2.3 ( (266 <= temperature < 273) | (303 < temperature <= 308) ) // 5.2.5 ) output bool is_ambient_exceeded := !is_ambient_normal & !is_ambient_extended ////////////////////////////////////////////////////////// // ANNEX IIIA, Appendix 7a, Overall Trip Dynamics // ////////////////////////////////////////////////////////// // we only do this exemplary for the urban speed bin // 3.1.2 (this assumes a sample frequency of 1 Hz) output float a := (v[+1,0] - v[-1,0]) / (2 * 3.6) output float va := (v * a / 3.6) // 3.1.3 (we only do this exemplary for the urban speed bin) output int u_a_gt_01 := ite(a > 0.1 & is_urban, 1, 0) output int count_u_a_gt := sum(u_a_gt_01[-N:0]) // 3.1.4 (without interpolation) output bool u_a_ge_01 := a >= 0.1 & is_urban output float Dt_x_va := Dt * va output float u_va_pct := percentile95(va[-N:0 | u_a_ge_01]) output float u_rpa := ( sum(Dt_x_va[-N:0 | u_a_ge_01]) / sum(Dd[-N:0 | is_urban]) ) output bool invalid_trip_dynamics := ( count_u_a_gt < 150 | // 3.1.3 // 4.1.1 ( u_avg_v <= 74.6 & u_va_pct > (0.136*u_avg_v + 14.44) ) | ( u_avg_v > 74.6 & u_va_pct > (0.0742*u_avg_v + 18.966) ) | // 4.1.2 (u_avg_v <= 94.05 & u_rpa < (-0.0016*u_avg_v + 0.1755)) | (u_avg_v > 94.05 & u_rpa < -0.025) ) ////////////////////////////////////////////////////////// // ANNEX IIIA, Appendix 5, Moving Averaging Window // ////////////////////////////////////////////////////////// // half of the CO2 mass [g] emitted during the WLTP constant float MCO2REF = ... output int sample := sample[-1, 0] + 1 output bool win_created(start: int) : inv: sample; ext: true; ter: win_completed(start) := sample == start output float win_start_co2(start: int) : inv: sample; ext: win_created(start); ter: win_completed(start) := total_co2_mass output float win_co2_mass(start: int) : inv: sample; ext: true; ter: win_completed(start) := total_co2_mass - win_start_co2(start) output bool win_completed(start: int) : inv: sample; ext: true; ter: win_completed := win_co2_mass >= MCO2REF // 3.2 calculation of window emissions and averages output float win_v(start: int) : inv: sample; ext: true; ter: win_completed := v output float win_avg_v(start: int) : inv: sample; ext: true; ter: win_completed := avg(win_v(start)[-N:0]) output float win_d(start: int) : inv: sample; ext: true; ter: win_completed := win_d(start)[-1,0] + Dd output float win_nox_mass(start: int) : inv: sample; ext: true; ter: win_completed := win_nox(start)[-1,0] + nox_flow * Dt output float win_co2_per_d(start: int) : inv: sample; ext: true; ter: win_completed := ite( win_d(start) > 0, win_co2_mass(start) / win_d(start), 0 ) output float win_nox_per_d(start: int) : inv: sample; ext: true; ter: win_completed := ite( win_d(start) > 0, win_nox_mass(start) / win_d(start), 0 ) ////////////////////////////////////////////////////////// // ANNEX IIIA, Appendix 4, Calculating Emissions // ////////////////////////////////////////////////////////// // we use diesel specific values here output float nox_mass_flow // in [g/s] for diesel := exhaust_mass_flow * 0.001586 * nox_ppm output float co2_mass_flow // in [g/s] for diesel := exhaust_mass_flow * 0.001517 * co2_ppm output total_co2_mass // in [g] := total_co2_mass[-1,0] + co2_mass_flow * Dt ////////////////////////////////////////////////////////// // Determine trip validity and check emissions. // ////////////////////////////////////////////////////////// output bool is_valid_test := ( satisfies_trip_requirements & !is_ambient_exceeded & !invalid_trip_dynamics ) output float D_nox_mass := nox_mass_flow * Dt output bool nox_exceeded := ite(d > 0, sum(D_nox_mass[-N:0]) / d, 0) > 0.08 output bool emission_limits_exceeded := nox_exceeded ////////////////////////////////////////////////////////// // Trigger iff the RDE regulation is violated. // ////////////////////////////////////////////////////////// trigger is_valid_test & emission_limits_exceeded ///////////////////////////////////////////////////////// // Trigger iff the trip is not an RDE trip. // ///////////////////////////////////////////////////////// output bool not_rde_test := ( duration > 120 * 60 | // 6.10 trip too long max_speed > 160 | // max speed exceeded is_ambient_exceeded // ambient exceeded )