/****************************************************************************/1// Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.dev/sumo2// Copyright (C) 2001-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 HelpersEnergy.cpp14/// @author Daniel Krajzewicz15/// @author Jakob Erdmann16/// @author Michael Behrisch17/// @author Mirko Barthauer18/// @date Mon, 10.05.200419///20// Helper methods for HBEFA-based emission computation21/****************************************************************************/22#include <config.h>2324#include <complex> // due to sqrt of complex25#include <utils/common/MsgHandler.h> // due to WRITE_WARNING26#include <utils/common/SUMOTime.h>27#include <utils/common/ToString.h>28#include <utils/common/PolySolver.h>29#include "HelpersEnergy.h"303132// ===========================================================================33// method definitions34// ===========================================================================35HelpersEnergy::HelpersEnergy():36PollutantsInterface::Helper("Energy", ENERGY_BASE, ENERGY_BASE)37{ }383940double41HelpersEnergy::compute(const SUMOEmissionClass /* c */, const PollutantsInterface::EmissionType e, const double v, const double a, const double slope, const EnergyParams* param) const {42if (e != PollutantsInterface::ELEC) {43return 0.;44}45if (param == nullptr) {46param = EnergyParams::getDefault();47}48if (param->isOff()) {49return 0.;50}51//@ToDo: All formulas below work with the logic of the euler update (refs #860).52// Approximation order could be improved. Refs. #2592.5354const double lastV = v - ACCEL2SPEED(a);55const double mass = param->getTotalMass(myDefaultMass, 0.);5657// calculate power needed for potential energy difference58double power = mass * GRAVITY * sin(DEG2RAD(slope)) * v;5960// power needed for kinetic energy difference of vehicle61power += 0.5 * mass * (v * v - lastV * lastV) / TS;6263// add power needed for rotational energy diff of internal rotating elements64power += 0.5 * param->getDoubleOptional(SUMO_ATTR_ROTATINGMASS, myDefaultRotatingMass) * (v * v - lastV * lastV) / TS;6566// power needed for Energy loss through Air resistance [Ws]67// Calculate energy losses:68// EnergyLoss,Air = 1/2 * rho_air [kg/m^3] * myFrontSurfaceArea [m^2] * myAirDragCoefficient [-] * v_Veh^2 [m/s] * s [m]69// ... with rho_air [kg/m^3] = 1,2041 kg/m^3 (at T = 20C)70// ... with s [m] = v_Veh [m/s] * TS [s]71// divided by TS to get power instead of energy72power += 0.5 * 1.2041 * param->getDoubleOptional(SUMO_ATTR_FRONTSURFACEAREA, myDefaultFrontSurfaceArea) * param->getDoubleOptional(SUMO_ATTR_AIRDRAGCOEFFICIENT, myDefaultAirDragCoefficient) * v * v * v;7374// power needed for Energy loss through Roll resistance [Ws]75// ... (fabs(veh.getSpeed())>=0.01) = 0, if vehicle isn't moving76// EnergyLoss,Tire = c_R [-] * F_N [N] * s [m]77// ... with c_R = ~0.012 (car tire on asphalt)78// ... with F_N [N] = myMass [kg] * g [m/s^2]79// divided by TS to get power instead of energy80power += param->getDoubleOptional(SUMO_ATTR_ROLLDRAGCOEFFICIENT, myDefaultRollDragCoefficient) * GRAVITY * mass * v;8182// Energy loss through friction by radial force [Ws]83// If angle of vehicle was changed84const double angleDiff = param->getAngleDiff();85if (angleDiff != 0.) {86// Compute new radio87double radius = SPEED2DIST(v) / fabs(angleDiff);8889// Check if radius is in the interval [0.0001 - 10000] (To avoid overflow and division by zero)90if (radius < 0.0001) {91radius = 0.0001;92} else if (radius > 10000) {93radius = 10000;94}95// EnergyLoss,internalFrictionRadialForce = c [m] * F_rad [N];96// Energy loss through friction by radial force [Ws]97// divided by TS to get power instead of energy98power += param->getDoubleOptional(SUMO_ATTR_RADIALDRAGCOEFFICIENT, myDefaultRadialDragCoefficient) * mass * v * v / radius / TS;99}100101// E_Bat = E_kin_pot + EnergyLoss;102if (power > 0) {103// Assumption: Efficiency of myPropulsionEfficiency when accelerating104power /= param->getDoubleOptional(SUMO_ATTR_PROPULSIONEFFICIENCY, myDefaultPropulsionEfficiency);105} else {106// Assumption: Efficiency of myRecuperationEfficiency when recuperating107power *= param->getDoubleOptional(SUMO_ATTR_RECUPERATIONEFFICIENCY, myDefaultRecuperationEfficiency);108if (a != 0) {109// Fiori, Chiara & Ahn, Kyoungho & Rakha, Hesham. (2016).110// Power-based electric vehicle energy consumption model: Model111// development and validation. Applied Energy. 168. 257-268.112// 10.1016/j.apenergy.2016.01.097.113//114// Insaf Sagaama, Amine Kchiche, Wassim Trojet, Farouk Kamoun115// Improving The Accuracy of The Energy Consumption Model for116// Electric Vehicle in SUMO Considering The Ambient Temperature117// Effects118power *= (1 / exp(param->getDoubleOptional(SUMO_ATTR_RECUPERATIONEFFICIENCY_BY_DECELERATION, myDefaultRecuperationEfficiencyByDeceleration) / fabs(a)));119}120}121122// EnergyLoss,constantConsumers123// Energy loss through constant loads (e.g. A/C) [W]124power += param->getDoubleOptional(SUMO_ATTR_CONSTANTPOWERINTAKE, myDefaultConstantPowerIntake);125126// convert from [W], to [Wh/s] (3600s / 1h):127return power / 3600.;128}129130131double132HelpersEnergy::acceleration(const SUMOEmissionClass /* c */, const PollutantsInterface::EmissionType e, const double v, const double P, const double slope, const EnergyParams* param) const {133if (e != PollutantsInterface::ELEC) {134return 0.;135}136137if (param == nullptr) {138param = EnergyParams::getDefault();139}140141// Inverse formula for the function compute()142143// Computes the achievable acceleration using the given speed and drive power in [Wh/s]144// It does not consider friction losses by radial force and the acceleration-dependent145// recuperation efficiency (eta_recuperation is considered constant)146//147// The power `Prest` used for acceleration is given by a cubic polynomial in acceleration,148// i.e.the equation149//150// Prest = const1*a + const2*a^2 + const3*a^3151//152// and we need to find `a` given `Prest`.153//154// The solutions of `a(P)` is stored in variables `x1`, `x2`, and `x3` returned by155// the method `PolySolver::cubicSolve()`, see below.156//157// Power used for accelerating, `Prest`, is the total used power minus power wasted by running resistances.158159const double mass = param->getTotalMass(myDefaultMass, 0.);160const double rotMass = param->getDoubleOptional(SUMO_ATTR_ROTATINGMASS, myDefaultRotatingMass);161double const1, const2, const3;162double Prest;163int numX;164double x1, x2, x3;165166// Power delivered by the drive167if (P > 0) {168// Assumption: Efficiency of myPropulsionEfficiency when accelerating169Prest = P * 3600 * param->getDoubleOptional(SUMO_ATTR_PROPULSIONEFFICIENCY, myDefaultPropulsionEfficiency);170} else {171// Assumption: Efficiency of myRecuperationEfficiency when recuperating172Prest = P * 3600 / param->getDoubleOptional(SUMO_ATTR_RECUPERATIONEFFICIENCY, myDefaultRecuperationEfficiency);173}174175// calculate power drop due to a potential energy difference176// in inverse original formula: power = mass * GRAVITY * sin(DEG2RAD(slope)) * v;177// i.e. in terms of 'lastV' and 'a': power = mass * GRAVITY * sin(DEG2RAD(slope)) * (lastV + TS * a);178Prest -= mass * GRAVITY * sin(DEG2RAD(slope)) * (v);179const1 = mass * GRAVITY * sin(DEG2RAD(slope)) * (TS);180181// update coefficients of a(P) equation considering power loss through Roll resistance182// in inverse original formula: power += param->getDoubleOptional(SUMO_ATTR_ROLLDRAGCOEFFICIENT) * GRAVITY * mass * v;183// i.e. in terms of 'lastV' and 'a': power += param->getDoubleOptional(SUMO_ATTR_ROLLDRAGCOEFFICIENT) * GRAVITY * mass * (lastV + TS * a);184Prest -= param->getDoubleOptional(SUMO_ATTR_ROLLDRAGCOEFFICIENT, myDefaultRollDragCoefficient) * GRAVITY * mass * v;185const1 += param->getDoubleOptional(SUMO_ATTR_ROLLDRAGCOEFFICIENT, myDefaultRollDragCoefficient) * GRAVITY * mass * (TS);186187//Constant loads are omitted. We assume P as the max limit for the main traction drive. Constant loads are often covered by an auxiliary drive188//Power loss through constant loads (e.g. A/C) [W]189//Prest -= param->getDoubleOptional(SUMO_ATTR_CONSTANTPOWERINTAKE) / TS;190191// update coefficients of a(P) equation considering kinetic energy difference of vehicle192// in inverse original formula: power += 0.5 * mass * (v * v - lastV * lastV) / TS;193// i.e. in terms of 'lastV' and 'a': power += 0.5 * mass * (2 * lastV * a + TS * a * a);194const1 += 0.5 * mass * (2 * v);195const2 = 0.5 * mass * (TS);196197// update coefficients of a(P) equation considering rotational energy diff of internal rotating elements198// in inverse original formula: power += 0.5 * rotMass * (v * v - lastV * lastV) / TS;199// i.e. in terms of 'lastV' and 'a': power += 0.5 * rotMass * (2 * lastV * a + TS * a * a);200const1 += 0.5 * rotMass * (2 * v);201const2 += 0.5 * rotMass * (TS);202203// update coefficients of a(P) equation considering energy loss through Air resistance204// in inverse original formula: power += 0.5 * 1.2041 * param->getDoubleOptional(SUMO_ATTR_FRONTSURFACEAREA) * param->getDoubleOptional(SUMO_ATTR_AIRDRAGCOEFFICIENT) * v * v * v;205// i.e. in terms of 'lastV' and 'a': power += 0.5 * rotMass * (lastV^3 + 3* lastV^2 * TS * a + 3* lastV * TS^2 *a^2 + TS^3 * a^3);206const double drag = 0.5 * 1.2041 * param->getDoubleOptional(SUMO_ATTR_FRONTSURFACEAREA, myDefaultFrontSurfaceArea) * param->getDoubleOptional(SUMO_ATTR_AIRDRAGCOEFFICIENT, myDefaultAirDragCoefficient);207Prest -= drag * (v * v * v);208const1 += drag * (3 * v * v * TS);209const2 += drag * (3 * v * TS * TS);210const3 = drag * (TS * TS * TS);211212// Prest = const1*a + const2*a^2 + const3*a^3213// Solve cubic equation in `a` for real roots, and return the number of roots in `numX`214// and the roots in `x1`, `x2`, and `x3`.215std::tie(numX, x1, x2, x3) = PolySolver::cubicSolve(const3, const2, const1, -Prest);216217218switch (numX) {219case 1:220return x1;221case 2:222return MAX2(x1, x2);223case 3:224return MAX3(x1, x2, x3);225default:226WRITE_ERROR(TL("An acceleration given by the power was not found."));227return 0;228}229230}231232233/****************************************************************************/234235236