From 6e68af492346dd68fd73093b4fd0b4066d296dfd Mon Sep 17 00:00:00 2001 From: "stefan.schirmeister" Date: Wed, 31 Jul 2024 12:37:37 +0200 Subject: [PATCH 1/5] add load_pickle mode and create_pickle_in_report option --- data/examples/simba.cfg | 7 ++++++- requirements.txt | 1 + simba/report.py | 11 +++++++++++ simba/simulate.py | 42 ++++++++++++++++++++++++++++++----------- simba/util.py | 7 ++++++- 5 files changed, 55 insertions(+), 13 deletions(-) diff --git a/data/examples/simba.cfg b/data/examples/simba.cfg index 82ef0801..c6c5eb08 100644 --- a/data/examples/simba.cfg +++ b/data/examples/simba.cfg @@ -38,7 +38,7 @@ rotation_filter = data/examples/rotation_filter.csv # recombination splits and merges rotations to use the optimal number of busses # report generates simulation output files, including costs. mode = ["sim", "report"] -#mode = ["sim", "neg_depb_to_oppb", "service_optimization", "station_optimization", "remove_negative", "split_negative_depb", "report"] +#mode = ["sim", "load_pickle", "neg_depb_to_oppb", "service_optimization", "station_optimization", "remove_negative", "split_negative_depb", "report"] ##### Flags ##### ### Activate optional functions ### @@ -58,6 +58,11 @@ show_plots = false rotation_filter_variable = null # Write a new trips.csv during report mode to output directory? (default: false) create_trips_in_report = false +# Pickle current schedule and scenario during report mode +# create_pickle_in_report = false +# Load this pickle file, expects load_pickle as first mode +# load_pickle = "example.pkl" + ##### Charging strategy ##### # Preferred charging type. Options: depb, oppb (default: depb) diff --git a/requirements.txt b/requirements.txt index 25e02659..6b80e00d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,3 +2,4 @@ numpy >= 1.12.0 matplotlib -e git+https://github.com/rl-institut/spice_ev.git@dev#egg=spice_ev pandas +dill \ No newline at end of file diff --git a/simba/report.py b/simba/report.py index f2a2a364..dc5bbc6e 100644 --- a/simba/report.py +++ b/simba/report.py @@ -2,10 +2,12 @@ import csv import datetime import logging +import dill as pickle import re from typing import Iterable import matplotlib.pyplot as plt +from simba.trip import Trip from spice_ev.report import aggregate_global_results, plot, generate_reports @@ -294,6 +296,15 @@ def generate(schedule, scenario, args): file_path = args.results_directory / "trips.csv" write_csv(generate_trips_timeseries_data(schedule), file_path) + if vars(args).get('create_pickle_in_report', False): + file_path = args.results_directory / "scenario.pkl" + with file_path.open('wb') as f: + pickle.dump({ + "schedule": schedule, + "scenario": scenario, + "consumption": Trip.consumption, + }, f) + # summary of used vehicle types and all costs if args.cost_calculation: file_path = args.results_directory / "summary_vehicles_costs.csv" diff --git a/simba/simulate.py b/simba/simulate.py index 67092284..2d590e47 100644 --- a/simba/simulate.py +++ b/simba/simulate.py @@ -1,4 +1,5 @@ import logging +import dill as pickle import traceback from simba import report, optimization, util @@ -22,8 +23,16 @@ def simulate(args): :return: final schedule and scenario :rtype: tuple """ - schedule = pre_simulation(args) - scenario = schedule.run(args) + args.cost_parameters = load_cost_parameters_file(args.cost_parameters_file) + if vars(args).get("load_pickle"): + # load pickle file: skip pre_simulation + assert args.mode[0] == "load_pickle", "Load pickle: first mode must be load_pickle" + # schedule and scenario read out from pickle file in first mode + schedule = None + scenario = "pickle" # must not be None + else: + schedule = pre_simulation(args) + scenario = schedule.run(args) return modes_simulation(schedule, scenario, args) @@ -54,15 +63,6 @@ def pre_simulation(args): raise Exception(f"Path to electrified stations ({args.electrified_stations}) " "does not exist. Exiting...") - # load cost parameters - if args.cost_parameters_file is not None: - try: - with open(args.cost_parameters_file, encoding='utf-8') as f: - args.cost_parameters = util.uncomment_json_file(f) - except FileNotFoundError: - raise Exception(f"Path to cost parameters ({args.cost_parameters_file}) " - "does not exist. Exiting...") - # setup consumption calculator that can be accessed by all trips Trip.consumption = Consumption( vehicle_types, @@ -81,6 +81,18 @@ def pre_simulation(args): return schedule +def load_cost_parameters_file(file_path): + # load cost parameters + # needed for both normal simulation and load_pickle + if file_path is None: + return None + try: + with open(file_path, encoding='utf-8') as f: + return util.uncomment_json_file(f) + except FileNotFoundError: + raise Exception(f"Path to cost parameters ({file_path}) does not exist. Exiting...") + + def modes_simulation(schedule, scenario, args): """ Run the mode(s) specified in config. @@ -226,6 +238,14 @@ def split_negative_depb(schedule, scenario, args, _i): scenario = recombined_schedule.run(args) return recombined_schedule, scenario + def load_pickle(_schedule=None, _scenario=None, args=None, _i=None): + with open(args.load_pickle, 'rb') as f: + unpickle = pickle.load(f) + schedule = unpickle["schedule"] + scenario = unpickle["scenario"] + Trip.consumption = unpickle["consumption"] + return schedule, scenario + def report(schedule, scenario, args, i): if args.output_directory is None: return schedule, scenario diff --git a/simba/util.py b/simba/util.py index 2c2ffe61..10e7ce5a 100644 --- a/simba/util.py +++ b/simba/util.py @@ -279,12 +279,13 @@ def get_args(): # #### Modes ##### mode_choices = [ - 'sim', 'neg_depb_to_oppb', 'neg_oppb_to_depb', 'service_optimization', + 'sim', 'load_pickle', 'neg_depb_to_oppb', 'neg_oppb_to_depb', 'service_optimization', 'station_optimization', 'remove_negative', 'split_negative_depb', 'report'] parser.add_argument('--mode', default=['sim', 'report'], nargs='*', choices=mode_choices, help=f"Specify what you want to do. Choose one or more from \ {', '.join(mode_choices)}. \ sim runs a single simulation with the given inputs. \ + load_pickle loads files specified in load_pickle argument. \ neg_depb_to_oppb changes charging type of negative depb rotations. \ neg_oppb_to_depb changes charging type of negative oppb rotations. \ service optimization finds the largest set of electrified rotations. \ @@ -307,6 +308,10 @@ def get_args(): parser.add_argument('--create-scenario-file', help='Write scenario.json to file') parser.add_argument('--create-trips-in-report', action='store_true', help='Write a trips.csv during report mode') + parser.add_argument('--create-pickle-in-report', action='store_true', + help='Pickle current schedule and scenario during report mode') + parser.add_argument('--load-pickle', + help='Load given pickle file, expects load_pickle as first mode') parser.add_argument('--rotation-filter-variable', default=None, choices=[None, 'include', 'exclude'], help='set mode for filtering schedule rotations') From d19b28af067feb787e6abe0765939d34c275172f Mon Sep 17 00:00:00 2001 From: "stefan.schirmeister" Date: Wed, 31 Jul 2024 13:12:04 +0200 Subject: [PATCH 2/5] add load_pickle to docs, minor doc fixes --- docs/source/modes.rst | 11 ++++++++++- docs/source/simulation_parameters.rst | 16 ++++++++++++++++ simba/schedule.py | 1 + 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/docs/source/modes.rst b/docs/source/modes.rst index 12640d35..6280e083 100644 --- a/docs/source/modes.rst +++ b/docs/source/modes.rst @@ -43,6 +43,15 @@ Simple Simulation The simple simulation case is the default mode. Its usage is explained in :ref:`Getting Started`. Every chain of modes starts with a simple simulation, even if it is not explicitly listed in the modes. The simulation takes the scenario as is. No parameters will be adjusted, optimized or changed in any way. The charging type for each vehicle is read from the rotation information from the trips.csv if this data is included. If the data is not included *preferred_charging_type* from the config file is used, as long as the provided vehicles data provides the preferred_charging_type for the specified vehicle type. +Load Pickle +----------- + +:: + + mode = ["load_pickle"] + +Instead of simulating given scenario, read in pickle file instead. Needs the `load_pickle` option to point to a pickle file. If the `load_pickle` option is set, `load_pickle` must be the first mode. However, this mode may come in later in the mode chain again, reloading schedule and scenario from file. Options in config file that are stored in schedule are ignored. + .. _neg_depb_to_oppb: Negative Depot to Opportunity Charger @@ -79,7 +88,7 @@ Now, only rotations are left that are non-negative when viewed alone, but might In the end, the largest number of rotations that produce a non-negative result when taken together is returned as the optimized scenario. Recombination: split negative depot rotations into smaller rotations -------------- +-------------------------------------------------------------------- :: mode = split_negative_depb diff --git a/docs/source/simulation_parameters.rst b/docs/source/simulation_parameters.rst index de8189c0..2c96fa18 100644 --- a/docs/source/simulation_parameters.rst +++ b/docs/source/simulation_parameters.rst @@ -80,6 +80,22 @@ The example (data/simba.cfg) contains parameter descriptions which are explained - false - Boolean - If activated, plots are displayed with every run of :ref:`report` mode + * - rotation_filter_variable + - null + - string + - How to filter rotations according to file 'rotation_filter': options are "include" (whitelist), "exclude" (blacklist), null (ignore) + * - create_trips_in_report + - false + - Boolean + - Write a new trips.csv during report mode to output directory? + * - create_pickle_in_report + - false + - Boolean + - Pickle current schedule and scenario during report mode + * - load_pickle + - Optional, no default given + - Path to pickle file + - Load schedule and scenario from this pickle file, expects load_pickle as first mode * - preferred_charging_type - depb diff --git a/simba/schedule.py b/simba/schedule.py index 98550307..17103650 100644 --- a/simba/schedule.py +++ b/simba/schedule.py @@ -921,6 +921,7 @@ def update_csv_file_info(file_info, gc_name): - set grid_connector_id - update csv_file path - set start_time and step_duration_s from CSV information if not given + :param file_info: csv information from electrified station :type file_info: dict :param gc_name: station name From 67552ee803b165c5bb145a04b27b99e1e780943e Mon Sep 17 00:00:00 2001 From: "stefan.schirmeister" Date: Thu, 19 Sep 2024 09:58:17 +0200 Subject: [PATCH 3/5] load_pickle: cost params mutable, fix mode check, add test --- simba/simulate.py | 11 +++++++- tests/test_simulate.py | 59 ++++++++++++++++++++++++++++-------------- 2 files changed, 50 insertions(+), 20 deletions(-) diff --git a/simba/simulate.py b/simba/simulate.py index 61d2af21..9a5baa2d 100644 --- a/simba/simulate.py +++ b/simba/simulate.py @@ -25,7 +25,11 @@ def simulate(args): """ if vars(args).get("load_pickle"): # load pickle file: skip pre_simulation - assert args.mode[0] == "load_pickle", "Load pickle: first mode must be load_pickle" + if isinstance(args.mode, list): + first_mode = args.mode[0] + else: + first_mode = args.mode + assert first_mode == "load_pickle", "Load pickle: first mode must be load_pickle" # schedule and scenario read out from pickle file in first mode # DataContainer is part of schedule schedule = None @@ -271,6 +275,11 @@ def load_pickle(_schedule=None, _scenario=None, args=None, _i=None): unpickle = pickle.load(f) schedule = unpickle["schedule"] scenario = unpickle["scenario"] + # DataContainer is part of schedule + # However, cost parameters are supposed to be mutable after loading from pickle + if args.cost_parameters_path: + schedule.data_container.add_cost_parameters_from_json(args.cost_parameters_path) + return schedule, scenario @staticmethod diff --git a/tests/test_simulate.py b/tests/test_simulate.py index 030c7ff4..1ff4bc33 100644 --- a/tests/test_simulate.py +++ b/tests/test_simulate.py @@ -65,6 +65,7 @@ def get_args(self): parser.set_defaults(**self.NON_DEFAULT_VALUES) # get all args with default values args, _ = parser.parse_known_args() + util.mutate_args_for_spiceev(args) return args def test_basic(self): @@ -118,7 +119,6 @@ def test_mode_service_opt(self): # all rotations remain negative args.desired_soc_deps = 0 args.desired_soc_opps = 0 - args.ALLOW_NEGATIVE_SOC = True simulate(args) def test_mode_change_charge_type(self): @@ -127,14 +127,12 @@ def test_mode_change_charge_type(self): args.mode = "neg_oppb_to_depb" args.desired_soc_deps = 0 args.desired_soc_opps = 0 - args.ALLOW_NEGATIVE_SOC = True simulate(args) def test_mode_remove_negative(self): args = self.get_args() args.mode = "remove_negative" args.desired_soc_deps = 0 - args.ALLOW_NEGATIVE_SOC = True simulate(args) def test_mode_report(self, tmp_path): @@ -158,7 +156,6 @@ def test_empty_report(self, tmp_path): args = self.get_args() args.mode = ["remove_negative", "report"] args.desired_soc_deps = 0 - args.ALLOW_NEGATIVE_SOC = True args.cost_calculation = True args.output_directory = tmp_path args.show_plots = False @@ -168,26 +165,50 @@ def test_empty_report(self, tmp_path): def test_create_trips_in_report(self, tmp_path): # create_trips_in_report option: must generate valid input trips.csv - args_dict = vars(self.get_args()) - update_dict = { - "mode": ["report"], - "desired_soc_deps": 0, - "ALLOW_NEGATIVE_SOC": True, - "cost_calculation": False, - "output_directory": tmp_path, - "show_plots": False, - "create_trips_in_report": True, - } - args_dict.update(update_dict) + args = self.get_args() + args.mode = "report" + args.desired_soc_deps = 0 + args.cost_calculation = False + args.output_directory = tmp_path + args.show_plots = False + args.create_trips_in_report = True # simulate base scenario, report generates new trips.csv in (tmp) output with warnings.catch_warnings(): warnings.simplefilter("ignore") - simulate(Namespace(**args_dict)) + simulate(args) # new simulation with generated trips.csv - args_dict = vars(self.get_args()) - args_dict["input_schedule"] = tmp_path / "report_1/trips.csv" - simulate(Namespace(**(args_dict))) + args = self.get_args() + args.input_schedule = tmp_path / "report_1/trips.csv" + simulate(args) + + def test_pickle(self, tmp_path): + # create pickle in report + args = self.get_args() + args.mode = "report" + args.show_plots = False + args.cost_calculation = False + args.output_directory = tmp_path + args.create_pickle_in_report = True + simulate(args) + pickle_path = tmp_path / "report_1/scenario.pkl" + assert pickle_path.exists() + + # read in pickle for new simulation + args.mode = "load_pickle" + args.output_directory = None + args.load_pickle = pickle_path + args.cost_parameters_path = None # keep original cost parameters + schedule, _ = simulate(args) + assert "foo" not in schedule.data_container.cost_parameters_data + + # replace cost parameters after loading pickle + args.mode = ["load_pickle"] + args.cost_parameters_path = tmp_path / "cost_params.json" + with open(args.cost_parameters_path, "w") as f: + f.write('{"foo": 1}') + schedule, _ = simulate(args) + assert schedule.data_container.cost_parameters_data["foo"] == 1 def test_mode_recombination(self): args = self.get_args() From 196b382a14e5b60c751998dde64d0a5c14d08005 Mon Sep 17 00:00:00 2001 From: "stefan.schirmeister" Date: Wed, 25 Sep 2024 15:29:57 +0200 Subject: [PATCH 4/5] pickle: add minimal example configs --- data/examples/minimal.cfg | 9 ++++ data/examples/minimal_pickle.cfg | 3 ++ .../vehicle_types_constant_mileage.json | 43 +++++++++++++++++++ simba/util.py | 12 ++++-- 4 files changed, 63 insertions(+), 4 deletions(-) create mode 100644 data/examples/minimal.cfg create mode 100644 data/examples/minimal_pickle.cfg create mode 100644 data/examples/vehicle_types_constant_mileage.json diff --git a/data/examples/minimal.cfg b/data/examples/minimal.cfg new file mode 100644 index 00000000..fdc1d90f --- /dev/null +++ b/data/examples/minimal.cfg @@ -0,0 +1,9 @@ +### Input and output files and paths ### +# Input file containing trip information +schedule_path = data/examples/trips_example.csv +# Electrified stations +electrified_stations_path = data/examples/electrified_stations.json +# Vehicle types +# Not strictly needed (defaults to: ./data/examples/vehicle_types.json), +# but if vehicle types have constant mileage, no other mileage files are needed +vehicle_types_path = data/examples/vehicle_types_constant_mileage.json diff --git a/data/examples/minimal_pickle.cfg b/data/examples/minimal_pickle.cfg new file mode 100644 index 00000000..60d70a48 --- /dev/null +++ b/data/examples/minimal_pickle.cfg @@ -0,0 +1,3 @@ +mode = ["load_pickle", "report"] +# Load this pickle file, expects load_pickle as first mode +load_pickle = scenario.pkl diff --git a/data/examples/vehicle_types_constant_mileage.json b/data/examples/vehicle_types_constant_mileage.json new file mode 100644 index 00000000..decaccde --- /dev/null +++ b/data/examples/vehicle_types_constant_mileage.json @@ -0,0 +1,43 @@ +{ + "AB": { // vehicle_type + "depb": { // charging_type + "name": "articulated bus - depot charging", // long name + "capacity": 250, // battery capacity in kWh + "charging_curve": [[0, 150], [0.8, 150], [1, 15]], // charging curve [SoC, kW] + "min_charging_power": 0, // min charging power in KW + "v2g": false, // Is vehicle capable of vehicle to grid? + "mileage": 1.0, // mileage in kWh/km or link to consumption.csv + "battery_efficiency": 0.95, // optional. default: 0.95 + "idle_consumption" : 0.0 // [kWh/h] consumption while standing during a rotation + }, + "oppb": { + "name": "articulated bus - opportunity charging", + "capacity": 150, + "charging_curve": [[0, 250], [0.8, 250], [1, 25]], + "min_charging_power": 0, + "v2g": false, + "mileage": 1.0, + "idle_consumption" : 0.0 + } + }, + "SB": { + "depb": { + "name": "solo bus - depot charging", + "capacity": 250, + "charging_curve": [[0, 150], [0.8, 150], [1, 15]], + "min_charging_power": 0, + "v2g": false, + "mileage": 1.2, + "idle_consumption" : 0.0 + }, + "oppb": { + "name": "solo bus - opportunity charging", + "capacity": 150, + "charging_curve": [[0, 250], [0.8, 250], [1, 25]], + "min_charging_power": 0, + "v2g": false, + "mileage": 1.1, + "idle_consumption" : 0.0 + } + } +} diff --git a/simba/util.py b/simba/util.py index f3c16b7b..6f557aad 100644 --- a/simba/util.py +++ b/simba/util.py @@ -446,10 +446,14 @@ def get_args(): # rename special options args.timing = args.eta - mandatory_arguments = ["schedule_path", "electrified_stations_path"] - missing = [a for a in mandatory_arguments if vars(args).get(a) is None] - if missing: - raise Exception("The following arguments are required: {}".format(", ".join(missing))) + # check mandatory arguments + if not vars(args).get("load_pickle"): + mandatory_arguments = ["schedule_path", "electrified_stations_path"] + if "load_pickle" in args.mode: + mandatory_arguments.append("load_pickle") + missing = [a for a in mandatory_arguments if vars(args).get(a) is None] + if missing: + raise Exception("The following arguments are required: {}".format(", ".join(missing))) return args From 465074e0dcfb646cfeae722807cd1fee69e6ed6c Mon Sep 17 00:00:00 2001 From: "stefan.schirmeister" Date: Wed, 11 Dec 2024 11:30:53 +0100 Subject: [PATCH 5/5] changes to pickle mode - enforce modes list in util.get_args, remove cast in simulate - change argument name from load_pickle to load_pickle_path - if load_pickle is first mode, there must be further modes --- data/examples/simba.cfg | 2 +- docs/source/modes.rst | 4 ++- docs/source/simulation_parameters.rst | 2 +- simba/simulate.py | 23 +++++----------- simba/util.py | 12 ++++++--- tests/test_simulate.py | 38 +++++++++++++-------------- 6 files changed, 39 insertions(+), 42 deletions(-) diff --git a/data/examples/simba.cfg b/data/examples/simba.cfg index 80a4a960..713ef66a 100644 --- a/data/examples/simba.cfg +++ b/data/examples/simba.cfg @@ -66,7 +66,7 @@ create_trips_in_report = false # Pickle current schedule and scenario during report mode # create_pickle_in_report = false # Load this pickle file, expects load_pickle as first mode -# load_pickle = "example.pkl" +# load_pickle_path = "example.pkl" ##### Charging strategy ##### diff --git a/docs/source/modes.rst b/docs/source/modes.rst index 6f6bfd2c..9963afca 100644 --- a/docs/source/modes.rst +++ b/docs/source/modes.rst @@ -50,7 +50,9 @@ Load Pickle mode = ["load_pickle"] -Instead of simulating given scenario, read in pickle file instead. Needs the `load_pickle` option to point to a pickle file. If the `load_pickle` option is set, `load_pickle` must be the first mode. However, this mode may come in later in the mode chain again, reloading schedule and scenario from file. Options in config file that are stored in schedule are ignored. +While normally scenarios are generated from a trips file and ancillary data, with this mode a previously simulated scenario can be loaded from a pickle file instead. This scenario already contains results. Modes can be chained after load_pickle in the usual fashion. Multiple "load_pickle" modes per chain are allowed, reloading the scenario again. +This mode needs the `load_pickle_path` option to point to a pickle file. If the `load_pickle_path` option is set, `load_pickle` must be the first mode. +All options in the config file (except for cost parameters) that are stored in schedule are ignored. .. _neg_depb_to_oppb: diff --git a/docs/source/simulation_parameters.rst b/docs/source/simulation_parameters.rst index bf0a2b3e..6b1820da 100644 --- a/docs/source/simulation_parameters.rst +++ b/docs/source/simulation_parameters.rst @@ -104,7 +104,7 @@ The example (data/simba.cfg) contains parameter descriptions which are explained - false - Boolean - Pickle current schedule and scenario during report mode - * - load_pickle + * - load_pickle_path - Optional, no default given - Path to pickle file - Load schedule and scenario from this pickle file, expects load_pickle as first mode diff --git a/simba/simulate.py b/simba/simulate.py index 08bb731d..29f9e4f9 100644 --- a/simba/simulate.py +++ b/simba/simulate.py @@ -24,17 +24,13 @@ def simulate(args): :return: final schedule and scenario :rtype: tuple """ - if vars(args).get("load_pickle"): + if vars(args).get("load_pickle_path"): # load pickle file: skip pre_simulation - if isinstance(args.mode, list): - first_mode = args.mode[0] - else: - first_mode = args.mode - assert first_mode == "load_pickle", "Load pickle: first mode must be load_pickle" - # schedule and scenario read out from pickle file in first mode - # DataContainer is part of schedule - schedule = None - scenario = "pickle" # must not be None + assert len(args.mode) > 1, "Load pickle: follow-up modes required, for example report" + assert args.mode[0] == "load_pickle", "Load pickle: first mode must be load_pickle" + # read out schedule and scenario as first mode + schedule, scenario = Mode.load_pickle(None, None, args=args) + args.mode = args.mode[1:] else: # DataContainer stores various input data data_container = DataContainer().fill_with_args(args) @@ -94,11 +90,6 @@ def modes_simulation(schedule, scenario, args): :rtype: tuple :raises Exception: if args.propagate_mode_errors is set, re-raises error instead of continuing """ - - if not isinstance(args.mode, list): - # backwards compatibility: run single mode - args.mode = [args.mode] - for i, mode in enumerate(args.mode): # scenario must be set from initial run / prior modes assert scenario is not None, f"Scenario became None after mode {args.mode[i-1]} (index {i})" @@ -274,7 +265,7 @@ def split_negative_depb(schedule, scenario, args, _i): @staticmethod def load_pickle(_schedule=None, _scenario=None, args=None, _i=None): - with open(args.load_pickle, 'rb') as f: + with open(args.load_pickle_path, 'rb') as f: unpickle = pickle.load(f) schedule = unpickle["schedule"] scenario = unpickle["scenario"] diff --git a/simba/util.py b/simba/util.py index a0a48b8c..fa44ff70 100644 --- a/simba/util.py +++ b/simba/util.py @@ -447,11 +447,15 @@ def get_args(): # rename special options args.timing = args.eta + # enforce modes to be a list + if not isinstance(args.mode, list): + args.mode = [args.mode] + # check mandatory arguments - if not vars(args).get("load_pickle"): + if not vars(args).get("load_pickle_path"): mandatory_arguments = ["schedule_path", "electrified_stations_path"] if "load_pickle" in args.mode: - mandatory_arguments.append("load_pickle") + mandatory_arguments.append("load_pickle_path") missing = [a for a in mandatory_arguments if vars(args).get(a) is None] if missing: raise Exception("The following arguments are required: {}".format(", ".join(missing))) @@ -494,7 +498,7 @@ def get_parser(): help=f"Specify what you want to do. Choose one or more from \ {', '.join(mode_choices)}. \ sim runs a single simulation with the given inputs. \ - load_pickle loads files specified in load_pickle argument. \ + load_pickle loads files specified in load_pickle_path argument. \ neg_depb_to_oppb changes charging type of negative depb rotations. \ neg_oppb_to_depb changes charging type of negative oppb rotations. \ service optimization finds the largest set of electrified rotations. \ @@ -521,7 +525,7 @@ def get_parser(): help='Write a trips.csv during report mode') parser.add_argument('--create-pickle-in-report', action='store_true', help='Pickle current schedule and scenario during report mode') - parser.add_argument('--load-pickle', + parser.add_argument('--load-pickle-path', help='Load given pickle file, expects load_pickle as first mode') parser.add_argument('--optimizer-config-path', default=None, diff --git a/tests/test_simulate.py b/tests/test_simulate.py index 3f8b98bd..620cab93 100644 --- a/tests/test_simulate.py +++ b/tests/test_simulate.py @@ -98,7 +98,7 @@ def test_unknown_mode(self, caplog): # try to run a mode that does not exist # Get the parser from util. This way the test is directly coupled to the parser arguments args = self.get_args() - args.mode = "foo" + args.mode = ["foo"] with caplog.at_level(logging.ERROR): simulate(args) assert caplog.record_tuples == [('root', logging.ERROR, 'Unknown mode foo ignored')] @@ -128,22 +128,22 @@ def test_modes(self, caplog, tmp_path): args.propagate_mode_errors = True schedule, scenario = simulate(args) with caplog.at_level(logging.ERROR): - args.mode = "sim" + args.mode = ["sim"] modes_simulation(schedule, scenario, args) - args.mode = "sim_greedy" + args.mode = ["sim_greedy"] modes_simulation(schedule, scenario, args) - args.mode = "neg_depb_to_oppb" + args.mode = ["neg_depb_to_oppb"] modes_simulation(schedule, scenario, args) - args.mode = "split_negative_depb" + args.mode = ["split_negative_depb"] modes_simulation(schedule, scenario, args) - args.mode = "station_optimization_single_step" + args.mode = ["station_optimization_single_step"] modes_simulation(schedule, scenario, args) - args.mode = "station_optimization" + args.mode = ["station_optimization"] modes_simulation(schedule, scenario, args) assert len(caplog.record_tuples) == 0 @@ -152,7 +152,7 @@ def test_mode_service_opt(self): # basic run # Get the parser from util. This way the test is directly coupled to the parser arguments args = self.get_args() - args.mode = "service_optimization" + args.mode = ["service_optimization"] simulate(args) # all rotations remain negative args.desired_soc_deps = 0 @@ -162,21 +162,21 @@ def test_mode_service_opt(self): def test_mode_change_charge_type(self): # all rotations remain negative args = self.get_args() - args.mode = "neg_oppb_to_depb" + args.mode = ["neg_oppb_to_depb"] args.desired_soc_deps = 0 args.desired_soc_opps = 0 simulate(args) def test_mode_remove_negative(self): args = self.get_args() - args.mode = "remove_negative" + args.mode = ["remove_negative"] args.desired_soc_deps = 0 simulate(args) def test_mode_report(self, tmp_path): # report with cost calculation, write to tmp args = self.get_args() - args.mode = "report" + args.mode = ["report"] args.cost_calculation = True args.output_path = tmp_path args.strategy_deps = "balanced" @@ -222,7 +222,7 @@ def test_empty_report(self, tmp_path): def test_extended_plot(self, tmp_path): args = self.get_args() - args.mode = "report" + args.mode = ["report"] args.output_path = tmp_path args.show_plots = False args.extended_output_plots = True @@ -239,7 +239,7 @@ def test_extended_plot(self, tmp_path): def test_create_trips_in_report(self, tmp_path): # create_trips_in_report option: must generate valid input trips.csv args = self.get_args() - args.mode = "report" + args.mode = ["report"] args.desired_soc_deps = 0 args.cost_calculation = False args.output_path = tmp_path @@ -258,7 +258,7 @@ def test_create_trips_in_report(self, tmp_path): def test_pickle(self, tmp_path): # create pickle in report args = self.get_args() - args.mode = "report" + args.mode = ["report"] args.show_plots = False args.cost_calculation = False args.output_path = tmp_path @@ -267,16 +267,16 @@ def test_pickle(self, tmp_path): pickle_path = tmp_path / "report_1/scenario.pkl" assert pickle_path.exists() - # read in pickle for new simulation - args.mode = "load_pickle" + # read in pickle for new simulation. load_pickle can't be only mode, add dummy sim + args.mode = ["load_pickle", "sim"] args.output_path = None - args.load_pickle = pickle_path + args.load_pickle_path = pickle_path args.cost_parameters_path = None # keep original cost parameters schedule, _ = simulate(args) assert "foo" not in schedule.data_container.cost_parameters_data # replace cost parameters after loading pickle - args.mode = ["load_pickle"] + args.mode = ["load_pickle", "sim"] args.cost_parameters_path = tmp_path / "cost_params.json" with open(args.cost_parameters_path, "w") as f: f.write('{"foo": 1}') @@ -285,5 +285,5 @@ def test_pickle(self, tmp_path): def test_mode_recombination(self): args = self.get_args() - args.mode = "recombination" + args.mode = ["recombination"] simulate(args)