eflips-depot 4.13.0__tar.gz → 4.13.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of eflips-depot might be problematic. Click here for more details.

Files changed (42) hide show
  1. {eflips_depot-4.13.0 → eflips_depot-4.13.1}/PKG-INFO +1 -1
  2. {eflips_depot-4.13.0 → eflips_depot-4.13.1}/eflips/depot/__init__.py +43 -3
  3. {eflips_depot-4.13.0 → eflips_depot-4.13.1}/eflips/depot/api/__init__.py +120 -62
  4. {eflips_depot-4.13.0 → eflips_depot-4.13.1}/eflips/depot/api/private/depot.py +14 -3
  5. {eflips_depot-4.13.0 → eflips_depot-4.13.1}/eflips/depot/api/private/results_to_database.py +54 -34
  6. {eflips_depot-4.13.0 → eflips_depot-4.13.1}/pyproject.toml +1 -1
  7. {eflips_depot-4.13.0 → eflips_depot-4.13.1}/LICENSE.md +0 -0
  8. {eflips_depot-4.13.0 → eflips_depot-4.13.1}/README.md +0 -0
  9. {eflips_depot-4.13.0 → eflips_depot-4.13.1}/eflips/depot/api/defaults/default_settings.json +0 -0
  10. {eflips_depot-4.13.0 → eflips_depot-4.13.1}/eflips/depot/api/private/__init__.py +0 -0
  11. {eflips_depot-4.13.0 → eflips_depot-4.13.1}/eflips/depot/api/private/consumption.py +0 -0
  12. {eflips_depot-4.13.0 → eflips_depot-4.13.1}/eflips/depot/api/private/util.py +0 -0
  13. {eflips_depot-4.13.0 → eflips_depot-4.13.1}/eflips/depot/configuration.py +0 -0
  14. {eflips_depot-4.13.0 → eflips_depot-4.13.1}/eflips/depot/depot.py +0 -0
  15. {eflips_depot-4.13.0 → eflips_depot-4.13.1}/eflips/depot/evaluation.py +0 -0
  16. {eflips_depot-4.13.0 → eflips_depot-4.13.1}/eflips/depot/filters.py +0 -0
  17. {eflips_depot-4.13.0 → eflips_depot-4.13.1}/eflips/depot/input_epex_power_price.py +0 -0
  18. {eflips_depot-4.13.0 → eflips_depot-4.13.1}/eflips/depot/layout_opt/__init__.py +0 -0
  19. {eflips_depot-4.13.0 → eflips_depot-4.13.1}/eflips/depot/layout_opt/doc/__init__.py +0 -0
  20. {eflips_depot-4.13.0 → eflips_depot-4.13.1}/eflips/depot/layout_opt/doc/direct_details.pdf +0 -0
  21. {eflips_depot-4.13.0 → eflips_depot-4.13.1}/eflips/depot/layout_opt/evaluation.py +0 -0
  22. {eflips_depot-4.13.0 → eflips_depot-4.13.1}/eflips/depot/layout_opt/opt_tools/__init__.py +0 -0
  23. {eflips_depot-4.13.0 → eflips_depot-4.13.1}/eflips/depot/layout_opt/opt_tools/crossover.py +0 -0
  24. {eflips_depot-4.13.0 → eflips_depot-4.13.1}/eflips/depot/layout_opt/opt_tools/fitness_c_urfd.py +0 -0
  25. {eflips_depot-4.13.0 → eflips_depot-4.13.1}/eflips/depot/layout_opt/opt_tools/fitness_util.py +0 -0
  26. {eflips_depot-4.13.0 → eflips_depot-4.13.1}/eflips/depot/layout_opt/opt_tools/init.py +0 -0
  27. {eflips_depot-4.13.0 → eflips_depot-4.13.1}/eflips/depot/layout_opt/opt_tools/mutation.py +0 -0
  28. {eflips_depot-4.13.0 → eflips_depot-4.13.1}/eflips/depot/layout_opt/optimize_c_urfd.py +0 -0
  29. {eflips_depot-4.13.0 → eflips_depot-4.13.1}/eflips/depot/layout_opt/packing.py +0 -0
  30. {eflips_depot-4.13.0 → eflips_depot-4.13.1}/eflips/depot/layout_opt/settings.py +0 -0
  31. {eflips_depot-4.13.0 → eflips_depot-4.13.1}/eflips/depot/layout_opt/template_creation.py +0 -0
  32. {eflips_depot-4.13.0 → eflips_depot-4.13.1}/eflips/depot/layout_opt/util.py +0 -0
  33. {eflips_depot-4.13.0 → eflips_depot-4.13.1}/eflips/depot/plots.py +0 -0
  34. {eflips_depot-4.13.0 → eflips_depot-4.13.1}/eflips/depot/processes.py +0 -0
  35. {eflips_depot-4.13.0 → eflips_depot-4.13.1}/eflips/depot/rating.py +0 -0
  36. {eflips_depot-4.13.0 → eflips_depot-4.13.1}/eflips/depot/resources.py +0 -0
  37. {eflips_depot-4.13.0 → eflips_depot-4.13.1}/eflips/depot/settings_config.py +0 -0
  38. {eflips_depot-4.13.0 → eflips_depot-4.13.1}/eflips/depot/simple_vehicle.py +0 -0
  39. {eflips_depot-4.13.0 → eflips_depot-4.13.1}/eflips/depot/simulation.py +0 -0
  40. {eflips_depot-4.13.0 → eflips_depot-4.13.1}/eflips/depot/smart_charging.py +0 -0
  41. {eflips_depot-4.13.0 → eflips_depot-4.13.1}/eflips/depot/standalone.py +0 -0
  42. {eflips_depot-4.13.0 → eflips_depot-4.13.1}/eflips/depot/validation.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: eflips-depot
3
- Version: 4.13.0
3
+ Version: 4.13.1
4
4
  Summary: Depot Simulation for eFLIPS
5
5
  License: AGPL-3.0-or-later
6
6
  Author: Enrico Lauth
@@ -54,9 +54,49 @@ from eflips.depot.standalone import VehicleGenerator, SimpleTrip, Timetable
54
54
  from eflips.depot.validation import Validator
55
55
 
56
56
 
57
+ class DelayedTripException(Exception):
58
+ def __init__(self):
59
+ self._delayed_trips = []
60
+
61
+ def raise_later(self, simple_trip):
62
+ self._delayed_trips.append(simple_trip)
63
+
64
+ @property
65
+ def has_errors(self):
66
+ return len(self._delayed_trips) > 0
67
+
68
+ def __str__(self):
69
+ trip_names = ", ".join(
70
+ f"{trip.ID} originally departure at {trip.std}"
71
+ for trip in self._delayed_trips
72
+ )
73
+
74
+ return (
75
+ f"The following blocks/rotations are delayed. "
76
+ f"Ignoring this error will write related depot events into database. However, this may lead to errors due "
77
+ f"to conflicts with driving events: {trip_names}"
78
+ )
79
+
80
+
57
81
  class UnstableSimulationException(Exception):
58
- pass
82
+ def __init__(self):
83
+ self._unstable_trips = []
59
84
 
85
+ def raise_later(self, simple_trip):
86
+ self._unstable_trips.append(simple_trip)
60
87
 
61
- class DelayedTripException(Exception):
62
- pass
88
+ @property
89
+ def has_errors(self):
90
+ return len(self._unstable_trips) > 0
91
+
92
+ def __str__(self):
93
+ trip_names = ", ".join(trip.ID for trip in self._unstable_trips)
94
+ return (
95
+ f"The following blocks/rotations require a new vehicle. This suggests an unstable "
96
+ f" simulation result, where a repeated schedule might require more vehicles: {trip_names}"
97
+ )
98
+
99
+
100
+ class MultipleErrors(Exception):
101
+ def __init__(self, errors):
102
+ self.errors = errors
@@ -29,7 +29,6 @@ import logging
29
29
  import os
30
30
  import warnings
31
31
  from collections import OrderedDict
32
- from dataclasses import dataclass
33
32
  from datetime import timedelta, datetime
34
33
  from enum import Enum
35
34
  from math import ceil
@@ -51,7 +50,6 @@ from eflips.model import (
51
50
  Route,
52
51
  ConsistencyWarning,
53
52
  Station,
54
- ConsumptionLut,
55
53
  )
56
54
  from sqlalchemy.orm import Session
57
55
 
@@ -59,6 +57,9 @@ import eflips.depot
59
57
  from eflips.depot import (
60
58
  DepotEvaluation,
61
59
  SimulationHost,
60
+ UnstableSimulationException,
61
+ DelayedTripException,
62
+ MultipleErrors,
62
63
  )
63
64
  from eflips.depot.api.private.consumption import ConsumptionResult
64
65
  from eflips.depot.api.private.consumption import (
@@ -637,16 +638,30 @@ def simulate_scenario(
637
638
  ev = run_simulation(simulation_host)
638
639
  try:
639
640
  add_evaluation_to_database(scenario, ev, session)
640
- except eflips.depot.UnstableSimulationException as e:
641
- if ignore_unstable_simulation:
642
- logger.warning("Simulation is unstable. Continuing.")
643
- else:
644
- raise e
645
- except eflips.depot.DelayedTripException as e:
646
- if ignore_delayed_trips:
647
- logger.warning("Simulation has delayed trips. Continuing.")
648
- else:
649
- raise e
641
+
642
+ except MultipleErrors as e:
643
+ if e.errors:
644
+ for error in e.errors:
645
+ if (
646
+ isinstance(error, DelayedTripException)
647
+ and not ignore_delayed_trips
648
+ ):
649
+ logger.error(
650
+ "There are delayed trips in the simulation. "
651
+ "Please check the input data and try again."
652
+ )
653
+ raise error
654
+ if (
655
+ isinstance(error, UnstableSimulationException)
656
+ and not ignore_unstable_simulation
657
+ ):
658
+ logger.error(
659
+ "Simulation became unstable. "
660
+ "Please check the input data and try again."
661
+ )
662
+ raise error
663
+
664
+ logger.warning("An error occurred during the simulation: %s", error)
650
665
 
651
666
  match smart_charging_strategy:
652
667
  case SmartChargingStrategy.NONE:
@@ -784,6 +799,8 @@ def init_simulation(
784
799
  # Clear old vehicle counts, if they exist
785
800
  eflips.globalConstants["depot"]["vehicle_count"] = {}
786
801
 
802
+ grouped_rotations = group_rotations_by_start_end_stop(scenario.id, session)
803
+
787
804
  # We need to calculate roughly how many vehicles we need for each depot
788
805
  for depot in session.query(Depot).filter(Depot.scenario_id == scenario.id).all():
789
806
  depot_id = str(depot.id)
@@ -806,31 +823,32 @@ def init_simulation(
806
823
  depot_id
807
824
  ] = vehicle_count_dict[depot_id]
808
825
  else:
809
- # Calculate it from the size of the charging area with a 2x margin
826
+ # Calculate it from the amount of rotations with a 4x margin because 4 times of repetition
827
+ # in repeat_vehicle_schedules()
828
+ rotations = grouped_rotations[(depot.station, depot.station)]
810
829
 
811
830
  for vehicle_type in vehicle_types_for_depot:
812
- vehicle_count = 0
813
- for area in depot.areas:
814
- if (
815
- area.vehicle_type_id == int(vehicle_type)
816
- or area.vehicle_type_id is None
817
- ):
818
- # The areas allow either one type, or all vehicle types
819
- for p in area.processes:
820
- if p.electric_power is not None and p.duration is None:
821
- row_count = (
822
- area.row_count if area.row_count is not None else 1
823
- )
824
-
825
- vehicle_count += area.capacity * row_count
826
-
827
- assert (
828
- vehicle_count > 0
829
- ), f"The charging area capacity for vehicle type {vehicle_type} should not be 0."
830
-
831
- eflips.globalConstants["depot"]["vehicle_count"][depot_id][
832
- vehicle_type
833
- ] = (vehicle_count * 2)
831
+ vehicle_type_object = (
832
+ session.query(VehicleType)
833
+ .filter(
834
+ VehicleType.id == vehicle_type,
835
+ VehicleType.scenario_id == scenario.id,
836
+ )
837
+ .one()
838
+ )
839
+ vehicle_count = len(rotations.get(vehicle_type_object, []))
840
+
841
+ if vehicle_count > 0:
842
+ eflips.globalConstants["depot"]["vehicle_count"][depot_id][
843
+ vehicle_type
844
+ ] = (
845
+ vehicle_count
846
+ * 4 # We multiply by 4 because we repeat the vehicle schedules 4 times
847
+ )
848
+ else:
849
+ warnings.warn(
850
+ f"There are no rotations assigned to type {vehicle_type_object} in depot {depot_id}"
851
+ )
834
852
 
835
853
  # We need to put the vehicle type objects into the GlobalConstants
836
854
  for vehicle_type in (
@@ -1023,6 +1041,9 @@ def add_evaluation_to_database(
1023
1041
  f"one waiting area."
1024
1042
  )
1025
1043
 
1044
+ unstable_exp = UnstableSimulationException()
1045
+ delay_exp = DelayedTripException()
1046
+
1026
1047
  for current_vehicle in depot_evaluation.vehicle_generator.items:
1027
1048
  # Vehicle-layer operations
1028
1049
 
@@ -1049,23 +1070,16 @@ def add_evaluation_to_database(
1049
1070
  # handled. It is usually the departure time of the last copy trip in the "early-shifted" copy
1050
1071
  # schedules and the departure time of the first copy trip in the "late-shifted" copy schedules.
1051
1072
  ) = get_finished_schedules_per_vehicle(
1052
- dict_of_events, current_vehicle.finished_trips, current_vehicle_db.id
1073
+ dict_of_events,
1074
+ current_vehicle.finished_trips,
1075
+ current_vehicle_db.id,
1076
+ unstable_exp,
1077
+ delay_exp,
1053
1078
  )
1054
1079
 
1055
- try:
1056
- assert earliest_time is not None and latest_time is not None
1057
-
1058
- except AssertionError as e:
1059
- warnings.warn(
1060
- f"Vehicle {current_vehicle_db.id} has only copied trips. The profiles of this vehicle "
1061
- f"will not be written into database."
1062
- )
1080
+ if schedule_current_vehicle is None:
1063
1081
  continue
1064
1082
 
1065
- assert (
1066
- earliest_time < latest_time
1067
- ), f"Earliest time {earliest_time} is not less than latest time {latest_time}."
1068
-
1069
1083
  list_of_assigned_schedules.extend(schedule_current_vehicle)
1070
1084
 
1071
1085
  generate_vehicle_events(
@@ -1082,16 +1096,6 @@ def add_evaluation_to_database(
1082
1096
 
1083
1097
  add_soc_to_events(dict_of_events, current_vehicle.battery_logs)
1084
1098
 
1085
- try:
1086
- assert (not dict_of_events) is False
1087
- except AssertionError as e:
1088
- warnings.warn(
1089
- f"Vehicle {current_vehicle_db.id} has no valid events. The vehicle will not be written "
1090
- f"into database."
1091
- )
1092
-
1093
- continue
1094
-
1095
1099
  add_events_into_database(
1096
1100
  current_vehicle_db,
1097
1101
  dict_of_events,
@@ -1104,6 +1108,16 @@ def add_evaluation_to_database(
1104
1108
  update_vehicle_in_rotation(session, scenario, list_of_assigned_schedules)
1105
1109
  update_waiting_events(session, scenario, waiting_area_id)
1106
1110
 
1111
+ errors = []
1112
+
1113
+ if delay_exp.has_errors:
1114
+ errors.append(delay_exp)
1115
+ if unstable_exp.has_errors:
1116
+ errors.append(unstable_exp)
1117
+
1118
+ if len(errors) > 0:
1119
+ raise MultipleErrors(errors)
1120
+
1107
1121
 
1108
1122
  def generate_depot_optimal_size(
1109
1123
  scenario: Union[Scenario, int, Any],
@@ -1112,6 +1126,7 @@ def generate_depot_optimal_size(
1112
1126
  database_url: Optional[str] = None,
1113
1127
  delete_existing_depot: bool = False,
1114
1128
  use_consumption_lut: bool = False,
1129
+ repetition_period: Optional[timedelta] = None,
1115
1130
  ) -> None:
1116
1131
  """
1117
1132
  Generates an optimal depot layout with the smallest possible size for each depot in the scenario. Line charging areas
@@ -1125,8 +1140,12 @@ def generate_depot_optimal_size(
1125
1140
  :param database_url: An optional database URL. Used if no database url is given by the environment variable.
1126
1141
  :param delete_existing_depot: If there is already a depot existing in this scenario, set True to delete this
1127
1142
  existing depot. Set to False and a ValueError will be raised if there is a depot in this scenario.
1128
- :param using_consumption_lut: If True, the depot layout will be generated based on the consumption lookup table.
1143
+ :param use_consumption_lut: If True, the depot layout will be generated based on the consumption lookup table.
1129
1144
  If False, constant consumption stored in VehicleType table will be used.
1145
+ :param repetition_period: An optional timedelta object specifying the period of the vehicle schedules. If not
1146
+ specified, a default repetition period will be generated in simulate_scenario(). If the depot layout generated
1147
+ in this function will be used for further simulations, make sure that the repetition period is set to the same
1148
+ value as in the simulation.
1130
1149
 
1131
1150
  :return: None. The depot layout will be added to the database.
1132
1151
 
@@ -1183,10 +1202,12 @@ def generate_depot_optimal_size(
1183
1202
 
1184
1203
  num_rotations_for_scenario: Dict[Station, int] = {}
1185
1204
 
1205
+ grouped_rotations = group_rotations_by_start_end_stop(scenario.id, session)
1206
+
1186
1207
  for (
1187
1208
  first_last_stop_tup,
1188
1209
  vehicle_type_dict,
1189
- ) in group_rotations_by_start_end_stop(scenario.id, session).items():
1210
+ ) in grouped_rotations.items():
1190
1211
  first_stop, last_stop = first_last_stop_tup
1191
1212
  if first_stop != last_stop:
1192
1213
  raise ValueError("First and last stop of a rotation are not the same.")
@@ -1230,6 +1251,7 @@ def generate_depot_optimal_size(
1230
1251
  session,
1231
1252
  standard_block_length,
1232
1253
  charging_power,
1254
+ repetition_period,
1233
1255
  )
1234
1256
 
1235
1257
  depot_capacities_for_scenario[station] = vt_capacities_for_station
@@ -1239,8 +1261,44 @@ def generate_depot_optimal_size(
1239
1261
 
1240
1262
  outer_savepoint.rollback()
1241
1263
 
1264
+ # Estimation of the number of shunting and cleaning slots
1265
+
1242
1266
  # Create depot using the calculated capacities
1243
1267
  for depot_station, capacities in depot_capacities_for_scenario.items():
1268
+ vehicle_type_rot_dict = grouped_rotations[depot_station, depot_station]
1269
+
1270
+ all_rotations_this_depot = []
1271
+
1272
+ for vehicle_type, rotations in vehicle_type_rot_dict.items():
1273
+ all_rotations_this_depot.extend(rotations)
1274
+
1275
+ # sort the rotations by their start time
1276
+ all_rotations_this_depot.sort(key=lambda r: r.trips[0].departure_time)
1277
+
1278
+ start_time = all_rotations_this_depot[0].trips[0].departure_time
1279
+ end_time = all_rotations_this_depot[-1].trips[-1].arrival_time
1280
+
1281
+ elapsed_time = (end_time - start_time).total_seconds()
1282
+ # make them into a numpy with 30 min resolution
1283
+ import numpy as np
1284
+
1285
+ TIME_RESOLUTION = 30 * 60 # 30 minutes in seconds
1286
+
1287
+ time_range = np.zeros(int(elapsed_time / TIME_RESOLUTION) + 1)
1288
+ # calculate the number of rotations per time slot
1289
+ for rot in all_rotations_this_depot:
1290
+ start_time_index = int(
1291
+ (rot.trips[0].departure_time - start_time).total_seconds()
1292
+ // TIME_RESOLUTION
1293
+ )
1294
+ end_time_index = int(
1295
+ (rot.trips[-1].arrival_time - start_time).total_seconds()
1296
+ // TIME_RESOLUTION
1297
+ )
1298
+ # interpolate the start and end time to the time range
1299
+
1300
+ time_range[start_time_index : end_time_index + 1] += 1
1301
+
1244
1302
  generate_depot(
1245
1303
  capacities,
1246
1304
  depot_station,
@@ -1248,6 +1306,6 @@ def generate_depot_optimal_size(
1248
1306
  session,
1249
1307
  standard_block_length=standard_block_length,
1250
1308
  charging_power=charging_power,
1251
- num_shunting_slots=num_rotations_for_scenario[depot_station] // 10,
1252
- num_cleaning_slots=num_rotations_for_scenario[depot_station] // 10,
1309
+ num_shunting_slots=int(max(time_range)),
1310
+ num_cleaning_slots=int(max(time_range)),
1253
1311
  )
@@ -3,7 +3,7 @@ import logging
3
3
  import math
4
4
  from datetime import timedelta
5
5
  from enum import Enum, auto
6
- from typing import Dict, List, Tuple
6
+ from typing import Dict, List, Tuple, Optional
7
7
 
8
8
  import numpy as np
9
9
  import sqlalchemy.orm
@@ -583,12 +583,17 @@ def generate_depot(
583
583
  session.add_all(assocs) # It's complete, so add all at once
584
584
 
585
585
  # Create shared waiting area
586
+ rotation_count = len(
587
+ session.query(Rotation).filter(Rotation.scenario_id == scenario.id).all()
588
+ )
589
+
586
590
  waiting_area = Area(
587
591
  scenario=scenario,
588
592
  name=f"Waiting Area for every type of vehicle",
589
593
  depot=depot,
590
594
  area_type=AreaType.DIRECT_ONESIDE,
591
- capacity=100,
595
+ capacity=rotation_count
596
+ * 4, # Initialize with 4 times of rotation count because all rotations are copied three times. Assuming each rotation needs a vehicle.
592
597
  )
593
598
  session.add(waiting_area)
594
599
 
@@ -849,6 +854,7 @@ def depot_smallest_possible_size(
849
854
  session: sqlalchemy.orm.session.Session,
850
855
  standard_block_length: int = 6,
851
856
  charging_power: float = 90,
857
+ repetition_period: Optional[timedelta] = None,
852
858
  ) -> Dict[VehicleType, Dict[AreaType, None | int]]:
853
859
  """
854
860
  Identifies the smallest (in terms of area footprint) depot that can still fit the required vehicles.
@@ -919,7 +925,12 @@ def depot_smallest_possible_size(
919
925
  from eflips.depot.api import SmartChargingStrategy
920
926
 
921
927
  # Simulate the depot
922
- simulate_scenario(scenario, smart_charging_strategy=SmartChargingStrategy.NONE)
928
+
929
+ simulate_scenario(
930
+ scenario,
931
+ smart_charging_strategy=SmartChargingStrategy.NONE,
932
+ repetition_period=repetition_period,
933
+ )
923
934
 
924
935
  # Find the peak usage of the depot
925
936
  peak_occupancies: Dict[VehicleType, Dict[AreaType, int]] = find_peak_usage(
@@ -1,6 +1,7 @@
1
1
  import datetime
2
2
  import itertools
3
3
  import logging
4
+ import warnings
4
5
  from datetime import timedelta
5
6
  from typing import List, Dict
6
7
 
@@ -13,7 +14,11 @@ from eflips.depot import UnstableSimulationException, DelayedTripException
13
14
 
14
15
 
15
16
  def get_finished_schedules_per_vehicle(
16
- dict_of_events, list_of_finished_trips: List, db_vehicle_id: int
17
+ dict_of_events,
18
+ list_of_finished_trips: List,
19
+ db_vehicle_id: int,
20
+ unstable_exp: UnstableSimulationException,
21
+ delay_exp: DelayedTripException,
17
22
  ):
18
23
  """
19
24
  This function completes the following tasks:
@@ -42,47 +47,62 @@ def get_finished_schedules_per_vehicle(
42
47
  :return: A tuple of three elements. The first element is a list of finished schedules of the vehicle. The second and
43
48
  third elements are the earliest and latest time of the vehicle's schedules.
44
49
  """
45
- finished_schedules = []
46
50
 
47
51
  list_of_finished_trips.sort(key=lambda x: x.atd)
48
- earliest_time = None
49
- latest_time = None
50
52
 
51
53
  for i in range(len(list_of_finished_trips)):
52
- try:
53
- assert list_of_finished_trips[i].atd == list_of_finished_trips[i].std
54
- except AssertionError:
55
- raise DelayedTripException(
56
- f"The trip {list_of_finished_trips[i].ID} is delayed. The simulation doesn't "
57
- "support delayed trips for now."
58
- )
54
+ if list_of_finished_trips[i].atd != list_of_finished_trips[i].std:
55
+ delay_exp.raise_later(list_of_finished_trips[i])
59
56
 
60
- if list_of_finished_trips[i].is_copy is False:
61
- current_trip = list_of_finished_trips[i]
57
+ non_copy_trips = [trip for trip in list_of_finished_trips if trip.is_copy is False]
62
58
 
63
- finished_schedules.append((int(current_trip.ID), db_vehicle_id))
64
- dict_of_events[current_trip.atd] = {
65
- "type": "Trip",
66
- "id": int(current_trip.ID),
67
- }
68
- if i == 0:
69
- raise UnstableSimulationException(
70
- f"New Vehicle required for the rotation/block {current_trip.ID}, which suggests the fleet or the "
71
- f"infrastructure might not be enough for the full electrification. Please add charging "
72
- f"interfaces or increase charging power ."
73
- )
59
+ if len(non_copy_trips) == 0:
60
+ warnings.warn(
61
+ f"Vehicle {db_vehicle_id} has only copied trips and will not be stored in the database. "
62
+ f"This might suggest an unstable simulation result. "
63
+ f"Repetition of this schedule may require more vehicles."
64
+ )
74
65
 
75
- elif i != 0 and i == len(list_of_finished_trips) - 1:
76
- # Vehicle's last trip is a non-copy trip
77
- if earliest_time is None:
78
- earliest_time = list_of_finished_trips[i - 1].ata
79
- latest_time = list_of_finished_trips[i].ata
66
+ return None, None, None
80
67
 
81
- else:
82
- if list_of_finished_trips[i - 1].is_copy is True:
83
- earliest_time = list_of_finished_trips[i - 1].ata
84
- if list_of_finished_trips[i + 1].is_copy is True:
85
- latest_time = list_of_finished_trips[i + 1].atd
68
+ idx_first_orig_trip = list_of_finished_trips.index(non_copy_trips[0])
69
+ idx_last_orig_trip = list_of_finished_trips.index(non_copy_trips[-1])
70
+
71
+ if idx_first_orig_trip == 0:
72
+ unstable_exp.raise_later(non_copy_trips[0])
73
+ earliest_time = non_copy_trips[0].ata
74
+
75
+ else:
76
+ earliest_time = list_of_finished_trips[idx_first_orig_trip - 1].ata
77
+
78
+ if idx_last_orig_trip == len(list_of_finished_trips) - 1:
79
+ # the last trip of this vehicle is a non-copy trip
80
+ all_processes_current_vehicle = non_copy_trips[-1].vehicle.logger.loggedData[
81
+ "dwd.active_processes_copy"
82
+ ]
83
+ time_stamps = [k for k, v in all_processes_current_vehicle.items()]
84
+ last_time_stamp = max(time_stamps)
85
+ if len(all_processes_current_vehicle[last_time_stamp]) == 0:
86
+ latest_time = last_time_stamp
87
+ else:
88
+ current_processes = all_processes_current_vehicle[last_time_stamp]
89
+
90
+ latest_time = 0
91
+ for p in current_processes:
92
+ latest_time = max((p.starts[0] + p.dur), latest_time)
93
+
94
+ else:
95
+ latest_time = list_of_finished_trips[idx_last_orig_trip + 1].atd
96
+
97
+ assert earliest_time <= latest_time
98
+
99
+ finished_schedules = []
100
+ for non_copy_trip in non_copy_trips:
101
+ finished_schedules.append((int(non_copy_trip.ID), db_vehicle_id))
102
+ dict_of_events[non_copy_trip.atd] = {
103
+ "type": "Trip",
104
+ "id": int(non_copy_trip.ID),
105
+ }
86
106
 
87
107
  return finished_schedules, earliest_time, latest_time
88
108
 
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "eflips-depot"
3
- version = "4.13.0"
3
+ version = "4.13.1"
4
4
  description = "Depot Simulation for eFLIPS"
5
5
  authors = ["Enrico Lauth <enrico.lauth@tu-berlin.de>",
6
6
  "Ludger Heide <ludger.heide@tu-berlin.de",
File without changes
File without changes