eflips-depot 4.6.9__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.
- eflips/depot/api/__init__.py +19 -53
- eflips/depot/api/private/depot.py +7 -2
- {eflips_depot-4.6.9.dist-info → eflips_depot-4.7.0.dist-info}/METADATA +2 -2
- {eflips_depot-4.6.9.dist-info → eflips_depot-4.7.0.dist-info}/RECORD +6 -7
- eflips/depot/api/private/smart_charging.py +0 -314
- {eflips_depot-4.6.9.dist-info → eflips_depot-4.7.0.dist-info}/LICENSE.md +0 -0
- {eflips_depot-4.6.9.dist-info → eflips_depot-4.7.0.dist-info}/WHEEL +0 -0
eflips/depot/api/__init__.py
CHANGED
|
@@ -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
|
-
|
|
549
|
-
|
|
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
|
-
|
|
558
|
-
|
|
559
|
-
|
|
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(
|
|
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[
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: eflips-depot
|
|
3
|
-
Version: 4.
|
|
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,<
|
|
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,11 +1,10 @@
|
|
|
1
1
|
eflips/depot/__init__.py,sha256=06GUem0JIEunIyJ0_P_MLAGfibGEnNqcPPY0OBpO2NQ,1662
|
|
2
|
-
eflips/depot/api/__init__.py,sha256=
|
|
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=
|
|
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
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
|
|
@@ -37,7 +36,7 @@ eflips/depot/simulation.py,sha256=ee0qTzOzG-8ybN36ie_NJallXfC7jUaS9JZvaYFziLs,10
|
|
|
37
36
|
eflips/depot/smart_charging.py,sha256=C3BYqzn2-OYY4ipXm0ETtavbAM9QXZMYULBpVoChf0E,54311
|
|
38
37
|
eflips/depot/standalone.py,sha256=8O01zEXghFG9zZBu0fUD0sXvbHQ-AXw6RB5M750a_sM,22419
|
|
39
38
|
eflips/depot/validation.py,sha256=TIuY7cQtEJI4H2VVMSuY5IIVkacEEZ67weeMuY3NSAM,7097
|
|
40
|
-
eflips_depot-4.
|
|
41
|
-
eflips_depot-4.
|
|
42
|
-
eflips_depot-4.
|
|
43
|
-
eflips_depot-4.
|
|
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()
|
|
File without changes
|
|
File without changes
|