From c823bcdaa38573ef73dda1581b696f5d8e0de802 Mon Sep 17 00:00:00 2001 From: "stefan.schirmeister" Date: Fri, 26 May 2023 12:26:37 +0200 Subject: [PATCH 1/5] support for scenarios with zero intervals --- spice_ev/scenario.py | 1 + tests/test_strategies.py | 13 +++++++++++++ 2 files changed, 14 insertions(+) diff --git a/spice_ev/scenario.py b/spice_ev/scenario.py index a5d6d3a7..f6b76a55 100644 --- a/spice_ev/scenario.py +++ b/spice_ev/scenario.py @@ -96,6 +96,7 @@ def run(self, strategy_name, options): begin = datetime.datetime.now() error = None + step_i = -1 for step_i in range(self.n_intervals): if options.get("timing", False): diff --git a/tests/test_strategies.py b/tests/test_strategies.py index 560dbb09..0f16e953 100644 --- a/tests/test_strategies.py +++ b/tests/test_strategies.py @@ -114,6 +114,19 @@ def test_empty(self): # GC has neither cost nor schedule strat.step() + def test_zero_intervals(self): + test_json = { + "scenario": { + "start_time": "1970-01-01T00:00:00+00:00", + "interval": 15, + "n_intervals": 0 + } + } + s = scenario.Scenario(test_json) + s.run('greedy', {}) + assert s.n_intervals == 0 + assert s.step_i == 0 + def test_file(self): # open from file input = TEST_REPO_PATH / 'test_data/input_test_strategies/scenario_A.json' From 4fe3fff0febfec1ab54103269592338aa137ae73 Mon Sep 17 00:00:00 2001 From: mosc5 Date: Wed, 31 May 2023 14:15:39 +0200 Subject: [PATCH 2/5] change balanced market discharge to check charging station power limit #175 --- spice_ev/strategies/balanced_market.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spice_ev/strategies/balanced_market.py b/spice_ev/strategies/balanced_market.py index e797dd6f..601586b4 100644 --- a/spice_ev/strategies/balanced_market.py +++ b/spice_ev/strategies/balanced_market.py @@ -236,7 +236,8 @@ def step(self): gc_cur_discharge_power_limit = (timesteps[v2g_ts_idx]["power"] - 2*timesteps[v2g_ts_idx]["max_power"]) - p = min(max(gc_cur_discharge_power_limit, p), 0) + cs_cur_discharge_power_limit = timesteps[v2g_ts_idx]["power"] - 2*cs.max_power + p = min(max(gc_cur_discharge_power_limit, cs_cur_discharge_power_limit, p), 0) power[v2g_ts_idx] = p if v2g_ts_idx == 0: From 85530765b0fa414aa2a14a8539068c9c8fa833ec Mon Sep 17 00:00:00 2001 From: mosc5 Date: Wed, 31 May 2023 16:01:53 +0200 Subject: [PATCH 3/5] recalculate maximum charge point discharge #175 --- spice_ev/strategies/balanced_market.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spice_ev/strategies/balanced_market.py b/spice_ev/strategies/balanced_market.py index 601586b4..cba1fb45 100644 --- a/spice_ev/strategies/balanced_market.py +++ b/spice_ev/strategies/balanced_market.py @@ -236,7 +236,7 @@ def step(self): gc_cur_discharge_power_limit = (timesteps[v2g_ts_idx]["power"] - 2*timesteps[v2g_ts_idx]["max_power"]) - cs_cur_discharge_power_limit = timesteps[v2g_ts_idx]["power"] - 2*cs.max_power + cs_cur_discharge_power_limit = -(cs.max_power + cs.current_power) p = min(max(gc_cur_discharge_power_limit, cs_cur_discharge_power_limit, p), 0) power[v2g_ts_idx] = p From 8c53e98e78b1808724785c8c9a8a204220bde51d Mon Sep 17 00:00:00 2001 From: "stefan.schirmeister" Date: Wed, 2 Aug 2023 09:39:20 +0200 Subject: [PATCH 4/5] make flake8 happy --- simulate.py | 2 +- spice_ev/events.py | 5 ++++- spice_ev/generate/generate_from_simbev.py | 2 +- spice_ev/generate/generate_schedule.py | 8 ++++---- spice_ev/strategies/balanced_market.py | 4 ++-- spice_ev/strategies/distributed.py | 2 +- spice_ev/strategies/flex_window.py | 4 ++-- spice_ev/strategies/schedule.py | 6 +++--- spice_ev/strategy.py | 8 ++++---- spice_ev/util.py | 2 +- 10 files changed, 23 insertions(+), 20 deletions(-) diff --git a/simulate.py b/simulate.py index 3a4a10e7..06cc51ae 100755 --- a/simulate.py +++ b/simulate.py @@ -24,7 +24,7 @@ def simulate(args): :raises NotImplementedError: if unknown strategy is given """ - if type(args) == argparse.Namespace: + if type(args) is argparse.Namespace: # cast arguments to dictionary for default handling args = vars(args) diff --git a/spice_ev/events.py b/spice_ev/events.py index f777e2b5..d058dc30 100644 --- a/spice_ev/events.py +++ b/spice_ev/events.py @@ -53,6 +53,7 @@ def get_event_steps(self, start_time, n_intervals, interval): local_generation_list.get_events(name, LocalEnergyGeneration, has_perfect_foresight=True)) + moved = 0 ignored = 0 for event in all_events: @@ -60,13 +61,15 @@ def get_event_steps(self, start_time, n_intervals, interval): index = -((start_time - event.signal_time) // interval) if index < 0: - warn('Event is before start of scenario, placing at first time step: ' + str(event)) + moved += 1 steps[0].append(event) elif index >= n_intervals: ignored += 1 else: steps[index].append(event) + if moved: + warn('{} events before start of scenario, placed at first time step'.format(moved)) if ignored: warn('{} events ignored after end of scenario'.format(ignored)) diff --git a/spice_ev/generate/generate_from_simbev.py b/spice_ev/generate/generate_from_simbev.py index 4f430ea4..daa3817d 100755 --- a/spice_ev/generate/generate_from_simbev.py +++ b/spice_ev/generate/generate_from_simbev.py @@ -59,7 +59,7 @@ def generate_from_simbev(args): pathlist.sort() def datetime_from_timestep(timestep): - assert type(timestep) == int + assert type(timestep) is int return start + (interval * timestep) # take start time from SimBEV metadata diff --git a/spice_ev/generate/generate_schedule.py b/spice_ev/generate/generate_schedule.py index 9c61a95a..d2ed3865 100644 --- a/spice_ev/generate/generate_schedule.py +++ b/spice_ev/generate/generate_schedule.py @@ -300,13 +300,13 @@ def get_v2g_energy(vehicle): if idx != 0: flex["vehicles"].append([]) for event in timestep: - if type(event) == events.FixedLoad and event.grid_connector_id == gcID: + if type(event) is events.FixedLoad and event.grid_connector_id == gcID: # fixed load event at this GC gc.current_loads[event.name] = event.value - elif type(event) == events.LocalEnergyGeneration and event.grid_connector_id == gcID: + elif type(event) is events.LocalEnergyGeneration and event.grid_connector_id == gcID: # local generation event behind this GC gc.current_loads[event.name] = -event.value - elif type(event) == events.GridOperatorSignal and event.grid_connector_id == gcID: + elif type(event) is events.GridOperatorSignal and event.grid_connector_id == gcID: # grid op event at this GC if gc.max_power: if event.max_power is None: @@ -317,7 +317,7 @@ def get_v2g_energy(vehicle): else: # connector max power not set gc.cur_max_power = event.max_power - elif type(event) == events.VehicleEvent: + elif type(event) is events.VehicleEvent: # vehicle event: check if this GC vid = event.vehicle_id vehicle = vehicles[vid] diff --git a/spice_ev/strategies/balanced_market.py b/spice_ev/strategies/balanced_market.py index cba1fb45..ce29dc4a 100644 --- a/spice_ev/strategies/balanced_market.py +++ b/spice_ev/strategies/balanced_market.py @@ -78,13 +78,13 @@ def step(self): # not this timestep break event_idx += 1 - if type(event) == events.GridOperatorSignal: + if type(event) is events.GridOperatorSignal: # update GC info if event.max_power is not None: cur_max_power = event.max_power if event.cost is not None: cur_cost = event.cost - elif type(event) == events.LocalEnergyGeneration: + elif type(event) is events.LocalEnergyGeneration: cur_local_generation[event.name] = event.value # vehicle events ignored (use vehicle info such as estimated_time_of_departure) diff --git a/spice_ev/strategies/distributed.py b/spice_ev/strategies/distributed.py index 123865c7..a22a3672 100644 --- a/spice_ev/strategies/distributed.py +++ b/spice_ev/strategies/distributed.py @@ -76,7 +76,7 @@ def step(self): if event.start_time > event.start_time + datetime.timedelta(minutes=self.C_HORIZON): # not this timestep break - if type(event) == events.VehicleEvent: + if type(event) is events.VehicleEvent: if event.vehicle_id == vehicle_id: # not this vehicle event continue diff --git a/spice_ev/strategies/flex_window.py b/spice_ev/strategies/flex_window.py index 9e6e4e2f..956f9764 100644 --- a/spice_ev/strategies/flex_window.py +++ b/spice_ev/strategies/flex_window.py @@ -73,12 +73,12 @@ def step(self): # not this timestep break event_idx += 1 - if type(event) == events.GridOperatorSignal: + if type(event) is events.GridOperatorSignal: # update GC info cur_max_power = event.max_power or cur_max_power if event.window is not None: cur_window = event.window - elif type(event) == events.LocalEnergyGeneration: + elif type(event) is events.LocalEnergyGeneration: cur_local_generation[event.name] = event.value # vehicle events ignored (use vehicle info such as estimated_time_of_departure) diff --git a/spice_ev/strategies/schedule.py b/spice_ev/strategies/schedule.py index dbb60e59..b048b22f 100644 --- a/spice_ev/strategies/schedule.py +++ b/spice_ev/strategies/schedule.py @@ -160,13 +160,13 @@ def collect_future_gc_info(self, dt=timedelta(days=1)): break # event handled: don't handle again, so increase index event_idx += 1 - if type(event) == events.GridOperatorSignal: + if type(event) is events.GridOperatorSignal: # update GC info gc_info[-1]["target"] = \ event.target if event.target is not None else gc_info[-1]["target"] gc_info[-1]["charge"] = \ event.window if event.window is not None else gc_info[-1]["charge"] - elif type(event) == events.LocalEnergyGeneration: + elif type(event) is events.LocalEnergyGeneration: gc_info[-1]["current_loads"][event.name] = -event.value # ignore vehicle events, use vehicle data directly # ignore local generation for now as well @@ -667,7 +667,7 @@ def charge_individually(self): break # event handled: don't handle again, so increase index event_idx += 1 - if type(event) == events.VehicleEvent and event.vehicle_id == vid: + if type(event) is events.VehicleEvent and event.vehicle_id == vid: if event.event_type == 'schedule': cur_schedule = event.update["schedule"] elif event.event_type == 'departure': diff --git a/spice_ev/strategy.py b/spice_ev/strategy.py index 64feeefd..803b7cb7 100644 --- a/spice_ev/strategy.py +++ b/spice_ev/strategy.py @@ -87,17 +87,17 @@ def step(self, event_list=[]): # remove event from list ev = self.world_state.future_events.pop(0) - if type(ev) == events.FixedLoad: + if type(ev) is events.FixedLoad: connector = self.world_state.grid_connectors[ev.grid_connector_id] assert ev.name not in self.world_state.charging_stations, ( "Fixed load must not be from charging station") connector.current_loads[ev.name] = ev.value # not reset after last event - elif type(ev) == events.LocalEnergyGeneration: + elif type(ev) is events.LocalEnergyGeneration: assert ev.name not in self.world_state.charging_stations, ( "Local energy generation must not be from charging station") connector = self.world_state.grid_connectors[ev.grid_connector_id] connector.current_loads[ev.name] = -ev.value - elif type(ev) == events.GridOperatorSignal: + elif type(ev) is events.GridOperatorSignal: connector = self.world_state.grid_connectors[ev.grid_connector_id] if ev.cost is not None: # set power cost @@ -114,7 +114,7 @@ def step(self, event_list=[]): else: # connector max power not set connector.cur_max_power = ev.max_power - elif type(ev) == events.VehicleEvent: + elif type(ev) is events.VehicleEvent: vehicle = self.world_state.vehicles[ev.vehicle_id] # update vehicle attributes for k, v in ev.update.items(): diff --git a/spice_ev/util.py b/spice_ev/util.py index 7517bca5..c711725e 100644 --- a/spice_ev/util.py +++ b/spice_ev/util.py @@ -243,7 +243,7 @@ def set_options_from_config(args, check=None, verbose=True): except IndexError: raise Exception(f"Unknown option {k}") # check each item in list individually - v_list = [v] if type(v) != list else v + v_list = [v] if type(v) is not list else v for v_item in v_list: # check item. Returns None on success # may raise ArgumentError if not successful From 7e6fbe96a95403b404fd63c369076592fce7153e Mon Sep 17 00:00:00 2001 From: "stefan.schirmeister" Date: Fri, 4 Aug 2023 11:43:47 +0200 Subject: [PATCH 5/5] update version number, changelog --- CHANGELOG.md | 8 +++++++- setup.py | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 892e7f32..2db6fff5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,9 +17,15 @@ Template: - [(#)]() ``` +## [1.0.1] - Minor Fixes - 2023-08-04 + +### Changed +- fixed bug if scenario has no timesteps +- fixed bug regarding V2G discharge power computation in balanced_market +- give summary of adjusted event times instead of warning for every affected event +- flake8 compatibility ## [1.0.0] - Initial Release SpiceEV - 2023-05-24 ### Added - first release! 🎉 - diff --git a/setup.py b/setup.py index de0a7f92..e7dbeca7 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ setup( name="spice_ev", - version="1.0.0", + version="1.0.1", description="Simulation Program for Individual Charging Events of Electric Vehicles.", url="https://github.com/rl-institut/spice_ev", author="Reiner Lemoine Institut",