Path: blob/main/src/utils/emissions/HelpersPHEMlight5.cpp
169678 views
/****************************************************************************/1// Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.dev/sumo2// Copyright (C) 2013-2025 German Aerospace Center (DLR) and others.3// This program and the accompanying materials are made available under the4// terms of the Eclipse Public License 2.0 which is available at5// https://www.eclipse.org/legal/epl-2.0/6// This Source Code may also be made available under the following Secondary7// Licenses when the conditions for such availability set forth in the Eclipse8// Public License 2.0 are satisfied: GNU General Public License, version 29// or later which is available at10// https://www.gnu.org/licenses/old-licenses/gpl-2.0-standalone.html11// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-or-later12/****************************************************************************/13/// @file HelpersPHEMlight5.cpp14/// @author Daniel Krajzewicz15/// @author Michael Behrisch16/// @author Nikolaus Furian17/// @date Sat, 20.04.201318///19// Helper methods for PHEMlight-based emission computation20/****************************************************************************/21#include <config.h>2223#include <limits>24#include <cmath>25#include <foreign/PHEMlight/V5/cpp/Constants.h>26#include <foreign/PHEMlight/V5/cpp/Correction.h>27#include <utils/common/StringUtils.h>28#include <utils/geom/GeomHelper.h>29#include <utils/options/OptionsCont.h>3031#include "EnergyParams.h"32#include "HelpersPHEMlight5.h"333435// ===========================================================================36// method definitions37// ===========================================================================38HelpersPHEMlight5::HelpersPHEMlight5() :39HelpersPHEMlight("PHEMlight5", PHEMLIGHT5_BASE, -1),40myIndex(PHEMLIGHT5_BASE) {41}424344HelpersPHEMlight5::~HelpersPHEMlight5() {45for (const auto& cep : myCEPs) {46delete cep.second;47}48}495051SUMOEmissionClass52HelpersPHEMlight5::getClassByName(const std::string& eClass, const SUMOVehicleClass vc) {53if (eClass == "unknown" && !myEmissionClassStrings.hasString("unknown")) {54myEmissionClassStrings.addAlias("unknown", getClassByName("PC_EU4_G", vc));55}56if (eClass == "default" && !myEmissionClassStrings.hasString("default")) {57myEmissionClassStrings.addAlias("default", getClassByName("PC_EU4_G", vc));58}59if (myEmissionClassStrings.hasString(eClass)) {60return myEmissionClassStrings.get(eClass);61}62if (eClass.size() < 6) {63throw InvalidArgument("Unknown emission class '" + eClass + "'.");64}65const OptionsCont& oc = OptionsCont::getOptions();66myVolumetricFuel = oc.getBool("emissions.volumetric-fuel");67std::vector<std::string> phemPath;68phemPath.push_back(oc.getString("phemlight-path") + "/");69if (getenv("PHEMLIGHT_PATH") != nullptr) {70phemPath.push_back(std::string(getenv("PHEMLIGHT_PATH")) + "/");71}72if (getenv("SUMO_HOME") != nullptr) {73phemPath.push_back(std::string(getenv("SUMO_HOME")) + "/data/emissions/PHEMlight5/");74}75if (myCorrection == nullptr && (!oc.isDefault("phemlight-year") || !oc.isDefault("phemlight-temperature"))) {76myCorrection = new PHEMlightdllV5::Correction(phemPath);77if (!oc.isDefault("phemlight-year")) {78myCorrection->setYear(oc.getInt("phemlight-year"));79std::string err;80if (!myCorrection->ReadDet(err)) {81throw InvalidArgument("Error reading PHEMlight5 deterioration data.\n" + err);82}83myCorrection->setUseDet(true);84}85if (!oc.isDefault("phemlight-temperature")) {86myCorrection->setAmbTemp(oc.getFloat("phemlight-temperature"));87std::string err;88if (!myCorrection->ReadTNOx(err)) {89throw InvalidArgument("Error reading PHEMlight5 deterioration data.\n" + err);90}91myCorrection->setUseTNOx(true);92}93}94myHelper.setCommentPrefix("c");95myHelper.setPHEMDataV("V5");96myHelper.setclass(eClass);97if (!myCEPHandler.GetCEP(phemPath, &myHelper, myCorrection)) {98throw InvalidArgument("File for PHEMlight5 emission class " + eClass + " not found.\n" + myHelper.getErrMsg());99}100PHEMlightdllV5::CEP* const currCep = myCEPHandler.getCEPS().find(myHelper.getgClass())->second;101int index = myIndex++;102if (currCep->getHeavyVehicle()) {103index |= PollutantsInterface::HEAVY_BIT;104}105myEmissionClassStrings.insert(eClass, index);106myCEPs[index] = currCep;107myEmissionClassStrings.addAlias(StringUtils::to_lower_case(eClass), index);108return index;109}110111112std::string113HelpersPHEMlight5::getFuel(const SUMOEmissionClass c) const {114const std::string name = myEmissionClassStrings.getString(c);115std::string fuel = "Gasoline";116if (name.find("_D_") != std::string::npos) {117fuel = "Diesel";118}119if (name.find("_BEV_") != std::string::npos) {120fuel = "Electricity";121}122if (name.find("_HEV") != std::string::npos) {123fuel = "Hybrid" + fuel;124}125return fuel;126}127128129double130HelpersPHEMlight5::getWeight(const SUMOEmissionClass c) const {131return myCEPs.find(c)->second->getVehicleMass();132}133134135double136HelpersPHEMlight5::getEmission(PHEMlightdllV5::CEP* currCep, const std::string& e, const double p, const double v, const double drivingPower, const double ratedPower) const {137return currCep->GetEmission(e, p, v, &myHelper, drivingPower, ratedPower);138}139140141double142HelpersPHEMlight5::calcPower(PHEMlightdllV5::CEP* currCep, const double v, const double a, const double slope, const EnergyParams* param) const {143// copy of CEP::CalcPower144const double power = calcWheelPower(currCep, v, a, slope, param) / PHEMlightdllV5::Constants::_DRIVE_TRAIN_EFFICIENCY;145if (!(currCep->getCalcType() == "HEV" || currCep->getCalcType() == "BEV")) {146return power + param->getDoubleOptional(SUMO_ATTR_CONSTANTPOWERINTAKE, currCep->getAuxPower() * 1000.) / 1000.;147}148return power;149}150151152double153HelpersPHEMlight5::calcWheelPower(PHEMlightdllV5::CEP* currCep, const double v, const double a, const double slope, const EnergyParams* param) const {154// copy of CEP::CalcWheelPower155const double rotFactor = currCep->GetRotationalCoeffecient(v);156const double mass = param->getDoubleOptional(SUMO_ATTR_MASS, currCep->getVehicleMass());157const double massRot = param->getDoubleOptional(SUMO_ATTR_ROTATINGMASS, currCep->getVehicleMassRot());158const double load = param->getDoubleOptional(SUMO_ATTR_LOADING, currCep->getVehicleLoading()) + param->getTransportableMass();159const double cw = param->getDoubleOptional(SUMO_ATTR_FRONTSURFACEAREA, currCep->getCrossSectionalArea()) * param->getDoubleOptional(SUMO_ATTR_AIRDRAGCOEFFICIENT, currCep->getCWValue());160const double rf0 = param->getDoubleOptional(SUMO_ATTR_ROLLDRAGCOEFFICIENT, currCep->getResistanceF0());161162double power = (mass + load) * PHEMlightdllV5::Constants::GRAVITY_CONST * currCep->getResistance(v, rf0) * v;163power += (cw * PHEMlightdllV5::Constants::AIR_DENSITY_CONST / 2) * std::pow(v, 3);164power += (mass * rotFactor + massRot + load) * a * v;165power += (mass + load) * PHEMlightdllV5::Constants::GRAVITY_CONST * slope * 0.01 * v;166return power / 1000.;167}168169170double171HelpersPHEMlight5::getModifiedAccel(const SUMOEmissionClass c, const double v, const double a, const double slope, const EnergyParams* param) const {172PHEMlightdllV5::CEP* currCep = myCEPs.count(c) == 0 ? nullptr : myCEPs.find(c)->second;173if (currCep != nullptr) {174if (v == 0.) {175return 0.;176}177// this is a copy of CEP::GetMaxAccel178const double rotFactor = currCep->GetRotationalCoeffecient(v);179const double mass = param->getDoubleOptional(SUMO_ATTR_MASS, currCep->getVehicleMass());180const double massRot = param->getDoubleOptional(SUMO_ATTR_ROTATINGMASS, currCep->getVehicleMassRot());181const double load = param->getDoubleOptional(SUMO_ATTR_LOADING, currCep->getVehicleLoading()) + param->getTransportableMass();182const double ratedPower = param->getDoubleOptional(SUMO_ATTR_MAXIMUMPOWER, 1000. * currCep->getRatedPower()) / 1000.;183const double pMaxForAcc = currCep->GetPMaxNorm(v) * ratedPower - calcPower(currCep, v, 0, slope, param);184const double maxAcc = (pMaxForAcc * 1000) / ((mass * rotFactor + massRot + load) * v);185return MIN2(a, maxAcc);186}187return a;188}189190191double192HelpersPHEMlight5::getCoastingDecel(const SUMOEmissionClass c, const double v, const double a, const double slope, const EnergyParams* param) const {193PHEMlightdllV5::CEP* const currCep = myCEPs.find(c)->second;194// this is a copy of CEP::GetDecelCoast with an adapted slope force calculation195if (v < PHEMlightdllV5::Constants::SPEED_DCEL_MIN) {196return v / PHEMlightdllV5::Constants::SPEED_DCEL_MIN * getCoastingDecel(c, PHEMlightdllV5::Constants::SPEED_DCEL_MIN, a, slope, param);197}198const double rotFactor = currCep->GetRotationalCoeffecient(v);199const double mass = param->getDoubleOptional(SUMO_ATTR_MASS, currCep->getVehicleMass());200const double load = param->getDoubleOptional(SUMO_ATTR_LOADING, currCep->getVehicleLoading()) + param->getTransportableMass();201const double cw = param->getDoubleOptional(SUMO_ATTR_FRONTSURFACEAREA, currCep->getCrossSectionalArea()) * param->getDoubleOptional(SUMO_ATTR_AIRDRAGCOEFFICIENT, currCep->getCWValue());202const double ratedPower = param->getDoubleOptional(SUMO_ATTR_MAXIMUMPOWER, 1000. * currCep->getRatedPower()) / 1000.;203const double wheelRadius = param->getDoubleOptional(SUMO_ATTR_WHEELRADIUS, currCep->getWheelRadius());204const double rf0 = param->getDoubleOptional(SUMO_ATTR_ROLLDRAGCOEFFICIENT, currCep->getResistanceF0());205206const double fRoll = currCep->getResistance(v, rf0) * (mass + load) * PHEMlightdllV5::Constants::GRAVITY_CONST;207const double fAir = cw * PHEMlightdllV5::Constants::AIR_DENSITY_CONST * 0.5 * std::pow(v, 2);208const double fGrad = (mass + load) * PHEMlightdllV5::Constants::GRAVITY_CONST * sin(DEG2RAD(slope));209210return -(currCep->getFMot(v, ratedPower, wheelRadius) + fRoll + fAir + fGrad) / ((mass + load) * rotFactor);211}212213214double215HelpersPHEMlight5::compute(const SUMOEmissionClass c, const PollutantsInterface::EmissionType e, const double v, const double a, const double slope, const EnergyParams* param) const {216if (param != nullptr && param->isEngineOff()) {217return 0.;218}219const double corrSpeed = MAX2(0.0, v);220assert(myCEPs.count(c) == 1);221PHEMlightdllV5::CEP* const currCep = myCEPs.find(c)->second;222const double corrAcc = getModifiedAccel(c, corrSpeed, a, slope, param);223const bool isBEV = currCep->getFuelType() == PHEMlightdllV5::Constants::strBEV;224const bool isHybrid = currCep->getCalcType() == PHEMlightdllV5::Constants::strHybrid;225const double power_raw = calcPower(currCep, corrSpeed, corrAcc, slope, param);226const double ratedPower = param->getDoubleOptional(SUMO_ATTR_MAXIMUMPOWER, 1000. * currCep->getRatedPower()) / 1000.;227const double power = isHybrid ? calcWheelPower(currCep, corrSpeed, corrAcc, slope, param) : currCep->CalcEngPower(power_raw, ratedPower);228229if (!isBEV && corrAcc < getCoastingDecel(c, corrSpeed, corrAcc, slope, param) &&230corrSpeed > PHEMlightdllV5::Constants::ZERO_SPEED_ACCURACY) {231return 0.;232}233// TODO: this is probably only needed for non-heavy vehicles, so if execution speed becomes an issue this could be optimized out234const double drivingPower = calcPower(currCep, PHEMlightdllV5::Constants::NORMALIZING_SPEED, PHEMlightdllV5::Constants::NORMALIZING_ACCELARATION, 0, param);235switch (e) {236case PollutantsInterface::CO:237return getEmission(currCep, "CO", power, corrSpeed, drivingPower, ratedPower) / SECONDS_PER_HOUR * 1000.;238case PollutantsInterface::CO2:239return currCep->GetCO2Emission(getEmission(currCep, "FC", power, corrSpeed, drivingPower, ratedPower),240getEmission(currCep, "CO", power, corrSpeed, drivingPower, ratedPower),241getEmission(currCep, "HC", power, corrSpeed, drivingPower, ratedPower), &myHelper) / SECONDS_PER_HOUR * 1000.;242case PollutantsInterface::HC:243return getEmission(currCep, "HC", power, corrSpeed, drivingPower, ratedPower) / SECONDS_PER_HOUR * 1000.;244case PollutantsInterface::NO_X:245return getEmission(currCep, "NOx", power, corrSpeed, drivingPower, ratedPower) / SECONDS_PER_HOUR * 1000.;246case PollutantsInterface::PM_X:247return getEmission(currCep, "PM", power, corrSpeed, drivingPower, ratedPower) / SECONDS_PER_HOUR * 1000.;248case PollutantsInterface::FUEL: {249if (myVolumetricFuel && currCep->getFuelType() == PHEMlightdllV5::Constants::strDiesel) { // divide by average diesel density of 836 g/l250return getEmission(currCep, "FC", power, corrSpeed, drivingPower, ratedPower) / 836. / SECONDS_PER_HOUR * 1000.;251}252if (myVolumetricFuel && currCep->getFuelType() == PHEMlightdllV5::Constants::strGasoline) { // divide by average gasoline density of 742 g/l253return getEmission(currCep, "FC", power, corrSpeed, drivingPower, ratedPower) / 742. / SECONDS_PER_HOUR * 1000.;254}255if (isBEV) {256return 0.;257}258return getEmission(currCep, "FC", power, corrSpeed, drivingPower, ratedPower) / SECONDS_PER_HOUR * 1000.; // still in mg even if myVolumetricFuel is set!259}260case PollutantsInterface::ELEC:261if (isBEV) {262const double auxPower = param->getDoubleOptional(SUMO_ATTR_CONSTANTPOWERINTAKE, currCep->getAuxPower() * 1000.) / 1000.;263return (getEmission(currCep, "FC_el", power, corrSpeed, drivingPower, ratedPower) + auxPower) / SECONDS_PER_HOUR * 1000.;264}265return 0;266}267// should never get here268return 0.;269}270271272/****************************************************************************/273274275