eflips-depot 3.1.5__py3-none-any.whl → 3.2.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.
Potentially problematic release.
This version of eflips-depot might be problematic. Click here for more details.
- eflips/depot/api/__init__.py +119 -2
- eflips/depot/api/private/smart_charging.py +257 -0
- {eflips_depot-3.1.5.dist-info → eflips_depot-3.2.0.dist-info}/METADATA +3 -2
- {eflips_depot-3.1.5.dist-info → eflips_depot-3.2.0.dist-info}/RECORD +6 -5
- {eflips_depot-3.1.5.dist-info → eflips_depot-3.2.0.dist-info}/LICENSE.md +0 -0
- {eflips_depot-3.1.5.dist-info → eflips_depot-3.2.0.dist-info}/WHEEL +0 -0
eflips/depot/api/__init__.py
CHANGED
|
@@ -28,6 +28,7 @@ import copy
|
|
|
28
28
|
import os
|
|
29
29
|
import warnings
|
|
30
30
|
from datetime import timedelta
|
|
31
|
+
from enum import Enum, auto
|
|
31
32
|
from math import ceil
|
|
32
33
|
from typing import Any, Dict, Optional, Union
|
|
33
34
|
|
|
@@ -56,6 +57,7 @@ from eflips.depot.api.private.depot import (
|
|
|
56
57
|
depot_to_template,
|
|
57
58
|
group_rotations_by_start_end_stop,
|
|
58
59
|
)
|
|
60
|
+
from eflips.depot.api.private.smart_charging import optimize_charging_events_even
|
|
59
61
|
from eflips.depot.api.private.util import (
|
|
60
62
|
create_session,
|
|
61
63
|
repeat_vehicle_schedules,
|
|
@@ -65,6 +67,32 @@ from eflips.depot.api.private.util import (
|
|
|
65
67
|
)
|
|
66
68
|
|
|
67
69
|
|
|
70
|
+
class SmartChargingStragegy(Enum):
|
|
71
|
+
"""Enum class for different smart charging strategies."""
|
|
72
|
+
|
|
73
|
+
NONE = auto
|
|
74
|
+
"""
|
|
75
|
+
Do not use smart charging.
|
|
76
|
+
|
|
77
|
+
Buses are charged with the maximum power available, from the time they arrive at the depot
|
|
78
|
+
until they are full (or leave the depot).
|
|
79
|
+
"""
|
|
80
|
+
EVEN = auto
|
|
81
|
+
"""
|
|
82
|
+
Use smart charging with an even distribution of charging power over the time the bus is at the depot.
|
|
83
|
+
|
|
84
|
+
This aims to
|
|
85
|
+
minimize the peak power demand.
|
|
86
|
+
"""
|
|
87
|
+
MIN_PRICE = auto
|
|
88
|
+
"""
|
|
89
|
+
Use smart charging in order to minimize the cost of charging.
|
|
90
|
+
|
|
91
|
+
The price profile can be specified using the
|
|
92
|
+
PRICE_PROFILE environment variable. If this is not set, the price is loaded using an API.
|
|
93
|
+
"""
|
|
94
|
+
|
|
95
|
+
|
|
68
96
|
def simple_consumption_simulation(
|
|
69
97
|
scenario: Union[Scenario, int, Any],
|
|
70
98
|
initialize_vehicles: bool,
|
|
@@ -350,10 +378,85 @@ def generate_depot_layout(
|
|
|
350
378
|
)
|
|
351
379
|
|
|
352
380
|
|
|
381
|
+
def apply_even_smart_charging(
|
|
382
|
+
scenario: Union[Scenario, int, Any],
|
|
383
|
+
database_url: Optional[str] = None,
|
|
384
|
+
standby_departure_duration: timedelta = timedelta(minutes=5),
|
|
385
|
+
) -> None:
|
|
386
|
+
"""
|
|
387
|
+
Takes a scenario where depot simulation has been run and applies smart charging to the depot.
|
|
388
|
+
|
|
389
|
+
This modifies the
|
|
390
|
+
time and power of the charging events in the database. The arrival and departure times and SoCs at these times are
|
|
391
|
+
not modified.
|
|
392
|
+
|
|
393
|
+
:param scenario: A :class:`eflips.model.Scenario` object containing the input data for the simulation.
|
|
394
|
+
:param database_url: An optional database URL. If no database URL is passed and the `scenario` parameter is not a
|
|
395
|
+
:class:`eflips.model.Scenario` object, the environment variable `DATABASE_URL` must be set to a valid database
|
|
396
|
+
URL.
|
|
397
|
+
:param standby_departure_duration: The duration of the STANDBY_DEPARTURE event. This is the time the vehicle is
|
|
398
|
+
allowed to wait at the depot before it has to leave. The default is 5 minutes.
|
|
399
|
+
:return: None. The results are added to the database.
|
|
400
|
+
"""
|
|
401
|
+
with create_session(scenario, database_url) as (session, scenario):
|
|
402
|
+
depots = session.query(Depot).filter(Depot.scenario_id == scenario.id).all()
|
|
403
|
+
for depot in depots:
|
|
404
|
+
# Load all the charging events at this depot
|
|
405
|
+
charging_events = (
|
|
406
|
+
session.query(Event)
|
|
407
|
+
.join(Area)
|
|
408
|
+
.filter(Area.depot_id == depot.id)
|
|
409
|
+
.filter(Event.event_type == EventType.CHARGING_DEPOT)
|
|
410
|
+
.all()
|
|
411
|
+
)
|
|
412
|
+
|
|
413
|
+
# For each event, take the subsequent STANDBY_DEPARTURE event of the same vehicle
|
|
414
|
+
# Reduce the STANDBY_DEPARTURE events duration to 5 minutes
|
|
415
|
+
# Move the end time of the charging event to the start time of the STANDBY_DEPARTURE event
|
|
416
|
+
for charging_event in charging_events:
|
|
417
|
+
next_event = (
|
|
418
|
+
session.query(Event)
|
|
419
|
+
.filter(Event.time_start >= charging_event.time_end)
|
|
420
|
+
.filter(Event.vehicle_id == charging_event.vehicle_id)
|
|
421
|
+
.order_by(Event.time_start)
|
|
422
|
+
.first()
|
|
423
|
+
)
|
|
424
|
+
assert next_event is not None
|
|
425
|
+
assert next_event.event_type == EventType.STANDBY_DEPARTURE
|
|
426
|
+
assert next_event.time_start == charging_event.time_end
|
|
427
|
+
|
|
428
|
+
if (
|
|
429
|
+
next_event.time_end - next_event.time_start
|
|
430
|
+
) > standby_departure_duration:
|
|
431
|
+
next_event.time_start = (
|
|
432
|
+
next_event.time_end - standby_departure_duration
|
|
433
|
+
)
|
|
434
|
+
session.flush()
|
|
435
|
+
# Add a timeseries to the charging event
|
|
436
|
+
assert charging_event.timeseries is None
|
|
437
|
+
charging_event.timeseries = {
|
|
438
|
+
"time": [
|
|
439
|
+
charging_event.time_start.isoformat(),
|
|
440
|
+
charging_event.time_end.isoformat(),
|
|
441
|
+
next_event.time_start.isoformat(),
|
|
442
|
+
],
|
|
443
|
+
"soc": [
|
|
444
|
+
charging_event.soc_start,
|
|
445
|
+
charging_event.soc_end,
|
|
446
|
+
charging_event.soc_end,
|
|
447
|
+
],
|
|
448
|
+
}
|
|
449
|
+
charging_event.time_end = next_event.time_start
|
|
450
|
+
session.flush()
|
|
451
|
+
|
|
452
|
+
optimize_charging_events_even(charging_events)
|
|
453
|
+
|
|
454
|
+
|
|
353
455
|
def simulate_scenario(
|
|
354
456
|
scenario: Union[Scenario, int, Any],
|
|
355
457
|
repetition_period: Optional[timedelta] = None,
|
|
356
458
|
database_url: Optional[str] = None,
|
|
459
|
+
smart_charging_strategy: SmartChargingStragegy = SmartChargingStragegy.EVEN,
|
|
357
460
|
) -> None:
|
|
358
461
|
"""
|
|
359
462
|
This method simulates a scenario and adds the results to the database.
|
|
@@ -375,11 +478,15 @@ def simulate_scenario(
|
|
|
375
478
|
:param database_url: An optional database URL. If no database URL is passed and the `scenario` parameter is not a
|
|
376
479
|
:class:`eflips.model.Scenario` object, the environment variable `DATABASE_URL` must be set to a valid database
|
|
377
480
|
URL.
|
|
481
|
+
:param smart_charging_strategy: An optional parameter specifying the smart charging strategy to be used. The
|
|
482
|
+
default is SmartChargingStragegy.NONE. The following strategies are available:
|
|
483
|
+
- SmartChargingStragegy.NONE: Do not use smart charging. Buses are charged with the maximum power available,
|
|
484
|
+
from the time they arrive at the depot until they are full (or leave the depot).
|
|
485
|
+
- SmartChargingStragegy.EVEN: Use smart charging with an even distribution of charging power over the time the
|
|
486
|
+
bus is at the depot. This aims to minimize the peak power demand.
|
|
378
487
|
|
|
379
488
|
:return: Nothing. The results are added to the database.
|
|
380
489
|
"""
|
|
381
|
-
|
|
382
|
-
# Step 0: Load the scenario
|
|
383
490
|
with create_session(scenario, database_url) as (session, scenario):
|
|
384
491
|
simulation_host = init_simulation(
|
|
385
492
|
scenario=scenario,
|
|
@@ -389,6 +496,16 @@ def simulate_scenario(
|
|
|
389
496
|
ev = run_simulation(simulation_host)
|
|
390
497
|
add_evaluation_to_database(scenario, ev, session)
|
|
391
498
|
|
|
499
|
+
match smart_charging_strategy:
|
|
500
|
+
case SmartChargingStragegy.NONE:
|
|
501
|
+
pass
|
|
502
|
+
case SmartChargingStragegy.EVEN:
|
|
503
|
+
apply_even_smart_charging(scenario, database_url)
|
|
504
|
+
case SmartChargingStragegy.MIN_PRICE:
|
|
505
|
+
raise NotImplementedError("MIN_PRICE strategy is not implemented yet.")
|
|
506
|
+
case _:
|
|
507
|
+
raise NotImplementedError()
|
|
508
|
+
|
|
392
509
|
|
|
393
510
|
def _init_simulation(
|
|
394
511
|
scenario: Scenario,
|
|
@@ -0,0 +1,257 @@
|
|
|
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(charging_events: List[Event]) -> None:
|
|
10
|
+
"""
|
|
11
|
+
This function optimizes the power draw of a list of charging events.
|
|
12
|
+
|
|
13
|
+
The power draw is optimized such that the total
|
|
14
|
+
power draw is minimized, while the energy transferred remains constant.
|
|
15
|
+
:param charging_events: The list of charging events to optimize
|
|
16
|
+
:return: Nothing, the charging events are updated in place
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
TEMPORAL_RESOLUTION = timedelta(seconds=60)
|
|
20
|
+
|
|
21
|
+
assert all(
|
|
22
|
+
[event.event_type == EventType.CHARGING_DEPOT for event in charging_events]
|
|
23
|
+
)
|
|
24
|
+
start_time = min([event.time_start for event in charging_events])
|
|
25
|
+
end_time = max([event.time_end for event in charging_events])
|
|
26
|
+
|
|
27
|
+
# Formulate the optimzation problem.
|
|
28
|
+
# - Each charging event has a peak power, which cannot be exceeded.
|
|
29
|
+
# - Each charging event has a start and end time.
|
|
30
|
+
# - betweeen the start time and end time, the energy transferred must remain constant.
|
|
31
|
+
# - the total power draw is the sum of all events' power at this point in time
|
|
32
|
+
# - the total power draw must be minimized
|
|
33
|
+
|
|
34
|
+
total_duration = int((end_time - start_time) / TEMPORAL_RESOLUTION)
|
|
35
|
+
total_time = np.arange(
|
|
36
|
+
start_time.timestamp(),
|
|
37
|
+
end_time.timestamp(),
|
|
38
|
+
TEMPORAL_RESOLUTION.total_seconds(),
|
|
39
|
+
) # The time axis, used to resample the power draw
|
|
40
|
+
|
|
41
|
+
# For each event, create an array of power draws and a boolean array of charging allowed
|
|
42
|
+
# Also note down the peak power and transferred energy
|
|
43
|
+
params_for_events: List[Dict[str, float | np.ndarray]] = []
|
|
44
|
+
for event in charging_events:
|
|
45
|
+
power_draw = np.zeros(total_duration, dtype=float)
|
|
46
|
+
charging_allowed = np.zeros(total_duration, dtype=int)
|
|
47
|
+
|
|
48
|
+
# Calculate the power draw vector, from the start SoC, end SoC and timeseries, if available
|
|
49
|
+
event_soc = [event.soc_start]
|
|
50
|
+
event_time = [event.time_start.timestamp()]
|
|
51
|
+
if event.timeseries is not None:
|
|
52
|
+
event_soc.extend(event.timeseries["soc"])
|
|
53
|
+
event_time.extend(
|
|
54
|
+
[
|
|
55
|
+
datetime.fromisoformat(t).timestamp()
|
|
56
|
+
for t in event.timeseries["time"]
|
|
57
|
+
]
|
|
58
|
+
)
|
|
59
|
+
event_soc.append(event.soc_end)
|
|
60
|
+
event_time.append(event.time_end.timestamp())
|
|
61
|
+
|
|
62
|
+
# Resample the timeseries to the temporal resolution
|
|
63
|
+
expanded_soc = np.interp(total_time, event_time, event_soc)
|
|
64
|
+
expanded_power = (
|
|
65
|
+
np.diff(expanded_soc, prepend=expanded_soc[0])
|
|
66
|
+
* event.vehicle.vehicle_type.battery_capacity
|
|
67
|
+
) # Change in kWh each minute
|
|
68
|
+
expanded_power = (
|
|
69
|
+
expanded_power / TEMPORAL_RESOLUTION.total_seconds() * 3600
|
|
70
|
+
) # to kW
|
|
71
|
+
|
|
72
|
+
# Between the start and end time, charging is allowed
|
|
73
|
+
start_index = int((event.time_start - start_time) / TEMPORAL_RESOLUTION)
|
|
74
|
+
end_index = int((event.time_end - start_time) / TEMPORAL_RESOLUTION)
|
|
75
|
+
charging_allowed[start_index:end_index] = 1
|
|
76
|
+
max_power = max(expanded_power)
|
|
77
|
+
transferred_energy = (
|
|
78
|
+
event_soc[-1] - event_soc[0]
|
|
79
|
+
) * event.vehicle.vehicle_type.battery_capacity # kWh
|
|
80
|
+
|
|
81
|
+
params_for_events.append(
|
|
82
|
+
{
|
|
83
|
+
"power_draw": expanded_power,
|
|
84
|
+
"charging_allowed": charging_allowed,
|
|
85
|
+
"max_power": max_power,
|
|
86
|
+
"transferred_energy": transferred_energy,
|
|
87
|
+
}
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
# The total number of vehicles at the depot is the sum of the charging allowed for each event, since it is 1 if
|
|
91
|
+
# the bus is at the depot and 0 otherwise
|
|
92
|
+
total_occupancy = np.sum(
|
|
93
|
+
[event["charging_allowed"] for event in params_for_events], axis=0
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
# For each event, calculate an optimized power draw
|
|
97
|
+
for params_for_event in params_for_events:
|
|
98
|
+
# Calculate the mean power that would be drawn if the vehicle was charged evenly
|
|
99
|
+
charging_duration = (
|
|
100
|
+
np.sum(params_for_event["charging_allowed"])
|
|
101
|
+
* TEMPORAL_RESOLUTION.total_seconds()
|
|
102
|
+
)
|
|
103
|
+
mean_power = (
|
|
104
|
+
params_for_event["transferred_energy"] / charging_duration
|
|
105
|
+
) * 3600 # kW
|
|
106
|
+
|
|
107
|
+
# Calculate the mean amount of vehicles present at the depot when this vehicle is charging
|
|
108
|
+
# We only consider the time when the vehicle is charging
|
|
109
|
+
total_occupancy_while_charging = total_occupancy[
|
|
110
|
+
params_for_event["charging_allowed"] == 1
|
|
111
|
+
]
|
|
112
|
+
mean_occupancy = np.mean(total_occupancy_while_charging)
|
|
113
|
+
|
|
114
|
+
# for each timestep optimize the power draw. We want to charge less when there are more vehicles at the depot
|
|
115
|
+
# The power draw is varies with the amount of vehicles present at the depot relative to the mean amount
|
|
116
|
+
charging_factor = (mean_occupancy / (total_occupancy)) * params_for_event[
|
|
117
|
+
"charging_allowed"
|
|
118
|
+
]
|
|
119
|
+
|
|
120
|
+
# Make sure the charging factor is not infinite or NaN
|
|
121
|
+
charging_factor[np.isnan(charging_factor)] = 1
|
|
122
|
+
charging_factor[np.isinf(charging_factor)] = 1
|
|
123
|
+
optimized_power = (
|
|
124
|
+
params_for_event["charging_allowed"] * mean_power * charging_factor
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
# Cap it at the peak power
|
|
128
|
+
optimized_power_capped = np.minimum(
|
|
129
|
+
optimized_power, params_for_event["max_power"]
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
# Count the energy that is we need to distribute over the time when the vehicle is not at peak power
|
|
133
|
+
energy_to_distribute = (
|
|
134
|
+
np.trapz((optimized_power - optimized_power_capped), total_time) / 3600
|
|
135
|
+
) # kWh
|
|
136
|
+
if energy_to_distribute > 0:
|
|
137
|
+
optimized_power_not_capped = np.where(
|
|
138
|
+
optimized_power > optimized_power_capped
|
|
139
|
+
)
|
|
140
|
+
# Distribute the energy over the time when the vehicle is not at peak power
|
|
141
|
+
uncapped_duration = (
|
|
142
|
+
optimized_power_not_capped[0].shape[0]
|
|
143
|
+
* TEMPORAL_RESOLUTION.total_seconds()
|
|
144
|
+
)
|
|
145
|
+
power_to_add = energy_to_distribute / (uncapped_duration / 3600)
|
|
146
|
+
optimized_power[optimized_power_not_capped[0]] += power_to_add
|
|
147
|
+
|
|
148
|
+
# Make sure the transferred energy is the same
|
|
149
|
+
post_opt_energy = (
|
|
150
|
+
scipy.integrate.trapz(optimized_power, total_time) / 3600
|
|
151
|
+
) # kWh
|
|
152
|
+
assert post_opt_energy >= params_for_event["transferred_energy"]
|
|
153
|
+
# Make it fit exactly
|
|
154
|
+
optimized_power = optimized_power * (
|
|
155
|
+
params_for_event["transferred_energy"] / post_opt_energy
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
optimized_power2 = (
|
|
159
|
+
params_for_event["charging_allowed"] * mean_power
|
|
160
|
+
) # * (mean_occupancy / total_occupancy)
|
|
161
|
+
# Fill NaNs with the zero power draw
|
|
162
|
+
optimized_power[np.isnan(optimized_power)] = 0
|
|
163
|
+
optimized_power2[np.isnan(optimized_power2)] = 0
|
|
164
|
+
|
|
165
|
+
params_for_event["optimized_power"] = optimized_power
|
|
166
|
+
params_for_event["optimized_power2"] = optimized_power2
|
|
167
|
+
|
|
168
|
+
# Now we have the power draw and charging allowed for each event
|
|
169
|
+
if False:
|
|
170
|
+
from matplotlib import pyplot as plt
|
|
171
|
+
|
|
172
|
+
plt.subplot(3, 1, 1)
|
|
173
|
+
total_power = np.sum(
|
|
174
|
+
[event["power_draw"] for event in params_for_events], axis=0
|
|
175
|
+
)
|
|
176
|
+
plt.plot(total_time, total_power, label="Original power draw")
|
|
177
|
+
optimized_power = np.sum(
|
|
178
|
+
[event["optimized_power"] for event in params_for_events], axis=0
|
|
179
|
+
)
|
|
180
|
+
plt.plot(total_time, optimized_power, label="Optimized power draw")
|
|
181
|
+
optimized_power2 = np.sum(
|
|
182
|
+
[event["optimized_power2"] for event in params_for_events], axis=0
|
|
183
|
+
)
|
|
184
|
+
plt.plot(total_time, optimized_power2, label="Mean power draw")
|
|
185
|
+
plt.xlabel("Time")
|
|
186
|
+
plt.ylabel("Total power draw (kW)")
|
|
187
|
+
|
|
188
|
+
plt.axhline(
|
|
189
|
+
y=max(total_power), color="blue", linestyle="--", label="Max power draw"
|
|
190
|
+
)
|
|
191
|
+
plt.axhline(
|
|
192
|
+
y=max(optimized_power),
|
|
193
|
+
color="orange",
|
|
194
|
+
linestyle="--",
|
|
195
|
+
label="Max optimized power draw",
|
|
196
|
+
)
|
|
197
|
+
plt.axhline(
|
|
198
|
+
y=max(optimized_power2),
|
|
199
|
+
color="green",
|
|
200
|
+
linestyle="--",
|
|
201
|
+
label="Max mean power draw",
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
plt.subplot(3, 1, 2)
|
|
205
|
+
# Energy transferred
|
|
206
|
+
total_energy = scipy.integrate.cumtrapz(total_power, total_time, initial=0)
|
|
207
|
+
plt.plot(total_time, total_energy, label="Original energy transferred")
|
|
208
|
+
optimized_energy = scipy.integrate.cumtrapz(
|
|
209
|
+
optimized_power, total_time, initial=0
|
|
210
|
+
)
|
|
211
|
+
plt.plot(total_time, optimized_energy, label="Optimized energy transferred")
|
|
212
|
+
optimized_energy2 = scipy.integrate.cumtrapz(
|
|
213
|
+
optimized_power2, total_time, initial=0
|
|
214
|
+
)
|
|
215
|
+
plt.plot(total_time, optimized_energy2, label="Mean energy transferred")
|
|
216
|
+
plt.xlabel("Time")
|
|
217
|
+
plt.ylabel("Total energy transferred (kWh)")
|
|
218
|
+
|
|
219
|
+
plt.subplot(3, 1, 3)
|
|
220
|
+
plt.plot(total_time, total_occupancy)
|
|
221
|
+
plt.xlabel("Time")
|
|
222
|
+
plt.ylabel("Vehicle count")
|
|
223
|
+
plt.show()
|
|
224
|
+
|
|
225
|
+
# Finally, update the events in the database
|
|
226
|
+
for i in range(len(charging_events)):
|
|
227
|
+
event = charging_events[i]
|
|
228
|
+
start_index = int((event.time_start - start_time) / TEMPORAL_RESOLUTION)
|
|
229
|
+
end_index = int((event.time_end - start_time) / TEMPORAL_RESOLUTION)
|
|
230
|
+
powers = params_for_events[i]["optimized_power"][start_index:end_index]
|
|
231
|
+
energies = scipy.integrate.cumtrapz(powers, initial=0) / (
|
|
232
|
+
3600 / TEMPORAL_RESOLUTION.total_seconds()
|
|
233
|
+
) # kWh
|
|
234
|
+
socs = event.soc_start + energies / event.vehicle.vehicle_type.battery_capacity
|
|
235
|
+
|
|
236
|
+
# Make sure the last SoC is the same as the end SoC
|
|
237
|
+
assert np.isclose(socs[-1], event.soc_end, atol=0.01)
|
|
238
|
+
# Make sure the first SoC is the same as the start SoC
|
|
239
|
+
assert np.isclose(socs[0], event.soc_start, atol=0.01)
|
|
240
|
+
|
|
241
|
+
# Make the socs match exactly, setting all those smaller than the start SoC to the start SoC and
|
|
242
|
+
# all those larger than the end SoC to the end SoC
|
|
243
|
+
socs[socs < event.soc_start] = event.soc_start
|
|
244
|
+
socs[socs > event.soc_end] = event.soc_end
|
|
245
|
+
|
|
246
|
+
# Add a timeseries to the event
|
|
247
|
+
event.timeseries = {
|
|
248
|
+
"time": [
|
|
249
|
+
datetime.fromtimestamp(t).astimezone().isoformat()
|
|
250
|
+
for t in total_time[start_index:end_index]
|
|
251
|
+
],
|
|
252
|
+
"soc": socs.tolist(),
|
|
253
|
+
}
|
|
254
|
+
if event.timeseries["time"][0] < event.time_start.isoformat():
|
|
255
|
+
event.timeseries["time"][0] = event.time_start.isoformat()
|
|
256
|
+
if event.timeseries["time"][-1] > event.time_end.isoformat():
|
|
257
|
+
event.timeseries["time"][-1] = event.time_end.isoformat()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: eflips-depot
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.2.0
|
|
4
4
|
Summary: Depot Simulation for eFLIPS
|
|
5
5
|
Home-page: https://github.com/mpm-tu-berlin/eflips-depot
|
|
6
6
|
License: AGPL-3.0-or-later
|
|
@@ -13,8 +13,9 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.11
|
|
14
14
|
Classifier: Programming Language :: Python :: 3.12
|
|
15
15
|
Requires-Dist: eflips (>=0.1.3,<0.2.0)
|
|
16
|
-
Requires-Dist: eflips-model (>=3.
|
|
16
|
+
Requires-Dist: eflips-model (>=3.3.0,<4.0.0)
|
|
17
17
|
Requires-Dist: pandas (>=2.1.4,<3.0.0)
|
|
18
|
+
Requires-Dist: scipy (>=1.13.1,<2.0.0)
|
|
18
19
|
Requires-Dist: simpy (>=4.0.1,<5.0.0)
|
|
19
20
|
Requires-Dist: xlrd (<=1.2.0)
|
|
20
21
|
Requires-Dist: xlsxwriter (>=3.1.9,<4.0.0)
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
eflips/depot/__init__.py,sha256=n7jte8R6j_Ad4Mp4hkklKwil5r8u8Q_SbXrCC-nf5jM,1556
|
|
2
|
-
eflips/depot/api/__init__.py,sha256=
|
|
2
|
+
eflips/depot/api/__init__.py,sha256=sSNY1CBO2AmekxdNi-o_mCbWUlMpsBUfVaYT6jYFuxo,57378
|
|
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/depot.py,sha256=GlIk6vuanbQP3a4e9TYt6xJ-XJTdaFbaxxz_ni17zgo,17136
|
|
6
|
+
eflips/depot/api/private/smart_charging.py,sha256=lCocbtFtMY3X5dIOu4OLsSl_Hsasz4FHe1ZzDkiCH-Y,10902
|
|
6
7
|
eflips/depot/api/private/util.py,sha256=zyU3QtsyrYRgcLWzzUT1hQLDfkwlbvvukmgnrANEzlc,15362
|
|
7
8
|
eflips/depot/configuration.py,sha256=Op3hlir-dEN7yHr0kTqbYANoCBKFWK6uKOv3NJl8w_w,35678
|
|
8
9
|
eflips/depot/depot.py,sha256=afIlaiX-J-M5-K_oAGMr_soL3_QjIAwrQKDaZzTwle0,105566
|
|
@@ -34,7 +35,7 @@ eflips/depot/simulation.py,sha256=ee0qTzOzG-8ybN36ie_NJallXfC7jUaS9JZvaYFziLs,10
|
|
|
34
35
|
eflips/depot/smart_charging.py,sha256=C3BYqzn2-OYY4ipXm0ETtavbAM9QXZMYULBpVoChf0E,54311
|
|
35
36
|
eflips/depot/standalone.py,sha256=VxcTzBaB67fNJUMmjPRwKXjhqTy6oQ41Coote2LvAmk,22338
|
|
36
37
|
eflips/depot/validation.py,sha256=TIuY7cQtEJI4H2VVMSuY5IIVkacEEZ67weeMuY3NSAM,7097
|
|
37
|
-
eflips_depot-3.
|
|
38
|
-
eflips_depot-3.
|
|
39
|
-
eflips_depot-3.
|
|
40
|
-
eflips_depot-3.
|
|
38
|
+
eflips_depot-3.2.0.dist-info/LICENSE.md,sha256=KB4XTk1fPHjtZCYDyPyreu6h1LVJVZXYg-5vePcWZAc,34143
|
|
39
|
+
eflips_depot-3.2.0.dist-info/METADATA,sha256=NGC3zkAfRKAiqTXJ-URaEVjX1ro6vk0R_r4DE_jgmtM,5848
|
|
40
|
+
eflips_depot-3.2.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
|
41
|
+
eflips_depot-3.2.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|