eflips-depot 3.2.7__py3-none-any.whl → 4.0.1__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.
@@ -104,6 +104,8 @@ def depot_to_template(depot: Depot) -> Dict[str, str | Dict[str, str | int]]:
104
104
  "vehicle_types": [str(area.vehicle_type_id)],
105
105
  }
106
106
 
107
+ # TODO for cleaning area etc., enable non-vehicle_type areas
108
+
107
109
  for process in area.processes:
108
110
  # Add process into process list
109
111
  list_of_processes.append(
@@ -222,7 +224,7 @@ def depot_to_template(depot: Depot) -> Dict[str, str | Dict[str, str | int]]:
222
224
  }
223
225
  # Groups
224
226
  for process in depot.default_plan.processes:
225
- group_name = str(process_type(process)) + "_group"
227
+ group_name = str(process.name) + "_group"
226
228
  template["groups"][group_name] = {
227
229
  "typename": "AreaGroup",
228
230
  "stores": [str(area.id) for area in process.areas],
@@ -295,6 +297,7 @@ def create_simple_depot(
295
297
  charging_power: float,
296
298
  session: sqlalchemy.orm.session.Session,
297
299
  cleaning_duration: timedelta = timedelta(minutes=30),
300
+ safety_margin: float = 0.0,
298
301
  ) -> None:
299
302
  """
300
303
  Creates a simple depot for a given scenario.
@@ -302,6 +305,7 @@ def create_simple_depot(
302
305
  It has one area for each vehicle type and a charging process for each
303
306
  area. Also an arrival area for each vehicle type.
304
307
 
308
+ :param safety_margin: a safety margin for the number of charging and cleaning capacities. Default is 0.0
305
309
  :param scenario: The scenario to be simulated
306
310
  :param station: The station where the depot is located
307
311
  :param charging_capacities: A dictionary of vehicle types and the number of vehicles that can be charged at the same time
@@ -328,10 +332,11 @@ def create_simple_depot(
328
332
  depot.default_plan = plan
329
333
 
330
334
  # Create processes
331
- standby_arrival = Process(
332
- name="Standby Arrival",
335
+ shunting_1 = Process(
336
+ name="Shunting 1",
333
337
  scenario=scenario,
334
338
  dispatchable=False,
339
+ duration=timedelta(minutes=5),
335
340
  )
336
341
  clean = Process(
337
342
  name="Arrival Cleaning",
@@ -339,6 +344,12 @@ def create_simple_depot(
339
344
  dispatchable=False,
340
345
  duration=cleaning_duration,
341
346
  )
347
+ shunting_2 = Process(
348
+ name="Shunting 2",
349
+ scenario=scenario,
350
+ dispatchable=False,
351
+ duration=timedelta(minutes=5),
352
+ )
342
353
  charging = Process(
343
354
  name="Charging",
344
355
  scenario=scenario,
@@ -350,15 +361,27 @@ def create_simple_depot(
350
361
  scenario=scenario,
351
362
  dispatchable=True,
352
363
  )
353
- session.add(standby_arrival)
364
+
354
365
  session.add(clean)
366
+ session.add(shunting_1)
355
367
  session.add(charging)
356
368
  session.add(standby_departure)
369
+ session.add(shunting_2)
370
+
371
+ # Create shared waiting area
372
+ waiting_area = Area(
373
+ scenario=scenario,
374
+ name=f"Waiting Area for every type of vehicle",
375
+ depot=depot,
376
+ area_type=AreaType.DIRECT_ONESIDE,
377
+ capacity=100,
378
+ )
379
+ session.add(waiting_area)
357
380
 
358
381
  for vehicle_type in charging_capacities.keys():
359
382
  charging_count = charging_capacities[vehicle_type]
360
- # Add a safety margin of 100% to the parking capacity
361
- charging_count = int(ceil(charging_count * 2))
383
+
384
+ charging_count = int(ceil(charging_count * (1 + safety_margin)))
362
385
 
363
386
  # Create charging area
364
387
  charging_area = Area(
@@ -366,15 +389,15 @@ def create_simple_depot(
366
389
  name=f"Direct Charging Area for {vehicle_type.name_short}",
367
390
  depot=depot,
368
391
  area_type=AreaType.DIRECT_ONESIDE,
369
- capacity=charging_count,
392
+ capacity=int(charging_count * 1),
370
393
  )
371
394
  session.add(charging_area)
372
395
  charging_area.vehicle_type = vehicle_type
373
396
 
374
397
  # Create cleaning area
375
398
  cleaning_count = cleaning_capacities[vehicle_type]
376
- # Add a safety margin of 100% to the parking capacity
377
- cleaning_count = int(ceil(cleaning_count * 2))
399
+
400
+ cleaning_count = int(ceil(cleaning_count * (1 + safety_margin)))
378
401
 
379
402
  cleaning_area = Area(
380
403
  scenario=scenario,
@@ -386,31 +409,45 @@ def create_simple_depot(
386
409
  session.add(cleaning_area)
387
410
  cleaning_area.vehicle_type = vehicle_type
388
411
 
389
- # Create stand by arrival area
390
- arrival_area = Area(
412
+ shunting_area_1 = Area(
391
413
  scenario=scenario,
392
- name=f"Arrival for {vehicle_type.name_short}",
414
+ name=f"Shunting Area 1 for {vehicle_type.name_short}",
393
415
  depot=depot,
394
416
  area_type=AreaType.DIRECT_ONESIDE,
395
- capacity=(charging_count + cleaning_count)
396
- * 2, # SHould be huge, not all of it will be used
417
+ capacity=10,
397
418
  )
398
- session.add(arrival_area)
399
- arrival_area.vehicle_type = vehicle_type
400
419
 
401
- arrival_area.processes.append(standby_arrival)
420
+ session.add(shunting_area_1)
421
+ shunting_area_1.vehicle_type = vehicle_type
422
+
423
+ shunting_area_2 = Area(
424
+ scenario=scenario,
425
+ name=f"Shunting Area 2 for {vehicle_type.name_short}",
426
+ depot=depot,
427
+ area_type=AreaType.DIRECT_ONESIDE,
428
+ capacity=10,
429
+ )
430
+
431
+ session.add(shunting_area_2)
432
+ shunting_area_2.vehicle_type = vehicle_type
433
+
402
434
  cleaning_area.processes.append(clean)
403
435
  charging_area.processes.append(charging)
404
436
  charging_area.processes.append(standby_departure)
437
+ shunting_area_1.processes.append(shunting_1)
438
+ shunting_area_2.processes.append(shunting_2)
405
439
 
406
440
  assocs = [
407
441
  AssocPlanProcess(
408
- scenario=scenario, process=standby_arrival, plan=plan, ordinal=0
442
+ scenario=scenario, process=shunting_1, plan=plan, ordinal=0
409
443
  ),
410
444
  AssocPlanProcess(scenario=scenario, process=clean, plan=plan, ordinal=1),
411
- AssocPlanProcess(scenario=scenario, process=charging, plan=plan, ordinal=2),
412
445
  AssocPlanProcess(
413
- scenario=scenario, process=standby_departure, plan=plan, ordinal=3
446
+ scenario=scenario, process=shunting_2, plan=plan, ordinal=2
447
+ ),
448
+ AssocPlanProcess(scenario=scenario, process=charging, plan=plan, ordinal=3),
449
+ AssocPlanProcess(
450
+ scenario=scenario, process=standby_departure, plan=plan, ordinal=4
414
451
  ),
415
452
  ]
416
453
  session.add_all(assocs)
@@ -40,10 +40,10 @@ def optimize_charging_events_even(charging_events: List[Event]) -> None:
40
40
 
41
41
  # For each event, create an array of power draws and a boolean array of charging allowed
42
42
  # Also note down the peak power and transferred energy
43
- params_for_events: List[Dict[str, float | np.ndarray]] = []
43
+ params_for_events: List[Dict[str, float | np.ndarray | Event]] = []
44
44
  for event in charging_events:
45
- power_draw = np.zeros(total_duration, dtype=float)
46
- charging_allowed = np.zeros(total_duration, dtype=int)
45
+ power_draw = np.zeros_like(total_time, dtype=float)
46
+ charging_allowed = np.zeros_like(total_time, dtype=int)
47
47
 
48
48
  # Calculate the power draw vector, from the start SoC, end SoC and timeseries, if available
49
49
  event_soc = [event.soc_start]
@@ -80,6 +80,7 @@ def optimize_charging_events_even(charging_events: List[Event]) -> None:
80
80
 
81
81
  params_for_events.append(
82
82
  {
83
+ "event": event,
83
84
  "power_draw": expanded_power,
84
85
  "charging_allowed": charging_allowed,
85
86
  "max_power": max_power,
@@ -230,6 +231,41 @@ def optimize_charging_events_even(charging_events: List[Event]) -> None:
230
231
  params_for_event["optimized_power"] = optimized_power
231
232
  params_for_event["optimized_power2"] = optimized_power2
232
233
 
234
+ event = params_for_event["event"]
235
+ start_index = int((event.time_start - start_time) / TEMPORAL_RESOLUTION)
236
+ end_index = (
237
+ int((event.time_end - start_time) / TEMPORAL_RESOLUTION) + 1
238
+ ) # +1 to include the last index
239
+ powers = params_for_event["optimized_power"][start_index:end_index]
240
+
241
+ energies = scipy.integrate.cumtrapz(powers, initial=0) / (
242
+ 3600 / TEMPORAL_RESOLUTION.total_seconds()
243
+ ) # kWh
244
+ socs = event.soc_start + energies / event.vehicle.vehicle_type.battery_capacity
245
+
246
+ # Make sure the last SoC is the same as the end SoC
247
+ assert np.isclose(socs[-1], event.soc_end, atol=0.01)
248
+ # Make sure the first SoC is the same as the start SoC
249
+ assert np.isclose(socs[0], event.soc_start, atol=0.01)
250
+
251
+ # Make the socs match exactly, setting all those smaller than the start SoC to the start SoC and
252
+ # all those larger than the end SoC to the end SoC
253
+ socs[socs < event.soc_start] = event.soc_start
254
+ socs[socs > event.soc_end] = event.soc_end
255
+
256
+ # Add a timeseries to the event
257
+ event.timeseries = {
258
+ "time": [
259
+ datetime.fromtimestamp(t).astimezone().isoformat()
260
+ for t in total_time[start_index:end_index]
261
+ ],
262
+ "soc": socs.tolist(),
263
+ }
264
+ if event.timeseries["time"][0] < event.time_start.isoformat():
265
+ event.timeseries["time"][0] = event.time_start.isoformat()
266
+ if event.timeseries["time"][-1] > event.time_end.isoformat():
267
+ event.timeseries["time"][-1] = event.time_end.isoformat()
268
+
233
269
  # Now we have the power draw and charging allowed for each event
234
270
  if False:
235
271
  from matplotlib import pyplot as plt
@@ -284,37 +320,3 @@ def optimize_charging_events_even(charging_events: List[Event]) -> None:
284
320
  axs[2].set_xlabel("Time")
285
321
  axs[2].set_ylabel("Vehicle count")
286
322
  plt.show()
287
-
288
- # Finally, update the events in the database
289
- for i in range(len(charging_events)):
290
- event = charging_events[i]
291
- start_index = int((event.time_start - start_time) / TEMPORAL_RESOLUTION)
292
- end_index = int((event.time_end - start_time) / TEMPORAL_RESOLUTION)
293
- powers = params_for_events[i]["optimized_power"][start_index:end_index]
294
- energies = scipy.integrate.cumtrapz(powers, initial=0) / (
295
- 3600 / TEMPORAL_RESOLUTION.total_seconds()
296
- ) # kWh
297
- socs = event.soc_start + energies / event.vehicle.vehicle_type.battery_capacity
298
-
299
- # Make sure the last SoC is the same as the end SoC
300
- assert np.isclose(socs[-1], event.soc_end, atol=0.01)
301
- # Make sure the first SoC is the same as the start SoC
302
- assert np.isclose(socs[0], event.soc_start, atol=0.01)
303
-
304
- # Make the socs match exactly, setting all those smaller than the start SoC to the start SoC and
305
- # all those larger than the end SoC to the end SoC
306
- socs[socs < event.soc_start] = event.soc_start
307
- socs[socs > event.soc_end] = event.soc_end
308
-
309
- # Add a timeseries to the event
310
- event.timeseries = {
311
- "time": [
312
- datetime.fromtimestamp(t).astimezone().isoformat()
313
- for t in total_time[start_index:end_index]
314
- ],
315
- "soc": socs.tolist(),
316
- }
317
- if event.timeseries["time"][0] < event.time_start.isoformat():
318
- event.timeseries["time"][0] = event.time_start.isoformat()
319
- if event.timeseries["time"][-1] > event.time_end.isoformat():
320
- event.timeseries["time"][-1] = event.time_end.isoformat()
@@ -7,7 +7,15 @@ from datetime import timedelta, datetime
7
7
  from typing import Union, Any, Optional, Tuple, Dict, List
8
8
 
9
9
  import simpy
10
- from eflips.model import Scenario, VehicleType, Rotation, Event, EventType, Trip
10
+ from eflips.model import (
11
+ Scenario,
12
+ VehicleType,
13
+ Rotation,
14
+ Event,
15
+ EventType,
16
+ Trip,
17
+ Depot,
18
+ )
11
19
  from sqlalchemy import inspect, create_engine
12
20
  from sqlalchemy.orm import Session
13
21
 
@@ -164,6 +172,40 @@ def start_and_end_times(vehicle_schedules) -> Tuple[datetime, int]:
164
172
  return midnight_of_first_departure_day, total_duration_seconds
165
173
 
166
174
 
175
+ def check_depot_validity(depot: Depot) -> None:
176
+ """
177
+ Check if the depot is valid for the eflips-depot simulation. Raise an AssertionError if it is not.
178
+ :param depot: a :class:`eflips.model.Depot` object.
179
+ :return: None
180
+ """
181
+ # 1. There must be an area containing no vehicle types
182
+ has_waiting_area = False
183
+ areas = depot.areas
184
+ for area in areas:
185
+ if area.vehicle_type_id is None:
186
+ # TODO might change to len(area.vehicle_types) == 0 after a list of vehicle types is allowed
187
+ has_waiting_area = True
188
+
189
+ assert (
190
+ has_waiting_area
191
+ ), "There must be an area containing no vehicle types as the waiting area."
192
+
193
+ # 2. There must be only one process with no duration and no electric power, and it must be the last process in the plan
194
+ plan = depot.default_plan
195
+ processes = plan.processes
196
+ last_process = processes[-1]
197
+ assert (
198
+ last_process.duration is None
199
+ and last_process.electric_power is None
200
+ and last_process.dispatchable is True
201
+ ), "The last process must be dispatchable and have no duration and no electric power."
202
+
203
+ for process in processes[:-1]:
204
+ assert (
205
+ process.electric_power is not None or process.duration is not None
206
+ ), "All processes except the last one must have electric power."
207
+
208
+
167
209
  @dataclass
168
210
  class VehicleSchedule:
169
211
  """
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: eflips-depot
3
- Version: 3.2.7
3
+ Version: 4.0.1
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,7 +13,7 @@ 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.3.0,<4.0.0)
16
+ Requires-Dist: eflips-model (>=4.0.0a2,<5.0.0)
17
17
  Requires-Dist: pandas (>=2.1.4,<3.0.0)
18
18
  Requires-Dist: scipy (>=1.13.1,<2.0.0)
19
19
  Requires-Dist: simpy (>=4.0.1,<5.0.0)
@@ -1,10 +1,10 @@
1
1
  eflips/depot/__init__.py,sha256=n7jte8R6j_Ad4Mp4hkklKwil5r8u8Q_SbXrCC-nf5jM,1556
2
- eflips/depot/api/__init__.py,sha256=_j2nB0nN2uLMaYyRRhHWkNBEfr4U6BTMHkmF1q9Caak,58416
2
+ eflips/depot/api/__init__.py,sha256=ecO_I4Tdh2vcNtxoYrNXehgKscgl26sMYqhkLoPO2lU,63743
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
- eflips/depot/api/private/depot.py,sha256=GlIk6vuanbQP3a4e9TYt6xJ-XJTdaFbaxxz_ni17zgo,17136
6
- eflips/depot/api/private/smart_charging.py,sha256=GpNFaExjvyTCja4Uzx-cRW_Qck3Vv5A3Qt9HUJUfFLU,13510
7
- eflips/depot/api/private/util.py,sha256=zyU3QtsyrYRgcLWzzUT1hQLDfkwlbvvukmgnrANEzlc,15362
5
+ eflips/depot/api/private/depot.py,sha256=z17kGwXyt3XmOze8hn0hKpDrAUOd4pFV9ojNDEQmx5A,18112
6
+ eflips/depot/api/private/smart_charging.py,sha256=Z_e7E0lpr6L-WeT6FmfaT1pYQw5b4ZW3ESDEY0NUoMI,13525
7
+ eflips/depot/api/private/util.py,sha256=BFdhyaCSClLJo8DjlIQCV_db-Cc-qqNuGnNa50GzaLs,16741
8
8
  eflips/depot/configuration.py,sha256=Op3hlir-dEN7yHr0kTqbYANoCBKFWK6uKOv3NJl8w_w,35678
9
9
  eflips/depot/depot.py,sha256=afIlaiX-J-M5-K_oAGMr_soL3_QjIAwrQKDaZzTwle0,105566
10
10
  eflips/depot/evaluation.py,sha256=qqXyP4jA1zFcKuWhliQ6n25ZlGl9mJV-vtXf0yu8WN8,140842
@@ -35,7 +35,7 @@ eflips/depot/simulation.py,sha256=ee0qTzOzG-8ybN36ie_NJallXfC7jUaS9JZvaYFziLs,10
35
35
  eflips/depot/smart_charging.py,sha256=C3BYqzn2-OYY4ipXm0ETtavbAM9QXZMYULBpVoChf0E,54311
36
36
  eflips/depot/standalone.py,sha256=VxcTzBaB67fNJUMmjPRwKXjhqTy6oQ41Coote2LvAmk,22338
37
37
  eflips/depot/validation.py,sha256=TIuY7cQtEJI4H2VVMSuY5IIVkacEEZ67weeMuY3NSAM,7097
38
- eflips_depot-3.2.7.dist-info/LICENSE.md,sha256=KB4XTk1fPHjtZCYDyPyreu6h1LVJVZXYg-5vePcWZAc,34143
39
- eflips_depot-3.2.7.dist-info/METADATA,sha256=bkJyYUx0lEcId79_wDtnftrHM7QySf-HPCpUW6tmQe4,5786
40
- eflips_depot-3.2.7.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
41
- eflips_depot-3.2.7.dist-info/RECORD,,
38
+ eflips_depot-4.0.1.dist-info/LICENSE.md,sha256=KB4XTk1fPHjtZCYDyPyreu6h1LVJVZXYg-5vePcWZAc,34143
39
+ eflips_depot-4.0.1.dist-info/METADATA,sha256=tYJoehTjjI2wRXYxOQQw4q41jTYFdlJylEKGsHfAUBQ,5788
40
+ eflips_depot-4.0.1.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
41
+ eflips_depot-4.0.1.dist-info/RECORD,,