Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make lol and temperatures optional. make cfg parameter backwards compatible #211

Merged
merged 6 commits into from
Sep 24, 2024
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
8 changes: 4 additions & 4 deletions data/examples/simba.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ scenario_name = example
##### Paths #####
### Input and output files and paths ###
# Input file containing trip information (required)
input_schedule = data/examples/trips_example.csv
schedule_path = data/examples/trips_example.csv
# Output files are stored here (defaults to: data/sim_outputs)
# Attention: In Windows the path-length is limited to 256 characters!
# Deactivate storage of output by setting output_directory = null
output_directory = data/output/
output_path = data/output/
# Electrified stations (required)
electrified_stations_path = data/examples/electrified_stations.json
# Vehicle types (defaults to: ./data/examples/vehicle_types.json)
Expand All @@ -23,11 +23,11 @@ outside_temperature_over_day_path = data/examples/default_temp_winter.csv
# (Optional: needed if mileage in vehicle types not constant)
level_of_loading_over_day_path = data/examples/default_level_of_loading_over_day.csv
# Path to configuration file for the station optimization. Only needed for mode "station_optimization"
optimizer_config = data/examples/default_optimizer.cfg
optimizer_config_path = data/examples/default_optimizer.cfg
# Cost parameters (needed if cost_calculation flag is set to true, see Flag section below)
cost_parameters_path = data/examples/cost_params.json
# Path to rotation filter
rotation_filter = data/examples/rotation_filter.csv
rotation_filter_path = data/examples/rotation_filter.csv

##### Modes #####
### Specify how you want to simulate the scenario ###
Expand Down
2 changes: 2 additions & 0 deletions docs/source/modes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,8 @@ To make use of this feature the parameters in the optimizer.cfg have to be set.
decision_tree_path = data/last_optimization.pickle
save_decision_tree = True

.. _optimizer_config:

Optimizer Configuration
###################################
The functionality of the optimizer is controlled through the optimizer.cfg specified in the simba.cfg used for calling SimBA.
Expand Down
16 changes: 12 additions & 4 deletions docs/source/simulation_parameters.rst
Original file line number Diff line number Diff line change
Expand Up @@ -32,19 +32,19 @@ The example (data/simba.cfg) contains parameter descriptions which are explained
- Optional: no default given
- string
- scenario identifier, appended to output directory name and report file names
* - input_schedule
* - schedule_path
- Mandatory: no default given
- Path as string
- Input file containing :ref:`schedule` information
* - Output_directory
* - output_path
- Data/sim_outputs
- Path as string
- Output files are stored here; set to null to deactivate
* - electrified_stations_path
- ./data/examples/vehicle_types.json
- Path as string
- Path to Electrified stations data
* - vehicle_types
* - vehicle_types_path
- ./data/examples/vehicle_types.json
- Path as string
- Path to :ref:`vehicle_types`
Expand All @@ -60,10 +60,18 @@ The example (data/simba.cfg) contains parameter descriptions which are explained
- Optional: no default given
- Path as string
- Path to :ref:`level_of_loading`
* - cost_parameters_file
* - cost_parameters_path
- Optional: no default given
- Path as string
- Path to :ref:`cost_params`
* - optimizer_config_path
- Optional: no default given
- Path as string
- Path to station optimizer config :ref:`optimizer_config`
* - rotation_filter_path
- Optional: no default given
- Path as string
- Path to rotation filter json
* - mode
- ['sim', 'report']
- List of modes is any order in range of ['sim', 'neg_depb_to_oppb', 'neg_oppb_to_depb', 'service_optimization', 'report']
Expand Down
8 changes: 4 additions & 4 deletions simba/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@
dir_name = time_str + '_' + args.scenario_name
else:
dir_name = time_str
if args.output_directory is not None:
args.output_directory = Path(args.output_directory) / dir_name
if args.output_path is not None:
args.output_path = Path(args.output_path) / dir_name
# create subfolder for specific sim results with timestamp.
# if folder doesn't exist, create folder.
# needs to happen after set_options_from_config since
# args.output_directory can be overwritten by config
args.output_directory_input = args.output_directory / "input_data"
# args.output_path can be overwritten by config
args.output_directory_input = args.output_path / "input_data"
try:
args.output_directory_input.mkdir(parents=True, exist_ok=True)
except NotADirectoryError:
Expand Down
4 changes: 2 additions & 2 deletions simba/data_container.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,15 @@ def __init__(self):
def fill_with_args(self, args: argparse.Namespace) -> 'DataContainer':
""" Fill DataContainer with data from file_paths defined in args.

:param args: Arguments containing paths for input_schedule, vehicle_types_path,
:param args: Arguments containing paths for schedule_path, vehicle_types_path,
electrified_stations_path, cost_parameters_path, outside_temperature_over_day_path,
level_of_loading_over_day_path, station_data_path
:return: self
"""
self.args = args

return self.fill_with_paths(
trips_file_path=args.input_schedule,
trips_file_path=args.schedule_path,
vehicle_types_path=args.vehicle_types_path,
electrified_stations_path=args.electrified_stations_path,
cost_parameters_path=args.cost_parameters_path,
Expand Down
46 changes: 27 additions & 19 deletions simba/schedule.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,29 +103,38 @@ def from_datacontainer(cls, data: DataContainer, args):
# Add geo data to schedule
schedule.station_data = data.station_geo_data

if not schedule.station_data:
logging.warning(
"No station data found for schedule. Height difference for all trips set to 0")

# Add consumption calculator to trip class
schedule.consumption_calculator = Consumption.create_from_data_container(data)

for trip in data.trip_data:
# De-link DataContainer and Schedule instances
trip = {key: data for key, data in trip.items()}
rotation_id = trip['rotation_id']

# Get height difference from station_data
trip["height_difference"] = schedule.get_height_difference(
trip["departure_name"], trip["arrival_name"])

if trip["level_of_loading"] is None:
assert len(data.level_of_loading_data) == 24, "Need 24 entries in level of loading"
trip["level_of_loading"] = util.get_mean_from_hourly_dict(
data.level_of_loading_data, trip["departure_time"], trip["arrival_time"])
if data.level_of_loading_data:
assert len(data.level_of_loading_data) == 24, \
"Need 24 entries in level of loading"
trip["level_of_loading"] = util.get_mean_from_hourly_dict(
data.level_of_loading_data, trip["departure_time"], trip["arrival_time"])
else:
if not 0 <= trip["level_of_loading"] <= 1:
logging.warning("Level of loading is out of range [0,1] and will be clipped.")
trip["level_of_loading"] = min(1, max(0, trip["level_of_loading"]))

if trip["temperature"] is None:
assert len(data.temperature_data) == 24, "Need 24 entries in temperature data"
trip["temperature"] = util.get_mean_from_hourly_dict(
data.temperature_data, trip["departure_time"], trip["arrival_time"])
if data.temperature_data:
assert len(data.temperature_data) == 24, "Need 24 entries in temperature data"
trip["temperature"] = util.get_mean_from_hourly_dict(
data.temperature_data, trip["departure_time"], trip["arrival_time"])

if rotation_id not in schedule.rotations.keys():
schedule.rotations.update({
Expand All @@ -143,9 +152,9 @@ def from_datacontainer(cls, data: DataContainer, args):
if vars(args).get("check_rotation_consistency"):
# check rotation expectations
inconsistent_rotations = cls.check_consistency(schedule)
if inconsistent_rotations and args.output_directory is not None:
if inconsistent_rotations and args.output_path is not None:
# write errors to file
filepath = args.output_directory / "inconsistent_rotations.csv"
filepath = args.output_path / "inconsistent_rotations.csv"
with open(filepath, "w", encoding='utf-8') as f:
for rot_id, e in inconsistent_rotations.items():
f.write(f"Rotation {rot_id}: {e}\n")
Expand Down Expand Up @@ -214,7 +223,7 @@ def run(self, args, mode="distributed"):
For external usage the core run functionality is accessible through this function.
It allows for defining a custom-made assign_vehicles method for the schedule.

:param args: used arguments are rotation_filter, path to rotation ids,
:param args: used arguments are rotation_filter_path, path to rotation ids,
and rotation_filter_variable that sets mode (options: include, exclude)
:type args: argparse.Namespace
:param mode: SpiceEV strategy name
Expand Down Expand Up @@ -727,7 +736,7 @@ def get_height_difference(self, departure_name, arrival_name):
:return: Height difference. Defaults to 0 if height data is not found.
:rtype: float
"""
if isinstance(self.station_data, dict):
if isinstance(self.station_data, dict) and self.station_data:
station_name = departure_name
try:
start_height = self.station_data[station_name]["elevation"]
Expand All @@ -736,9 +745,8 @@ def get_height_difference(self, departure_name, arrival_name):
return end_height - start_height
except KeyError:
logging.error(
f"No elevation data found for {station_name}. Height difference set to 0")
else:
logging.error("No station data found for schedule. Height difference set to 0")
f"No elevation data found for {station_name} in station_data. "
"Height difference set to 0")
return 0

def get_negative_rotations(self, scenario):
Expand Down Expand Up @@ -792,7 +800,7 @@ def get_total_distance(self):
def rotation_filter(self, args, rf_list=[]):
""" Edits rotations according to args.rotation_filter_variable.

:param args: used arguments are rotation_filter, path to rotation ids,
:param args: used arguments are rotation_filter_path, path to rotation ids,
and rotation_filter_variable that sets mode (options: include, exclude)
:type args: argparse.Namespace
:param rf_list: rotation filter list with strings of rotation ids (default is None)
Expand All @@ -804,21 +812,21 @@ def rotation_filter(self, args, rf_list=[]):
# cast rotations in filter to string
rf_list = [str(i) for i in rf_list]

if args.rotation_filter is None and not rf_list:
if args.rotation_filter_path is None and not rf_list:
warnings.warn("Rotation filter variable is enabled but file and list are not used.")
return

if args.rotation_filter:
if args.rotation_filter_path:
# read out rotations from file (one rotation ID per line)
try:
with open(args.rotation_filter, encoding='utf-8') as f:
with open(args.rotation_filter_path, encoding='utf-8') as f:
for line in f:
rf_list.append(line.strip())
except FileNotFoundError:
warnings.warn(f"Path to rotation filter {args.rotation_filter} is invalid.")
warnings.warn(f"Path to rotation filter {args.rotation_filter_path} is invalid.")
# no file, no change
return
util.save_input_file(args.rotation_filter, args)
util.save_input_file(args.rotation_filter_path, args)
# filter out rotations in self.rotations
if args.rotation_filter_variable == "exclude":
self.rotations = {k: v for k, v in self.rotations.items() if k not in rf_list}
Expand Down
8 changes: 4 additions & 4 deletions simba/simulate.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ def modes_simulation(schedule, scenario, args):
if scenario is not None and scenario.step_i > 0:
# generate plot of failed scenario
args.mode = args.mode[:i] + ["ABORTED"]
if args.output_directory is None:
if args.output_path is None:
create_results_directory(args, i+1)
if not args.skip_plots:
report.generate_plots(scenario, args)
Expand Down Expand Up @@ -262,7 +262,7 @@ def split_negative_depb(schedule, scenario, args, _i):

@staticmethod
def report(schedule, scenario, args, i):
if args.output_directory is None:
if args.output_path is None:
return schedule, scenario

# create report based on all previous modes
Expand Down Expand Up @@ -290,12 +290,12 @@ def create_results_directory(args, i):
:type i: int
"""

if args.output_directory is None:
if args.output_path is None:
return

prior_reports = sum([m.count('report') for m in args.mode[:i]])
report_name = f"report_{prior_reports+1}"
args.results_directory = args.output_directory.joinpath(report_name)
args.results_directory = args.output_path.joinpath(report_name)
args.results_directory.mkdir(parents=True, exist_ok=True)
# save used modes in report version
used_modes = ['sim'] + [m for m in args.mode[:i] if m not in ['sim', 'report']]
Expand Down
Loading
Loading