eflips-eval 1.2.0__tar.gz → 1.2.2__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_eval-1.2.0 → eflips_eval-1.2.2}/PKG-INFO +1 -1
- {eflips_eval-1.2.0 → eflips_eval-1.2.2}/eflips/eval/input/prepare.py +36 -23
- {eflips_eval-1.2.0 → eflips_eval-1.2.2}/eflips/eval/input/visualize.py +15 -1
- {eflips_eval-1.2.0 → eflips_eval-1.2.2}/eflips/eval/output/prepare.py +13 -8
- {eflips_eval-1.2.0 → eflips_eval-1.2.2}/pyproject.toml +1 -1
- {eflips_eval-1.2.0 → eflips_eval-1.2.2}/LICENSE.md +0 -0
- {eflips_eval-1.2.0 → eflips_eval-1.2.2}/README.md +0 -0
- {eflips_eval-1.2.0 → eflips_eval-1.2.2}/eflips/eval/input/__init__.py +0 -0
- {eflips_eval-1.2.0 → eflips_eval-1.2.2}/eflips/eval/output/__init__.py +0 -0
- {eflips_eval-1.2.0 → eflips_eval-1.2.2}/eflips/eval/output/visualize.py +0 -0
|
@@ -1,26 +1,10 @@
|
|
|
1
|
+
from collections import Counter
|
|
1
2
|
from datetime import datetime
|
|
2
3
|
from typing import Dict, List
|
|
3
4
|
|
|
4
5
|
import pandas as pd
|
|
5
6
|
import sqlalchemy
|
|
6
|
-
from eflips.model import Rotation, Trip
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
def rotation_name_for_sorting(rotation_name: str) -> str:
|
|
10
|
-
"""
|
|
11
|
-
Takes a rotation name, which in the BVG is a string of two numbers separated by a '/' character, and returns a string
|
|
12
|
-
that can be used for sorting. The first part of the string is returned for BVG rotations.
|
|
13
|
-
|
|
14
|
-
Other rotation names are not supported and will return the rotation name itself.
|
|
15
|
-
|
|
16
|
-
:param rotation_name: The rotation name
|
|
17
|
-
:return: a sortable string
|
|
18
|
-
"""
|
|
19
|
-
|
|
20
|
-
if rotation_name is not None and "/" in rotation_name:
|
|
21
|
-
return rotation_name.split("/")[0]
|
|
22
|
-
else:
|
|
23
|
-
return rotation_name
|
|
7
|
+
from eflips.model import Rotation, Trip, Route
|
|
24
8
|
|
|
25
9
|
|
|
26
10
|
def rotation_info(
|
|
@@ -40,6 +24,9 @@ def rotation_info(
|
|
|
40
24
|
- time_start: the departure of the first trip
|
|
41
25
|
- time_end: the arrival of the last trip
|
|
42
26
|
- line_name: the name of the line, which is the first part of the rotation name. Used for sorting
|
|
27
|
+
- line_is_unified: True if the rotation only contains one line
|
|
28
|
+
- start_station: the name of the departure station
|
|
29
|
+
- end_station: the name of the arrival station
|
|
43
30
|
|
|
44
31
|
:param scenario_id: The scenario id for which to create the dataframe
|
|
45
32
|
:param session: An sqlalchemy session to an eflips-model database
|
|
@@ -49,7 +36,22 @@ def rotation_info(
|
|
|
49
36
|
|
|
50
37
|
result: List[Dict[str, int | float | str | datetime]] = []
|
|
51
38
|
|
|
52
|
-
rotations =
|
|
39
|
+
rotations = (
|
|
40
|
+
session.query(Rotation)
|
|
41
|
+
.filter(Rotation.scenario_id == scenario_id)
|
|
42
|
+
.options(
|
|
43
|
+
sqlalchemy.orm.joinedload(Rotation.trips)
|
|
44
|
+
.joinedload(Trip.route)
|
|
45
|
+
.joinedload(Route.line),
|
|
46
|
+
sqlalchemy.orm.joinedload(Rotation.vehicle_type),
|
|
47
|
+
sqlalchemy.orm.joinedload(Rotation.trips)
|
|
48
|
+
.joinedload(Trip.route)
|
|
49
|
+
.joinedload(Route.departure_station),
|
|
50
|
+
sqlalchemy.orm.joinedload(Rotation.trips)
|
|
51
|
+
.joinedload(Trip.route)
|
|
52
|
+
.joinedload(Route.arrival_station),
|
|
53
|
+
)
|
|
54
|
+
)
|
|
53
55
|
|
|
54
56
|
if rotation_ids is not None:
|
|
55
57
|
if isinstance(rotation_ids, int):
|
|
@@ -62,6 +64,16 @@ def rotation_info(
|
|
|
62
64
|
for trip in rotation.trips:
|
|
63
65
|
distance += trip.route.distance / 1000
|
|
64
66
|
|
|
67
|
+
# We want to be able to sort and/or group by line. Therefore we need to identify the predominant line name for each
|
|
68
|
+
# rotation.
|
|
69
|
+
line_names: Dict[str, int] = {}
|
|
70
|
+
for trip in rotation.trips:
|
|
71
|
+
line_name = trip.route.line.name
|
|
72
|
+
if line_name not in line_names:
|
|
73
|
+
line_names[line_name] = 0
|
|
74
|
+
line_names[line_name] += 1
|
|
75
|
+
line_name = Counter(line_names).most_common(1)[0][0]
|
|
76
|
+
|
|
65
77
|
result.append(
|
|
66
78
|
{
|
|
67
79
|
"rotation_id": rotation.id,
|
|
@@ -69,16 +81,17 @@ def rotation_info(
|
|
|
69
81
|
"vehicle_type_id": rotation.vehicle_type_id,
|
|
70
82
|
"vehicle_type_name": rotation.vehicle_type.name,
|
|
71
83
|
"total_distance": distance,
|
|
84
|
+
"line_name": line_name,
|
|
85
|
+
"line_is_unified": len(line_names)
|
|
86
|
+
== 1, # True if there is only one line in the rotation
|
|
72
87
|
"time_start": rotation.trips[0].departure_time,
|
|
73
88
|
"time_end": rotation.trips[-1].arrival_time,
|
|
89
|
+
"start_station": rotation.trips[0].route.departure_station.name,
|
|
90
|
+
"end_station": rotation.trips[-1].route.arrival_station.name,
|
|
74
91
|
}
|
|
75
92
|
)
|
|
76
93
|
|
|
77
|
-
# We want to properly sort by rotation name, which is a bit intricate, as it's a string of two numbers divided by a
|
|
78
|
-
# '/' character. We can't just sort by the string, as "10/11" would come after "10/1". We need to split the string
|
|
79
|
-
# into its components and sort by them.
|
|
80
94
|
df = pd.DataFrame(result)
|
|
81
|
-
df["line_name"] = df["rotation_name"].apply(rotation_name_for_sorting)
|
|
82
95
|
|
|
83
96
|
df.sort_values(by=["line_name", "time_start"], inplace=True)
|
|
84
97
|
|
|
@@ -19,6 +19,9 @@ def rotation_info(prepared_data: pd.DataFrame) -> go.Figure:
|
|
|
19
19
|
- time_start: the departure of the first trip
|
|
20
20
|
- time_end: the arrival of the last trip
|
|
21
21
|
- line_name: the name of the line, which is the first part of the rotation name. Used for sorting
|
|
22
|
+
- line_is_unified: True if the rotation only contains one line
|
|
23
|
+
- start_station: the name of the departure station
|
|
24
|
+
- end_station: the name of the arrival station
|
|
22
25
|
|
|
23
26
|
:return: A plotly figure object
|
|
24
27
|
"""
|
|
@@ -30,11 +33,22 @@ def rotation_info(prepared_data: pd.DataFrame) -> go.Figure:
|
|
|
30
33
|
y="rotation_name",
|
|
31
34
|
color="total_distance",
|
|
32
35
|
labels={
|
|
36
|
+
"time_start": "Departure Time",
|
|
37
|
+
"time_end": "Arrival Time",
|
|
33
38
|
"rotation_name": "Rotation Name",
|
|
34
39
|
"total_distance": "Total Distance (km)",
|
|
35
40
|
"line_name": "Line Name",
|
|
41
|
+
"vehicle_type_name": "Vehicle Type Name",
|
|
42
|
+
"start_station": "Start Station",
|
|
43
|
+
"end_station": "End Station",
|
|
36
44
|
},
|
|
37
|
-
|
|
45
|
+
hover_name="rotation_name",
|
|
46
|
+
hover_data=[
|
|
47
|
+
"vehicle_type_name",
|
|
48
|
+
"total_distance",
|
|
49
|
+
"start_station",
|
|
50
|
+
"end_station",
|
|
51
|
+
],
|
|
38
52
|
pattern_shape="line_name",
|
|
39
53
|
)
|
|
40
54
|
fig.update_layout(legend_orientation="h")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import zoneinfo
|
|
2
2
|
from datetime import datetime, timedelta
|
|
3
|
-
from typing import Dict, List, Iterable, Tuple
|
|
3
|
+
from typing import Dict, List, Iterable, Tuple, Optional
|
|
4
4
|
|
|
5
5
|
import numpy as np
|
|
6
6
|
import numpy.typing as npt
|
|
@@ -247,8 +247,8 @@ def power_and_occupancy(
|
|
|
247
247
|
)
|
|
248
248
|
energy += this_event_energy
|
|
249
249
|
|
|
250
|
-
# For occupancy, we
|
|
251
|
-
# with left=0 and right=0
|
|
250
|
+
# For occupancy, we create an entry at the beginning and end of the event, then resample to 1-second
|
|
251
|
+
# intervals with left=0 and right=0
|
|
252
252
|
this_event_occupancy = np.interp(
|
|
253
253
|
time_as_unix,
|
|
254
254
|
[event.time_start.timestamp(), event.time_end.timestamp() - 1],
|
|
@@ -262,11 +262,16 @@ def power_and_occupancy(
|
|
|
262
262
|
power = (np.diff(energy) / np.diff(time_as_unix).astype(float)) * 3600 # kW
|
|
263
263
|
|
|
264
264
|
# Create the dataframe
|
|
265
|
+
# First, change the time to the local timezone
|
|
266
|
+
|
|
267
|
+
tz = datetime.now().astimezone().tzinfo
|
|
268
|
+
time_as_local_tz = pd.to_datetime(time).tz_localize("UTC").tz_convert(tz)
|
|
269
|
+
|
|
265
270
|
result = pd.DataFrame(
|
|
266
271
|
{
|
|
267
|
-
"time":
|
|
272
|
+
"time": time_as_local_tz[:-1],
|
|
268
273
|
"power": power,
|
|
269
|
-
"occupancy": occupancy[1
|
|
274
|
+
"occupancy": occupancy[:-1],
|
|
270
275
|
}
|
|
271
276
|
)
|
|
272
277
|
return result
|
|
@@ -316,7 +321,7 @@ def specific_energy_consumption(scenario_id: int, session: Session) -> pd.DataFr
|
|
|
316
321
|
def vehicle_soc(
|
|
317
322
|
vehicle_id: int,
|
|
318
323
|
session: Session,
|
|
319
|
-
timezone: zoneinfo.ZoneInfo = zoneinfo.ZoneInfo("
|
|
324
|
+
timezone: Optional[zoneinfo.ZoneInfo] = zoneinfo.ZoneInfo("Europe/Berlin"),
|
|
320
325
|
) -> Tuple[pd.DataFrame, Dict[str, List[Tuple[str, datetime, datetime]]]]:
|
|
321
326
|
"""
|
|
322
327
|
This function takes in a vehicle id and returns a description what happened to the vehicle over time.
|
|
@@ -331,7 +336,7 @@ def vehicle_soc(
|
|
|
331
336
|
- "rotation": A list of rotation names and the time the rotation started and ended
|
|
332
337
|
- "charging": A list of the location of the charging and the time the charging started and ended
|
|
333
338
|
|
|
334
|
-
:param timezone: Explicit timezone information to use for the visualization. Default is
|
|
339
|
+
:param timezone: Explicit timezone information to use for the visualization. Default is Europe/Berlin
|
|
335
340
|
:param vehicle_id: the unique identifier of the vehicle
|
|
336
341
|
:param session: A :class:`sqlalchemy.orm.session.Session` object to an eflips-model database
|
|
337
342
|
:return: A pandas DataFrame
|
|
@@ -395,4 +400,4 @@ def vehicle_soc(
|
|
|
395
400
|
)
|
|
396
401
|
)
|
|
397
402
|
|
|
398
|
-
return
|
|
403
|
+
return pd.DataFrame({"time": all_times, "soc": all_soc}), descriptions
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|