eflips-depot 3.0.2__tar.gz → 3.0.3__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.
Files changed (59) hide show
  1. {eflips_depot-3.0.2 → eflips_depot-3.0.3}/PKG-INFO +3 -3
  2. {eflips_depot-3.0.2 → eflips_depot-3.0.3}/eflips/depot/api/__init__.py +88 -51
  3. {eflips_depot-3.0.2 → eflips_depot-3.0.3}/eflips/depot/api/private/depot.py +23 -19
  4. {eflips_depot-3.0.2 → eflips_depot-3.0.3}/eflips/depot/filters.py +14 -0
  5. {eflips_depot-3.0.2 → eflips_depot-3.0.3}/pyproject.toml +3 -3
  6. eflips_depot-3.0.2/eflips/depot/.DS_Store +0 -0
  7. eflips_depot-3.0.2/eflips/depot/api/__pycache__/__init__.cpython-311.pyc +0 -0
  8. eflips_depot-3.0.2/eflips/depot/api/__pycache__/__init__.cpython-312.pyc +0 -0
  9. eflips_depot-3.0.2/eflips/depot/api/private/__pycache__/__init__.cpython-312.pyc +0 -0
  10. eflips_depot-3.0.2/eflips/depot/api/private/__pycache__/depot.cpython-312.pyc +0 -0
  11. eflips_depot-3.0.2/eflips/depot/api/private/__pycache__/depot_layout.cpython-312.pyc +0 -0
  12. eflips_depot-3.0.2/eflips/depot/api/private/__pycache__/util.cpython-312.pyc +0 -0
  13. eflips_depot-3.0.2/eflips/depot/layout_opt/__pycache__/__init__.cpython-311.pyc +0 -0
  14. eflips_depot-3.0.2/eflips/depot/layout_opt/__pycache__/__init__.cpython-312.pyc +0 -0
  15. eflips_depot-3.0.2/eflips/depot/layout_opt/__pycache__/evaluation.cpython-311.pyc +0 -0
  16. eflips_depot-3.0.2/eflips/depot/layout_opt/__pycache__/packing.cpython-311.pyc +0 -0
  17. eflips_depot-3.0.2/eflips/depot/layout_opt/__pycache__/settings.cpython-311.pyc +0 -0
  18. eflips_depot-3.0.2/eflips/depot/layout_opt/__pycache__/util.cpython-311.pyc +0 -0
  19. eflips_depot-3.0.2/eflips/depot/layout_opt/opt_tools/__pycache__/__init__.cpython-311.pyc +0 -0
  20. eflips_depot-3.0.2/eflips/depot/layout_opt/opt_tools/__pycache__/crossover.cpython-311.pyc +0 -0
  21. eflips_depot-3.0.2/eflips/depot/layout_opt/opt_tools/__pycache__/fitness_c_urfd.cpython-311.pyc +0 -0
  22. eflips_depot-3.0.2/eflips/depot/layout_opt/opt_tools/__pycache__/fitness_util.cpython-311.pyc +0 -0
  23. eflips_depot-3.0.2/eflips/depot/layout_opt/opt_tools/__pycache__/init.cpython-311.pyc +0 -0
  24. eflips_depot-3.0.2/eflips/depot/layout_opt/opt_tools/__pycache__/mutation.cpython-311.pyc +0 -0
  25. {eflips_depot-3.0.2 → eflips_depot-3.0.3}/LICENSE.md +0 -0
  26. {eflips_depot-3.0.2 → eflips_depot-3.0.3}/README.md +0 -0
  27. {eflips_depot-3.0.2 → eflips_depot-3.0.3}/eflips/depot/__init__.py +0 -0
  28. {eflips_depot-3.0.2 → eflips_depot-3.0.3}/eflips/depot/api/defaults/default_settings.json +0 -0
  29. {eflips_depot-3.0.2 → eflips_depot-3.0.3}/eflips/depot/api/private/__init__.py +0 -0
  30. {eflips_depot-3.0.2 → eflips_depot-3.0.3}/eflips/depot/api/private/util.py +0 -0
  31. {eflips_depot-3.0.2 → eflips_depot-3.0.3}/eflips/depot/configuration.py +0 -0
  32. {eflips_depot-3.0.2 → eflips_depot-3.0.3}/eflips/depot/depot.py +0 -0
  33. {eflips_depot-3.0.2 → eflips_depot-3.0.3}/eflips/depot/evaluation.py +0 -0
  34. {eflips_depot-3.0.2 → eflips_depot-3.0.3}/eflips/depot/input_epex_power_price.py +0 -0
  35. {eflips_depot-3.0.2 → eflips_depot-3.0.3}/eflips/depot/layout_opt/__init__.py +0 -0
  36. {eflips_depot-3.0.2 → eflips_depot-3.0.3}/eflips/depot/layout_opt/doc/__init__.py +0 -0
  37. {eflips_depot-3.0.2 → eflips_depot-3.0.3}/eflips/depot/layout_opt/doc/direct_details.pdf +0 -0
  38. {eflips_depot-3.0.2 → eflips_depot-3.0.3}/eflips/depot/layout_opt/evaluation.py +0 -0
  39. {eflips_depot-3.0.2 → eflips_depot-3.0.3}/eflips/depot/layout_opt/opt_tools/__init__.py +0 -0
  40. {eflips_depot-3.0.2 → eflips_depot-3.0.3}/eflips/depot/layout_opt/opt_tools/crossover.py +0 -0
  41. {eflips_depot-3.0.2 → eflips_depot-3.0.3}/eflips/depot/layout_opt/opt_tools/fitness_c_urfd.py +0 -0
  42. {eflips_depot-3.0.2 → eflips_depot-3.0.3}/eflips/depot/layout_opt/opt_tools/fitness_util.py +0 -0
  43. {eflips_depot-3.0.2 → eflips_depot-3.0.3}/eflips/depot/layout_opt/opt_tools/init.py +0 -0
  44. {eflips_depot-3.0.2 → eflips_depot-3.0.3}/eflips/depot/layout_opt/opt_tools/mutation.py +0 -0
  45. {eflips_depot-3.0.2 → eflips_depot-3.0.3}/eflips/depot/layout_opt/optimize_c_urfd.py +0 -0
  46. {eflips_depot-3.0.2 → eflips_depot-3.0.3}/eflips/depot/layout_opt/packing.py +0 -0
  47. {eflips_depot-3.0.2 → eflips_depot-3.0.3}/eflips/depot/layout_opt/settings.py +0 -0
  48. {eflips_depot-3.0.2 → eflips_depot-3.0.3}/eflips/depot/layout_opt/template_creation.py +0 -0
  49. {eflips_depot-3.0.2 → eflips_depot-3.0.3}/eflips/depot/layout_opt/util.py +0 -0
  50. {eflips_depot-3.0.2 → eflips_depot-3.0.3}/eflips/depot/plots.py +0 -0
  51. {eflips_depot-3.0.2 → eflips_depot-3.0.3}/eflips/depot/processes.py +0 -0
  52. {eflips_depot-3.0.2 → eflips_depot-3.0.3}/eflips/depot/rating.py +0 -0
  53. {eflips_depot-3.0.2 → eflips_depot-3.0.3}/eflips/depot/resources.py +0 -0
  54. {eflips_depot-3.0.2 → eflips_depot-3.0.3}/eflips/depot/settings_config.py +0 -0
  55. {eflips_depot-3.0.2 → eflips_depot-3.0.3}/eflips/depot/simple_vehicle.py +0 -0
  56. {eflips_depot-3.0.2 → eflips_depot-3.0.3}/eflips/depot/simulation.py +0 -0
  57. {eflips_depot-3.0.2 → eflips_depot-3.0.3}/eflips/depot/smart_charging.py +0 -0
  58. {eflips_depot-3.0.2 → eflips_depot-3.0.3}/eflips/depot/standalone.py +0 -0
  59. {eflips_depot-3.0.2 → eflips_depot-3.0.3}/eflips/depot/validation.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: eflips-depot
3
- Version: 3.0.2
3
+ Version: 3.0.3
4
4
  Summary: Depot Simulation for eFLIPS
5
5
  License: AGPLv3
6
6
  Author: Enrico Lauth
@@ -11,8 +11,8 @@ Classifier: Programming Language :: Python :: 3
11
11
  Classifier: Programming Language :: Python :: 3.10
12
12
  Classifier: Programming Language :: Python :: 3.11
13
13
  Classifier: Programming Language :: Python :: 3.12
14
- Requires-Dist: eflips (>=0.1.1,<0.2.0)
15
- Requires-Dist: eflips-model (>=3.1.0,<4.0.0)
14
+ Requires-Dist: eflips (>=0.1.3,<0.2.0)
15
+ Requires-Dist: eflips-model (>=3.2.0,<4.0.0)
16
16
  Requires-Dist: pandas (>=2.1.4,<3.0.0)
17
17
  Requires-Dist: simpy (>=4.0.1,<5.0.0)
18
18
  Requires-Dist: xlrd (<=1.2.0)
@@ -26,6 +26,7 @@ The following steps are recommended for using the API:
26
26
  """
27
27
  import copy
28
28
  import os
29
+ import warnings
29
30
  from datetime import timedelta
30
31
  from math import ceil
31
32
  from typing import Any, Dict, Optional, Union
@@ -41,6 +42,8 @@ from eflips.model import (
41
42
  Scenario,
42
43
  Trip,
43
44
  Vehicle,
45
+ Process,
46
+ AssocAreaProcess,
44
47
  )
45
48
  from sqlalchemy.orm import Session
46
49
  from sqlalchemy.sql import select
@@ -529,13 +532,15 @@ def init_simulation(
529
532
  depot_id
530
533
  ] = vehicle_count_dict[depot_id]
531
534
  else:
532
- # Calculate it from the size of the areas, with a 2x margin
535
+ # Calculate it from the size of the areas (except the area for the first standby process, which is already
536
+ # really large), with a 2x margin
533
537
  for vehicle_type in vehicle_types_for_depot:
534
538
  vehicle_count = sum(
535
539
  [
536
540
  area.capacity
537
541
  for area in depot.areas
538
542
  if area.vehicle_type_id == int(vehicle_type)
543
+ and depot.default_plan.processes[0] not in area.processes
539
544
  ]
540
545
  )
541
546
  eflips.globalConstants["depot"]["vehicle_count"][depot_id][
@@ -684,6 +689,7 @@ def add_evaluation_to_database(
684
689
 
685
690
  dict_of_events = {}
686
691
 
692
+ # Generate process log for each schedule
687
693
  for finished_trip in current_vehicle.finished_trips:
688
694
  dict_of_events[finished_trip.atd] = {
689
695
  "type": "trip",
@@ -710,6 +716,57 @@ def add_evaluation_to_database(
710
716
  waiting_log = current_vehicle.logger.loggedData["area_waiting_time"]
711
717
  battery_log = current_vehicle.battery_logs
712
718
 
719
+ # Create standby events according to waiting_log
720
+ waiting_log_timekeys = sorted(waiting_log.keys())
721
+
722
+ for idx in range(len(waiting_log_timekeys)):
723
+ end_time = waiting_log_timekeys[idx]
724
+ waiting_info = waiting_log[end_time]
725
+
726
+ if waiting_info["waiting_time"] == 0:
727
+ continue
728
+
729
+ # Vehicle is waiting in the last area in waiting_log and expecting to enter the current area
730
+ expected_area = waiting_info["area"]
731
+ # Find the area for standby arrival event
732
+
733
+ waiting_area_id = (
734
+ session.query(Area.id)
735
+ .join(AssocAreaProcess, AssocAreaProcess.area_id == Area.id)
736
+ .join(Process, Process.id == AssocAreaProcess.process_id)
737
+ .filter(
738
+ Process.dispatchable == False,
739
+ Process.duration.is_(None),
740
+ Process.electric_power.is_(None),
741
+ Area.vehicle_type_id == int(current_vehicle.vehicle_type.ID),
742
+ Area.scenario_id == scenario.id,
743
+ )
744
+ .one()[0]
745
+ )
746
+
747
+ # Make sure the vehicle is waiting at an area with enough capacity
748
+
749
+ current_slot = slot_log[waiting_log_timekeys[idx - 1]]
750
+
751
+ start_time = end_time - waiting_info["waiting_time"]
752
+
753
+ warnings.warn(
754
+ f"Vehicle {current_vehicle.ID} is waiting at {waiting_area_id} because area {expected_area} is full."
755
+ )
756
+
757
+ dict_of_events[start_time] = {
758
+ "type": "Standby",
759
+ "end": end_time,
760
+ "area": waiting_area_id,
761
+ "slot": current_slot,
762
+ "is_area_sink": waiting_area_id,
763
+ }
764
+
765
+ # Create a list of battery log in order of time asc. Convenient for looking up corresponding soc
766
+ battery_log_list = []
767
+ for log in battery_log:
768
+ battery_log_list.append((log.t, log.energy / log.energy_real))
769
+
713
770
  for start_time, process_log in current_vehicle.logger.loggedData[
714
771
  "dwd.active_processes_copy"
715
772
  ].items():
@@ -843,11 +900,11 @@ def add_evaluation_to_database(
843
900
  f"Invalid process status {process.status} for process {process.ID}."
844
901
  )
845
902
 
846
- time_keys = sorted(dict_of_events.keys(), reverse=True)
847
- if len(time_keys) != 0:
903
+ reversed_time_keys = sorted(dict_of_events.keys(), reverse=True)
904
+ if len(reversed_time_keys) != 0:
848
905
  # Generating valid event-list
849
906
  is_copy = True
850
- for start_time in time_keys:
907
+ for start_time in reversed_time_keys:
851
908
  process_dict = dict_of_events[start_time]
852
909
  if process_dict["type"] == "trip":
853
910
  is_copy = process_dict["is_copy"]
@@ -875,17 +932,31 @@ def add_evaluation_to_database(
875
932
  # End time of 0-duration processes are start time of the next process
876
933
 
877
934
  if "end" not in process_dict:
878
- end_time = time_keys[time_keys.index(start_time) - 1]
935
+ # End time will be the one time key "later"
936
+ end_time = reversed_time_keys[
937
+ reversed_time_keys.index(start_time) - 1
938
+ ]
879
939
  process_dict["end"] = end_time
880
940
 
881
941
  # Get soc
882
942
  soc_start = None
883
943
  soc_end = None
884
- for log in battery_log:
885
- if log.t == start_time:
886
- soc_start = log.energy / log.energy_real
887
- if log.t == process_dict["end"]:
888
- soc_end = log.energy / log.energy_real
944
+
945
+ for i in range(len(battery_log_list)):
946
+ log = battery_log_list[i]
947
+
948
+ if log[0] == start_time:
949
+ soc_start = log[1]
950
+ if log[0] == process_dict["end"]:
951
+ soc_end = log[1]
952
+ if log[0] < start_time < battery_log_list[i + 1][0]:
953
+ soc_start = log[1]
954
+ if (
955
+ log[0]
956
+ < process_dict["end"]
957
+ < battery_log_list[i + 1][0]
958
+ ):
959
+ soc_end = log[1]
889
960
 
890
961
  current_event = Event(
891
962
  scenario=scenario,
@@ -917,12 +988,12 @@ def add_evaluation_to_database(
917
988
 
918
989
  # For non-copy schedules with no predecessor events, adding a dummy standby-departure
919
990
  if (
920
- dict_of_events[time_keys[-1]]["type"] == "trip"
921
- and dict_of_events[time_keys[-1]]["is_copy"] is False
991
+ dict_of_events[reversed_time_keys[-1]]["type"] == "trip"
992
+ and dict_of_events[reversed_time_keys[-1]]["is_copy"] is False
922
993
  ):
923
- standby_start = time_keys[-1] - 1
924
- standby_end = time_keys[-1]
925
- rotation_id = str(dict_of_events[time_keys[-1]]["id"])
994
+ standby_start = reversed_time_keys[-1] - 1
995
+ standby_end = reversed_time_keys[-1]
996
+ rotation_id = str(dict_of_events[reversed_time_keys[-1]]["id"])
926
997
  area = (
927
998
  session.query(Area)
928
999
  .filter(Area.vehicle_type_id == vehicle_type_id)
@@ -961,49 +1032,16 @@ def add_evaluation_to_database(
961
1032
  timeseries=None,
962
1033
  )
963
1034
 
1035
+ session.add(standby_event)
964
1036
  list_of_events.append(standby_event)
965
1037
 
966
- new_old_vehicle = {}
967
- matched_vehicle_id = 0
968
- for schedule_id, vehicle_id in list_of_assigned_schedules:
969
- if vehicle_id != matched_vehicle_id:
970
- matched_vehicle_id = vehicle_id
971
- # Get rotation from db with id
972
- rotation_q = session.query(Rotation).filter(Rotation.id == schedule_id)
973
- # Match old and new vehicle id
974
- old_vehicle_id = rotation_q.one().vehicle_id
975
- new_old_vehicle[vehicle_id] = old_vehicle_id
976
-
977
1038
  # New rotation assignment
978
-
979
1039
  for schedule_id, vehicle_id in list_of_assigned_schedules:
980
1040
  # Get corresponding old vehicle id
981
- old_vehicle_id = new_old_vehicle[vehicle_id]
982
1041
  session.query(Rotation).filter(Rotation.id == schedule_id).update(
983
- {"vehicle_id": old_vehicle_id}, synchronize_session="auto"
1042
+ {"vehicle_id": vehicle_id}, synchronize_session="auto"
984
1043
  )
985
1044
 
986
- # Delete all non-depot events
987
- session.query(Event).filter(
988
- Event.scenario == scenario,
989
- Event.trip_id.isnot(None) | Event.station_id.isnot(None),
990
- ).delete()
991
-
992
- session.flush()
993
-
994
- # Update depot events with old vehicle id
995
- for new_vehicle_id, old_vehicle_id in new_old_vehicle.items():
996
- session.query(Event).filter(
997
- Event.scenario == scenario,
998
- Event.vehicle_id == new_vehicle_id,
999
- ).update({"vehicle_id": old_vehicle_id}, synchronize_session="auto")
1000
-
1001
- session.query(Vehicle).filter(
1002
- Vehicle.id == new_vehicle_id,
1003
- ).delete(synchronize_session="auto")
1004
-
1005
- session.flush()
1006
-
1007
1045
  # Delete all non-depot events
1008
1046
  session.query(Event).filter(
1009
1047
  Event.scenario == scenario,
@@ -1013,7 +1051,6 @@ def add_evaluation_to_database(
1013
1051
  session.flush()
1014
1052
 
1015
1053
  # Delete all vehicles without rotations
1016
-
1017
1054
  vehicle_assigned_sq = (
1018
1055
  session.query(Rotation.vehicle_id)
1019
1056
  .filter(Rotation.scenario == scenario)
@@ -302,10 +302,13 @@ def create_simple_depot(
302
302
  It has one area for each vehicle type and a charging process for each
303
303
  area. Also an arrival area for each vehicle type.
304
304
 
305
- :param scenario:
306
- :param station:
307
- :param vehicle_type_dict:
308
- :param session:
305
+ :param scenario: The scenario to be simulated
306
+ :param station: The station where the depot is located
307
+ :param charging_capacities: A dictionary of vehicle types and the number of vehicles that can be charged at the same time
308
+ :param cleaning_capacities: A dictionary of vehicle types and the number of vehicles that can be cleaned at the same time
309
+ :param charging_power: The power of the charging process
310
+ :param cleaning_duration: The duration of the cleaning process
311
+ :param session: An SQLAlchemy session object to the database
309
312
  :return: Nothing. Depots are created in the database.
310
313
  """
311
314
 
@@ -354,19 +357,8 @@ def create_simple_depot(
354
357
 
355
358
  for vehicle_type in charging_capacities.keys():
356
359
  charging_count = charging_capacities[vehicle_type]
357
- # Add a safety margin of 20% to the parking capacity
358
- charging_count = int(ceil(charging_count * 1.2))
359
-
360
- # Create stand by arrival area
361
- arrival_area = Area(
362
- scenario=scenario,
363
- name=f"Arrival for {vehicle_type.name_short}",
364
- depot=depot,
365
- area_type=AreaType.DIRECT_ONESIDE,
366
- capacity=charging_count,
367
- )
368
- session.add(arrival_area)
369
- arrival_area.vehicle_type = vehicle_type
360
+ # Add a safety margin of 100% to the parking capacity
361
+ charging_count = int(ceil(charging_count * 2))
370
362
 
371
363
  # Create charging area
372
364
  charging_area = Area(
@@ -381,8 +373,8 @@ def create_simple_depot(
381
373
 
382
374
  # Create cleaning area
383
375
  cleaning_count = cleaning_capacities[vehicle_type]
384
- # Add a safety margin of 20% to the parking capacity
385
- cleaning_count = int(ceil(cleaning_count * 1.2))
376
+ # Add a safety margin of 100% to the parking capacity
377
+ cleaning_count = int(ceil(cleaning_count * 2))
386
378
 
387
379
  cleaning_area = Area(
388
380
  scenario=scenario,
@@ -394,6 +386,18 @@ def create_simple_depot(
394
386
  session.add(cleaning_area)
395
387
  cleaning_area.vehicle_type = vehicle_type
396
388
 
389
+ # Create stand by arrival area
390
+ arrival_area = Area(
391
+ scenario=scenario,
392
+ name=f"Arrival for {vehicle_type.name_short}",
393
+ depot=depot,
394
+ area_type=AreaType.DIRECT_ONESIDE,
395
+ capacity=(charging_count + cleaning_count)
396
+ * 2, # SHould be huge, not all of it will be used
397
+ )
398
+ session.add(arrival_area)
399
+ arrival_area.vehicle_type = vehicle_type
400
+
397
401
  arrival_area.processes.append(standby_arrival)
398
402
  cleaning_area.processes.append(clean)
399
403
  charging_area.processes.append(charging)
@@ -1,4 +1,6 @@
1
1
  # -*- coding: utf-8 -*-
2
+ import warnings
3
+
2
4
  import eflips
3
5
  from eflips.settings import globalConstants
4
6
  from eflips.helperFunctions import flexprint
@@ -248,6 +250,18 @@ class VehicleFilter:
248
250
  required_energy = (
249
251
  self.trip.start_soc - self.trip.end_soc
250
252
  ) * vehicle.battery.energy_real
253
+
254
+ # If the vehicle is fully charged and its fully charged energy is still lower than the required energy,
255
+ # dispatch anyway and warn the user
256
+ if (
257
+ abs(vehicle.battery.soc - 1) < 1e-6
258
+ and vehicle.battery.energy_real < required_energy
259
+ ):
260
+ warnings.warn(
261
+ f"Vehicle {vehicle.ID} is fully charged but the required energy for the trip is higher than the fully charged energy. Dispatching anyway."
262
+ )
263
+ return True
264
+
251
265
  result = required_energy <= vehicle.battery.energy_remaining
252
266
 
253
267
  else:
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "eflips-depot"
3
- version = "3.0.2"
3
+ version = "3.0.3"
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",
@@ -12,9 +12,9 @@ packages = [{ include = "eflips/depot" }]
12
12
  [tool.poetry.dependencies]
13
13
  python = "^3.10"
14
14
  simpy = "^4.0.1"
15
- eflips-model = "^3.1.0"
15
+ eflips-model = "^3.2.0"
16
16
  # Legacy dependencies, which are still needed until we refactor the code
17
- eflips = "^0.1.1"
17
+ eflips = "^0.1.3"
18
18
  xlsxwriter = "^3.1.9"
19
19
  pandas = "^2.1.4"
20
20
  xlrd = "<=1.2.0"
Binary file
File without changes
File without changes