diff --git a/data/examples/simba.cfg b/data/examples/simba.cfg index da6287f..c0302bd 100644 --- a/data/examples/simba.cfg +++ b/data/examples/simba.cfg @@ -43,19 +43,21 @@ mode = ["sim", "report"] ##### Flags ##### ### Activate optional functions ### # Set flag for cost calculation: (default: false) -cost_calculation = true +cost_calculation = false # Check rotation assumptions when building schedule? (default: false) check_rotation_consistency = false # Remove rotations from schedule that violate assumptions? # Needs check_rotation_consistency to have an effect (default: false) skip_inconsistent_rotations = false # Show plots for users to view, only used in mode report (default: false) -show_plots = true +show_plots = false # Rotation filter variable, options: # "include": include only the rotations from file 'rotation_filter' # "exclude": exclude the rotations from file 'rotation_filter' from the schedule # null: deactivate function rotation_filter_variable = null +# Write a new trips.csv during report mode to output directory? (default: false) +create_trips_in_report = false ##### Charging strategy ##### # Preferred charging type. Options: depb, oppb (default: depb) diff --git a/simba/report.py b/simba/report.py index 703d372..60c612f 100644 --- a/simba/report.py +++ b/simba/report.py @@ -107,6 +107,39 @@ def generate_gc_overview(schedule, scenario, args): *use_factors]) +def generate_trips_timeseries_data(schedule): + """ + Build a trip data structure that can be saved to CSV. + + This should be in the form of a valid trips.csv input file. + :param schedule: current schedule for the simulation + :type schedule: simba.Schedule + :return: trip data + :rtype: Iterable + """ + header = [ + # identifier + "rotation_id", "line", + # stations + "departure_name", "departure_time", "arrival_name", "arrival_time", + # types + "vehicle_type", "charging_type", + # consumption (minimal) + "distance", "consumption" + # consumption (extended). Not strictly needed. + # "distance", "temperature", "height_diff", "level_of_loading", "mean_speed", + ] + data = [header] + for rotation in schedule.rotations.values(): + for trip in rotation.trips: + # get trip info from trip or trip.rotation (same name in Trip/Rotation as in CSV) + row = [vars(trip).get(k, vars(trip.rotation).get(k)) for k in header] + # special case rotation_id + row[0] = trip.rotation.id + data.append(row) + return data + + def generate_plots(scenario, args): """ Save plots as png and pdf. @@ -251,6 +284,10 @@ def generate(schedule, scenario, args): csv_writer.writeheader() csv_writer.writerows(rotation_infos) + if vars(args).get('create_trips_in_report', False): + file_path = args.results_directory / "trips.csv" + write_csv(generate_trips_timeseries_data(schedule), file_path) + # 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/trip.py b/simba/trip.py index 5d44d13..286ac91 100644 --- a/simba/trip.py +++ b/simba/trip.py @@ -42,7 +42,7 @@ def __init__(self, rotation, departure_time, departure_name, mean_speed = kwargs.get("mean_speed", (self.distance / 1000) / max(1 / 60, ((self.arrival_time - self.departure_time) / timedelta( hours=1)))) - self.mean_speed = mean_speed + self.mean_speed = float(mean_speed) # Attention: Circular reference! # While a rotation carries a references to this trip, this trip diff --git a/simba/util.py b/simba/util.py index cc53ec7..2c2ffe6 100644 --- a/simba/util.py +++ b/simba/util.py @@ -305,6 +305,8 @@ def get_args(): parser.add_argument('--propagate-mode-errors', default=False, help='Re-raise errors instead of continuing during simulation modes') 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('--rotation-filter-variable', default=None, choices=[None, 'include', 'exclude'], help='set mode for filtering schedule rotations') diff --git a/tests/test_schedule.py b/tests/test_schedule.py index adbd568..62af0cd 100644 --- a/tests/test_schedule.py +++ b/tests/test_schedule.py @@ -459,6 +459,7 @@ def test_peak_load_window(self): sys.argv = ["foo", "--config", str(example_root / "simba.cfg")] args = util.get_args() + args.cost_calculation = True # needed for timeseries, but default is false for station in generated_schedule.stations.values(): station["gc_power"] = 1000 station.pop("peak_load_window_power", None) diff --git a/tests/test_simulate.py b/tests/test_simulate.py index 542cf20..7a93612 100644 --- a/tests/test_simulate.py +++ b/tests/test_simulate.py @@ -147,6 +147,27 @@ def test_empty_report(self, tmp_path): warnings.simplefilter("ignore") simulate(Namespace(**values)) + def test_create_trips_in_report(self, tmp_path): + # create_trips_in_report option: must generate valid input trips.csv + values = self.DEFAULT_VALUES.copy() + values.update({ + "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, + }) + # simulate base scenario, report generates new trips.csv in (tmp) output + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + simulate(Namespace(**values)) + # new simulation with generated trips.csv + values = self.DEFAULT_VALUES.copy() + values["input_schedule"] = tmp_path / "report_1/trips.csv" + simulate(Namespace(**(values))) + def test_mode_recombination(self): values = self.DEFAULT_VALUES.copy() values["mode"] = "recombination"