Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[do not merge yet] add energy model classes to master #992

Open
wants to merge 15 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions flow/core/kernel/vehicle/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,22 @@ def get_fuel_consumption(self, veh_id, error=-1001):
"""
pass

@abstractmethod
def get_energy_model(self, veh_id, error=""):
"""Return the energy model class object of the specified vehicle.

Parameters
----------
veh_id : str or list of str
vehicle id, or list of vehicle ids
error : str
value that is returned if the vehicle is not found
Returns
-------
subclass of BaseEnergyModel
"""
pass

@abstractmethod
def get_speed(self, veh_id, error=-1001):
"""Return the speed of the specified vehicle.
Expand Down
14 changes: 14 additions & 0 deletions flow/core/kernel/vehicle/traci.py
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,10 @@ def _add_departed(self, veh_id, veh_type):
# specify the type
self.__vehicles[veh_id]["type"] = veh_type

# specify energy model
self.__vehicles[veh_id]["energy_model"] = self.type_parameters[
veh_type]["energy_model"]()

car_following_params = \
self.type_parameters[veh_type]["car_following_params"]

Expand Down Expand Up @@ -547,6 +551,16 @@ def get_fuel_consumption(self, veh_id, error=-1001):
return [self.get_fuel_consumption(vehID, error) for vehID in veh_id]
return self.__sumo_obs.get(veh_id, {}).get(tc.VAR_FUELCONSUMPTION, error) * ml_to_gallons

def get_energy_model(self, veh_id, error=""):
"""See parent class."""
if isinstance(veh_id, (list, np.ndarray)):
return [self.get_energy_model(vehID) for vehID in veh_id]
try:
return self.__vehicles.get(veh_id, {'energy_model': error})['energy_model']
except KeyError:
print("Energy model not specified for vehicle {}".format(veh_id))
raise

def get_previous_speed(self, veh_id, error=-1001):
"""See parent class."""
if isinstance(veh_id, (list, np.ndarray)):
Expand Down
19 changes: 17 additions & 2 deletions flow/core/params.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
from flow.controllers.car_following_models import SimCarFollowingController
from flow.controllers.rlcontroller import RLController
from flow.controllers.lane_change_controllers import SimLaneChangeController
from flow.energy_models.power_demand import PDMCombustionEngine
from flow.energy_models.power_demand import PDMElectric


SPEED_MODES = {
Expand Down Expand Up @@ -39,6 +41,9 @@
"only_right_drive_safe": 576
}

ENERGY_MODELS = set([PDMCombustionEngine, PDMElectric])
DEFAULT_ENERGY_MODEL = PDMCombustionEngine

# Traffic light defaults
PROGRAM_ID = 1
MAX_GAP = 3.0
Expand Down Expand Up @@ -262,6 +267,7 @@ def add(self,
num_vehicles=0,
car_following_params=None,
lane_change_params=None,
energy_model=DEFAULT_ENERGY_MODEL,
color=None):
"""Add a sequence of vehicles to the list of vehicles in the network.

Expand Down Expand Up @@ -298,6 +304,12 @@ def add(self,
# FIXME: depends on simulator
lane_change_params = SumoLaneChangeParams()

if energy_model not in ENERGY_MODELS:
print('{} for vehicle {} is not a valid energy model. Defaulting to {}\n'.format(energy_model,
veh_id,
DEFAULT_ENERGY_MODEL))
energy_model = DEFAULT_ENERGY_MODEL

type_params = {}
type_params.update(car_following_params.controller_params)
type_params.update(lane_change_params.controller_params)
Expand All @@ -311,7 +323,8 @@ def add(self,
"routing_controller": routing_controller,
"initial_speed": initial_speed,
"car_following_params": car_following_params,
"lane_change_params": lane_change_params}
"lane_change_params": lane_change_params,
"energy_model": energy_model}

if color:
type_params['color'] = color
Expand All @@ -334,7 +347,9 @@ def add(self,
"car_following_params":
car_following_params,
"lane_change_params":
lane_change_params
lane_change_params,
"energy_model":
energy_model
})

# This is used to return the actual headways from the vehicles class.
Expand Down
130 changes: 46 additions & 84 deletions flow/core/rewards.py
Original file line number Diff line number Diff line change
Expand Up @@ -307,58 +307,26 @@ def punish_rl_lane_changes(env, penalty=1):


def energy_consumption(env, gain=.001):
"""Calculate power consumption of a vehicle.
"""Calculate power consumption for all vehicle.

Assumes vehicle is an average sized vehicle.
The power calculated here is the lower bound of the actual power consumed
by a vehicle.
"""
power = 0

M = 1200 # mass of average sized vehicle (kg)
g = 9.81 # gravitational acceleration (m/s^2)
Cr = 0.005 # rolling resistance coefficient
Ca = 0.3 # aerodynamic drag coefficient
rho = 1.225 # air density (kg/m^3)
A = 2.6 # vehicle cross sectional area (m^2)
for veh_id in env.k.vehicle.get_ids():
speed = env.k.vehicle.get_speed(veh_id)
prev_speed = env.k.vehicle.get_previous_speed(veh_id)

accel = abs(speed - prev_speed) / env.sim_step

power += M * speed * accel + M * g * Cr * speed + 0.5 * rho * A * Ca * speed ** 3

return -gain * power


def veh_energy_consumption(env, veh_id, gain=.001):
"""Calculate power consumption of a vehicle.

Assumes vehicle is an average sized vehicle.
The power calculated here is the lower bound of the actual power consumed
by a vehicle.
Parameters
----------
env : flow.envs.Env
the environment variable, which contains information on the current
state of the system.
gain : float
scaling factor for the reward
"""
power = 0

M = 1200 # mass of average sized vehicle (kg)
g = 9.81 # gravitational acceleration (m/s^2)
Cr = 0.005 # rolling resistance coefficient
Ca = 0.3 # aerodynamic drag coefficient
rho = 1.225 # air density (kg/m^3)
A = 2.6 # vehicle cross sectional area (m^2)
speed = env.k.vehicle.get_speed(veh_id)
prev_speed = env.k.vehicle.get_previous_speed(veh_id)

accel = abs(speed - prev_speed) / env.sim_step

power += M * speed * accel + M * g * Cr * speed + 0.5 * rho * A * Ca * speed ** 3

return -gain * power
veh_ids = env.k.vehicle.get_ids()
return veh_energy_consumption(env, veh_ids, gain)


def miles_per_megajoule(env, veh_ids=None, gain=.001):
"""Calculate miles per mega-joule of either a particular vehicle or the total average of all the vehicles.
def veh_energy_consumption(env, veh_ids=None, gain=.001):
"""Calculate power consumption of a vehicle.

Assumes vehicle is an average sized vehicle.
The power calculated here is the lower bound of the actual power consumed
Expand All @@ -369,70 +337,64 @@ def miles_per_megajoule(env, veh_ids=None, gain=.001):
env : flow.envs.Env
the environment variable, which contains information on the current
state of the system.
veh_ids : [list]
list of veh_ids to compute the reward over
veh_ids : [list] or str
list of veh_ids or single veh_id to compute the reward over
gain : float
scaling factor for the reward
"""
mpj = 0
counter = 0
if veh_ids is None:
veh_ids = env.k.vehicle.get_ids()
elif not isinstance(veh_ids, list):
veh_ids = [veh_ids]
for veh_id in veh_ids:
speed = env.k.vehicle.get_speed(veh_id)
# convert to be positive since the function called is a penalty
power = -veh_energy_consumption(env, veh_id, gain=1.0)
if power > 0 and speed >= 0.0:
counter += 1
# meters / joule is (v * \delta t) / (power * \delta t)
mpj += speed / power
if counter > 0:
mpj /= counter

# convert from meters per joule to miles per joule
mpj /= 1609.0
# convert from miles per joule to miles per megajoule
mpj *= 10**6
power = 0
for veh_id in veh_ids:
if veh_id not in env.k.vehicle.previous_speeds:
continue
energy_model = env.k.vehicle.get_energy_model(veh_id)
if energy_model != "":
speed = env.k.vehicle.get_speed(veh_id)
accel = env.k.vehicle.get_accel(veh_id, noise=False, failsafe=True)
grade = env.k.vehicle.get_road_grade(veh_id)
power += energy_model.get_instantaneous_power(accel, speed, grade)

return mpj * gain
return -gain * power


def miles_per_gallon(env, veh_ids=None, gain=.001):
"""Calculate mpg of either a particular vehicle or the total average of all the vehicles.

Assumes vehicle is an average sized vehicle.
The power calculated here is the lower bound of the actual power consumed
by a vehicle.
def instantaneous_mpg(env, veh_ids=None, gain=.001):
"""Calculate the instantaneous mpg for every simulation step specific to the vehicle type.

Parameters
----------
env : flow.envs.Env
the environment variable, which contains information on the current
state of the system.
veh_ids : [list]
list of veh_ids to compute the reward over
veh_ids : [list] or str
list of veh_ids or single veh_id to compute the reward over
gain : float
scaling factor for the reward
"""
mpg = 0
counter = 0
if veh_ids is None:
veh_ids = env.k.vehicle.get_ids()
elif not isinstance(veh_ids, list):
veh_ids = [veh_ids]

cumulative_gallons = 0
cumulative_distance = 0
for veh_id in veh_ids:
speed = env.k.vehicle.get_speed(veh_id)
gallons_per_s = env.k.vehicle.get_fuel_consumption(veh_id)
if gallons_per_s > 0 and speed >= 0.0:
counter += 1
# meters / gallon is (v * \delta t) / (gallons_per_s * \delta t)
mpg += speed / gallons_per_s
if counter > 0:
mpg /= counter

# convert from meters per gallon to miles per gallon
mpg /= 1609.0
energy_model = env.k.vehicle.get_energy_model(veh_id)
if energy_model != "":
speed = env.k.vehicle.get_speed(veh_id)
accel = env.k.vehicle.get_accel_no_noise_with_failsafe(veh_id)
grade = env.k.vehicle.get_road_grade(veh_id)
gallons_per_hr = energy_model.get_instantaneous_fuel_consumption(accel, speed, grade)
if gallons_per_hr > 0 and speed >= 0.0:
cumulative_gallons += gallons_per_hr
cumulative_distance += speed

cumulative_gallons /= 3600.0
cumulative_distance /= 1609.34
# miles / gallon is (distance_dot * \delta t) / (gallons_dot * \delta t)
mpg = cumulative_distance / cumulative_gallons

return mpg * gain
57 changes: 57 additions & 0 deletions flow/energy_models/base_energy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
"""Script containing the base vehicle energy class."""
from abc import ABCMeta, abstractmethod


class BaseEnergyModel(metaclass=ABCMeta):
"""Base energy model class.

Calculate the instantaneous power consumption of a vehicle in
the network. It returns the power in Watts regardless of the
vehicle type: whether EV or Combustion Engine, Toyota Prius or Tacoma
or non-Toyota vehicles. Non-Toyota vehicles are set by default
to be an averaged-size vehicle.
"""

def __init__(self):
# 15 kilowatts = 1 gallon/hour conversion factor
self.conversion = 15e3

@abstractmethod
def get_instantaneous_power(self, accel, speed, grade):
"""Calculate the instantaneous power consumption of a vehicle.

Must be implemented by child classes.

Parameters
----------
accel : float
Instantaneous acceleration of the vehicle
speed : float
Instantaneous speed of the vehicle
grade : float
Instantaneous road grade of the vehicle
Returns
-------
float
"""
pass

def get_instantaneous_fuel_consumption(self, accel, speed, grade):
"""Calculate the instantaneous fuel consumption of a vehicle.

Fuel consumption is reported in gallons per hour, with the conversion
rate of 15kW = 1 gallon/hour.

Parameters
----------
accel : float
Instantaneous acceleration of the vehicle
speed : float
Instantaneous speed of the vehicle
grade : float
Instantaneous road grade of the vehicle
Returns
-------
float
"""
return self.get_instantaneous_power(accel, speed, grade) / self.conversion
Loading