-
Notifications
You must be signed in to change notification settings - Fork 9
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Lab 583 support vehicles #58
Conversation
be due to missing suitable attribute change methods, which are coming next
applying functions and dictionary maps to iterator objects of the same shape
iterating more than once issue
lower level objects
fix a couple of tests
methods for network
# Conflicts: # genet/core.py # tests/test_core_schedule_elements.py
or service, test the option to pass schedule.stop('id') for this purpose too
# Conflicts: # genet/exceptions.py # genet/outputs_handler/matsim_xml_writer.py # genet/schedule_elements.py # genet/utils/persistence.py # genet/variables.py # tests/test_core_components_route.py # tests/test_core_components_service.py # tests/test_core_schedule.py # tests/test_core_schedule_elements.py # tests/test_utils_persistence.py
# Conflicts: # genet/exceptions.py # genet/outputs_handler/matsim_xml_writer.py # genet/schedule_elements.py # genet/utils/persistence.py # genet/variables.py # tests/test_core_components_route.py # tests/test_core_components_service.py # tests/test_core_schedule.py # tests/test_core_schedule_elements.py # tests/test_utils_persistence.py
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Slick. Hard to imagine making these kind of changes without good tests now ;)
@@ -0,0 +1,155 @@ | |||
# form basis for vehicle modes in the schedule, used when reading in GTFS and present in the schedule | |||
# unless replaced by reading vehicles.xml file | |||
VEHICLE_TYPES: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Much easier to read
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And also much easier to edit! 👍
# check mode consistency | ||
vehicles_to_modes = df.groupby('vehicle_id').apply(lambda x: list(x['type'].unique())) | ||
if (vehicles_to_modes.str.len() > 1).any(): | ||
# there are vehicles which are shared across routes with different modes |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this ever happen? Would hope not but good to have a catch for it.
If it does with our ITO data it is something we should feed back to them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
so when reading GTFS I give unique vehicles to each trip so this doesn't happen, is there data in the GTFS that could serve as a vehicle? I added this because
- people might like to generate vehicles that are shared between trips to make things more realistic, and it's really easily done with the
route_trips_to_dataframe
andset_route_trips_dataframe
methods - People could get their vehicle ids muddled when adding routes and services
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Got it
# Conflicts: # tests/test_core_schedule.py
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good. Added some minor comments.
...route_trips_to_dataframe which generates a DataFrame holding all the trips, their departure times and vehicle IDs, next to the route ID and service ID. ---!! I need help with naming this method, there is another one generate_trips_dataframe which extends this further, expanding across all stops (for all trips) in the route and giving scheduled timestamps at those stops for all trips for all routes, I think they sound too similar and I'm not sure they convey what the outputs are very well >_>
How about route_trips_to_dataframe
and route_trips_with_stops_to_dataframe
? Or route_trips_to_summary_dataframe
and route_trips_to__full_dataframe
?
@@ -0,0 +1,155 @@ | |||
# form basis for vehicle modes in the schedule, used when reading in GTFS and present in the schedule | |||
# unless replaced by reading vehicles.xml file | |||
VEHICLE_TYPES: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And also much easier to edit! 👍
@@ -60,3 +60,10 @@ class StopIndexError(Exception): | |||
Raised in case of Stop indexing inconsistency | |||
""" | |||
pass | |||
|
|||
|
|||
class InconsistentVehicleModeError(Exception): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
brownie_points += 100
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm hooked now
@@ -219,9 +220,15 @@ def write_transitLinesTransitRoute(transitLine, transitRoutes, transportMode): | |||
|
|||
route = [r_val['link']['refId'] for r_val in transitRoute_val['links']] | |||
|
|||
trips = {} | |||
trips = { | |||
'trip_id': [], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a one-to-one relationship between trip IDs, trip departure times, and vehicle IDs? Or will these 3 lists each be of different sizes?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah it's one to one, so at index x
in each list there should be a time and vehicle corresponding to that trip_id. It would make sense to make a Trip class and just have a list here but the nestedness of classes is destroying me 😭 Schedule(Service(Route(Stop)))
is about as much as I can handle. That's partly why I introduced the dataframe setting method. Thinking about it now, it might be good to have something like this when instantiating a Route
, instead of giving the trips dictionary that is exactly matching those assumptions, you can just give a dataframe with those columns, that would enforce equal length of those lists and would be more user friendly I think
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The Trip
class idea was why I was asking this question. I don't think it needs to sit inside a class hierarchy though. You could use a python dataclass, maybe, if the class is really just a way to hold data and won't define any logic.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ooh, first time I'm seeing this, I like! I'll add a jira card to that effect, thanks!
read_capacity = False | ||
elif tag == 'capacity': | ||
read_capacity = True | ||
elif read_capacity: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this belong in the elif
? Wouldn't it work as just an if?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
took me a while to clock this 😂 , the capacity attributes get read before the capacity tag appears, the flag read_capacity
will change to True, and then the vehicle type dictionary v
will get its capacity
set as an empty dictionary (the value of the capacity elem.attrib
)
@@ -98,115 +98,3 @@ | |||
# 'freespeedFactor': 1.0, | |||
'capacity': 9999.0} | |||
} | |||
|
|||
|
|||
MODE_DICT = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Delete is the best refactoring 😄
|
||
def test_generating_vehicles_with_shared_vehicles_and_consistent_modes(mocker, schedule): | ||
schedule.vehicles = {} | ||
mocker.patch.object(DataFrame, 'drop', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like this test because it's readily apparent from reading the test alone why we should expect the generated vehicles map to look this way.
'v_3': {'type': 'rail'}}) | ||
|
||
|
||
def test_generating_additional_vehicles_by_default(schedule): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are we testing two things here? The test name suggests we're going to assert that an additional vehicle has been generated, but the assertion also seems to be checking that the modes have changed for the pre-existing vehicles. If so, these might be better in separate tests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nah, it's checking that all the others haven't changed, they are all the weird _bus
type
tests/test_core_schedule.py
Outdated
'veh_1_bus': {'type': 'bus'}, 'veh_2_bus': {'type': 'bus'}}) | ||
|
||
|
||
def test_generating_vehicles_with_shared_vehicles_and_inconsistent_modes(mocker, schedule): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A better test name might be something like test_rejects_inconsistent_modes_when_generating_vehicles
Test names like does_something_when_something
can give a better clue about the intent of the test and the expected behaviour of the code under test.
@@ -1,10 +1,10 @@ | |||
<?xml version="1.0" encoding="UTF-8"?> | |||
|
|||
<vehicleDefinitions xmlns="http://www.matsim.org/files/dtd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.matsim.org/files/dtd http://www.matsim.org/files/dtd/vehicleDefinitions_v1.0.xsd"> | |||
<vehicleType id="Bus"> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, MATSim XML schemas and case...
tests/test_utils_persistence.py
Outdated
@@ -33,6 +33,19 @@ def test_swallows_exceptions_making_new_directories(mocker): | |||
os.makedirs.assert_called_once() | |||
|
|||
|
|||
def test_is_yml_identifies_yml(): | |||
zip_dir = os.path.join('path', 'to', 'dir', 'file.yml') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
zip_dir
feels like a misleading name. Although you don't really need the variable:
assert persistence.is_yml(os.path.join('path', 'to', 'dir', 'file.yml'))
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
copy pasta fail, typical >_>'
vehicles
andvehicle_types
attribute toSchedule
object.vehicles
is a mapping between vehicle IDs stored within Routes and their types so for a bus route with a trip which uses vehicle'veh_1'
, there will be an entry'veh_1': {'type': 'bus'}
. Types need not be exactly the same as mode of the route but are as default if generated by genet.vehicle_types
is a dictionary holding vehicle definitions which used to be hard coded here. Now you can pass a vehicle definitions config when instantiating a schedule. If not passed, it defaults to that example config (so no change in behaviour, but gives flexibility to change definitions for your use case and also modify them when theSchedule
already exists.vehicles
andvehicle_types
attributes) if passedroute.trips
attribute to hold vehicles next to trips. BeforeAfter
which also makes it look more sensible in route data summary method. From
to
route_trips_to_dataframe
which generates a DataFrame holding all the trips, their departure times and vehicle IDs, next to the route ID and service ID. ---!! I need help with naming this method, there is another onegenerate_trips_dataframe
which extends this further, expanding across all stops (for all trips) in the route and giving scheduled timestamps at those stops for all trips for all routes, I think they sound too similar and I'm not sure they convey what the outputs are very well >_>set_route_trips_dataframe
which takes the output ofroute_trips_to_dataframe
and changesroute.trips
attributes of all routes present in the DataFrame to what is in the passed DataFrame. This means you can pull out a DataFrame representation of all trips in the Schedule, change their times, IDs or vehicles (so maybe have some vehicles shared based on some logic) and then update the Schedule with those changes just using that DataFrame representation. Example from a jupyter notebook: