Line data Source code
1 : /****************************************************************************/
2 : // Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.dev/sumo
3 : // Copyright (C) 2013-2024 German Aerospace Center (DLR) and others.
4 : // This program and the accompanying materials are made available under the
5 : // terms of the Eclipse Public License 2.0 which is available at
6 : // https://www.eclipse.org/legal/epl-2.0/
7 : // This Source Code may also be made available under the following Secondary
8 : // Licenses when the conditions for such availability set forth in the Eclipse
9 : // Public License 2.0 are satisfied: GNU General Public License, version 2
10 : // or later which is available at
11 : // https://www.gnu.org/licenses/old-licenses/gpl-2.0-standalone.html
12 : // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-or-later
13 : /****************************************************************************/
14 : /// @file HelpersPHEMlight.cpp
15 : /// @author Daniel Krajzewicz
16 : /// @author Michael Behrisch
17 : /// @author Nikolaus Furian
18 : /// @date Sat, 20.04.2013
19 : ///
20 : // Helper methods for PHEMlight-based emission computation
21 : /****************************************************************************/
22 : #include <config.h>
23 :
24 : #include <limits>
25 : #include <cmath>
26 : #ifdef INTERNAL_PHEM
27 : #include "PHEMCEPHandler.h"
28 : #include "PHEMConstants.h"
29 : #endif
30 : #include <foreign/PHEMlight/cpp/Constants.h>
31 : #include <utils/common/StringUtils.h>
32 : #include <utils/options/OptionsCont.h>
33 :
34 : #include "EnergyParams.h"
35 : #include "HelpersPHEMlight.h"
36 :
37 : // idle speed is usually given in rpm (but may depend on electrical consumers). Actual speed depends on the gear so this number is only a rough estimate
38 : #define IDLE_SPEED (10 / 3.6)
39 :
40 : // ===========================================================================
41 : // method definitions
42 : // ===========================================================================
43 55645 : HelpersPHEMlight::HelpersPHEMlight() :
44 : PollutantsInterface::Helper("PHEMlight", PHEMLIGHT_BASE, -1),
45 111290 : myIndex(PHEMLIGHT_BASE) {
46 55645 : }
47 :
48 :
49 111290 : HelpersPHEMlight::~HelpersPHEMlight() {
50 111399 : for (const auto& cep : myCEPs) {
51 109 : delete cep.second;
52 : }
53 111290 : }
54 :
55 :
56 : SUMOEmissionClass
57 156 : HelpersPHEMlight::getClassByName(const std::string& eClass, const SUMOVehicleClass vc) {
58 216 : if (eClass == "unknown" && !myEmissionClassStrings.hasString("unknown")) {
59 60 : myEmissionClassStrings.addAlias("unknown", getClassByName("PC_G_EU4", vc));
60 : }
61 156 : if (eClass == "default" && !myEmissionClassStrings.hasString("default")) {
62 0 : myEmissionClassStrings.addAlias("default", getClassByName("PC_G_EU4", vc));
63 : }
64 : if (myEmissionClassStrings.hasString(eClass)) {
65 47 : return myEmissionClassStrings.get(eClass);
66 : }
67 109 : if (eClass.size() < 6) {
68 0 : throw InvalidArgument("Unknown emission class '" + eClass + "'.");
69 : }
70 109 : int index = myIndex++;
71 109 : const std::string type = eClass.substr(0, 3);
72 218 : if (type == "HDV" || type == "LB_" || type == "RB_" || type == "LSZ" || eClass.find("LKW") != std::string::npos) {
73 0 : index |= PollutantsInterface::HEAVY_BIT;
74 : }
75 218 : myEmissionClassStrings.insert(eClass, index);
76 : #ifdef INTERNAL_PHEM
77 109 : if (type == "HDV" || type == "LCV" || type == "PC_" || !PHEMCEPHandler::getHandlerInstance().Load(index, eClass)) {
78 : #endif
79 109 : myVolumetricFuel = OptionsCont::getOptions().getBool("emissions.volumetric-fuel");
80 : std::vector<std::string> phemPath;
81 109 : phemPath.push_back(OptionsCont::getOptions().getString("phemlight-path") + "/");
82 109 : if (getenv("PHEMLIGHT_PATH") != nullptr) {
83 0 : phemPath.push_back(std::string(getenv("PHEMLIGHT_PATH")) + "/");
84 : }
85 109 : if (getenv("SUMO_HOME") != nullptr) {
86 218 : phemPath.push_back(std::string(getenv("SUMO_HOME")) + "/data/emissions/PHEMlight/");
87 : }
88 109 : myHelper.setCommentPrefix("c");
89 109 : myHelper.setPHEMDataV("V4");
90 109 : myHelper.setclass(eClass);
91 109 : if (!myCEPHandler.GetCEP(phemPath, &myHelper)) {
92 0 : myEmissionClassStrings.remove(eClass, index);
93 0 : myIndex--;
94 0 : throw InvalidArgument("File for PHEM emission class " + eClass + " not found.\n" + myHelper.getErrMsg());
95 : }
96 109 : myCEPs[index] = myCEPHandler.getCEPS().find(myHelper.getgClass())->second;
97 : #ifdef INTERNAL_PHEM
98 109 : }
99 : #endif
100 109 : myEmissionClassStrings.addAlias(StringUtils::to_lower_case(eClass), index);
101 109 : return index;
102 : }
103 :
104 :
105 : SUMOEmissionClass
106 0 : HelpersPHEMlight::getClass(const SUMOEmissionClass base, const std::string& vClass, const std::string& fuel, const std::string& eClass, const double weight) const {
107 0 : std::string eClassOffset = "0";
108 0 : if (eClass.length() == 5 && eClass.substr(0, 4) == "Euro") {
109 0 : if (eClass[4] >= '0' && eClass[4] <= '6') {
110 0 : eClassOffset = eClass.substr(4, 1);
111 : }
112 : }
113 : std::string desc;
114 0 : if (vClass == "Passenger") {
115 : desc = "PKW_";
116 0 : if (fuel == "Gasoline") {
117 : desc += "G_";
118 0 : } else if (fuel == "Diesel") {
119 : desc += "D_";
120 0 : } else if (fuel == "HybridGasoline") {
121 0 : desc = "H_" + desc + "G_";
122 0 : } else if (fuel == "HybridDiesel") {
123 0 : desc = "H_" + desc + "G_";
124 : }
125 0 : desc += "EU" + eClassOffset;
126 0 : } else if (vClass == "Moped") {
127 0 : desc = "KKR_G_EU" + eClassOffset;
128 0 : } else if (vClass == "Motorcycle") {
129 0 : desc = "MR_G_EU" + eClassOffset;
130 0 : if (fuel == "Gasoline2S") {
131 : desc += "_2T";
132 : } else {
133 : desc += "_4T";
134 : }
135 0 : } else if (vClass == "Delivery") {
136 : desc = "LNF_";
137 0 : if (fuel == "Gasoline") {
138 : desc += "G_";
139 0 : } else if (fuel == "Diesel") {
140 : desc += "D_";
141 : }
142 0 : desc += "EU" + eClassOffset + "_I";
143 0 : if (weight > 1305.) {
144 : desc += "I";
145 0 : if (weight > 1760.) {
146 : desc += "I";
147 : }
148 : }
149 0 : } else if (vClass == "UrbanBus") {
150 0 : desc = "LB_D_EU" + eClassOffset;
151 0 : } else if (vClass == "Coach") {
152 0 : desc = "RB_D_EU" + eClassOffset;
153 0 : } else if (vClass == "Truck") {
154 0 : desc = "Solo_LKW_D_EU" + eClassOffset + "_I";
155 0 : if (weight > 1305.) {
156 : desc += "I";
157 : }
158 0 : } else if (vClass == "Trailer") {
159 0 : desc = "LSZ_D_EU" + eClassOffset;
160 : }
161 : if (myEmissionClassStrings.hasString(desc)) {
162 0 : return myEmissionClassStrings.get(desc);
163 : }
164 : return base;
165 : }
166 :
167 :
168 : std::string
169 0 : HelpersPHEMlight::getAmitranVehicleClass(const SUMOEmissionClass c) const {
170 0 : const std::string name = myEmissionClassStrings.getString(c);
171 0 : if (name.find("KKR_") != std::string::npos) {
172 0 : return "Moped";
173 0 : } else if (name.find("RB_") != std::string::npos) {
174 0 : return "Coach";
175 0 : } else if (name.find("LB_") != std::string::npos) {
176 0 : return "UrbanBus";
177 0 : } else if (name.find("LNF_") != std::string::npos) {
178 0 : return "Delivery";
179 0 : } else if (name.find("LSZ_") != std::string::npos) {
180 0 : return "Trailer";
181 0 : } else if (name.find("MR_") != std::string::npos) {
182 0 : return "Motorcycle";
183 0 : } else if (name.find("LKW_") != std::string::npos) {
184 0 : return "Truck";
185 : }
186 0 : return "Passenger";
187 : }
188 :
189 :
190 : std::string
191 0 : HelpersPHEMlight::getFuel(const SUMOEmissionClass c) const {
192 0 : const std::string name = myEmissionClassStrings.getString(c);
193 0 : std::string fuel = "Gasoline";
194 0 : if (name.find("_D_") != std::string::npos) {
195 : fuel = "Diesel";
196 : }
197 0 : if (name.find("H_") != std::string::npos) {
198 0 : fuel = "Hybrid" + fuel;
199 : }
200 0 : return fuel;
201 : }
202 :
203 :
204 : int
205 0 : HelpersPHEMlight::getEuroClass(const SUMOEmissionClass c) const {
206 0 : const std::string name = myEmissionClassStrings.getString(c);
207 0 : if (name.find("_EU1") != std::string::npos) {
208 : return 1;
209 0 : } else if (name.find("_EU2") != std::string::npos) {
210 : return 2;
211 0 : } else if (name.find("_EU3") != std::string::npos) {
212 : return 3;
213 0 : } else if (name.find("_EU4") != std::string::npos) {
214 : return 4;
215 0 : } else if (name.find("_EU5") != std::string::npos) {
216 : return 5;
217 0 : } else if (name.find("_EU6") != std::string::npos) {
218 0 : return 6;
219 : }
220 : return 0;
221 : }
222 :
223 :
224 : double
225 0 : HelpersPHEMlight::getWeight(const SUMOEmissionClass c) const {
226 0 : const std::string name = myEmissionClassStrings.getString(c);
227 0 : if (name.find("LNF_") != std::string::npos) {
228 0 : if (name.find("_III") != std::string::npos) {
229 : return 2630.;
230 0 : } else if (name.find("_II") != std::string::npos) {
231 : return 1532.;
232 0 : } else if (name.find("_I") != std::string::npos) {
233 : return 652.;
234 : }
235 : }
236 0 : if (name.find("Solo_LKW_") != std::string::npos) {
237 0 : if (name.find("_II") != std::string::npos) {
238 : return 8398.;
239 0 : } else if (name.find("_I") != std::string::npos) {
240 : return 18702.;
241 : }
242 : }
243 : return -1.;
244 : }
245 :
246 :
247 : double
248 1051968 : HelpersPHEMlight::getEmission(const PHEMCEP* oldCep, PHEMlightdll::CEP* currCep, const std::string& e, const double p, const double v) const {
249 1051968 : if (oldCep != nullptr) {
250 0 : return oldCep->GetEmission(e, p, v);
251 : }
252 1051968 : return currCep->GetEmission(e, p, v, &myHelper);
253 : }
254 :
255 :
256 : double
257 942732 : HelpersPHEMlight::getModifiedAccel(const SUMOEmissionClass c, const double v, const double a, const double slope, const EnergyParams* /* param */) const {
258 942732 : PHEMlightdll::CEP* currCep = myCEPs.count(c) == 0 ? 0 : myCEPs.find(c)->second;
259 942732 : if (currCep != nullptr) {
260 942732 : return v == 0.0 ? 0.0 : MIN2(a, currCep->GetMaxAccel(v, slope));
261 : }
262 : return a;
263 : }
264 :
265 :
266 : double
267 2358 : HelpersPHEMlight::getCoastingDecel(const SUMOEmissionClass c, const double v, const double a, const double slope, const EnergyParams* /* param */) const {
268 2358 : return myCEPs.count(c) == 0 ? 0. : myCEPs.find(c)->second->GetDecelCoast(v, a, slope);
269 : }
270 :
271 :
272 : double
273 963452 : HelpersPHEMlight::compute(const SUMOEmissionClass c, const PollutantsInterface::EmissionType e, const double v, const double a, const double slope, const EnergyParams* param) const {
274 963452 : if (param != nullptr && param->isEngineOff()) {
275 : return 0.;
276 : }
277 : const double corrSpeed = MAX2(0.0, v);
278 : double power = 0.;
279 : #ifdef INTERNAL_PHEM
280 942732 : const PHEMCEP* const oldCep = PHEMCEPHandler::getHandlerInstance().GetCep(c);
281 942732 : if (oldCep != nullptr) {
282 0 : if (v > IDLE_SPEED && a < oldCep->GetDecelCoast(corrSpeed, a, slope, 0)) {
283 : // coasting without power use only works if the engine runs above idle speed and
284 : // the vehicle does not accelerate beyond friction losses
285 : return 0;
286 : }
287 0 : power = oldCep->CalcPower(corrSpeed, a, slope);
288 : }
289 : #else
290 : const PHEMCEP* const oldCep = 0;
291 : #endif
292 942732 : PHEMlightdll::CEP* currCep = myCEPs.count(c) == 0 ? 0 : myCEPs.find(c)->second;
293 942732 : if (currCep != nullptr) {
294 942732 : const double corrAcc = getModifiedAccel(c, corrSpeed, a, slope, param);
295 1885464 : if (currCep->getFuelType() != PHEMlightdll::Constants::strBEV &&
296 942732 : corrAcc < currCep->GetDecelCoast(corrSpeed, corrAcc, slope) &&
297 25536 : corrSpeed > PHEMlightdll::Constants::ZERO_SPEED_ACCURACY) {
298 : // the IDLE_SPEED fix above is now directly in the decel coast calculation.
299 : return 0;
300 : }
301 920472 : power = currCep->CalcPower(corrSpeed, corrAcc, slope);
302 : }
303 920472 : const std::string& fuelType = oldCep != nullptr ? oldCep->GetVehicleFuelType() : currCep->getFuelType();
304 920472 : switch (e) {
305 : case PollutantsInterface::CO:
306 131496 : return getEmission(oldCep, currCep, "CO", power, corrSpeed) / SECONDS_PER_HOUR * 1000.;
307 131496 : case PollutantsInterface::CO2:
308 131496 : if (oldCep != nullptr) {
309 0 : return getEmission(oldCep, currCep, "FC", power, corrSpeed) * 3.15 / SECONDS_PER_HOUR * 1000.;
310 : }
311 262992 : return currCep->GetCO2Emission(getEmission(nullptr, currCep, "FC", power, corrSpeed),
312 : getEmission(nullptr, currCep, "CO", power, corrSpeed),
313 131496 : getEmission(nullptr, currCep, "HC", power, corrSpeed), &myHelper) / SECONDS_PER_HOUR * 1000.;
314 : case PollutantsInterface::HC:
315 131496 : return getEmission(oldCep, currCep, "HC", power, corrSpeed) / SECONDS_PER_HOUR * 1000.;
316 : case PollutantsInterface::NO_X:
317 131496 : return getEmission(oldCep, currCep, "NOx", power, corrSpeed) / SECONDS_PER_HOUR * 1000.;
318 : case PollutantsInterface::PM_X:
319 131496 : return getEmission(oldCep, currCep, "PM", power, corrSpeed) / SECONDS_PER_HOUR * 1000.;
320 131496 : case PollutantsInterface::FUEL: {
321 131496 : if (myVolumetricFuel && fuelType == PHEMlightdll::Constants::strDiesel) { // divide by average diesel density of 836 g/l
322 0 : return getEmission(oldCep, currCep, "FC", power, corrSpeed) / 836. / SECONDS_PER_HOUR * 1000.;
323 : }
324 131496 : if (myVolumetricFuel && fuelType == PHEMlightdll::Constants::strGasoline) { // divide by average gasoline density of 742 g/l
325 0 : return getEmission(oldCep, currCep, "FC", power, corrSpeed) / 742. / SECONDS_PER_HOUR * 1000.;
326 : }
327 131496 : if (fuelType == PHEMlightdll::Constants::strBEV) {
328 : return 0.;
329 : }
330 131496 : return getEmission(oldCep, currCep, "FC", power, corrSpeed) / SECONDS_PER_HOUR * 1000.; // still in mg even if myVolumetricFuel is set!
331 : }
332 131496 : case PollutantsInterface::ELEC:
333 131496 : if (fuelType == PHEMlightdll::Constants::strBEV) {
334 0 : return getEmission(oldCep, currCep, "FC", power, corrSpeed) / SECONDS_PER_HOUR * 1000.;
335 : }
336 : return 0;
337 : }
338 : // should never get here
339 : return 0.;
340 : }
341 :
342 :
343 : /****************************************************************************/
|