Skip to content

Commit

Permalink
Merge pull request #135 from rl-institut/fix/optimization_return_sche…
Browse files Browse the repository at this point in the history
…dule

Fix: Station Optimization now returns original rotation set
  • Loading branch information
stefansc1 authored Aug 10, 2023
2 parents bc109b6 + 18e1836 commit 9dba82d
Show file tree
Hide file tree
Showing 3 changed files with 72 additions and 27 deletions.
48 changes: 25 additions & 23 deletions data/examples/default_optimizer.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,17 @@ debug_level = 1
console_level = 99

#SCENARIO
# Use "" for ids and not ''
# these rotations are excluded from optimization. they will not be simulated
# Use "" for ids and not ''.
# These rotations are excluded from optimization. They will not be simulated.
exclusion_rots = []

# these stations are excluded from optimization. optimization will not electrify them
# These stations are excluded from optimization. optimization will not electrify them.
exclusion_stations = []
# optimization will electrify these stations.
# if using inclusion stations, scenario should be rebased
# 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}
# rotations which drop below this value are considered not electrified (default value is 0)
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

#OPTIMIZER
Expand All @@ -31,42 +31,44 @@ solver = quick

# Optimization procedure is developed from the input schedule and scenario. If the scenario is changed
# through this configuration rebasing should be set to true. This will simulate the scenario again with the
# given settings
# given settings.
rebase_scenario = False

# the rebased scenario can be pickled for analysis or repeated use
# The rebased scenario can be pickled for analysis or repeated use.
pickle_rebased = False
# the name of this pickle file can be defined here
# The name of this pickle file can be defined here.
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
# Should only be opportunity vehicles be rebased, since this optimization is not meant for depot chargers?
# The optimization will return ALL original rotations of the input in all cases.
run_only_oppb = True
# number of stations before optimal solution, where branch is checked for pruning
# number of stations before optimal solution, where branch is checked for pruning.
pruning_threshold = 3

# Optimization type greedy runs the optimization a single time as greedy as impossible
# Deep repeatedly searches for promising nodes but only new nodes which have not
# been checked before.
# "greedy" or "deep" without ""
# Deep repeatedly searches for promising nodes but only new nodes which have not been checked before.
# "greedy" or "deep" without "".
opt_type = greedy

# For Deep optimization only
# For Deep optimization only:
#############################
# How should the deep optimization choose the nodes.
# Brute is only recommended in smaller systems
# "step-by-step" or "brute" without ""
# Brute is only recommended in smaller systems.
# "step-by-step" or "brute" without "".
node_choice = step-by-step
# How many combinations is the deep method allowed to check
# How many combinations is the deep method allowed to check?
max_brute_loop = 300
# Factor with which the potential evaluation is multiplied before comparing it to the missing energy. A low estimation
# threshold will lead to a more conservative approach in dismissing branches
# threshold will lead to a more conservative approach in dismissing branches.
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
# Removing impossible rotations leads to a quick calculation estimating if rotations are impossible.
# If they are deemed as impossible, they are not optimized but discarded during optimization.
# The optimization will return ALL original rotations of the input in all cases.
remove_impossible_rotations = True
# Check if stations are mandatory for a fully electrified system. If they are, include them
# Check if stations are mandatory for a fully electrified system. If they are, include them.
check_for_must_stations = False


Expand Down
16 changes: 13 additions & 3 deletions simba/station_optimization.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
""" Optimization that tries minimizing the amount of electrified stations to achieve full
electrification.
"""

from copy import deepcopy
import json
import sys
from pathlib import Path
Expand Down Expand Up @@ -101,6 +100,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)

Expand All @@ -118,8 +119,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" == 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" == 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
Expand Down Expand Up @@ -187,6 +190,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)

Expand Down
35 changes: 34 additions & 1 deletion tests/test_station_optimization.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from copy import copy
from copy import copy, deepcopy
import json
from pathlib import Path
import pytest
Expand Down Expand Up @@ -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.
Expand Down

0 comments on commit 9dba82d

Please sign in to comment.