Skip to content

Commit

Permalink
Fixing stuff
Browse files Browse the repository at this point in the history
  • Loading branch information
romainsacchi committed Apr 24, 2023
1 parent 2bad203 commit 0337b05
Show file tree
Hide file tree
Showing 9 changed files with 1,440 additions and 1,329 deletions.
3 changes: 0 additions & 3 deletions README.md
Expand Up @@ -20,9 +20,6 @@ Based on the Life Cycle Assessment tool for passenger vehicles [carculator](http

See [the documentation](https://carculator_bus.readthedocs.io/en/latest/index.html) for more detail, validation, etc.

The model has been the subject of a submission to the journal <i>Environmental Science and Technology</i>.
You may find a preprint version <a href="https://www.psi.ch/en/ta/preprint" target="_blank">here<a/>.


## How to install?

Expand Down
6 changes: 4 additions & 2 deletions carculator_bus/__init__.py
Expand Up @@ -14,6 +14,8 @@
"fill_xarray_from_input_parameters",
"BusModel",
"InventoryBus",
"get_driving_cycle",
"get_road_gradient"
)

# library version
Expand All @@ -23,8 +25,8 @@

DATA_DIR = Path(__file__).resolve().parent / "data"

from carculator_utils.array import fill_xarray_from_input_parameters

from .bus_input_parameters import BusInputParameters
from .inventory import InventoryBus
from .model import BusModel
from .driving_cycles import get_driving_cycle, get_road_gradient
from carculator_utils.array import fill_xarray_from_input_parameters
18 changes: 18 additions & 0 deletions carculator_bus/driving_cycles.py
@@ -0,0 +1,18 @@
from carculator_utils import get_standard_driving_cycle_and_gradient
import numpy as np


def get_driving_cycle(size: list) -> np.ndarray:
return get_standard_driving_cycle_and_gradient(
vehicle_type="bus",
vehicle_sizes=size,
name="bus",
)[0]


def get_road_gradient(size: list) -> np.ndarray:
return get_standard_driving_cycle_and_gradient(
vehicle_type="bus",
vehicle_sizes=size,
name="bus",
)[1]
7 changes: 4 additions & 3 deletions carculator_bus/inventory.py
Expand Up @@ -350,6 +350,7 @@ def fill_in_A_matrix(self):
["BEV-depot", "PHEV-d"],
)


self.A[
np.ix_(
np.arange(self.iterations),
Expand All @@ -360,7 +361,7 @@ def fill_in_A_matrix(self):
)
] = -1 / (
self.array[self.array_inputs["kilometers per year"], :, index] * 2 * 24
)
).values[:, None]

# Opportunity charging BEV buses
# The charging station has a lifetime of 24 years
Expand All @@ -381,7 +382,7 @@ def fill_in_A_matrix(self):
)
] = -1 / (
self.array[self.array_inputs["kilometers per year"], :, index] * 10 * 24
)
).values[:, None]

# In-motion charging BEV buses
# The overhead lines have a lifetime of 40 years
Expand All @@ -400,6 +401,6 @@ def fill_in_A_matrix(self):
)
] = -1 / (
self.array[self.array_inputs["lifetime kilometers"], :, index] * 60 * 40
)
).values[:, None]

print("*********************************************************************")
204 changes: 108 additions & 96 deletions carculator_bus/model.py
Expand Up @@ -78,11 +78,13 @@ def set_all(self):

self.ecm = EnergyConsumptionModel(
vehicle_type="bus",
vehicle_size=self.array.coords["size"].values.tolist(),
vehicle_size=list(self.array.coords["size"].values),
cycle=self.cycle,
gradient=self.gradient,
country=self.country,
powertrains=self.array.coords["powertrain"].values.tolist(),
powertrains=list(self.array.coords["powertrain"].values),
ambient_temperature=self.ambient_temperature,
indoor_temperature=self.indoor_temperature,
)
self.set_trips_properties()

Expand Down Expand Up @@ -146,6 +148,7 @@ def set_all(self):
self.set_particulates_emission()
# defines noise emissions
self.set_noise_emissions()
self.remove_energy_consumption_from_unavailable_vehicles()
# self.check_compliance_of_buses()

print("")
Expand Down Expand Up @@ -391,50 +394,9 @@ def check_compliance_of_buses(self):

if len(l_size) > 0:
self.array.loc[
dict(
powertrain="BEV-motion",
parameter="is_available",
size=l_size,
)
dict(powertrain="BEV-motion", parameter="is_available", size=l_size,)
] = 0

# if the mass allowance left
# if not enough to welcome the average passenger number +50%
# then the bus is too heavy as undersized for peak times
self["is_too_heavy"] = np.where(
np.floor(
(self["gross mass"] - self["curb mass"])
/ self["average passenger mass"]
)
> self["average passengers"] * 1.5,
0,
1,
)

# check that batteries of plugin BEVs
# have enough time to charge

if "BEV-depot" in self.array.powertrain.values:
self.array.loc[
dict(powertrain="BEV-depot", parameter="has_schedule_issue")
] = np.where(
self.array.loc[
dict(powertrain="BEV-depot", parameter="electric energy stored")
]
* 1000
/ self.array.loc[
dict(powertrain="BEV-depot", parameter="plugin charging power")
]
< (
24
- self.array.loc[
dict(powertrain="BEV-depot", parameter="operation time")
]
),
0,
1,
)

def adjust_cost(self):
"""
This method adjusts costs of energy storage over time, to correct for the overly optimistic linear
Expand Down Expand Up @@ -486,13 +448,7 @@ def adjust_cost(self):
]

if len(l_pwt) > 0:
self.array.loc[
:,
l_pwt,
"energy battery cost per kWh",
:,
:,
] = np.reshape(
self.array.loc[:, l_pwt, "energy battery cost per kWh", :, :,] = np.reshape(
(2.75e86 * np.exp(-9.61e-2 * self.array.year.values) + 5.059e1)
* cost_factor,
(1, 1, n_year, n_iterations),
Expand All @@ -506,13 +462,7 @@ def adjust_cost(self):
]

if len(l_pwt) > 0:
self.array.loc[
:,
l_pwt,
"power battery cost per kW",
:,
:,
] = np.reshape(
self.array.loc[:, l_pwt, "power battery cost per kW", :, :,] = np.reshape(
(8.337e40 * np.exp(-4.49e-2 * self.array.year.values) + 11.17)
* cost_factor,
(1, 1, n_year, n_iterations),
Expand All @@ -521,11 +471,7 @@ def adjust_cost(self):
# Correction of combustion powertrain cost for ICEV-g
if "ICEV-g" in self.array.powertrain.values:
self.array.loc[
:,
["ICEV-g"],
"combustion powertrain cost per kW",
:,
:,
:, ["ICEV-g"], "combustion powertrain cost per kW", :, :,
] = np.reshape(
(5.92e160 * np.exp(-0.1819 * self.array.year.values) + 26.76)
* cost_factor,
Expand Down Expand Up @@ -567,22 +513,36 @@ def calculate_ttw_energy(self):
}
)

if self.energy_consumption:
self.override_ttw_energy()

distance = self.energy.sel(parameter="velocity").sum(dim="second") / 1000

# Correction for CNG trucks
if "ICEV-g" in self.array.powertrain.values:
self.energy.loc[
dict(parameter="engine efficiency", powertrain="ICEV-g")
] *= 1 - self.array.sel(
parameter="CNG engine efficiency correction factor",
powertrain="ICEV-g",
)
] *= (
1
- self.array.sel(
parameter="CNG engine efficiency correction factor",
powertrain="ICEV-g",
)
).T.values

self["TtW energy"] = (
self.energy.sel(
parameter=["motive energy", "auxiliary energy", "recuperated energy"]
).sum(dim=["second", "parameter"])
/ distance
parameter=[
"motive energy",
"auxiliary energy",
"cooling energy",
"heating energy",
"battery cooling energy",
"battery heating energy",
"recuperated energy",
]
).sum(dim=["second", "parameter"]).values
/ distance.values
).T

self["engine efficiency"] = (
Expand Down Expand Up @@ -611,7 +571,16 @@ def calculate_ttw_energy(self):
)

self["auxiliary energy"] = (
self.energy.sel(parameter="auxiliary energy").sum(dim="second") / distance
self.energy.sel(
parameter=[
"auxiliary energy",
"cooling energy",
"heating energy",
"battery cooling energy",
"battery heating energy",
]
).sum(dim=["parameter", "second"]).values
/ distance.values
).T

def set_battery_fuel_cell_replacements(self):
Expand Down Expand Up @@ -765,10 +734,7 @@ def set_trips_properties(self):
# (we omitted the stops in the average)
speed = self.ecm.velocity / 1000 * 3600
speed[speed == 0] = np.nan
self["average speed"] = np.nanmean(
speed,
axis=0,
).T
self["average speed"] = np.nanmean(speed, axis=0,).T
self["daily distance"] = self["operation time"] * self["average speed"]

# the number of trips is simply the daily driven distance
Expand Down Expand Up @@ -833,15 +799,7 @@ def set_energy_stored_properties(self):
)

self.array.loc[dict(powertrain="FCEV", parameter="fuel tank mass")] = (
(
-0.1916
* np.power(
14.4,
2,
)
)
+ (14.586 * 14.4)
+ 10.805
(-0.1916 * np.power(14.4, 2,)) + (14.586 * 14.4) + 10.805
) * nb_cylinder

# for older trolley BEV buses,
Expand Down Expand Up @@ -1036,11 +994,14 @@ def set_energy_stored_properties(self):

self.array.loc[
dict(powertrain="FCEV", parameter="electric energy stored")
] = 20 + (
self.array.loc[dict(powertrain="FCEV", parameter="fuel mass")]
* 120
/ 3.6
* 0.06
] = (
20
+ (
self.array.loc[dict(powertrain="FCEV", parameter="fuel mass")]
* 120
/ 3.6
* 0.06
)
)

self["battery cell mass"] = self["electric energy stored"] / _(
Expand Down Expand Up @@ -1430,13 +1391,7 @@ def calculate_cost_impacts(self, sensitivity=False, scope=None):
dims=["size", "powertrain", "cost_type", "year", "value"],
)

response.loc[
:,
:,
list_cost_cat,
:,
:,
] = self.array.sel(
response.loc[:, :, list_cost_cat, :, :,] = self.array.sel(
powertrain=scope["powertrain"],
size=scope["size"],
year=scope["year"],
Expand All @@ -1455,3 +1410,60 @@ def calculate_cost_impacts(self, sensitivity=False, scope=None):
return response * (self.array.sel(parameter="total cargo mass") > 100)
else:
return response / response.sel(value="reference")

def remove_energy_consumption_from_unavailable_vehicles(self):
"""
This method sets the energy consumption of vehicles that are not available to zero.
"""

# we flag BEV powertrains before 2020

pwts = [
pt
for pt in ["BEV-depot", "BEV-opp", "BEV-motion",]
if pt in self.array.coords["powertrain"].values
]

years = [y for y in self.array.year.values if y < 2020]

if years:
self.array.loc[
dict(parameter="TtW energy", powertrain=pwts, year=years,)
] = 0

# and also coach buses with BEV-opp or BEV-motion powertrain
pwts = [
pt
for pt in ["BEV-opp", "BEV-motion",]
if pt in self.array.coords["powertrain"].values
]
sizes = [s for s in self.array.coords["size"].values if "coach" in s.lower()]

if pwts and sizes:
self.array.loc[
dict(parameter="TtW energy", powertrain=pwts, size=sizes,)
] = 0

# remove double-deck BEV-motion buses
if "BEV-motion" in self.array.coords["powertrain"].values:
if "13m-double-city" in self.array.coords["size"].values:
self.array.loc[
dict(
parameter="TtW energy",
powertrain="BEV-motion",
size="13m-double-city",
)
] = 0

# if the mass allowance left
# if not enough to welcome the average passenger number +50%
# then the bus is too heavy as undersized for peak times
self["TtW energy"] = np.where(
np.floor(
(self["gross mass"] - self["curb mass"])
/ self["average passenger mass"]
)
< self["average passengers"] * 1.5,
0,
self["TtW energy"],
)
Binary file removed dev/Assumptions and data.xlsx
Binary file not shown.

0 comments on commit 0337b05

Please sign in to comment.