Skip to content

Commit

Permalink
Merge branch 'dev' into feature/recombination_same_depot
Browse files Browse the repository at this point in the history
  • Loading branch information
j-brendel committed May 6, 2024
2 parents 8742319 + 5d3fd4a commit fcd5645
Show file tree
Hide file tree
Showing 5 changed files with 107 additions and 11 deletions.
2 changes: 2 additions & 0 deletions data/examples/simba.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -141,3 +141,5 @@ signal_time_dif = 10
# Show estimated time to finish simulation after each step. Not recommended for fast computations
# (default: false)
eta = false
# Save time by skipping often not needed flex_report in SpiceEV
skip_flex_report = true
4 changes: 4 additions & 0 deletions docs/source/simulation_parameters.rst
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,10 @@ The example (data/simba.cfg) contains parameter descriptions which are explained
- false
- Boolean
- Show estimated time to finish simulation after each step. Not recommended for fast computations
* - skip_flex_report
- false
- Boolean
- Skip generation of flex_report in SpiceEV. Activating can save time as this feature is rarely used


Schedule
Expand Down
88 changes: 83 additions & 5 deletions simba/costs.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ def calculate_costs(c_params, scenario, schedule, args):
# Get electricity costs from SpiceEV
cost_object.set_electricity_costs()

# Calculate total annual costs
cost_object.set_total_annual_costs()

# Calculate the costs per kilometer
cost_object.set_invest_per_km()

# Cumulate the costs of all gcs
cost_object.cumulate()

Expand All @@ -65,6 +71,8 @@ class Costs:
GARAGE = "garage"
NOT_ELECTRIFIED = "Non_electrified_station"

DAYS_PER_YEAR = 365.2422

def __init__(self, schedule: simba.schedule.Schedule, scenario: spice_ev.scenario.Scenario,
args, c_params: dict):
""" Initialize the Costs instance.
Expand Down Expand Up @@ -108,9 +116,11 @@ def info(self):
f"Investment cost: {cumulated['c_invest']} €. \n"
f"Annual investment costs: {cumulated['c_invest_annual']} €/a. \n"
f"Annual maintenance costs: {cumulated['c_maint_annual']} €/a. \n"
f"Annual costs for electricity: {cumulated['c_el_annual']} €/a.\n")
f"Annual costs for electricity: {cumulated['c_el_annual']} €/a.\n"
f"Costs per km: {cumulated['c_total_per_km']} €/km. \n"
f"Total kilometers per year: {self.get_total_annual_km()} km. \n")

def get_annual_or_not(self, key):
def get_unit(self, key):
""" Get the unit for annual or non-annual costs.
:param key: Key to get the unit for
Expand All @@ -120,6 +130,8 @@ def get_annual_or_not(self, key):
"""
if "annual" in key:
return "€/year"
elif "per_km" in key:
return "€/km"
else:
return "€"

Expand Down Expand Up @@ -171,7 +183,13 @@ def get_gc_cost_variables(self):
# annual electricity costs
"c_el_procurement_annual", "c_el_power_price_annual",
"c_el_energy_price_annual",
"c_el_taxes_annual", "c_el_feed_in_remuneration_annual", "c_el_annual"]
"c_el_taxes_annual", "c_el_feed_in_remuneration_annual", "c_el_annual",
# total annual costs
"c_total_annual",
# costs per kilometer
"c_vehicles_per_km", "c_invest_per_km", "c_maint_vehicles_per_km",
"c_maint_infrastructure_per_km",
"c_maint_per_km", "c_el_per_km", "c_total_per_km"]

def get_vehicle_types(self):
""" Get the types of vehicles used.
Expand Down Expand Up @@ -230,7 +248,6 @@ def set_charging_infrastructure_costs(self):

# calculate annual cost of charging stations, depending on their lifetime
self.costs_per_gc[gcID]["c_cs_annual"] = c_cs / self.params["cs"]["lifetime_cs"]

self.costs_per_gc[gcID]["c_maint_cs_annual"] = c_cs * self.params["cs"][
"c_maint_cs_per_year"]

Expand Down Expand Up @@ -492,6 +509,46 @@ def set_vehicle_costs_per_gc(self):

return self

def set_total_annual_costs(self):
""" Calculate the total annual investment.
:return: self
:rtype: Costs
"""
attributes = [
"c_invest_annual", "c_maint_annual", "c_el_annual"
]
for gcID in self.gcs:
for key in attributes:
self.costs_per_gc[gcID]["c_total_annual"] += self.costs_per_gc[gcID][key]
for key in attributes:
self.costs_per_gc[self.GARAGE]["c_total_annual"] += self.costs_per_gc[self.GARAGE][key]
return self

def set_invest_per_km(self):
""" Calculate the annual investment costs per total driven distance.
:return: self
:rtype: Costs
"""
total_annual_km = self.get_total_annual_km()
if total_annual_km:
attributes = [
"c_vehicles", "c_invest", "c_maint_vehicles", "c_maint_infrastructure",
"c_maint", "c_el", "c_total"
]
for gcID in self.gcs:
for key in attributes:
self.costs_per_gc[gcID][f"{key}_per_km"] = (
self.costs_per_gc[gcID][f"{key}_annual"] / total_annual_km)
self.costs_per_gc[self.GARAGE]["c_invest_per_km"] = (
self.costs_per_gc[self.GARAGE]["c_invest_annual"] /
total_annual_km)
self.costs_per_gc[self.GARAGE]["c_total_per_km"] = (
self.costs_per_gc[self.GARAGE]["c_total_annual"] /
total_annual_km)
return self

def cumulate(self):
""" Cumulate the costs of vehicles and infrastructure.
Expand Down Expand Up @@ -531,8 +588,29 @@ def cumulate(self):
+ self.costs_per_gc[self.GARAGE]["c_invest_annual"]
)

total_annual_km = self.get_total_annual_km()
if total_annual_km:
self.costs_per_gc[self.CUMULATED]["c_invest_per_km"] = (
self.costs_per_gc[self.CUMULATED]["c_invest_annual"] /
total_annual_km
)

return self

def get_total_annual_km(self):
""" Calculate total annual kilometers driven, linearly extrapolated from schedule distance.
:return: Total annual kilometers driven
:rtype: float
"""
# calculate yearly driven kilometers
total_km = self.schedule.get_total_distance() / 1000
# use full days to caclulate yearly km, since schedules can overlap with ones from next day
simulated_days = max(
round(self.scenario.n_intervals / self.scenario.stepsPerHour / 24, 0),
1)
return self.DAYS_PER_YEAR / simulated_days * total_km

def to_csv_lists(self):
""" Convert costs to a list of lists easily convertible to a CSV.
Expand All @@ -553,7 +631,7 @@ def to_csv_lists(self):
cost_parameters = self.get_gc_cost_variables()
for key in cost_parameters:
# The first two columns contain the parameter and unit
row = [key, self.get_annual_or_not(key)]
row = [key, self.get_unit(key)]
for col in self.get_columns():
# Get the cost at this gc. Stations which have no gc get 0
num = self.costs_per_gc.get(col, {}).get(key, 0)
Expand Down
11 changes: 11 additions & 0 deletions simba/schedule.py
Original file line number Diff line number Diff line change
Expand Up @@ -581,6 +581,17 @@ def get_negative_rotations(self, scenario):

return list(negative_rotations)

def get_total_distance(self):
""" Calculate the total distance of all trips in the schedule.
:return: total distance of schedule
:rtype: float
"""
total_distance = 0
for rotation in self.rotations.values():
total_distance += rotation.distance
return total_distance

def rotation_filter(self, args, rf_list=[]):
""" Edits rotations according to args.rotation_filter_variable.
Expand Down
13 changes: 7 additions & 6 deletions simba/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,8 @@ def get_args():
level of loading in case they are not in trips.csv")
parser.add_argument('--cost-parameters-file', default=None,
help='include cost parameters json, needed if cost_calculation==True')
parser.add_argument('--rotation-filter', default=None,
help='Use json data with rotation ids')

# #### Modes #####
mode_choices = [
Expand Down Expand Up @@ -303,6 +305,9 @@ def get_args():
parser.add_argument('--propagate-mode-errors', default=False,
help='Re-raise errors instead of continuing during simulation modes')
parser.add_argument('--create-scenario-file', help='Write scenario.json to file')
parser.add_argument('--rotation-filter-variable', default=None,
choices=[None, 'include', 'exclude'],
help='set mode for filtering schedule rotations')

# #### Charging strategy #####
parser.add_argument('--preferred-charging-type', '-pct', default='depb',
Expand Down Expand Up @@ -360,7 +365,6 @@ def get_args():
help='Default assumed mean speed for busses in km/h')
parser.add_argument('--default-depot-distance', type=float, default=5,
help='Default assumed average distance from any station to a depot in km')

# #### Simulation Parameters #####
parser.add_argument('--days', metavar='N', type=int, default=None,
help='set duration of scenario as number of days')
Expand All @@ -372,11 +376,8 @@ def get_args():
parser.add_argument('--eta', action='store_true',
help='Show estimated time to finish simulation after each step, '
'instead of progress bar. Not recommended for fast computations.')
parser.add_argument('--rotation-filter', default=None,
help='Use json data with rotation ids')
parser.add_argument('--rotation-filter-variable', default=None,
choices=[None, 'include', 'exclude'],
help='set mode for filtering schedule rotations')
parser.add_argument('--skip-flex-report', action='store_true',
help='Skip flex band creation when generating reports.')

# #### LOGGING PARAMETERS #### #
parser.add_argument('--loglevel', default='INFO', type=str.upper,
Expand Down

0 comments on commit fcd5645

Please sign in to comment.