diff --git a/data/examples/default_optimizer.cfg b/data/examples/default_optimizer.cfg index d8fecdfc..04be160a 100644 --- a/data/examples/default_optimizer.cfg +++ b/data/examples/default_optimizer.cfg @@ -17,7 +17,7 @@ exclusion_stations = [] # optimization will electrify these stations. # if using inclusion stations, scenario should be rebased inclusion_stations = [] -standard_opp_station = {"type": "opps", "n_charging_stations": 200,"distance_transformer": 50} +standard_opp_station = {"type": "opps", "n_charging_stations": null} # rotations which drop below this value are considered not electrified (default value is 0) min_soc = 0.05 @@ -41,6 +41,7 @@ pickle_rebased_name = "" # Should all rotations be rebased or can rotations which stay above the soc threshold be skipped? run_only_neg = False # Should only be opportunity vehicles be rebased, since this optimization is not meant for depot chargers +# The depot chargers will be added to the returned schedule in any case run_only_oppb = True # number of stations before optimal solution, where branch is checked for pruning pruning_threshold = 3 @@ -64,7 +65,8 @@ estimation_threshold = 0.80 # Removing impossible rotations leads to a quick calculation estimating if rotations are impossible.impossible -# If they are deemed as impossible, they are not optimized but discarded +# If they are deemed as impossible, they are not optimized but discarded during optimization. +# They will be added to the returned schedule in any case. remove_impossible_rotations = True # Check if stations are mandatory for a fully electrified system. If they are, include them check_for_must_stations = False diff --git a/simba/station_optimization.py b/simba/station_optimization.py index 581870de..72b2d258 100644 --- a/simba/station_optimization.py +++ b/simba/station_optimization.py @@ -3,6 +3,7 @@ """ +from copy import deepcopy import json import sys from pathlib import Path @@ -101,6 +102,8 @@ def run_optimization(conf, sched=None, scen=None, args=None): assert conf.args, error_message sched, scen, args = opt_util.toolbox_from_pickle(conf.schedule, conf.scenario, conf.args) + original_schedule = deepcopy(sched) + # setup folders, paths and copy config prepare_filesystem(args, conf) @@ -118,8 +121,10 @@ def run_optimization(conf, sched=None, scen=None, args=None): # filter out depot chargers if option is set if conf.run_only_oppb: + optimizer.config.exclusion_rots = optimizer.config.exclusion_rots.union( + r for r in sched.rotations if "depb" in sched.rotations[r].charging_type) sched.rotations = {r: sched.rotations[r] for r in sched.rotations - if "oppb" in sched.rotations[r].vehicle_id} + if "oppb" in sched.rotations[r].charging_type} assert len(sched.rotations) > 0, "No rotations left after removing depot chargers" # rebasing the scenario meaning simulating it again with SpiceEV and the given conditions of @@ -187,6 +192,13 @@ def run_optimization(conf, sched=None, scen=None, args=None): # Calculation with SpiceEV is more accurate and will show if the optimization is viable or not logger.debug("Detailed calculation of optimized case as a complete scenario") + + # Restore excluded rotations + for rotation_id in optimizer.config.exclusion_rots: + optimizer.schedule.rotations[rotation_id] = original_schedule.rotations[rotation_id] + + # remove exclusion since internally these would not be simulated + optimizer.config.exclusion_rots = set() _, __ = optimizer.preprocessing_scenario( electrified_stations=ele_stations, run_only_neg=False) diff --git a/tests/test_station_optimization.py b/tests/test_station_optimization.py index 9ea696d3..20041379 100644 --- a/tests/test_station_optimization.py +++ b/tests/test_station_optimization.py @@ -1,4 +1,4 @@ -from copy import copy +from copy import copy, deepcopy import json from pathlib import Path import pytest @@ -124,6 +124,39 @@ def test_basic_optimization(self): conf.solver = "spiceev" opt_sched, opt_scen = run_optimization(conf, sched=sched, scen=scen, args=args) + def test_schedule_consistency(self): + """ Test if the optimization returns all rotations even when some filters are active""" + trips_file_name = "trips_for_optimizer.csv" + sched, scen, args = self.basic_run(trips_file_name) + config_path = example_root / "default_optimizer.cfg" + conf = opt_util.read_config(config_path) + + sched_impossible = deepcopy(sched) + # Make a single trip impossible + next(iter(sched_impossible.rotations.values())).trips[0].distance = 99999999 + sched_impossible.calculate_consumption() + scen = sched_impossible.run(args) + + amount_rotations = len(sched_impossible.rotations) + conf.remove_impossible_rotations = True + conf.run_only_oppb = False + opt_sched, opt_scen = run_optimization(conf, sched=sched_impossible, scen=scen, args=args) + assert len(opt_sched.rotations) == amount_rotations + + # set a single rotation to depot + next(iter(sched.rotations.values())).set_charging_type("depb") + # and run again to make everything consistent + scen = sched.run(args) + + amount_rotations = len(sched.rotations) + conf.run_only_oppb = True + opt_sched, opt_scen = run_optimization(conf, sched=deepcopy(sched), scen=scen, args=args) + assert len(opt_sched.rotations) == amount_rotations + + conf.run_only_oppb = False + opt_sched, opt_scen = run_optimization(conf, sched=deepcopy(sched), scen=scen, args=args) + assert len(opt_sched.rotations) == amount_rotations + def test_deep_optimization(self): """ Check if deep analysis finds the prepared optimal solution for the test case.