eflips-depot 4.4.3__tar.gz → 4.5.0__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.
- {eflips_depot-4.4.3 → eflips_depot-4.5.0}/PKG-INFO +3 -3
- {eflips_depot-4.4.3 → eflips_depot-4.5.0}/eflips/depot/__init__.py +4 -0
- {eflips_depot-4.4.3 → eflips_depot-4.5.0}/eflips/depot/api/__init__.py +259 -74
- {eflips_depot-4.4.3 → eflips_depot-4.5.0}/pyproject.toml +1 -1
- {eflips_depot-4.4.3 → eflips_depot-4.5.0}/LICENSE.md +0 -0
- {eflips_depot-4.4.3 → eflips_depot-4.5.0}/README.md +0 -0
- {eflips_depot-4.4.3 → eflips_depot-4.5.0}/eflips/depot/api/defaults/default_settings.json +0 -0
- {eflips_depot-4.4.3 → eflips_depot-4.5.0}/eflips/depot/api/private/__init__.py +0 -0
- {eflips_depot-4.4.3 → eflips_depot-4.5.0}/eflips/depot/api/private/depot.py +0 -0
- {eflips_depot-4.4.3 → eflips_depot-4.5.0}/eflips/depot/api/private/results_to_database.py +0 -0
- {eflips_depot-4.4.3 → eflips_depot-4.5.0}/eflips/depot/api/private/smart_charging.py +0 -0
- {eflips_depot-4.4.3 → eflips_depot-4.5.0}/eflips/depot/api/private/util.py +0 -0
- {eflips_depot-4.4.3 → eflips_depot-4.5.0}/eflips/depot/configuration.py +0 -0
- {eflips_depot-4.4.3 → eflips_depot-4.5.0}/eflips/depot/depot.py +0 -0
- {eflips_depot-4.4.3 → eflips_depot-4.5.0}/eflips/depot/evaluation.py +0 -0
- {eflips_depot-4.4.3 → eflips_depot-4.5.0}/eflips/depot/filters.py +0 -0
- {eflips_depot-4.4.3 → eflips_depot-4.5.0}/eflips/depot/input_epex_power_price.py +0 -0
- {eflips_depot-4.4.3 → eflips_depot-4.5.0}/eflips/depot/layout_opt/__init__.py +0 -0
- {eflips_depot-4.4.3 → eflips_depot-4.5.0}/eflips/depot/layout_opt/doc/__init__.py +0 -0
- {eflips_depot-4.4.3 → eflips_depot-4.5.0}/eflips/depot/layout_opt/doc/direct_details.pdf +0 -0
- {eflips_depot-4.4.3 → eflips_depot-4.5.0}/eflips/depot/layout_opt/evaluation.py +0 -0
- {eflips_depot-4.4.3 → eflips_depot-4.5.0}/eflips/depot/layout_opt/opt_tools/__init__.py +0 -0
- {eflips_depot-4.4.3 → eflips_depot-4.5.0}/eflips/depot/layout_opt/opt_tools/crossover.py +0 -0
- {eflips_depot-4.4.3 → eflips_depot-4.5.0}/eflips/depot/layout_opt/opt_tools/fitness_c_urfd.py +0 -0
- {eflips_depot-4.4.3 → eflips_depot-4.5.0}/eflips/depot/layout_opt/opt_tools/fitness_util.py +0 -0
- {eflips_depot-4.4.3 → eflips_depot-4.5.0}/eflips/depot/layout_opt/opt_tools/init.py +0 -0
- {eflips_depot-4.4.3 → eflips_depot-4.5.0}/eflips/depot/layout_opt/opt_tools/mutation.py +0 -0
- {eflips_depot-4.4.3 → eflips_depot-4.5.0}/eflips/depot/layout_opt/optimize_c_urfd.py +0 -0
- {eflips_depot-4.4.3 → eflips_depot-4.5.0}/eflips/depot/layout_opt/packing.py +0 -0
- {eflips_depot-4.4.3 → eflips_depot-4.5.0}/eflips/depot/layout_opt/settings.py +0 -0
- {eflips_depot-4.4.3 → eflips_depot-4.5.0}/eflips/depot/layout_opt/template_creation.py +0 -0
- {eflips_depot-4.4.3 → eflips_depot-4.5.0}/eflips/depot/layout_opt/util.py +0 -0
- {eflips_depot-4.4.3 → eflips_depot-4.5.0}/eflips/depot/plots.py +0 -0
- {eflips_depot-4.4.3 → eflips_depot-4.5.0}/eflips/depot/processes.py +0 -0
- {eflips_depot-4.4.3 → eflips_depot-4.5.0}/eflips/depot/rating.py +0 -0
- {eflips_depot-4.4.3 → eflips_depot-4.5.0}/eflips/depot/resources.py +0 -0
- {eflips_depot-4.4.3 → eflips_depot-4.5.0}/eflips/depot/settings_config.py +0 -0
- {eflips_depot-4.4.3 → eflips_depot-4.5.0}/eflips/depot/simple_vehicle.py +0 -0
- {eflips_depot-4.4.3 → eflips_depot-4.5.0}/eflips/depot/simulation.py +0 -0
- {eflips_depot-4.4.3 → eflips_depot-4.5.0}/eflips/depot/smart_charging.py +0 -0
- {eflips_depot-4.4.3 → eflips_depot-4.5.0}/eflips/depot/standalone.py +0 -0
- {eflips_depot-4.4.3 → eflips_depot-4.5.0}/eflips/depot/validation.py +0 -0
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
2
|
Name: eflips-depot
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.5.0
|
|
4
4
|
Summary: Depot Simulation for eFLIPS
|
|
5
|
-
Home-page: https://github.com/mpm-tu-berlin/eflips-depot
|
|
6
5
|
License: AGPL-3.0-or-later
|
|
7
6
|
Author: Enrico Lauth
|
|
8
7
|
Author-email: enrico.lauth@tu-berlin.de
|
|
@@ -21,6 +20,7 @@ Requires-Dist: simpy (>=4.0.1,<5.0.0)
|
|
|
21
20
|
Requires-Dist: tqdm (>=4.67.0,<5.0.0)
|
|
22
21
|
Requires-Dist: xlrd (<=1.2.0)
|
|
23
22
|
Requires-Dist: xlsxwriter (>=3.1.9,<4.0.0)
|
|
23
|
+
Project-URL: Homepage, https://github.com/mpm-tu-berlin/eflips-depot
|
|
24
24
|
Project-URL: Repository, https://github.com/mpm-tu-berlin/eflips-depot
|
|
25
25
|
Description-Content-Type: text/markdown
|
|
26
26
|
|
|
@@ -53,3 +53,7 @@ from eflips.depot.simulation import (
|
|
|
53
53
|
from eflips.depot.smart_charging import SmartCharging, ControlSmartCharging
|
|
54
54
|
from eflips.depot.standalone import VehicleGenerator, SimpleTrip, Timetable
|
|
55
55
|
from eflips.depot.validation import Validator
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class UnstableSimulationException(Exception):
|
|
59
|
+
pass
|
|
@@ -25,15 +25,15 @@ The following steps are recommended for using the API:
|
|
|
25
25
|
b. Run the :func:`simple_consumption_simulation` function again, this time with ``initialize_vehicles=False``.
|
|
26
26
|
"""
|
|
27
27
|
import copy
|
|
28
|
-
import datetime
|
|
29
28
|
import logging
|
|
30
29
|
import os
|
|
31
30
|
import warnings
|
|
32
31
|
from collections import OrderedDict
|
|
33
|
-
from
|
|
32
|
+
from dataclasses import dataclass
|
|
33
|
+
from datetime import timedelta, datetime
|
|
34
34
|
from enum import Enum
|
|
35
35
|
from math import ceil
|
|
36
|
-
from typing import Any, Dict, Optional, Union
|
|
36
|
+
from typing import Any, Dict, Optional, Union, List
|
|
37
37
|
|
|
38
38
|
import sqlalchemy.orm
|
|
39
39
|
from eflips.model import (
|
|
@@ -107,47 +107,131 @@ class SmartChargingStrategy(Enum):
|
|
|
107
107
|
"""
|
|
108
108
|
|
|
109
109
|
|
|
110
|
+
@dataclass
|
|
111
|
+
class ConsumptionResult:
|
|
112
|
+
"""
|
|
113
|
+
A dataclass that stores the results of a charging simulation for a single trip.
|
|
114
|
+
|
|
115
|
+
This class holds both the total change in battery State of Charge (SoC) over the trip
|
|
116
|
+
as well as an optional timeseries of timestamps and incremental SoC changes. When
|
|
117
|
+
an entry exists for a given trip in ``consumption_result``, the simulation will use
|
|
118
|
+
these precomputed values instead of recalculating the SoC changes from the vehicle
|
|
119
|
+
distance and consumption.
|
|
120
|
+
|
|
121
|
+
:param delta_soc_total:
|
|
122
|
+
The total change in the vehicle's State of Charge over the trip, typically
|
|
123
|
+
negative if the vehicle is consuming energy (e.g., -0.15 means the SoC
|
|
124
|
+
dropped by 15%).
|
|
125
|
+
|
|
126
|
+
:param timestamps:
|
|
127
|
+
A list of timestamps (e.g., arrival times at stops) that mark the times
|
|
128
|
+
associated with the SoC changes. The number of timestamps must match the
|
|
129
|
+
number of entries in ``delta_soc``.
|
|
130
|
+
|
|
131
|
+
:param delta_soc:
|
|
132
|
+
A list of cumulative SoC changes corresponding to the ``timestamps``.
|
|
133
|
+
For example, if ``delta_soc[i] = -0.02``, it means the SoC decreased by 2%
|
|
134
|
+
between from the start of the trip to ``timestamps[i]``. This list should typically
|
|
135
|
+
be a monotonic decreasing sequence.
|
|
136
|
+
"""
|
|
137
|
+
|
|
138
|
+
delta_soc_total: float
|
|
139
|
+
timestamps: List[datetime] | None
|
|
140
|
+
delta_soc: List[float] | None
|
|
141
|
+
|
|
142
|
+
|
|
110
143
|
def simple_consumption_simulation(
|
|
111
144
|
scenario: Union[Scenario, int, Any],
|
|
112
145
|
initialize_vehicles: bool,
|
|
113
146
|
database_url: Optional[str] = None,
|
|
114
147
|
calculate_timeseries: bool = False,
|
|
115
148
|
terminus_deadtime: timedelta = timedelta(minutes=1),
|
|
149
|
+
consumption_result: Dict[int, ConsumptionResult] | None = None,
|
|
116
150
|
) -> None:
|
|
117
151
|
"""
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
``VehicleType.consumption
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
152
|
+
Run a simple consumption simulation and optionally initialize vehicles in the database.
|
|
153
|
+
|
|
154
|
+
This function calculates energy consumption by multiplying each vehicle's total traveled
|
|
155
|
+
distance by a constant ``VehicleType.consumption`` (kWh per km), then updates the database
|
|
156
|
+
with the resulting SoC (State of Charge) data. The function can also use precomputed results
|
|
157
|
+
for specific trips via the ``consumption_result`` parameter.
|
|
158
|
+
|
|
159
|
+
If ``initialize_vehicles`` is True, vehicles and an initial STANDBY event (with 100% SoC)
|
|
160
|
+
are created for each rotation that does not already have a vehicle. If it is False, existing
|
|
161
|
+
vehicles in the database are assumed, and a check is performed to ensure each rotation has a
|
|
162
|
+
vehicle.
|
|
163
|
+
|
|
164
|
+
Opportunity charging can optionally be applied at the end of each trip, if the vehicle and
|
|
165
|
+
station both allow it, and if the rotation is flagged to allow it. This charging event is
|
|
166
|
+
constrained by a configurable terminus deadtime.
|
|
167
|
+
|
|
168
|
+
**SoC Constraints**
|
|
169
|
+
|
|
170
|
+
- When no precomputed results are provided, SoC is computed by subtracting energy used
|
|
171
|
+
(`consumption * distance / battery_capacity`) from the previous event’s SoC.
|
|
172
|
+
- When precomputed ``ConsumptionResult`` objects are provided in ``consumption_result``,
|
|
173
|
+
they must have a non-positive total change in SoC (``delta_soc_total <= 0``).
|
|
174
|
+
If the function detects a positive ``delta_soc_total``, it raises a ``ValueError``.
|
|
175
|
+
|
|
176
|
+
**Timeseries Calculation**
|
|
177
|
+
|
|
178
|
+
- If ``calculate_timeseries`` is True, the function builds a more granular SoC timeseries
|
|
179
|
+
at each stop in the trip and stores it in the ``Event.timeseries`` column.
|
|
180
|
+
- If False, the event’s ``timeseries`` is set to ``None``, which may speed up the simulation
|
|
181
|
+
if you do not need intermediate SoC data.
|
|
182
|
+
|
|
183
|
+
:param scenario:
|
|
184
|
+
One of:
|
|
185
|
+
- A :class:`eflips.model.Scenario` instance containing the input data for the simulation.
|
|
186
|
+
- An integer specifying the ID of a scenario in the database.
|
|
187
|
+
- Any other object with an integer ``id`` attribute.
|
|
188
|
+
|
|
189
|
+
If not passing a :class:`eflips.model.Scenario` directly, the `database_url` parameter
|
|
190
|
+
or the environment variable ``DATABASE_URL`` must point to a valid database.
|
|
191
|
+
|
|
192
|
+
:param initialize_vehicles:
|
|
193
|
+
A boolean flag indicating whether new vehicles should be created and assigned
|
|
194
|
+
to rotations in the database. Set this to True the first time you run the simulation
|
|
195
|
+
so that vehicles are initialized. In subsequent runs, set to False if vehicles
|
|
196
|
+
are already present.
|
|
197
|
+
|
|
198
|
+
:param database_url:
|
|
199
|
+
A database connection string (e.g., ``postgresql://user:pass@host/db``).
|
|
200
|
+
If you do not provide this and ``scenario`` is not a
|
|
201
|
+
:class:`eflips.model.Scenario` instance, the environment variable
|
|
202
|
+
``DATABASE_URL`` must be set.
|
|
203
|
+
|
|
204
|
+
:param calculate_timeseries:
|
|
205
|
+
If True, each trip’s detailed SoC timeseries is computed and stored in the
|
|
206
|
+
``timeseries`` column of the corresponding driving and charging events.
|
|
207
|
+
If False, only the start/end SoC is recorded, and ``timeseries`` is set to None.
|
|
208
|
+
|
|
209
|
+
:param terminus_deadtime:
|
|
210
|
+
The total time overhead (attach + detach) for charging at the terminus.
|
|
211
|
+
If this deadtime exceeds the available layover time, no charging is performed.
|
|
212
|
+
|
|
213
|
+
:param consumption_result:
|
|
214
|
+
A dictionary mapping trip IDs to :class:`ConsumptionResult` instances for
|
|
215
|
+
precomputed SoC changes. If an entry exists for a trip, this function uses
|
|
216
|
+
those precomputed SoC changes instead of calculating them from distance
|
|
217
|
+
and consumption. Each ``ConsumptionResult`` must have:
|
|
218
|
+
|
|
219
|
+
- A non-positive ``delta_soc_total`` (<= 0).
|
|
220
|
+
- Optionally, matching lists of timestamps and delta SoC values that are
|
|
221
|
+
decreasing (i.e., the vehicle only loses or maintains SoC).
|
|
222
|
+
|
|
223
|
+
:returns:
|
|
224
|
+
``None``. All simulation results are written directly to the database as
|
|
225
|
+
:class:`eflips.model.Event` entries.
|
|
226
|
+
|
|
227
|
+
:raises ValueError:
|
|
228
|
+
- If a rotation in the scenario does not have a vehicle when
|
|
229
|
+
``initialize_vehicles=False``.
|
|
230
|
+
- If the vehicle type has no ``consumption`` value.
|
|
231
|
+
- If a provided ``ConsumptionResult`` has inconsistent list lengths,
|
|
232
|
+
or if its ``delta_soc_total`` is positive.
|
|
233
|
+
- If SoC timeseries are not decreasing when provided
|
|
234
|
+
via ``consumption_result``.
|
|
151
235
|
"""
|
|
152
236
|
logger = logging.getLogger(__name__)
|
|
153
237
|
|
|
@@ -211,7 +295,7 @@ def simple_consumption_simulation(
|
|
|
211
295
|
area = (
|
|
212
296
|
session.query(Area)
|
|
213
297
|
.filter(Area.scenario_id == scenario.id)
|
|
214
|
-
.filter(Area.vehicle_type_id ==
|
|
298
|
+
.filter(Area.vehicle_type_id == vehicle.vehicle_type_id)
|
|
215
299
|
.first()
|
|
216
300
|
)
|
|
217
301
|
|
|
@@ -253,9 +337,15 @@ def simple_consumption_simulation(
|
|
|
253
337
|
.one()
|
|
254
338
|
)
|
|
255
339
|
if vehicle_type.consumption is None:
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
340
|
+
# If the vehicle type has no consumption value, all trips must have a precomputed consumption result
|
|
341
|
+
all_trip_ids = [trip.id for trip in rotation.trips]
|
|
342
|
+
if not (
|
|
343
|
+
consumption_result is not None
|
|
344
|
+
and all(trip_id in consumption_result for trip_id in all_trip_ids)
|
|
345
|
+
):
|
|
346
|
+
raise ValueError(
|
|
347
|
+
"The vehicle type does not have a consumption value set and no consumption results are provided."
|
|
348
|
+
)
|
|
259
349
|
consumption = vehicle_type.consumption
|
|
260
350
|
|
|
261
351
|
# The departure SoC for this rotation is the SoC of the last event preceding the first trip
|
|
@@ -270,36 +360,79 @@ def simple_consumption_simulation(
|
|
|
270
360
|
|
|
271
361
|
for trip in rotation.trips:
|
|
272
362
|
# Set up a timeseries
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
i
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
timeseries["time"].append(
|
|
295
|
-
(current_time + dwell_duration).isoformat()
|
|
363
|
+
if consumption_result is None or trip.id not in consumption_result:
|
|
364
|
+
logger.info("Calculating timeseries for trip %s", trip.id)
|
|
365
|
+
soc_start = current_soc
|
|
366
|
+
if calculate_timeseries and len(trip.stop_times) > 0:
|
|
367
|
+
timeseries = {
|
|
368
|
+
"time": [],
|
|
369
|
+
"soc": [],
|
|
370
|
+
"distance": [],
|
|
371
|
+
}
|
|
372
|
+
for i in range(len(trip.stop_times)):
|
|
373
|
+
current_time = trip.stop_times[i].arrival_time
|
|
374
|
+
dwell_duration = trip.stop_times[i].dwell_duration
|
|
375
|
+
elapsed_distance = trip.route.assoc_route_stations[
|
|
376
|
+
i
|
|
377
|
+
].elapsed_distance
|
|
378
|
+
elapsed_energy = consumption * (
|
|
379
|
+
elapsed_distance / 1000
|
|
380
|
+
) # kWh
|
|
381
|
+
soc = (
|
|
382
|
+
current_soc
|
|
383
|
+
- elapsed_energy / vehicle_type.battery_capacity
|
|
296
384
|
)
|
|
385
|
+
timeseries["time"].append(current_time.isoformat())
|
|
297
386
|
timeseries["soc"].append(soc)
|
|
298
387
|
timeseries["distance"].append(elapsed_distance)
|
|
388
|
+
if dwell_duration > timedelta(seconds=0):
|
|
389
|
+
timeseries["time"].append(
|
|
390
|
+
(current_time + dwell_duration).isoformat()
|
|
391
|
+
)
|
|
392
|
+
timeseries["soc"].append(soc)
|
|
393
|
+
timeseries["distance"].append(elapsed_distance)
|
|
394
|
+
else:
|
|
395
|
+
timeseries = None
|
|
396
|
+
energy_used = consumption * trip.route.distance / 1000 # kWh
|
|
397
|
+
current_soc = (
|
|
398
|
+
soc_start - energy_used / vehicle_type.battery_capacity
|
|
399
|
+
)
|
|
299
400
|
else:
|
|
300
|
-
timeseries
|
|
301
|
-
|
|
302
|
-
|
|
401
|
+
logger.info(f"Using pre-calculated timeseries for trip {trip.id}")
|
|
402
|
+
if (
|
|
403
|
+
calculate_timeseries
|
|
404
|
+
and consumption_result[trip.id].timestamps is not None
|
|
405
|
+
):
|
|
406
|
+
assert consumption_result[trip.id].delta_soc is not None
|
|
407
|
+
timestamps = consumption_result[trip.id].timestamps
|
|
408
|
+
|
|
409
|
+
# Make sure the delta_soc is a monotonic decreasing function, with the same length as timestamps
|
|
410
|
+
if len(consumption_result[trip.id].delta_soc) != len(
|
|
411
|
+
timestamps
|
|
412
|
+
):
|
|
413
|
+
raise ValueError(
|
|
414
|
+
"The length of the delta_soc and timestamps lists must be the same."
|
|
415
|
+
)
|
|
416
|
+
delta_socs = consumption_result[trip.id].delta_soc
|
|
417
|
+
if delta_socs[-1] > 0:
|
|
418
|
+
raise ValueError(
|
|
419
|
+
"The delta_soc must be a decreasing function."
|
|
420
|
+
)
|
|
421
|
+
|
|
422
|
+
socs = [current_soc + d for d in delta_socs]
|
|
423
|
+
timeseries = {
|
|
424
|
+
"time": [t.isoformat() for t in timestamps],
|
|
425
|
+
"soc": socs,
|
|
426
|
+
}
|
|
427
|
+
else:
|
|
428
|
+
timeseries = None
|
|
429
|
+
|
|
430
|
+
if consumption_result[trip.id].delta_soc_total > 0:
|
|
431
|
+
raise ValueError(
|
|
432
|
+
"The current SoC must be <= 0 when using a consumption result."
|
|
433
|
+
)
|
|
434
|
+
soc_start = current_soc
|
|
435
|
+
current_soc += consumption_result[trip.id].delta_soc_total
|
|
303
436
|
|
|
304
437
|
# Create a driving event
|
|
305
438
|
current_event = Event(
|
|
@@ -356,6 +489,35 @@ def simple_consumption_simulation(
|
|
|
356
489
|
1,
|
|
357
490
|
)
|
|
358
491
|
|
|
492
|
+
# If the post_charge_soc is 1, calculate when the vehicle was full
|
|
493
|
+
if post_charge_soc == 1:
|
|
494
|
+
# 1. Get the max charging power (kW)
|
|
495
|
+
max_power = max([v[1] for v in vehicle_type.charging_curve])
|
|
496
|
+
|
|
497
|
+
# 2. Energy needed (kWh) to go from current_soc to 100%
|
|
498
|
+
energy_needed_kWh = (
|
|
499
|
+
1 - current_soc
|
|
500
|
+
) * vehicle_type.battery_capacity
|
|
501
|
+
|
|
502
|
+
# 3. Compute how long that takes at max_power (in hours)
|
|
503
|
+
time_needed_hours = energy_needed_kWh / max_power
|
|
504
|
+
|
|
505
|
+
# 4. Calculate the point in time the vehicle became full
|
|
506
|
+
# If charging effectively starts right after terminus_deadtime
|
|
507
|
+
time_full = (
|
|
508
|
+
trip.arrival_time
|
|
509
|
+
+ terminus_deadtime / 2
|
|
510
|
+
+ timedelta(hours=time_needed_hours)
|
|
511
|
+
)
|
|
512
|
+
|
|
513
|
+
# 5. Make sure it is before the time charging must end the latest
|
|
514
|
+
assert time_full <= next_trip.departure_time - (
|
|
515
|
+
terminus_deadtime / 2
|
|
516
|
+
)
|
|
517
|
+
|
|
518
|
+
else:
|
|
519
|
+
time_full = None
|
|
520
|
+
|
|
359
521
|
# Create a simple timeseries for the charging event
|
|
360
522
|
timeseries = {
|
|
361
523
|
"time": [
|
|
@@ -374,6 +536,11 @@ def simple_consumption_simulation(
|
|
|
374
536
|
],
|
|
375
537
|
}
|
|
376
538
|
|
|
539
|
+
# If time_full is not None, add it to the timeseries in the middle
|
|
540
|
+
if time_full is not None:
|
|
541
|
+
timeseries["time"].insert(2, time_full.isoformat())
|
|
542
|
+
timeseries["soc"].insert(2, 1)
|
|
543
|
+
|
|
377
544
|
# Create the charging event
|
|
378
545
|
current_event = Event(
|
|
379
546
|
scenario_id=scenario.id,
|
|
@@ -555,22 +722,23 @@ def simulate_scenario(
|
|
|
555
722
|
repetition_period: Optional[timedelta] = None,
|
|
556
723
|
database_url: Optional[str] = None,
|
|
557
724
|
smart_charging_strategy: SmartChargingStrategy = SmartChargingStrategy.EVEN,
|
|
725
|
+
ignore_unstable_simulation: bool = False,
|
|
558
726
|
) -> None:
|
|
559
727
|
"""
|
|
560
728
|
This method simulates a scenario and adds the results to the database.
|
|
561
729
|
|
|
562
730
|
It fills in the "Charging Events" in the :class:`eflips.model.Event` table and associates
|
|
563
731
|
:class:`eflips.model.Vehicle` objects with all the existing "Driving Events" in the :class:`eflips.model.Event`
|
|
564
|
-
table.
|
|
732
|
+
table. If the simulation becomes unstable, an :class:`UnstableSimulationException` is raised.
|
|
565
733
|
|
|
566
734
|
:param scenario: Either a :class:`eflips.model.Scenario` object containing the input data for the simulation. Or
|
|
567
735
|
an integer specifying the ID of a scenario in the database. Or any other object that has an attribute
|
|
568
736
|
``id`` that is an integer. If no :class:`eflips.model.Scenario` object is passed, the ``database_url``
|
|
569
|
-
parameter must be set to a valid database URL
|
|
737
|
+
parameter must be set to a valid database URL or the environment variable ``DATABASE_URL`` must be set to a
|
|
570
738
|
valid database URL.
|
|
571
739
|
|
|
572
740
|
:param repetition_period: An optional timedelta object specifying the period of the vehicle schedules. This
|
|
573
|
-
is needed because the result should be a steady-state result.
|
|
741
|
+
is needed because the result should be a steady-state result. This can only be achieved by simulating a
|
|
574
742
|
time period before and after our actual simulation, and then only using the "middle". eFLIPS tries to
|
|
575
743
|
automatically detect whether the schedule should be repeated daily or weekly. If this fails, a ValueError is
|
|
576
744
|
raised and repetition needs to be specified manually.
|
|
@@ -580,14 +748,22 @@ def simulate_scenario(
|
|
|
580
748
|
URL.
|
|
581
749
|
|
|
582
750
|
:param smart_charging_strategy: An optional parameter specifying the smart charging strategy to be used. The
|
|
583
|
-
default is
|
|
584
|
-
-
|
|
751
|
+
default is SmartChargingStrategy.NONE. The following strategies are available:
|
|
752
|
+
- SmartChargingStrategy.NONE: Do not use smart charging. Buses are charged with the maximum power available,
|
|
585
753
|
from the time they arrive at the depot until they are full (or leave the depot).
|
|
586
|
-
-
|
|
754
|
+
- SmartChargingStrategy.EVEN: Use smart charging with an even distribution of charging power over the time the
|
|
587
755
|
bus is at the depot. This aims to minimize the peak power demand.
|
|
756
|
+
- SmartChargingStrategy.MIN_PRICE: Not implemented yet.
|
|
757
|
+
|
|
758
|
+
:param ignore_unstable_simulation: If True, the simulation will not raise an exception if it becomes unstable.
|
|
588
759
|
|
|
589
760
|
:return: Nothing. The results are added to the database.
|
|
761
|
+
|
|
762
|
+
:raises UnstableSimulationException: If the simulation becomes numerically unstable or if
|
|
763
|
+
the parameters cause the solver to diverge.
|
|
590
764
|
"""
|
|
765
|
+
logger = logging.getLogger(__name__)
|
|
766
|
+
|
|
591
767
|
with create_session(scenario, database_url) as (session, scenario):
|
|
592
768
|
simulation_host = init_simulation(
|
|
593
769
|
scenario=scenario,
|
|
@@ -595,7 +771,13 @@ def simulate_scenario(
|
|
|
595
771
|
repetition_period=repetition_period,
|
|
596
772
|
)
|
|
597
773
|
ev = run_simulation(simulation_host)
|
|
598
|
-
|
|
774
|
+
try:
|
|
775
|
+
add_evaluation_to_database(scenario, ev, session)
|
|
776
|
+
except eflips.depot.UnstableSimulationException as e:
|
|
777
|
+
if ignore_unstable_simulation:
|
|
778
|
+
logger.warning("Simulation is unstable. Continuing.")
|
|
779
|
+
else:
|
|
780
|
+
raise e
|
|
599
781
|
|
|
600
782
|
match smart_charging_strategy:
|
|
601
783
|
case SmartChargingStrategy.NONE:
|
|
@@ -842,7 +1024,7 @@ def run_simulation(simulation_host: SimulationHost) -> Dict[str, DepotEvaluation
|
|
|
842
1024
|
|
|
843
1025
|
|
|
844
1026
|
def insert_dummy_standby_departure_events(
|
|
845
|
-
depot_id: int, session: Session, sim_time_end: Optional[datetime
|
|
1027
|
+
depot_id: int, session: Session, sim_time_end: Optional[datetime] = None
|
|
846
1028
|
) -> None:
|
|
847
1029
|
"""
|
|
848
1030
|
Workaround for the missing STANDBY_DEPARTURE events in the database.
|
|
@@ -934,6 +1116,9 @@ def add_evaluation_to_database(
|
|
|
934
1116
|
database.
|
|
935
1117
|
|
|
936
1118
|
:return: Nothing. The results are added to the database.
|
|
1119
|
+
|
|
1120
|
+
:raises UnstableSimulationException: If the simulation becomes numerically unstable or if
|
|
1121
|
+
the parameters cause the solver to diverge.
|
|
937
1122
|
"""
|
|
938
1123
|
|
|
939
1124
|
# Read simulation start time
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{eflips_depot-4.4.3 → eflips_depot-4.5.0}/eflips/depot/layout_opt/opt_tools/fitness_c_urfd.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|