eflips-depot 4.3.18__py3-none-any.whl → 4.4.0__py3-none-any.whl

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.
@@ -30,25 +30,23 @@ import logging
30
30
  import os
31
31
  import warnings
32
32
  from collections import OrderedDict
33
- from dataclasses import dataclass
34
33
  from datetime import timedelta
35
34
  from enum import Enum
36
35
  from math import ceil
37
- from typing import Any, Dict, Optional, Union, List
36
+ from typing import Any, Dict, Optional, Union
38
37
 
39
38
  import sqlalchemy.orm
40
39
  from eflips.model import (
41
40
  Area,
42
- AreaType,
43
41
  Depot,
44
42
  Event,
45
43
  EventType,
46
44
  Rotation,
47
45
  Scenario,
48
- Station,
49
46
  Trip,
50
47
  Vehicle,
51
48
  VehicleType,
49
+ AreaType,
52
50
  )
53
51
  from sqlalchemy.orm import Session
54
52
 
@@ -58,14 +56,10 @@ from eflips.depot import (
58
56
  SimulationHost,
59
57
  )
60
58
  from eflips.depot.api.private.depot import (
61
- _generate_all_direct_depot,
62
- create_simple_depot,
63
59
  delete_depots,
64
60
  depot_to_template,
65
61
  group_rotations_by_start_end_stop,
66
- generate_line_depot_layout,
67
- real_peak_area_utilization,
68
- real_peak_vehicle_count,
62
+ generate_depot,
69
63
  )
70
64
  from eflips.depot.api.private.results_to_database import (
71
65
  get_finished_schedules_per_vehicle,
@@ -245,8 +239,18 @@ def simple_consumption_simulation(
245
239
  for rotation in rotations:
246
240
  rotation: Rotation
247
241
  with session.no_autoflush:
248
- vehicle_type = rotation.vehicle_type
249
- vehicle = rotation.vehicle
242
+ vehicle_type = (
243
+ session.query(VehicleType)
244
+ .join(Rotation)
245
+ .filter(Rotation.id == rotation.id)
246
+ .one()
247
+ )
248
+ vehicle = (
249
+ session.query(Vehicle)
250
+ .join(Rotation)
251
+ .filter(Rotation.id == rotation.id)
252
+ .one()
253
+ )
250
254
  if vehicle_type.consumption is None:
251
255
  raise ValueError(
252
256
  "The vehicle type does not have a consumption value set."
@@ -387,297 +391,9 @@ def simple_consumption_simulation(
387
391
  session.add(current_event)
388
392
 
389
393
 
390
- @dataclass
391
- class DepotConfiguration:
392
- charging_power: float
393
- line_counts: Dict[VehicleType, int]
394
- direct_counts: Dict[VehicleType, int]
395
- clean_duration: int
396
- num_clean_areas: int
397
- num_shunting_areas: int
398
-
399
-
400
- def generate_realistic_depot_layout(
401
- scenario: Union[Scenario, int, Any],
402
- charging_power: float,
403
- database_url: Optional[str] = None,
404
- delete_existing_depot: bool = False,
405
- line_length: int = 8,
406
- CLEAN_DURATION: int = 10 * 60, # 10 minutes in seconds
407
- shunting_duration: timedelta = timedelta(minutes=5),
408
- ) -> DepotConfiguration:
409
- """
410
- Creates a realistic depot layout for the scenario.
411
-
412
- This is done by starting with an all direct depot layout,
413
- looking at the vehicle count, creating an "all line" layout and then turning some of these lines into direct
414
- areas until the vehicle count of the all direct depot layout (+ an allowance) is reached.
415
-
416
- :param scenario: The scenario for which the depot layout should be generated.
417
- :param charging_power: The charging power for the line areas in kW.
418
- :param database_url: An optional database URL. If no database URL is passed and the `scenario` parameter is not a
419
- :class:`eflips.model.Scenario` object, the environment variable `DATABASE_URL` must be set to a valid database
420
- URL.
421
- :param delete_existing_depot: Whether to delete an existing depot layout for this scenario. If set to False and a
422
- depot layout already exists, a ValueError will be raised.
423
- :param charging_power_direct: The charging power for the direct areas in kW. If not set, the charging power for the
424
- line areas will be used.
425
-
426
- :return: None. The depot layout will be added to the database.
427
- """
428
- logging.basicConfig(level=logging.DEBUG) # TODO: Remove this line
429
- logger = logging.getLogger(__name__)
430
-
431
- with create_session(scenario, database_url) as (session, scenario):
432
- # STEP 0: Delete existing depots if asked to, raise an Exception otherwise
433
- if session.query(Depot).filter(Depot.scenario_id == scenario.id).count() != 0:
434
- if delete_existing_depot is False:
435
- raise ValueError("Depot already exists.")
436
- delete_depots(scenario, session)
437
-
438
- # Make sure that the consumption simulation has been run
439
- if session.query(Event).filter(Event.scenario_id == scenario.id).count() == 0:
440
- raise ValueError(
441
- "No consumption simulation found. Please run the consumption simulation first."
442
- )
443
-
444
- # STEP 2: Identify the spots where we will put a depot
445
- # Identify all the spots that serve as start *and* end of a rotation
446
- depot_stations_and_vehicle_types = group_rotations_by_start_end_stop(
447
- scenario.id, session
448
- )
449
-
450
- # STEP 3: Put "all direct" depots at these spots and find the vehicle counts
451
- depot_stations = []
452
- vehicle_type_rotation_dict_by_station: Dict[
453
- Station, Dict[VehicleType, List[Rotation]]
454
- ] = dict()
455
- for (
456
- first_last_stop_tup,
457
- vehicle_type_rotation_dict,
458
- ) in depot_stations_and_vehicle_types.items():
459
- first_stop, last_stop = first_last_stop_tup
460
- if first_stop != last_stop:
461
- raise ValueError("First and last stop of a rotation are not the same.")
462
- depot_stations.append(first_stop)
463
- vehicle_type_rotation_dict_by_station[
464
- first_stop
465
- ] = vehicle_type_rotation_dict
466
-
467
- del first_last_stop_tup, vehicle_type_rotation_dict
468
-
469
- all_direct_counts: Dict[
470
- Station, Dict[VehicleType, int]
471
- ] = vehicle_counts_for_direct_layout(
472
- CLEAN_DURATION=CLEAN_DURATION,
473
- charging_power=charging_power,
474
- stations=depot_stations,
475
- scenario=scenario,
476
- session=session,
477
- vehicle_type_dict_by_station=vehicle_type_rotation_dict_by_station,
478
- shunting_duration=shunting_duration,
479
- )
480
-
481
- # STEP 4: Run the simulation with depots that also have a lot of line areas
482
- # I know I could probably skip step 3 and go directly to step 4, but that's how I got it working and
483
- # I'm too lazy to change it now
484
-
485
- for station, vehicle_type_and_counts in all_direct_counts.items():
486
- line_counts: Dict[VehicleType, int] = dict()
487
- direct_counts: Dict[VehicleType, int] = dict()
488
-
489
- # Create a Depot that has a lot of line areas as well
490
- for vehicle_type, count in vehicle_type_and_counts.items():
491
- line_counts[vehicle_type] = ceil(count / line_length)
492
- direct_counts[vehicle_type] = ceil(count) + 500
493
-
494
- # Run the simulation with this depot
495
- generate_line_depot_layout(
496
- CLEAN_DURATION=CLEAN_DURATION,
497
- charging_power=charging_power,
498
- station=station,
499
- scenario=scenario,
500
- session=session,
501
- direct_counts=direct_counts,
502
- line_counts=line_counts,
503
- line_length=line_length,
504
- vehicle_type_rotation_dict=vehicle_type_rotation_dict_by_station[
505
- station
506
- ],
507
- shunting_duration=shunting_duration,
508
- )
509
-
510
- # Simulate the depot
511
- # We will not be using add_evaluation_to_database instead taking the vehicle counts directly from the `ev` object
512
- logger.info("Simulating the scenario")
513
- logger.info("1/2: Initializing the simulation host")
514
- simulation_host = init_simulation(
515
- scenario=scenario,
516
- session=session,
517
- )
518
- logger.info("2/2: Running the simulation")
519
- depot_evaluations = run_simulation(simulation_host)
520
-
521
- # We need to remember the depot-id-station mapping
522
- depot_id_station_mapping: Dict[str, Station] = dict()
523
- for depot_id_as_str, ev in depot_evaluations.items():
524
- station = (
525
- session.query(Station)
526
- .join(Depot)
527
- .filter(Depot.id == int(depot_id_as_str))
528
- .one()
529
- )
530
- depot_id_station_mapping[depot_id_as_str] = station
531
-
532
- # Delete the old depot
533
- delete_depots(scenario, session)
534
-
535
- for depot_id_as_str, ev in depot_evaluations.items():
536
- assert isinstance(ev, DepotEvaluation)
537
-
538
- if False:
539
- ev.path_results = depot_id_as_str
540
- os.makedirs(depot_id_as_str, exist_ok=True)
541
-
542
- ev.vehicle_periods(
543
- periods={
544
- "depot general": "darkgray",
545
- "park": "lightgray",
546
- "Arrival Cleaning": "steelblue",
547
- "Charging": "forestgreen",
548
- "Standby Pre-departure": "darkblue",
549
- "precondition": "black",
550
- "trip": "wheat",
551
- },
552
- save=True,
553
- show=False,
554
- formats=(
555
- "pdf",
556
- "png",
557
- ),
558
- show_total_power=True,
559
- show_annotates=True,
560
- )
561
-
562
- # Find the actual utilization.
563
- utilization: Dict[str, int] = real_peak_area_utilization(ev)
564
- utilization = {
565
- session.query(VehicleType).filter(VehicleType.id == int(k)).one(): v
566
- for k, v in utilization.items()
567
- }
568
-
569
- # Turn utilization into a two dictionaries, one for line areas and one for direct areas
570
- for vehicle_type, counts in utilization.items():
571
- line_counts[vehicle_type] = counts[AreaType.LINE]
572
- direct_counts[vehicle_type] = counts[AreaType.DIRECT_ONESIDE] + 100
573
-
574
- station = depot_id_station_mapping[depot_id_as_str]
575
-
576
- generate_line_depot_layout(
577
- CLEAN_DURATION=CLEAN_DURATION,
578
- charging_power=charging_power,
579
- station=station,
580
- scenario=scenario,
581
- session=session,
582
- direct_counts=direct_counts,
583
- line_counts=line_counts,
584
- line_length=line_length,
585
- vehicle_type_rotation_dict=vehicle_type_rotation_dict_by_station[
586
- station
587
- ],
588
- shunting_duration=shunting_duration,
589
- )
590
-
591
-
592
- def vehicle_counts_for_direct_layout(
593
- CLEAN_DURATION: int,
594
- charging_power: float,
595
- stations: List[Station],
596
- scenario: Scenario,
597
- session: sqlalchemy.orm.session.Session,
598
- vehicle_type_dict_by_station: Dict[Station, Dict[VehicleType, List[Rotation]]],
599
- shunting_duration: timedelta = timedelta(minutes=5),
600
- ) -> Dict[Station, Dict[VehicleType, int]]:
601
- """
602
- Generate a simple depot, simulate it and return the number of vehicles for each vehicle type.
603
-
604
- Do this for each depot station in the scenario.
605
- :param CLEAN_DURATION: The duration of the cleaning process in seconds.
606
- :param charging_power: The charging power of the charging area in kW.
607
- :param station: The stop where the depot is located.
608
- :param scenario: The scenario for which the depot layout should be generated.
609
- :param session: The SQLAlchemy session object.
610
- :param vehicle_type_dict: A dictionary with vehicle types as keys and rotations as values.
611
- :return: A dictionary with vehicle types as keys and the number of vehicles as values.
612
- """
613
- logger = logging.getLogger(__name__)
614
-
615
- for station in stations:
616
- logger.info(f"Generating all direct depot layout at {station.name}")
617
- # Generate the depot
618
- direct_counts = {}
619
- line_counts = {}
620
- for vehicle_type, rotations in vehicle_type_dict_by_station[station].items():
621
- direct_counts[vehicle_type] = len(rotations)
622
- line_counts[vehicle_type] = 0
623
-
624
- generate_line_depot_layout(
625
- CLEAN_DURATION=CLEAN_DURATION,
626
- charging_power=charging_power,
627
- station=station,
628
- scenario=scenario,
629
- session=session,
630
- direct_counts=direct_counts,
631
- line_counts=line_counts,
632
- line_length=8, # We don't care about the line length here
633
- vehicle_type_rotation_dict=vehicle_type_dict_by_station[station],
634
- shunting_duration=shunting_duration,
635
- )
636
-
637
- # Simulate the scenario
638
- # We will not be using add_evaluation_to_database instead taking the vehicle counts directly from the `ev` object
639
- logger.info("Simulating the scenario")
640
- logger.info("1/2: Initializing the simulation host")
641
- simulation_host = init_simulation(
642
- scenario=scenario,
643
- session=session,
644
- )
645
- logger.info("2/2: Running the simulation")
646
- depot_evaluations = run_simulation(simulation_host)
647
-
648
- assert len(depot_evaluations) == len(stations)
649
- depot_evaluations: Dict[str, DepotEvaluation]
650
-
651
- ret_val: Dict[Station, Dict[VehicleType, int]] = dict()
652
-
653
- for depot_id_as_str, ev in depot_evaluations.items():
654
- assert isinstance(ev, DepotEvaluation)
655
- counts: Dict[str, int] = real_peak_vehicle_count(ev)
656
- # The key of the dictionary is the vehicle type ID as a string. We need to convert it to a vehicle type object
657
- vehicle_type_dict = {
658
- session.query(VehicleType).filter(VehicleType.id == int(k)).one(): v
659
- for k, v in counts.items()
660
- }
661
-
662
- # Find the station object
663
- station = (
664
- session.query(Station)
665
- .join(Depot)
666
- .filter(Depot.id == int(depot_id_as_str))
667
- .one()
668
- )
669
-
670
- ret_val[station] = vehicle_type_dict
671
-
672
- # Delete the old depots
673
- delete_depots(scenario, session)
674
-
675
- return ret_val
676
-
677
-
678
394
  def generate_depot_layout(
679
395
  scenario: Union[Scenario, int, Any],
680
- charging_power: float = 150,
396
+ charging_power: float = 90,
681
397
  database_url: Optional[str] = None,
682
398
  delete_existing_depot: bool = False,
683
399
  ) -> None:
@@ -710,8 +426,6 @@ def generate_depot_layout(
710
426
 
711
427
  :return: None. The depot layout will be added to the database.
712
428
  """
713
- CLEAN_DURATION = 30 * 60 # 30 minutes in seconds
714
-
715
429
  with create_session(scenario, database_url) as (session, scenario):
716
430
  # Handles existing depot
717
431
  if session.query(Depot).filter(Depot.scenario_id == scenario.id).count() != 0:
@@ -727,13 +441,24 @@ def generate_depot_layout(
727
441
  first_stop, last_stop = first_last_stop_tup
728
442
  if first_stop != last_stop:
729
443
  raise ValueError("First and last stop of a rotation are not the same.")
730
- _generate_all_direct_depot(
731
- CLEAN_DURATION,
732
- charging_power,
444
+
445
+ # Create one direct slot for each rotation (it's way too much, but should work)
446
+ vt_capacity_dict: Dict[VehicleType, Dict[AreaType, None | int]] = {}
447
+ for vehicle_type, rotations in vehicle_type_dict.items():
448
+ rotation_count = len(rotations)
449
+ vt_capacity_dict[vehicle_type] = {
450
+ AreaType.LINE: None,
451
+ AreaType.DIRECT_ONESIDE: rotation_count,
452
+ AreaType.DIRECT_TWOSIDE: None,
453
+ }
454
+ generate_depot(
455
+ vt_capacity_dict,
733
456
  first_stop,
734
457
  scenario,
735
458
  session,
736
- vehicle_type_dict,
459
+ charging_power=charging_power,
460
+ num_shunting_slots=max(rotation_count // 10, 1),
461
+ num_cleaning_slots=max(rotation_count // 10, 1),
737
462
  )
738
463
 
739
464