From dbb082aea973cf709535622504da3ad568467e59 Mon Sep 17 00:00:00 2001 From: mosc5 Date: Mon, 29 Apr 2024 15:06:37 +0200 Subject: [PATCH 01/14] add annual costs per km --- simba/costs.py | 48 +++++++++++++++++++++++++++++++++++++++++++++++ simba/schedule.py | 6 ++++++ 2 files changed, 54 insertions(+) diff --git a/simba/costs.py b/simba/costs.py index b0fd923f..e95dc873 100644 --- a/simba/costs.py +++ b/simba/costs.py @@ -65,6 +65,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. @@ -96,6 +98,7 @@ def __init__(self, schedule: simba.schedule.Schedule, scenario: spice_ev.scenari self.args = args self.params = c_params self.rounding_precision = 2 + self.total_annual_km = self.get_total_annual_km() def info(self): """ Provide information about the total costs. @@ -163,6 +166,9 @@ def get_gc_cost_variables(self): # annual investment costs "c_vehicles_annual", "c_gcs_annual", "c_cs_annual", "c_stat_storage_annual", "c_feed_in_annual", "c_invest_annual", + # annual investment costs per km + "c_vehicles_annual_per_km", "c_gcs_annual_per_km", "c_cs_annual_per_km", + "c_stat_storage_annual_per_km", "c_feed_in_annual_per_km", "c_invest_annual_per_km", # annual maintenance costs "c_maint_vehicles_annual", "c_maint_gc_annual", "c_maint_cs_annual", "c_maint_stat_storage_annual", "c_maint_feed_in_annual", @@ -230,6 +236,9 @@ 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_cs_annual_per_km"] = ( + self.costs_per_gc[gcID]["c_cs_annual"] / + self.total_annual_km) self.costs_per_gc[gcID]["c_maint_cs_annual"] = c_cs * self.params["cs"][ "c_maint_cs_per_year"] @@ -248,6 +257,12 @@ def set_charging_infrastructure_costs(self): + self.costs_per_gc[gcID]["c_gcs_annual"] + self.costs_per_gc[gcID]["c_stat_storage_annual"] + self.costs_per_gc[gcID]["c_feed_in_annual"]) + self.costs_per_gc[gcID]["c_invest_annual_per_km"] = ( + self.costs_per_gc[gcID]["c_vehicles_annual_per_km"] + + self.costs_per_gc[gcID]["c_cs_annual_per_km"] + + self.costs_per_gc[gcID]["c_gcs_annual_per_km"] + + self.costs_per_gc[gcID]["c_stat_storage_annual_per_km"] + + self.costs_per_gc[gcID]["c_feed_in_annual_per_km"]) # MAINTENANCE COSTS # self.costs_per_gc[gcID]["c_maint_infrastructure_annual"] = ( @@ -347,6 +362,9 @@ def set_garage_costs(self): c_garage_cs / self.params["cs"]["lifetime_cs"] + c_garage_workstations / self.params["garage"][ "lifetime_workstations"]) + self.costs_per_gc[self.GARAGE]["c_invest_annual_per_km"] = ( + self.costs_per_gc[self.GARAGE]["c_invest_annual"] / + self.total_annual_km) return self @@ -394,6 +412,9 @@ def set_grid_connection_costs(self): # Store the individual costs of the GC as well self.costs_per_gc[gcID]["c_gcs"] = c_gc + c_transformer self.costs_per_gc[gcID]["c_gcs_annual"] = c_gc_annual + self.costs_per_gc[gcID]["c_gcs_annual_per_km"] = ( + self.costs_per_gc[gcID]["c_gcs_annual"] / + self.total_annual_km) self.costs_per_gc[gcID]["c_maint_gc_annual"] = c_maint_gc_annual # STATIONARY STORAGE @@ -408,6 +429,9 @@ def set_grid_connection_costs(self): self.costs_per_gc[gcID]["c_stat_storage_annual"] = c_stat_storage / self.params[ "stationary_storage"]["lifetime_stat_storage"] + self.costs_per_gc[gcID]["c_stat_storage_annual_per_km"] = ( + self.costs_per_gc[gcID]["c_stat_storage_annual"] / + self.total_annual_km) self.costs_per_gc[gcID]["c_maint_stat_storage_annual"] = ( c_stat_storage * self.params["stationary_storage"][ "c_maint_stat_storage_per_year"]) @@ -425,6 +449,9 @@ def set_grid_connection_costs(self): self.costs_per_gc[gcID]["c_feed_in"] = c_feed_in self.costs_per_gc[gcID]["c_feed_in_annual"] = ( c_feed_in / self.params["feed_in"]["lifetime_feed_in"]) + self.costs_per_gc[gcID]["c_feed_in_annual_per_km"] = ( + self.costs_per_gc[gcID]["c_feed_in_annual"] / + self.total_annual_km) self.costs_per_gc[gcID]["c_maint_feed_in_annual"] = ( c_feed_in * self.params["feed_in"]["c_maint_feed_in_per_year"]) except KeyError: @@ -489,6 +516,9 @@ def set_vehicle_costs_per_gc(self): self.costs_per_gc[gc]["c_vehicles"] += c_vehicles_vt self.costs_per_gc[gc]["c_vehicles_annual"] += c_vehicles_annual + self.costs_per_gc[gc]["c_vehicles_annual_per_km"] = ( + self.costs_per_gc[gc]["c_vehicles_annual"] / + self.total_annual_km) return self @@ -531,8 +561,26 @@ def cumulate(self): + self.costs_per_gc[self.GARAGE]["c_invest_annual"] ) + self.costs_per_gc[self.CUMULATED]["c_invest_annual_per_km"] = ( + self.costs_per_gc[self.CUMULATED]["c_vehicles_annual_per_km"] + + self.costs_per_gc[self.CUMULATED]["c_cs_annual_per_km"] + + self.costs_per_gc[self.CUMULATED]["c_gcs_annual_per_km"] + + self.costs_per_gc[self.CUMULATED]["c_stat_storage_annual_per_km"] + + self.costs_per_gc[self.CUMULATED]["c_feed_in_annual_per_km"] + + self.costs_per_gc[self.GARAGE]["c_invest_annual_per_km"] + ) + return self + def get_total_annual_km(self): + # 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. diff --git a/simba/schedule.py b/simba/schedule.py index 953d8db8..a747a384 100644 --- a/simba/schedule.py +++ b/simba/schedule.py @@ -409,6 +409,12 @@ def get_negative_rotations(self, scenario): return list(negative_rotations) + def get_total_distance(self): + 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. From d8ce8ad4c5a7f5e79a344dfb8a0a1aa6dafde3d0 Mon Sep 17 00:00:00 2001 From: mosc5 Date: Mon, 29 Apr 2024 15:11:34 +0200 Subject: [PATCH 02/14] Add annual investment costs per km to info --- simba/costs.py | 1 + 1 file changed, 1 insertion(+) diff --git a/simba/costs.py b/simba/costs.py index e95dc873..9c386052 100644 --- a/simba/costs.py +++ b/simba/costs.py @@ -110,6 +110,7 @@ def info(self): return ("\nTotal costs:\n" f"Investment cost: {cumulated['c_invest']} €. \n" f"Annual investment costs: {cumulated['c_invest_annual']} €/a. \n" + f"Annual investment costs per km: {cumulated['c_invest_annual_per_km']} €/a. \n" f"Annual maintenance costs: {cumulated['c_maint_annual']} €/a. \n" f"Annual costs for electricity: {cumulated['c_el_annual']} €/a.\n") From 28961b99f19f452bcaf4ea74f4558ffe974ea465 Mon Sep 17 00:00:00 2001 From: mosc5 Date: Mon, 29 Apr 2024 15:25:08 +0200 Subject: [PATCH 03/14] correctly calculate vehicle costs per km --- simba/costs.py | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/simba/costs.py b/simba/costs.py index 9c386052..dd27cced 100644 --- a/simba/costs.py +++ b/simba/costs.py @@ -259,11 +259,8 @@ def set_charging_infrastructure_costs(self): + self.costs_per_gc[gcID]["c_stat_storage_annual"] + self.costs_per_gc[gcID]["c_feed_in_annual"]) self.costs_per_gc[gcID]["c_invest_annual_per_km"] = ( - self.costs_per_gc[gcID]["c_vehicles_annual_per_km"] - + self.costs_per_gc[gcID]["c_cs_annual_per_km"] - + self.costs_per_gc[gcID]["c_gcs_annual_per_km"] - + self.costs_per_gc[gcID]["c_stat_storage_annual_per_km"] - + self.costs_per_gc[gcID]["c_feed_in_annual_per_km"]) + self.costs_per_gc[gcID]["c_invest_annual"] / + self.total_annual_km) # MAINTENANCE COSTS # self.costs_per_gc[gcID]["c_maint_infrastructure_annual"] = ( @@ -517,9 +514,9 @@ def set_vehicle_costs_per_gc(self): self.costs_per_gc[gc]["c_vehicles"] += c_vehicles_vt self.costs_per_gc[gc]["c_vehicles_annual"] += c_vehicles_annual - self.costs_per_gc[gc]["c_vehicles_annual_per_km"] = ( - self.costs_per_gc[gc]["c_vehicles_annual"] / - self.total_annual_km) + self.costs_per_gc[gc]["c_vehicles_annual_per_km"] = ( + self.costs_per_gc[gc]["c_vehicles_annual"] / + self.total_annual_km) return self @@ -563,12 +560,8 @@ def cumulate(self): ) self.costs_per_gc[self.CUMULATED]["c_invest_annual_per_km"] = ( - self.costs_per_gc[self.CUMULATED]["c_vehicles_annual_per_km"] - + self.costs_per_gc[self.CUMULATED]["c_cs_annual_per_km"] - + self.costs_per_gc[self.CUMULATED]["c_gcs_annual_per_km"] - + self.costs_per_gc[self.CUMULATED]["c_stat_storage_annual_per_km"] - + self.costs_per_gc[self.CUMULATED]["c_feed_in_annual_per_km"] - + self.costs_per_gc[self.GARAGE]["c_invest_annual_per_km"] + self.costs_per_gc[self.CUMULATED]["c_invest_annual"] / + self.total_annual_km ) return self From ef25c0d915e145812948dab4d24ca61ea42e47bd Mon Sep 17 00:00:00 2001 From: mosc5 Date: Tue, 30 Apr 2024 11:55:48 +0200 Subject: [PATCH 04/14] don't divide by zero --- simba/costs.py | 58 ++++++++++++++++++++++++++++---------------------- 1 file changed, 33 insertions(+), 25 deletions(-) diff --git a/simba/costs.py b/simba/costs.py index dd27cced..6776cd8f 100644 --- a/simba/costs.py +++ b/simba/costs.py @@ -237,9 +237,10 @@ 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_cs_annual_per_km"] = ( - self.costs_per_gc[gcID]["c_cs_annual"] / - self.total_annual_km) + if self.total_annual_km: + self.costs_per_gc[gcID]["c_cs_annual_per_km"] = ( + self.costs_per_gc[gcID]["c_cs_annual"] / + self.total_annual_km) self.costs_per_gc[gcID]["c_maint_cs_annual"] = c_cs * self.params["cs"][ "c_maint_cs_per_year"] @@ -258,9 +259,10 @@ def set_charging_infrastructure_costs(self): + self.costs_per_gc[gcID]["c_gcs_annual"] + self.costs_per_gc[gcID]["c_stat_storage_annual"] + self.costs_per_gc[gcID]["c_feed_in_annual"]) - self.costs_per_gc[gcID]["c_invest_annual_per_km"] = ( - self.costs_per_gc[gcID]["c_invest_annual"] / - self.total_annual_km) + if self.total_annual_km: + self.costs_per_gc[gcID]["c_invest_annual_per_km"] = ( + self.costs_per_gc[gcID]["c_invest_annual"] / + self.total_annual_km) # MAINTENANCE COSTS # self.costs_per_gc[gcID]["c_maint_infrastructure_annual"] = ( @@ -360,9 +362,10 @@ def set_garage_costs(self): c_garage_cs / self.params["cs"]["lifetime_cs"] + c_garage_workstations / self.params["garage"][ "lifetime_workstations"]) - self.costs_per_gc[self.GARAGE]["c_invest_annual_per_km"] = ( - self.costs_per_gc[self.GARAGE]["c_invest_annual"] / - self.total_annual_km) + if self.total_annual_km: + self.costs_per_gc[self.GARAGE]["c_invest_annual_per_km"] = ( + self.costs_per_gc[self.GARAGE]["c_invest_annual"] / + self.total_annual_km) return self @@ -410,9 +413,10 @@ def set_grid_connection_costs(self): # Store the individual costs of the GC as well self.costs_per_gc[gcID]["c_gcs"] = c_gc + c_transformer self.costs_per_gc[gcID]["c_gcs_annual"] = c_gc_annual - self.costs_per_gc[gcID]["c_gcs_annual_per_km"] = ( - self.costs_per_gc[gcID]["c_gcs_annual"] / - self.total_annual_km) + if self.total_annual_km: + self.costs_per_gc[gcID]["c_gcs_annual_per_km"] = ( + self.costs_per_gc[gcID]["c_gcs_annual"] / + self.total_annual_km) self.costs_per_gc[gcID]["c_maint_gc_annual"] = c_maint_gc_annual # STATIONARY STORAGE @@ -427,9 +431,10 @@ def set_grid_connection_costs(self): self.costs_per_gc[gcID]["c_stat_storage_annual"] = c_stat_storage / self.params[ "stationary_storage"]["lifetime_stat_storage"] - self.costs_per_gc[gcID]["c_stat_storage_annual_per_km"] = ( - self.costs_per_gc[gcID]["c_stat_storage_annual"] / - self.total_annual_km) + if self.total_annual_km: + self.costs_per_gc[gcID]["c_stat_storage_annual_per_km"] = ( + self.costs_per_gc[gcID]["c_stat_storage_annual"] / + self.total_annual_km) self.costs_per_gc[gcID]["c_maint_stat_storage_annual"] = ( c_stat_storage * self.params["stationary_storage"][ "c_maint_stat_storage_per_year"]) @@ -447,9 +452,10 @@ def set_grid_connection_costs(self): self.costs_per_gc[gcID]["c_feed_in"] = c_feed_in self.costs_per_gc[gcID]["c_feed_in_annual"] = ( c_feed_in / self.params["feed_in"]["lifetime_feed_in"]) - self.costs_per_gc[gcID]["c_feed_in_annual_per_km"] = ( - self.costs_per_gc[gcID]["c_feed_in_annual"] / - self.total_annual_km) + if self.total_annual_km: + self.costs_per_gc[gcID]["c_feed_in_annual_per_km"] = ( + self.costs_per_gc[gcID]["c_feed_in_annual"] / + self.total_annual_km) self.costs_per_gc[gcID]["c_maint_feed_in_annual"] = ( c_feed_in * self.params["feed_in"]["c_maint_feed_in_per_year"]) except KeyError: @@ -514,9 +520,10 @@ def set_vehicle_costs_per_gc(self): self.costs_per_gc[gc]["c_vehicles"] += c_vehicles_vt self.costs_per_gc[gc]["c_vehicles_annual"] += c_vehicles_annual - self.costs_per_gc[gc]["c_vehicles_annual_per_km"] = ( - self.costs_per_gc[gc]["c_vehicles_annual"] / - self.total_annual_km) + if self.total_annual_km: + self.costs_per_gc[gc]["c_vehicles_annual_per_km"] = ( + self.costs_per_gc[gc]["c_vehicles_annual"] / + self.total_annual_km) return self @@ -559,10 +566,11 @@ def cumulate(self): + self.costs_per_gc[self.GARAGE]["c_invest_annual"] ) - self.costs_per_gc[self.CUMULATED]["c_invest_annual_per_km"] = ( - self.costs_per_gc[self.CUMULATED]["c_invest_annual"] / - self.total_annual_km - ) + if self.total_annual_km: + self.costs_per_gc[self.CUMULATED]["c_invest_annual_per_km"] = ( + self.costs_per_gc[self.CUMULATED]["c_invest_annual"] / + self.total_annual_km + ) return self From 839f0206a001ce6c3a9d0b75487e3902674ffaf9 Mon Sep 17 00:00:00 2001 From: mosc5 Date: Tue, 30 Apr 2024 15:47:12 +0200 Subject: [PATCH 05/14] refactor feature to new function set_annual_invest_per_km --- simba/costs.py | 53 ++++++++++++++++++++------------------------------ 1 file changed, 21 insertions(+), 32 deletions(-) diff --git a/simba/costs.py b/simba/costs.py index 6776cd8f..fe0d6117 100644 --- a/simba/costs.py +++ b/simba/costs.py @@ -42,6 +42,9 @@ def calculate_costs(c_params, scenario, schedule, args): # Get electricity costs from SpiceEV cost_object.set_electricity_costs() + # Calculate the annual costs per kilometer + cost_object.set_annual_invest_per_km() + # Cumulate the costs of all gcs cost_object.cumulate() @@ -98,7 +101,6 @@ def __init__(self, schedule: simba.schedule.Schedule, scenario: spice_ev.scenari self.args = args self.params = c_params self.rounding_precision = 2 - self.total_annual_km = self.get_total_annual_km() def info(self): """ Provide information about the total costs. @@ -237,11 +239,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"] - if self.total_annual_km: - self.costs_per_gc[gcID]["c_cs_annual_per_km"] = ( - self.costs_per_gc[gcID]["c_cs_annual"] / - self.total_annual_km) - self.costs_per_gc[gcID]["c_maint_cs_annual"] = c_cs * self.params["cs"][ "c_maint_cs_per_year"] @@ -259,10 +256,6 @@ def set_charging_infrastructure_costs(self): + self.costs_per_gc[gcID]["c_gcs_annual"] + self.costs_per_gc[gcID]["c_stat_storage_annual"] + self.costs_per_gc[gcID]["c_feed_in_annual"]) - if self.total_annual_km: - self.costs_per_gc[gcID]["c_invest_annual_per_km"] = ( - self.costs_per_gc[gcID]["c_invest_annual"] / - self.total_annual_km) # MAINTENANCE COSTS # self.costs_per_gc[gcID]["c_maint_infrastructure_annual"] = ( @@ -362,10 +355,6 @@ def set_garage_costs(self): c_garage_cs / self.params["cs"]["lifetime_cs"] + c_garage_workstations / self.params["garage"][ "lifetime_workstations"]) - if self.total_annual_km: - self.costs_per_gc[self.GARAGE]["c_invest_annual_per_km"] = ( - self.costs_per_gc[self.GARAGE]["c_invest_annual"] / - self.total_annual_km) return self @@ -413,10 +402,6 @@ def set_grid_connection_costs(self): # Store the individual costs of the GC as well self.costs_per_gc[gcID]["c_gcs"] = c_gc + c_transformer self.costs_per_gc[gcID]["c_gcs_annual"] = c_gc_annual - if self.total_annual_km: - self.costs_per_gc[gcID]["c_gcs_annual_per_km"] = ( - self.costs_per_gc[gcID]["c_gcs_annual"] / - self.total_annual_km) self.costs_per_gc[gcID]["c_maint_gc_annual"] = c_maint_gc_annual # STATIONARY STORAGE @@ -431,10 +416,6 @@ def set_grid_connection_costs(self): self.costs_per_gc[gcID]["c_stat_storage_annual"] = c_stat_storage / self.params[ "stationary_storage"]["lifetime_stat_storage"] - if self.total_annual_km: - self.costs_per_gc[gcID]["c_stat_storage_annual_per_km"] = ( - self.costs_per_gc[gcID]["c_stat_storage_annual"] / - self.total_annual_km) self.costs_per_gc[gcID]["c_maint_stat_storage_annual"] = ( c_stat_storage * self.params["stationary_storage"][ "c_maint_stat_storage_per_year"]) @@ -452,10 +433,6 @@ def set_grid_connection_costs(self): self.costs_per_gc[gcID]["c_feed_in"] = c_feed_in self.costs_per_gc[gcID]["c_feed_in_annual"] = ( c_feed_in / self.params["feed_in"]["lifetime_feed_in"]) - if self.total_annual_km: - self.costs_per_gc[gcID]["c_feed_in_annual_per_km"] = ( - self.costs_per_gc[gcID]["c_feed_in_annual"] / - self.total_annual_km) self.costs_per_gc[gcID]["c_maint_feed_in_annual"] = ( c_feed_in * self.params["feed_in"]["c_maint_feed_in_per_year"]) except KeyError: @@ -520,13 +497,24 @@ def set_vehicle_costs_per_gc(self): self.costs_per_gc[gc]["c_vehicles"] += c_vehicles_vt self.costs_per_gc[gc]["c_vehicles_annual"] += c_vehicles_annual - if self.total_annual_km: - self.costs_per_gc[gc]["c_vehicles_annual_per_km"] = ( - self.costs_per_gc[gc]["c_vehicles_annual"] / - self.total_annual_km) return self + def set_annual_invest_per_km(self): + total_annual_km = self.get_total_annual_km() + if total_annual_km: + attributes = [ + "c_vehicles_annual", "c_gcs_annual", "c_cs_annual", + "c_stat_storage_annual", "c_feed_in_annual", "c_invest_annual" + ] + for gcID in self.gcs: + for key in attributes: + self.costs_per_gc[gcID][f"{key}_per_km"] = ( + self.costs_per_gc[gcID][key] / total_annual_km) + self.costs_per_gc[self.GARAGE]["c_invest_annual_per_km"] = ( + self.costs_per_gc[self.GARAGE]["c_invest_annual"] / + total_annual_km) + def cumulate(self): """ Cumulate the costs of vehicles and infrastructure. @@ -566,10 +554,11 @@ def cumulate(self): + self.costs_per_gc[self.GARAGE]["c_invest_annual"] ) - if self.total_annual_km: + total_annual_km = self.get_total_annual_km() + if total_annual_km: self.costs_per_gc[self.CUMULATED]["c_invest_annual_per_km"] = ( self.costs_per_gc[self.CUMULATED]["c_invest_annual"] / - self.total_annual_km + total_annual_km ) return self From 94014754f9b2387510a2d7d9944b81d629dda96b Mon Sep 17 00:00:00 2001 From: mosc5 Date: Tue, 30 Apr 2024 16:53:14 +0200 Subject: [PATCH 06/14] docstrings --- simba/costs.py | 11 +++++++++++ simba/schedule.py | 5 +++++ 2 files changed, 16 insertions(+) diff --git a/simba/costs.py b/simba/costs.py index fe0d6117..6852b672 100644 --- a/simba/costs.py +++ b/simba/costs.py @@ -501,6 +501,11 @@ def set_vehicle_costs_per_gc(self): return self def set_annual_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 = [ @@ -514,6 +519,7 @@ def set_annual_invest_per_km(self): self.costs_per_gc[self.GARAGE]["c_invest_annual_per_km"] = ( self.costs_per_gc[self.GARAGE]["c_invest_annual"] / total_annual_km) + return self def cumulate(self): """ Cumulate the costs of vehicles and infrastructure. @@ -564,6 +570,11 @@ def cumulate(self): 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 diff --git a/simba/schedule.py b/simba/schedule.py index a747a384..7e2ebb1f 100644 --- a/simba/schedule.py +++ b/simba/schedule.py @@ -410,6 +410,11 @@ 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 From 7c4eff411131e6ed9dd474afe4fe6edc66902170 Mon Sep 17 00:00:00 2001 From: mosc5 Date: Thu, 2 May 2024 15:45:53 +0200 Subject: [PATCH 07/14] change result names to attribute_per_km --- simba/costs.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/simba/costs.py b/simba/costs.py index 6852b672..337ba939 100644 --- a/simba/costs.py +++ b/simba/costs.py @@ -42,8 +42,8 @@ def calculate_costs(c_params, scenario, schedule, args): # Get electricity costs from SpiceEV cost_object.set_electricity_costs() - # Calculate the annual costs per kilometer - cost_object.set_annual_invest_per_km() + # Calculate the costs per kilometer + cost_object.set_invest_per_km() # Cumulate the costs of all gcs cost_object.cumulate() @@ -112,7 +112,7 @@ def info(self): return ("\nTotal costs:\n" f"Investment cost: {cumulated['c_invest']} €. \n" f"Annual investment costs: {cumulated['c_invest_annual']} €/a. \n" - f"Annual investment costs per km: {cumulated['c_invest_annual_per_km']} €/a. \n" + f"Annual investment costs per km: {cumulated['c_invest_per_km']} €/a. \n" f"Annual maintenance costs: {cumulated['c_maint_annual']} €/a. \n" f"Annual costs for electricity: {cumulated['c_el_annual']} €/a.\n") @@ -170,8 +170,8 @@ def get_gc_cost_variables(self): "c_vehicles_annual", "c_gcs_annual", "c_cs_annual", "c_stat_storage_annual", "c_feed_in_annual", "c_invest_annual", # annual investment costs per km - "c_vehicles_annual_per_km", "c_gcs_annual_per_km", "c_cs_annual_per_km", - "c_stat_storage_annual_per_km", "c_feed_in_annual_per_km", "c_invest_annual_per_km", + "c_vehicles_per_km", "c_gcs_per_km", "c_cs_per_km", + "c_stat_storage_per_km", "c_feed_in_per_km", "c_invest_per_km", # annual maintenance costs "c_maint_vehicles_annual", "c_maint_gc_annual", "c_maint_cs_annual", "c_maint_stat_storage_annual", "c_maint_feed_in_annual", @@ -500,7 +500,7 @@ def set_vehicle_costs_per_gc(self): return self - def set_annual_invest_per_km(self): + def set_invest_per_km(self): """ Calculate the annual investment costs per total driven distance. :return: self @@ -509,14 +509,14 @@ def set_annual_invest_per_km(self): total_annual_km = self.get_total_annual_km() if total_annual_km: attributes = [ - "c_vehicles_annual", "c_gcs_annual", "c_cs_annual", - "c_stat_storage_annual", "c_feed_in_annual", "c_invest_annual" + "c_vehicles", "c_gcs", "c_cs", + "c_stat_storage", "c_feed_in", "c_invest" ] for gcID in self.gcs: for key in attributes: self.costs_per_gc[gcID][f"{key}_per_km"] = ( - self.costs_per_gc[gcID][key] / total_annual_km) - self.costs_per_gc[self.GARAGE]["c_invest_annual_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) return self @@ -562,7 +562,7 @@ def cumulate(self): total_annual_km = self.get_total_annual_km() if total_annual_km: - self.costs_per_gc[self.CUMULATED]["c_invest_annual_per_km"] = ( + self.costs_per_gc[self.CUMULATED]["c_invest_per_km"] = ( self.costs_per_gc[self.CUMULATED]["c_invest_annual"] / total_annual_km ) From afab35339d4b784083a81fc93246f06e4b75e4a5 Mon Sep 17 00:00:00 2001 From: Julian Brendel Date: Fri, 3 May 2024 13:01:12 +0200 Subject: [PATCH 08/14] add option skip_flex_report --- data/examples/simba.cfg | 2 ++ simba/util.py | 2 ++ 2 files changed, 4 insertions(+) diff --git a/data/examples/simba.cfg b/data/examples/simba.cfg index 131d446c..dd0a3431 100644 --- a/data/examples/simba.cfg +++ b/data/examples/simba.cfg @@ -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 diff --git a/simba/util.py b/simba/util.py index 19bc186f..6a2805ae 100644 --- a/simba/util.py +++ b/simba/util.py @@ -360,6 +360,8 @@ 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') + parser.add_argument('--skip-flex-report', action='store_true', + help='Skip flex band creation when generating reports.') # #### Simulation Parameters ##### parser.add_argument('--days', metavar='N', type=int, default=None, From 3a098c4bc64ee63cd3ac20f77520e109c352aea9 Mon Sep 17 00:00:00 2001 From: Julian Brendel Date: Fri, 3 May 2024 13:07:53 +0200 Subject: [PATCH 09/14] add rtd for skip_flex_report --- docs/source/simulation_parameters.rst | 4 ++++ simba/util.py | 16 ++++++++-------- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/docs/source/simulation_parameters.rst b/docs/source/simulation_parameters.rst index 646400bf..de8189c0 100644 --- a/docs/source/simulation_parameters.rst +++ b/docs/source/simulation_parameters.rst @@ -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 diff --git a/simba/util.py b/simba/util.py index 6a2805ae..74f715a0 100644 --- a/simba/util.py +++ b/simba/util.py @@ -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 = [ @@ -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', @@ -360,9 +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') - parser.add_argument('--skip-flex-report', action='store_true', - help='Skip flex band creation when generating reports.') - # #### Simulation Parameters ##### parser.add_argument('--days', metavar='N', type=int, default=None, help='set duration of scenario as number of days') @@ -374,11 +376,9 @@ 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, From 30585a7d7d2410de9106c6d3f55bef31161ef4bd Mon Sep 17 00:00:00 2001 From: Julian Brendel Date: Fri, 3 May 2024 13:09:32 +0200 Subject: [PATCH 10/14] make flake8 happy --- simba/util.py | 1 - 1 file changed, 1 deletion(-) diff --git a/simba/util.py b/simba/util.py index 74f715a0..cc53ec79 100644 --- a/simba/util.py +++ b/simba/util.py @@ -379,7 +379,6 @@ def get_args(): 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, choices=logging._nameToLevel.keys(), help='Log level.') From abcadcaf327d7bab44dbe969530d74dcd53d42c0 Mon Sep 17 00:00:00 2001 From: Julian Brendel Date: Fri, 3 May 2024 13:47:35 +0200 Subject: [PATCH 11/14] correct True to true --- data/examples/simba.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/examples/simba.cfg b/data/examples/simba.cfg index dd0a3431..da6287fa 100644 --- a/data/examples/simba.cfg +++ b/data/examples/simba.cfg @@ -142,4 +142,4 @@ signal_time_dif = 10 # (default: false) eta = false # Save time by skipping often not needed flex_report in SpiceEV -skip_flex_report = True +skip_flex_report = true From e6df951a183462be4bcba80f9afd77dd490a78b3 Mon Sep 17 00:00:00 2001 From: mosc5 Date: Fri, 3 May 2024 15:06:17 +0200 Subject: [PATCH 12/14] change calculated values and add proper units --- simba/costs.py | 46 ++++++++++++++++++++++++++++++++++++---------- 1 file changed, 36 insertions(+), 10 deletions(-) diff --git a/simba/costs.py b/simba/costs.py index 337ba939..61ee8a3f 100644 --- a/simba/costs.py +++ b/simba/costs.py @@ -42,6 +42,9 @@ 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() @@ -112,11 +115,11 @@ def info(self): return ("\nTotal costs:\n" f"Investment cost: {cumulated['c_invest']} €. \n" f"Annual investment costs: {cumulated['c_invest_annual']} €/a. \n" - f"Annual investment costs per km: {cumulated['c_invest_per_km']} €/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"Investment costs per km: {cumulated['c_invest_per_km']} €/a. \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 @@ -126,6 +129,8 @@ def get_annual_or_not(self, key): """ if "annual" in key: return "€/year" + elif "per_km" in key: + return "€/km" else: return "€" @@ -169,9 +174,6 @@ def get_gc_cost_variables(self): # annual investment costs "c_vehicles_annual", "c_gcs_annual", "c_cs_annual", "c_stat_storage_annual", "c_feed_in_annual", "c_invest_annual", - # annual investment costs per km - "c_vehicles_per_km", "c_gcs_per_km", "c_cs_per_km", - "c_stat_storage_per_km", "c_feed_in_per_km", "c_invest_per_km", # annual maintenance costs "c_maint_vehicles_annual", "c_maint_gc_annual", "c_maint_cs_annual", "c_maint_stat_storage_annual", "c_maint_feed_in_annual", @@ -180,7 +182,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", + # investment costs per km + "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. @@ -500,6 +508,21 @@ 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] + def set_invest_per_km(self): """ Calculate the annual investment costs per total driven distance. @@ -509,8 +532,8 @@ def set_invest_per_km(self): total_annual_km = self.get_total_annual_km() if total_annual_km: attributes = [ - "c_vehicles", "c_gcs", "c_cs", - "c_stat_storage", "c_feed_in", "c_invest" + "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: @@ -519,6 +542,9 @@ def set_invest_per_km(self): 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): @@ -603,7 +629,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) From ec58067b6166c32be008faf2f65b3896a6051087 Mon Sep 17 00:00:00 2001 From: mosc5 Date: Fri, 3 May 2024 15:19:53 +0200 Subject: [PATCH 13/14] change info --- simba/costs.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/simba/costs.py b/simba/costs.py index 61ee8a3f..77fd5c43 100644 --- a/simba/costs.py +++ b/simba/costs.py @@ -117,7 +117,8 @@ def info(self): 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"Investment costs per km: {cumulated['c_invest_per_km']} €/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_unit(self, key): """ Get the unit for annual or non-annual costs. @@ -185,7 +186,7 @@ def get_gc_cost_variables(self): "c_el_taxes_annual", "c_el_feed_in_remuneration_annual", "c_el_annual", # total annual costs "c_total_annual", - # investment costs per km + # 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"] From 26deaa5059322949fec32e1e4353288ce896d278 Mon Sep 17 00:00:00 2001 From: mosc5 Date: Fri, 3 May 2024 15:21:22 +0200 Subject: [PATCH 14/14] add return to set_total_annual_costs --- simba/costs.py | 1 + 1 file changed, 1 insertion(+) diff --git a/simba/costs.py b/simba/costs.py index 77fd5c43..3e96e0c8 100644 --- a/simba/costs.py +++ b/simba/costs.py @@ -523,6 +523,7 @@ def set_total_annual_costs(self): 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.