eflips-depot 4.6.8__py3-none-any.whl → 4.7.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.
@@ -77,7 +77,6 @@ from eflips.depot.api.private.results_to_database import (
77
77
  update_vehicle_in_rotation,
78
78
  update_waiting_events,
79
79
  )
80
- from eflips.depot.api.private.smart_charging import optimize_charging_events_even
81
80
  from eflips.depot.api.private.util import (
82
81
  create_session,
83
82
  repeat_vehicle_schedules,
@@ -542,11 +541,25 @@ def apply_even_smart_charging(
542
541
  """
543
542
  logger = logging.getLogger(__name__)
544
543
 
544
+ try:
545
+ from eflips.opt.smart_charging import (
546
+ optimize_charging_events_even,
547
+ add_slack_time_to_events_of_depot,
548
+ )
549
+ except ImportError:
550
+ logger.error(
551
+ "The eFLIPS smart charging module is not installed. Please install eflips-opt >= 0.2.0."
552
+ )
553
+ raise
554
+
545
555
  with create_session(scenario, database_url) as (session, scenario):
546
556
  depots = session.query(Depot).filter(Depot.scenario_id == scenario.id).all()
547
557
  for depot in depots:
548
- # Load all the charging events at this depot
549
- charging_events = (
558
+ add_slack_time_to_events_of_depot(
559
+ depot, session, standby_departure_duration
560
+ )
561
+
562
+ events_for_depot = (
550
563
  session.query(Event)
551
564
  .join(Area)
552
565
  .filter(Area.depot_id == depot.id)
@@ -554,55 +567,9 @@ def apply_even_smart_charging(
554
567
  .all()
555
568
  )
556
569
 
557
- # For each event, take the subsequent STANDBY_DEPARTURE event of the same vehicle
558
- # Reduce the STANDBY_DEPARTURE events duration to 5 minutes
559
- # Move the end time of the charging event to the start time of the STANDBY_DEPARTURE event
560
- for charging_event in charging_events:
561
- next_event = (
562
- session.query(Event)
563
- .filter(Event.time_start >= charging_event.time_end)
564
- .filter(Event.vehicle_id == charging_event.vehicle_id)
565
- .order_by(Event.time_start)
566
- .first()
567
- )
568
-
569
- if (
570
- next_event is None
571
- or next_event.event_type != EventType.STANDBY_DEPARTURE
572
- ):
573
- logger.info(
574
- f"Event {charging_event.id} has no STANDBY_DEPARTURE event after a CHARGING_DEPOT "
575
- f"event. No room for smart charging."
576
- )
577
- continue
578
-
579
- assert next_event.time_start == charging_event.time_end
580
-
581
- if (
582
- next_event.time_end - next_event.time_start
583
- ) > standby_departure_duration:
584
- next_event.time_start = (
585
- next_event.time_end - standby_departure_duration
586
- )
587
- session.flush()
588
- # Add a timeseries to the charging event
589
- assert charging_event.timeseries is None
590
- charging_event.timeseries = {
591
- "time": [
592
- charging_event.time_start.isoformat(),
593
- charging_event.time_end.isoformat(),
594
- next_event.time_start.isoformat(),
595
- ],
596
- "soc": [
597
- charging_event.soc_start,
598
- charging_event.soc_end,
599
- charging_event.soc_end,
600
- ],
601
- }
602
- charging_event.time_end = next_event.time_start
603
- session.flush()
604
-
605
- optimize_charging_events_even(charging_events)
570
+ optimize_charging_events_even(events_for_depot)
571
+ for event in events_for_depot:
572
+ session.add(event)
606
573
 
607
574
 
608
575
  def simulate_scenario(
@@ -1023,7 +990,6 @@ def add_evaluation_to_database(
1023
990
  :raises UnstableSimulationException: If the simulation becomes numerically unstable or if
1024
991
  the parameters cause the solver to diverge.
1025
992
  :raises DelayedTripException: If there are delayed trips in the simulation.
1026
-
1027
993
  """
1028
994
 
1029
995
  # Read simulation start time
@@ -861,8 +861,11 @@ def depot_smallest_possible_size(
861
861
  )
862
862
  depot = session.query(Depot).filter(Depot.scenario_id == scenario.id).one()
863
863
 
864
+ # Local imports to avoid circular imports
865
+ from eflips.depot.api import SmartChargingStrategy
866
+
864
867
  # Simulate the depot
865
- simulate_scenario(scenario)
868
+ simulate_scenario(scenario, smart_charging_strategy=SmartChargingStrategy.NONE)
866
869
 
867
870
  # Find the peak usage of the depot
868
871
  peak_occupancies: Dict[VehicleType, Dict[AreaType, int]] = find_peak_usage(
@@ -958,7 +961,9 @@ def depot_smallest_possible_size(
958
961
  )
959
962
 
960
963
  # Simulate the depot
961
- simulate_scenario(scenario)
964
+ simulate_scenario(
965
+ scenario, smart_charging_strategy=SmartChargingStrategy.NONE
966
+ )
962
967
 
963
968
  # Find the peak usage of the depot
964
969
  peak_occupancies: Dict[
@@ -394,6 +394,7 @@ class VehicleSchedule:
394
394
  distance=None,
395
395
  start_soc=self.departure_soc,
396
396
  end_soc=self.arrival_soc,
397
+ minimal_soc=self.minimal_soc,
397
398
  charge_on_track=self.opportunity_charging,
398
399
  is_copy=self._is_copy,
399
400
  )
eflips/depot/filters.py CHANGED
@@ -1,9 +1,9 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  import warnings
3
3
 
4
- import eflips
5
4
  from eflips.settings import globalConstants
6
- from eflips.helperFunctions import flexprint
5
+
6
+ import eflips
7
7
 
8
8
 
9
9
  class VehicleFilter:
@@ -245,7 +245,7 @@ class VehicleFilter:
245
245
 
246
246
  elif globalConstants["depot"]["consumption_calc_mode"] == "soc_given":
247
247
  if self.trip.charge_on_track:
248
- result = round(vehicle.battery.soc, 5) >= self.trip.start_soc
248
+ result = round(vehicle.battery.soc, 5) >= self.trip.minimal_soc
249
249
  else:
250
250
  required_energy = (
251
251
  self.trip.start_soc - self.trip.end_soc
eflips/depot/processes.py CHANGED
@@ -762,8 +762,6 @@ class ChargeAbstract(VehicleProcess, ABC):
762
762
  if called by Charge(sub-)-classes). Leave None for update requests
763
763
  from outside the process.
764
764
  """
765
- self.last_update = self.env.now
766
-
767
765
  if amount is None and (
768
766
  self.last_update == self.env.now or self.starts[-1] == self.env.now
769
767
  ):
@@ -791,6 +789,7 @@ class ChargeAbstract(VehicleProcess, ABC):
791
789
  self.vehicle.battery_logs.append(
792
790
  BatteryLog(self.env.now, self.vehicle, event_name)
793
791
  )
792
+ self.last_update = self.env.now
794
793
 
795
794
 
796
795
  class Charge(ChargeAbstract):
@@ -906,9 +905,7 @@ class Charge(ChargeAbstract):
906
905
 
907
906
  except simpy.Interrupt:
908
907
  flexprint("charge interrupted", env=self.env, switch="processes")
909
- actual_charging_duration = self.env.now - self.starts[0]
910
- actual_charged_energy = (effective_power * actual_charging_duration) / 3600
911
- self.update_battery("charge_interrupt", amount=actual_charged_energy)
908
+ self.update_battery("charge_interrupt")
912
909
 
913
910
  self.charging_interface.current_power = 0
914
911
  self.vehicle.power_logs[self.env.now] = 0
@@ -185,6 +185,7 @@ class SimpleTrip:
185
185
  distance,
186
186
  start_soc=None,
187
187
  end_soc=None,
188
+ minimal_soc: float | None = None,
188
189
  charge_on_track=False,
189
190
  is_copy=False,
190
191
  ):
@@ -199,6 +200,7 @@ class SimpleTrip:
199
200
  self.distance = distance
200
201
  self.start_soc = start_soc
201
202
  self.end_soc = end_soc
203
+ self.minimal_soc = minimal_soc
202
204
  self.charge_on_track = charge_on_track
203
205
  self.is_copy = is_copy
204
206
 
@@ -1,11 +1,11 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: eflips-depot
3
- Version: 4.6.8
3
+ Version: 4.7.0
4
4
  Summary: Depot Simulation for eFLIPS
5
5
  License: AGPL-3.0-or-later
6
6
  Author: Enrico Lauth
7
7
  Author-email: enrico.lauth@tu-berlin.de
8
- Requires-Python: >=3.10,<4.0
8
+ Requires-Python: >=3.10,<3.14
9
9
  Classifier: License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)
10
10
  Classifier: Programming Language :: Python :: 3
11
11
  Classifier: Programming Language :: Python :: 3.10
@@ -1,16 +1,15 @@
1
1
  eflips/depot/__init__.py,sha256=06GUem0JIEunIyJ0_P_MLAGfibGEnNqcPPY0OBpO2NQ,1662
2
- eflips/depot/api/__init__.py,sha256=GgFmHnpawNDdhc8ki4DwXzpMDrRjl1r0TYNXKdRUehk,49718
2
+ eflips/depot/api/__init__.py,sha256=9hSopelEhFIPj7Y9gc50oZgDvh5z_NKfwNtYV2Menik,47906
3
3
  eflips/depot/api/defaults/default_settings.json,sha256=0eUDTw_rtLQFvthP8oJL93iRXlmAOravAg-4qqGMQAY,5375
4
4
  eflips/depot/api/private/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
5
  eflips/depot/api/private/consumption.py,sha256=FTF_E-DsbvJNWZJKQy_CPoHQpLz--pWY-inJYg8lsjM,16453
6
- eflips/depot/api/private/depot.py,sha256=WcCcib3NSbvoL3G06I2ajT8U5L7AkDK5qe969PHq-fg,41014
6
+ eflips/depot/api/private/depot.py,sha256=2teoo4Yzqa5KpYMIiTTlO8uC1TCYFz9YoK6eoNOGg_s,41274
7
7
  eflips/depot/api/private/results_to_database.py,sha256=R3wwGuaEsof2sW_fFv5Y9K0cl2K8W0JKe5VoN2elXTQ,25037
8
- eflips/depot/api/private/smart_charging.py,sha256=MQ9fXdKByHAz6RSKXYcpJXDBccdJKZ2qGReCHagVCyo,13033
9
- eflips/depot/api/private/util.py,sha256=gk5TJZrcvtkyrqnjR3Cq-hPRGRCovhzKz3LrBDIvN0E,16799
8
+ eflips/depot/api/private/util.py,sha256=YcvjKikgdquYXx61pbDiB3Naa1FTEvEfiEcWbnFGNH8,16841
10
9
  eflips/depot/configuration.py,sha256=Op3hlir-dEN7yHr0kTqbYANoCBKFWK6uKOv3NJl8w_w,35678
11
10
  eflips/depot/depot.py,sha256=35P1P3jMBbfq9jevmZVI46oM82osEoDL_B6C65drww0,105711
12
11
  eflips/depot/evaluation.py,sha256=qqXyP4jA1zFcKuWhliQ6n25ZlGl9mJV-vtXf0yu8WN8,140842
13
- eflips/depot/filters.py,sha256=52esFT_kldMC9-LteY4CFe8BETwr0bn-CacegVe6jsA,16131
12
+ eflips/depot/filters.py,sha256=1aUK7czuhiATC3P3NN5oRtH1I-kN_-mp_-vkzyyBXn4,16089
14
13
  eflips/depot/input_epex_power_price.py,sha256=VPDC1zy-klQpveGIZ8941hL1P_jeNq3IHoLgFTsANig,5569
15
14
  eflips/depot/layout_opt/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
15
  eflips/depot/layout_opt/doc/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -28,16 +27,16 @@ eflips/depot/layout_opt/settings.py,sha256=EUGCp4dAX22j2uF8sKqbi9a5iP8hb6QpP7t2N
28
27
  eflips/depot/layout_opt/template_creation.py,sha256=H4LoFjQfbPjTt9rGvapH2tEUWcQ56kPwDucq7t6YahU,9736
29
28
  eflips/depot/layout_opt/util.py,sha256=EYh7IN58ZjysmCFdSieQqIQ9goe1a_ZwARRHxOgjEQo,3780
30
29
  eflips/depot/plots.py,sha256=85xInZWfJIOVm03onvppgP5yLTgQeMn-1t5aoNdavyY,2509
31
- eflips/depot/processes.py,sha256=fC9Eoy1N8QC1D1r8hZnuHG3oeli5vth8DVfxsugtemM,58798
30
+ eflips/depot/processes.py,sha256=wVacSuHQnHwe2kN18h4014eN4xs2XEegy9vJdHZ7wzQ,58610
32
31
  eflips/depot/rating.py,sha256=audUASHExeWg5ugytPLlcy5QGiTiFucnZ6-v3REoO2g,16427
33
32
  eflips/depot/resources.py,sha256=0SuzN8qgMmCqa7oUEXVC_XE6pCUtxTsqOfCsaM9Oh3o,13568
34
33
  eflips/depot/settings_config.py,sha256=z7CqPdjd8QRlgZj0Zis-H13cL7LOiheRT4ctYCYGguc,2527
35
34
  eflips/depot/simple_vehicle.py,sha256=Wl3IqYxMrs6J0U0iCrG3Qq8ONuDGoZ00r7h7svVyEik,9375
36
35
  eflips/depot/simulation.py,sha256=ee0qTzOzG-8ybN36ie_NJallXfC7jUaS9JZvaYFziLs,10676
37
36
  eflips/depot/smart_charging.py,sha256=C3BYqzn2-OYY4ipXm0ETtavbAM9QXZMYULBpVoChf0E,54311
38
- eflips/depot/standalone.py,sha256=VxcTzBaB67fNJUMmjPRwKXjhqTy6oQ41Coote2LvAmk,22338
37
+ eflips/depot/standalone.py,sha256=8O01zEXghFG9zZBu0fUD0sXvbHQ-AXw6RB5M750a_sM,22419
39
38
  eflips/depot/validation.py,sha256=TIuY7cQtEJI4H2VVMSuY5IIVkacEEZ67weeMuY3NSAM,7097
40
- eflips_depot-4.6.8.dist-info/LICENSE.md,sha256=KB4XTk1fPHjtZCYDyPyreu6h1LVJVZXYg-5vePcWZAc,34143
41
- eflips_depot-4.6.8.dist-info/METADATA,sha256=YeJBI-UcgIp0m_fqql6ef1a8eMmNZRG-kMm4R9pkTqo,5940
42
- eflips_depot-4.6.8.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
43
- eflips_depot-4.6.8.dist-info/RECORD,,
39
+ eflips_depot-4.7.0.dist-info/LICENSE.md,sha256=KB4XTk1fPHjtZCYDyPyreu6h1LVJVZXYg-5vePcWZAc,34143
40
+ eflips_depot-4.7.0.dist-info/METADATA,sha256=1La8tjP82xF3JLiVyB8tVnXeA2EniNtrZWE1FyloXQE,5941
41
+ eflips_depot-4.7.0.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
42
+ eflips_depot-4.7.0.dist-info/RECORD,,
@@ -1,314 +0,0 @@
1
- from datetime import timedelta, datetime
2
- from typing import List, Dict
3
-
4
- import numpy as np
5
- import scipy
6
- from eflips.model import Event, EventType
7
-
8
-
9
- def optimize_charging_events_even(
10
- charging_events: List[Event], debug_plots: bool = False
11
- ) -> None:
12
- """
13
- This function optimizes the power draw of a list of charging events.
14
-
15
- The power draw is optimized such that the total
16
- power draw is minimized, while the energy transferred remains constant.
17
- :param charging_events: The list of charging events to optimize
18
- :return: Nothing, the charging events are updated in place
19
- """
20
-
21
- TEMPORAL_RESOLUTION = timedelta(seconds=60)
22
-
23
- assert all(
24
- [event.event_type == EventType.CHARGING_DEPOT for event in charging_events]
25
- )
26
- start_time = min([event.time_start for event in charging_events])
27
- end_time = max([event.time_end for event in charging_events])
28
-
29
- # Formulate the optimzation problem.
30
- # - Each charging event has a peak power, which cannot be exceeded.
31
- # - Each charging event has a start and end time.
32
- # - betweeen the start time and end time, the energy transferred must remain constant.
33
- # - the total power draw is the sum of all events' power at this point in time
34
- # - the total power draw must be minimized
35
-
36
- total_duration = int((end_time - start_time) / TEMPORAL_RESOLUTION)
37
- total_time = np.arange(
38
- start_time.timestamp(),
39
- end_time.timestamp(),
40
- TEMPORAL_RESOLUTION.total_seconds(),
41
- ) # The time axis, used to resample the power draw
42
-
43
- # For each event, create an array of power draws and a boolean array of charging allowed
44
- # Also note down the peak power and transferred energy
45
- params_for_events: List[Dict[str, float | np.ndarray | Event]] = []
46
- for event in charging_events:
47
- charging_allowed = np.zeros_like(total_time, dtype=int)
48
-
49
- # Calculate the power draw vector, from the start SoC, end SoC and timeseries, if available
50
- event_soc = [event.soc_start]
51
- event_time = [event.time_start.timestamp()]
52
- if event.timeseries is not None:
53
- event_soc.extend(event.timeseries["soc"])
54
- event_time.extend(
55
- [
56
- datetime.fromisoformat(t).timestamp()
57
- for t in event.timeseries["time"]
58
- ]
59
- )
60
- event_soc.append(event.soc_end)
61
- event_time.append(event.time_end.timestamp())
62
-
63
- # Resample the timeseries to the temporal resolution
64
- expanded_soc = np.interp(total_time, event_time, event_soc)
65
- expanded_power = (
66
- np.diff(expanded_soc, prepend=expanded_soc[0])
67
- * event.vehicle.vehicle_type.battery_capacity
68
- ) # Change in kWh each minute
69
- expanded_power = (
70
- expanded_power / TEMPORAL_RESOLUTION.total_seconds() * 3600
71
- ) # to kW
72
-
73
- # Between the start and end time, charging is allowed
74
- start_index = int((event.time_start - start_time) / TEMPORAL_RESOLUTION)
75
- end_index = int((event.time_end - start_time) / TEMPORAL_RESOLUTION)
76
- charging_allowed[start_index:end_index] = 1
77
- max_power = max(expanded_power)
78
- transferred_energy = (
79
- event_soc[-1] - event_soc[0]
80
- ) * event.vehicle.vehicle_type.battery_capacity # kWh
81
-
82
- params_for_events.append(
83
- {
84
- "event": event,
85
- "power_draw": expanded_power,
86
- "charging_allowed": charging_allowed,
87
- "max_power": max_power,
88
- "transferred_energy": transferred_energy,
89
- }
90
- )
91
-
92
- # The total number of vehicles at the depot is the sum of the charging allowed for each event, since it is 1 if
93
- # the bus is at the depot and 0 otherwise
94
- total_occupancy = np.sum(
95
- [event["charging_allowed"] for event in params_for_events], axis=0
96
- )
97
-
98
- # For each event, calculate an optimized power draw
99
- for params_for_event in params_for_events:
100
- # Calculate the mean power that would be drawn if the vehicle was charged evenly
101
- charging_duration = (
102
- np.sum(params_for_event["charging_allowed"])
103
- * TEMPORAL_RESOLUTION.total_seconds()
104
- )
105
- mean_power = (
106
- params_for_event["transferred_energy"] / charging_duration
107
- ) * 3600 # kW
108
-
109
- # Calculate the mean amount of vehicles present at the depot when this vehicle is charging
110
- # We only consider the time when the vehicle is charging
111
- total_occupancy_while_charging = total_occupancy[
112
- params_for_event["charging_allowed"] == 1
113
- ]
114
- mean_occupancy = np.mean(total_occupancy_while_charging)
115
-
116
- # for each timestep optimize the power draw. We want to charge less when there are more vehicles at the depot
117
- # The power draw is varies with the amount of vehicles present at the depot relative to the mean amount
118
- charging_factor = (total_occupancy / mean_occupancy) * params_for_event[
119
- "charging_allowed"
120
- ]
121
-
122
- # Make sure the charging factor is not infinite or NaN
123
- charging_factor[np.isnan(charging_factor)] = 1
124
- charging_factor[np.isinf(charging_factor)] = 1
125
- power_scaling_vector = (
126
- (mean_power * charging_factor) - mean_power
127
- ) * -1 # How much to shift the power draw
128
- if min(power_scaling_vector) < -mean_power:
129
- power_scaling_vector /= min(power_scaling_vector) / -mean_power
130
- optimized_power = params_for_event["charging_allowed"] * (
131
- power_scaling_vector + mean_power
132
- )
133
-
134
- # Cap it at the peak power
135
- optimized_power_capped = np.minimum(
136
- optimized_power, params_for_event["max_power"]
137
- )
138
-
139
- if not np.all(optimized_power_capped == optimized_power):
140
- # If the power draw is capped, we will just use the mean power draw
141
- optimized_power = params_for_event["charging_allowed"] * mean_power
142
-
143
- # Make sure the transferred energy is the same
144
- post_opt_energy = (
145
- scipy.integrate.trapezoid(optimized_power, total_time) / 3600
146
- ) # kWh
147
-
148
- if debug_plots:
149
- # Some plots for only this charging event
150
- from matplotlib import pyplot as plt
151
-
152
- valid_charging_indices = np.where(
153
- params_for_event["charging_allowed"] == 1
154
- )[0]
155
-
156
- _, axs = plt.subplots(3, 1, sharex=True)
157
- axs[0].axhline(mean_power, color="red", linestyle="--", label="Mean power")
158
- axs[0].plot(
159
- total_time[valid_charging_indices],
160
- params_for_event["power_draw"][valid_charging_indices],
161
- label="Original power draw",
162
- )
163
- axs[0].plot(
164
- total_time[valid_charging_indices],
165
- optimized_power[valid_charging_indices],
166
- label="Optimized power draw",
167
- )
168
- axs[0].plot(
169
- total_time[valid_charging_indices],
170
- optimized_power_capped[valid_charging_indices],
171
- label="Optimized power draw capped",
172
- )
173
- axs[0].set_xlabel("Time")
174
- axs[0].legend()
175
-
176
- axs[1].plot(
177
- total_time[valid_charging_indices],
178
- np.cumsum(params_for_event["power_draw"][valid_charging_indices])
179
- / 3600,
180
- label="Original energy transferred",
181
- )
182
- axs[1].plot(
183
- total_time[valid_charging_indices],
184
- np.cumsum(optimized_power[valid_charging_indices]) / 3600,
185
- label="Optimized energy transferred",
186
- )
187
- axs[1].set_xlabel("Time")
188
-
189
- axs[2].axhline(
190
- mean_occupancy, color="red", linestyle="--", label="Mean occupancy"
191
- )
192
- axs[2].plot(
193
- total_time[valid_charging_indices],
194
- total_occupancy[valid_charging_indices],
195
- )
196
-
197
- mean_arr = np.ones_like(total_occupancy) * mean_occupancy
198
- mean_cumsum = np.cumsum(mean_arr[valid_charging_indices])
199
- total_cumsum = np.cumsum(total_occupancy[valid_charging_indices])
200
- assert np.isclose(mean_cumsum[-1], total_cumsum[-1], atol=0.01)
201
-
202
- axs[2].set_xlabel("Time")
203
- plt.show()
204
-
205
- if not np.isclose(
206
- post_opt_energy, params_for_event["transferred_energy"], rtol=0.001
207
- ):
208
- # Scale the power draw to match the transferred energy
209
- optimized_power = optimized_power * (
210
- params_for_event["transferred_energy"] / post_opt_energy
211
- )
212
-
213
- # Fill NaNs with the zero power draw
214
- optimized_power[np.isnan(optimized_power)] = 0
215
-
216
- params_for_event["optimized_power"] = optimized_power
217
-
218
- event = params_for_event["event"]
219
- start_index = int((event.time_start - start_time) / TEMPORAL_RESOLUTION)
220
- end_index = (
221
- int((event.time_end - start_time) / TEMPORAL_RESOLUTION) + 1
222
- ) # +1 to include the last index
223
- powers = params_for_event["optimized_power"][start_index:end_index]
224
-
225
- energies = scipy.integrate.cumulative_trapezoid(powers, initial=0) / (
226
- 3600 / TEMPORAL_RESOLUTION.total_seconds()
227
- ) # kWh
228
- socs = event.soc_start + energies / event.vehicle.vehicle_type.battery_capacity
229
-
230
- # Make sure the last SoC is the same as the end SoC
231
- assert np.isclose(socs[-1], event.soc_end, atol=0.01)
232
- # Make sure the first SoC is the same as the start SoC
233
- assert np.isclose(socs[0], event.soc_start, atol=0.01)
234
-
235
- # Make the socs match exactly, setting all those smaller than the start SoC to the start SoC and
236
- # all those larger than the end SoC to the end SoC
237
- socs[socs < event.soc_start] = event.soc_start
238
- socs[socs > event.soc_end] = event.soc_end
239
-
240
- # Add a timeseries to the event, removing the first and last index, since they will be the same as the start and
241
- # end SoC
242
- event.timeseries = {
243
- "time": [
244
- datetime.fromtimestamp(t).astimezone().isoformat()
245
- for t in total_time[start_index:end_index]
246
- ][
247
- 1:-1
248
- ], # Remove the first and last index
249
- "soc": socs.tolist()[1:-1],
250
- }
251
- if len(event.timeseries["time"]) > 0:
252
- if event.timeseries["time"][0] < event.time_start.isoformat():
253
- event.timeseries["time"][0] = event.time_start.isoformat()
254
- if event.timeseries["time"][-1] > event.time_end.isoformat():
255
- event.timeseries["time"][-1] = event.time_end.isoformat()
256
- else:
257
- event.timeseries = None
258
-
259
- # Now we have the power draw and charging allowed for each event
260
- if debug_plots:
261
- from matplotlib import pyplot as plt
262
-
263
- _, axs = plt.subplots(3, 1, sharex=True)
264
- total_power = np.sum(
265
- [event["power_draw"] for event in params_for_events], axis=0
266
- )
267
- axs[0].plot(total_time, total_power, label="Original power draw")
268
- optimized_power = np.sum(
269
- [event["optimized_power"] for event in params_for_events], axis=0
270
- )
271
- axs[0].plot(total_time, optimized_power, label="Optimized power draw")
272
- optimized_power2 = np.sum(
273
- [event["optimized_power2"] for event in params_for_events], axis=0
274
- )
275
- axs[0].plot(total_time, optimized_power2, label="Mean power draw")
276
- axs[0].set_xlabel("Time")
277
- axs[0].set_ylabel("Total power draw (kW)")
278
-
279
- axs[0].axhline(
280
- y=max(total_power), color="blue", linestyle="--", label="Max power draw"
281
- )
282
- axs[0].axhline(
283
- y=max(optimized_power),
284
- color="orange",
285
- linestyle="--",
286
- label="Max optimized power draw",
287
- )
288
- axs[0].axhline(
289
- y=max(optimized_power2),
290
- color="green",
291
- linestyle="--",
292
- label="Max mean power draw",
293
- )
294
-
295
- # Energy transferred
296
- total_energy = scipy.integrate.cumulative_trapezoid(
297
- total_power, total_time, initial=0
298
- )
299
- axs[1].plot(total_time, total_energy, label="Original energy transferred")
300
- optimized_energy = scipy.integrate.cumulative_trapezoid(
301
- optimized_power, total_time, initial=0
302
- )
303
- axs[1].plot(total_time, optimized_energy, label="Optimized energy transferred")
304
- optimized_energy2 = scipy.integrate.cumulative_trapezoid(
305
- optimized_power2, total_time, initial=0
306
- )
307
- axs[1].plot(total_time, optimized_energy2, label="Mean energy transferred")
308
- axs[1].set_xlabel("Time")
309
- axs[1].set_ylabel("Total energy transferred (kWh)")
310
-
311
- axs[2].plot(total_time, total_occupancy)
312
- axs[2].set_xlabel("Time")
313
- axs[2].set_ylabel("Vehicle count")
314
- plt.show()