Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 9 additions & 6 deletions simba/station_optimizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ def __init__(self, sched: schedule.Schedule, scen: scenario.Scenario, args,
self.base_stations = self.electrified_stations.copy()
self.base_electrified_station_set = set()

# Generator for producing combinations for the brute force algorithm
self.brute_generator = dict()

# stations which are included can not be included again. Therefore they go into
# the set of not possible stations together with excluded stations
self.not_possible_stations = config.inclusion_stations.union(config.exclusion_stations)
Expand Down Expand Up @@ -626,8 +629,7 @@ def node_to_tree(self, delta_base_energy):

@opt_util.time_it
def choose_station_brute(self, station_eval,
pre_optimized_set=None, missing_energy=0,
gens=dict()):
pre_optimized_set=None, missing_energy=0):
""" Return a possible set of stations to electrify which has not been tried yet.

Gives back a possible set of stations to electrify which shows potential and has not been
Expand All @@ -639,19 +641,20 @@ def choose_station_brute(self, station_eval,
:type pre_optimized_set: set
:param missing_energy: missing energy in this branch before electrification
:type missing_energy: float
:param gens: generators for brute force generation
:type gens: dict
:return: combination of stations to electrify and
false since this function does not support recursive calling
:raises AllCombinationsCheckedException: If all combinations have been checked
"""

station_ids = [x[0] for x in station_eval]
# get combination generator (reuse if possible)
generator_key = f"{station_ids} {len(pre_optimized_set) - 1}"
try:
generator = gens[str(station_ids) + str(len(pre_optimized_set) - 1)]
generator = self.brute_generator[generator_key]
except KeyError:
# create new generator
generator = opt_util.combination_generator(station_ids, len(pre_optimized_set) - 1)
gens[str(station_ids) + str(len(pre_optimized_set) - 1)] = generator
self.brute_generator[generator_key] = generator
station_eval_dict = {stat[0]: stat[1] for stat in station_eval}
for comb in generator:
node_name = opt_util.stations_hash(comb)
Expand Down
75 changes: 30 additions & 45 deletions tests/test_station_optimization.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,25 +173,48 @@ def test_fast_calculations_and_events(self):
sopt.create_charging_curves()
# remove none values from socs in the vehicle_socs
sopt.replace_socs_from_none_to_value()

# Let the optimizer approximate the socs, with the two stations which are electrified
vehicle_socs_fast = sopt.timeseries_calc(list(sched.stations.keys()))
for vehicle, socs in scen.vehicle_socs.items():
# Optimizer and SpiceEV should result in approximately the same socs
assert vehicle_socs_fast[vehicle][-1] == pytest.approx(socs[-1], 0.01, abs=0.01)

events = sopt.get_low_soc_events(soc_data=vehicle_socs_fast, rel_soc=True)
# The scenario was generated to create a single low soc event, i.e. lower than 0
assert 1 == sum(min(socs) < 0 for socs in scen.vehicle_socs.values())
assert len(events) == 1
e = events[0]
e1 = copy(e)
vehicle_socs_reduced = {vehicle: [soc - 1 for soc in socs] for vehicle, socs in
scen.vehicle_socs.items()}
# The vehicle socs were reduced. Now both vehicles have socs below 0
assert 2 == sum(min(socs) < 0 for socs in vehicle_socs_reduced.values())

# Depending on the option "rel_soc" a relative soc is used or not
events = sopt.get_low_soc_events(soc_data=vehicle_socs_reduced, rel_soc=False)
# Without a relative soc, there should be two low soc events
assert len(events) == 2

events = sopt.get_low_soc_events(soc_data=vehicle_socs_reduced, rel_soc=True)
# The optimizer should only show a single event, since the low socs which was artificially
# created would not be low if the vehicle started with a "full" soc.
assert len(events) == 1

e2 = events[0]
# Compare events found with and without rel_soc:
# The soc shift does not change the timestep, so both events refer to the same occurrence.
assert e1.start_idx == e2.start_idx
assert e1.end_idx == e2.end_idx
assert e1.min_soc == e2.min_soc

vehicle_socs_increased = {vehicle: [min(soc + abs(e1.min_soc) - 0.1, 1) for soc in socs] for
vehicle, socs in scen.vehicle_socs.items()}
new_low_soc = -0.01
# This increases the soc, so that only a single time step is negative with new_low_soc.
# Higher socs are increased.
# Since the soc stays at 1 for longer, the start index should change
vehicle_socs_increased = {
vehicle: [min(soc + abs(e1.min_soc) + new_low_soc, 1) for soc in socs] for
vehicle, socs in scen.vehicle_socs.items()}
events = sopt.get_low_soc_events(soc_data=vehicle_socs_increased, rel_soc=True)
e3 = events[0]
assert e1.start_idx != e3.start_idx
Expand Down Expand Up @@ -253,49 +276,11 @@ def test_schedule_consistency(self):
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.

# The Test case is a a 3 star like network with two rotations which are negative without
# electrification.
# Rotation 1:
# Depot --> Station-1 ---> Station-2 -->Station-1-->Depot
# Rotation 2:
# Depot --> Station-1 ---> Station-3 -->Station-1-->Depot

# Greedy optimization will electrify Station-1 first since it is helpful for both rotations.
# Since electrifying Station-1 is not enough to electrify either rotations, greedy
# optimization will electrify Station-2 and Station-3 as well. Deep Analysis will expand
# the checked nodes --> It should find that electrifying Station-2 and Station-3 is enough
# without electrifying Station-1

# """
# trips_file_name = "trips_for_optimizer_deep.csv"
# data_container, args = self.generate_datacontainer_args(trips_file_name)
# data_container.stations_data = {}
# args.preferred_charging_type = "oppb"
# for trip_d in data_container.trip_data:
# trip_d["distance"] *= 15
# sched, scen = self.generate_schedule_scenario(args, data_container)
# config_path = example_root / "default_optimizer.cfg"
# conf = opt_util.read_config(config_path)

# assert len(sched.get_negative_rotations(scen)) == 2

# solvers = ["quick", "spiceev"]
# node_choices = ["step-by-step", "brute"]
# conf.opt_type = "deep"
# for solver in solvers:
# for node_choice in node_choices:
# conf.solver = solver
# conf.node_choice = node_choice
# opt_sched, opt_scen = run_optimization(conf, sched=sched, scen=scen, args=args)
# assert len(opt_sched.get_negative_rotations(opt_scen)) == 0
# assert "Station-1" not in opt_sched.stations
# assert "Station-2" in opt_sched.stations
# assert "Station-3" in opt_sched.stations

@pytest.mark.parametrize("solver,node_choice", [("quick", "step-by-step"), ("quick", "brute"), ("spiceev", "step-by-step"), ("spiceev", "brute")])
@pytest.mark.parametrize("solver,node_choice",
[("quick", "step-by-step"),
("quick", "brute"),
("spiceev", "step-by-step"),
("spiceev", "brute")])
def test_deep_optimization(self, solver, node_choice):
trips_file_name = "trips_for_optimizer_deep.csv"
data_container, args = self.generate_datacontainer_args(trips_file_name)
Expand Down
Loading