Skip to content

Create initial logic for seperating maz table from taz table#215

Draft
lachlan-git wants to merge 26 commits intodevelopfrom
update_maz_seperation_logic
Draft

Create initial logic for seperating maz table from taz table#215
lachlan-git wants to merge 26 commits intodevelopfrom
update_maz_seperation_logic

Conversation

@lachlan-git
Copy link
Copy Markdown
Collaborator

@lachlan-git lachlan-git commented Oct 20, 2025

What existing problem does the pull request solve and why should we include it?

PR created to address issue #210 and #212

Separating / re-implementing MAZ travel demands and integrating with assignment in Emme without storing an intermediate OMX file.

@lachlan-git, please update the PR description to include brief descriptions for each work item, use checkbox to track progress. For example:

  • Separate MAZ auto demand out from TAZ demand
  • Save MAZ demand into EMME Bank
    • Waiting on @inrokevin for best practices
    • According to @inrokevin, the best practive for maz is to save them as a csv in the scenario directory this has been updated
  • Merge in yue's maz crosswalk implementation and confirm maz assignment lines up
  • Update MAZ demand to accept New Demands
  • Correct Taxi and TNC vehicle conversion
  • Create ENUM object for vehicle classes
    • May need name change depending feedback
  • Remove OMX matricies from taz demands to use trip-list tables stored inside EMME bank.
  • Correct logic in set background MAZ traffic
  • confirm person to vehicle trip converision for auto trips (where in code)
  • Comment / Document steps / assumptions in prepare demand function
  • Test and validate that the changes did not break a full model run

What is the testing plan?

Rerun household and highway_maz_assign, the following conditions should be met:

  • Total Maz Demand originating at MAZ node should match household demand
  • All maz trips should be less than XX miles
  • For Taz Total Centroid Connector Flows should equal twice the total demand for each class
  • For Taz, we should see very similar demands (less the maz trips) with before changes are implemented.

Code formatting

will be PEP8 Compliant on all changed files

Applicable Issues

Please do not create a Pull Request without creating an issue first.

Put closes #XXXX in your comment to auto-close the issue that your PR fixes.

@lachlan-git lachlan-git self-assigned this Oct 20, 2025
@lachlan-git
Copy link
Copy Markdown
Collaborator Author

Notice that in the original implementation is meant to treat ride shares differently, but this code path is never touched:

elif trip_mode in [15, 16]:

Does this need to be changed? Should 15, 16 be removed from the list above

image

@lachlan-git
Copy link
Copy Markdown
Collaborator Author

The threshold for whether the trip from CTRAMP becomes a maz trip or a taz trip is in the "highway" section of the model config

    # threshold (miles) where a drive trip is short enough to be moved to a taz
    maz_drive_distance_threshold = 0.5

This will require a new file in tm2py-utils.

it = it_full[it_full.time_period == time_period]
jt = jt_full[jt_full.time_period == time_period]
it_both_taz_and_maz = it_full[it_full.time_period == time_period]
jt_both_taz_and_maz = jt_full[jt_full.time_period == time_period]
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand that whoever first authored this function did not do this - can we please be more intentional with object naming? E.g., if it is a dataframe, put a _df suffix in the name please.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will keep naming convention for the code I write, I wont refactor the entire function

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, please do that.

@i-am-sijia
Copy link
Copy Markdown
Collaborator

Notice that in the original implementation is meant to treat ride shares differently, but this code path is never touched:

elif trip_mode in [15, 16]:

Does this need to be changed? Should 15, 16 be removed from the list above

Good catch. It does seem problematic. If 15-taxi and 16-tnc are removed from the list above, do they not get created in the output trip tables? I think that is fine if the intention here is to reclassify taxi and tnc and assign them as part of the DA, HOV2, HOV3 trip tables.

@lachlan-git

This comment was marked as duplicate.

@lachlan-git
Copy link
Copy Markdown
Collaborator Author

Notice that in the original implementation is meant to treat ride shares differently, but this code path is never touched:

elif trip_mode in [15, 16]:

Does this need to be changed? Should 15, 16 be removed from the list above

Good catch. It does seem problematic. If 15-taxi and 16-tnc are removed from the list above, do they not get created in the output trip tables? I think that is fine if the intention here is to reclassify taxi and tnc and assign them as part of the DA, HOV2, HOV3 trip tables.

Code seemed stale, has some updates to be reviewed

Copy link
Copy Markdown
Collaborator

@i-am-sijia i-am-sijia left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this is still a WIP. Just leaving some incremental comments before you finalize the code changes. We should also run the model through to confirm code changes do not break things.

interchange_nodes_file: str = Field()
apply_msa_demand: bool = True
reliability: bool = Field(default=True)
maz_drive_distance_threshold: float = Field()
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we are adding a new paramemter to the model config, we should create a corresponding PR with actual config changes to tm2py-utils.

taxi_split: Dict[str, float]
single_tnc_split: Dict[str, float]
shared_tnc_split: Dict[str, float]
ctramp_mode_names: Dict[float, str]
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, make sure this is in a PR to tm2py-utls. We should also run the model after deleting this from the model config to make sure it does not break anything.

def _copy_maz_flow(self):
"""Copy maz_flow from MAZ demand assignment to ul1 for background traffic."""
self._network_calculator("ul1", "0")
self._network_calculator("ul1", "@maz_flow")
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have this been tested to confirm there is background flow in the network?

Copy link
Copy Markdown
Collaborator Author

@lachlan-git lachlan-git Nov 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The testing of the background maz flows. was done by comparing the input demands (intermediatly saved as a csv) and the total volumes (inflow or and outflow) of the MAZ connectors on the network. It was noticed that more flow was missing than expected.

image image

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: On review of the code, this is likely because the code filters out MAZ trips longer than 10 miles.
https://github.com/BayAreaMetro/tm2py/blob/d454ee0b7aae02b8d454431ccc11077c2f8c2a5a/tm2py/components/network/highway/highway_maz.py#L52C1-L52C20

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 changes improved this, but did not completely mitigate this discrepency.

  1. by using a TAZ matrix (instead of ct-ramps internal distance) implemented in fe20e44. There were no trips over the 10 miles this lead. to incomplete demand assignment
  2. self trips (starting and ending at the same maz node) were also filtered out of the maz_demands, as these do not appear on the network.
image image

)
continue
self._process_demand(time, i, maz_ids)
self._process_demand(time, i, maz_ids, model_id_to_maz)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not a fan of calling it model_id_to_maz. Let's be as explicit as we can and avoid ambiguity. There are three types of IDs: MAZ labels (county-based), MAZ sequentials (starts from 1 and used by CT-RAMP), MAZ EMME node IDs. Maybe we should call them labels, sequentials, and emmes. Please coordinate with @yueshuaing to confirm the final namings and mappings.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately, reviewing the tests this week, it appears there may still be some crosswalk issues,

it = it_full[it_full.time_period == time_period]
jt = jt_full[jt_full.time_period == time_period]
individual_trip_both_taz_and_maz_df = it_full[it_full.time_period == time_period]
joint_trip_both_taz_and_maz = jt_full[jt_full.time_period == time_period]
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

joint_trip_both_taz_and_maz_df?

@lachlan-git
Copy link
Copy Markdown
Collaborator Author

@i-am-sijia The person trips get converted into vehicle trips at this point in the code

# 2) Convert Person Trips into Vehicle trips

@i-am-sijia
Copy link
Copy Markdown
Collaborator

@i-am-sijia The person trips get converted into vehicle trips at this point in the code

Thank you. CTRAMP used 0.5 to convert SR2 person trips into vehicles, and 0.3 for SR3. [1][2]. I do not like how this is hardcoded in tm2py. Shall we expose it to the config?

@Ennazus
Copy link
Copy Markdown
Collaborator

Ennazus commented Nov 13, 2025

Oh wow! You've been doing lots of work here which is great. Could you write me up a couple paragraphs to describe what you did and what you're still working on?

@i-am-sijia
Copy link
Copy Markdown
Collaborator

i-am-sijia commented Nov 14, 2025

@Ennazus

This PR started off to address the two existing issues of MAZ assignment. One being that the MAZ demand matrices in OMX format were all zero from CT-RAMP (#210), and the other being that the background traffic, which is a result of MAZ assignment, was not being set correctly (#212).

To address #210, instead of reading the MAZ OMX matrices generated by CT-RAMP, we decided to directly create MAZ vehicle trip tables from the CT-RAMP person trip list, because:

  1. MAZ assignment is just shortest path, which does not need OD in matrix format,
  2. It removes unnecessary writing and read of OMX files,
  3. tm2py already has a method called prepare_household_demand() that creates TAZ vehicle trip tables from the
    CT-RAMP person trip list, MAZ vehicle trip seems to be a nature fit in there, and
  4. It gives tm2py more control over how the MAZ trip tables are created.

To address #212, we just need to swap two lines of code in highway_assign.py as identified in the issue.

As Lachlan dug into the code to implement these changes, he found a few other things that were not originally part of the two issues, but worth fixing while he was at it. For example, the prepare_household_demand() method was using hardcoded integer values of travel modes which is not legible; the person to vehicle trip conversion in prepare_household_demand() was implemented incorrectly for Taxi and TNC; the MAZ assignment was divided into 3 county groups, which was an unnecessary complexity inherited from the old TM2 CUBE codebase; When translating MAZ demand from the sequential MAZ to MAZ node IDs in EMME, it lacked a canonical crosswalk - which is now implemented as part of PR #216.

Instead of making a bunch of small PRs for each of these additional fixes that all touch the same files, we decided to bundle them all into this one PR.

The following check list summarizes all the completed and open items in this PR:

  • Update prepare_household_demand() to create MAZ vehicle trip table from the CT-RAMP person trip list, and separate MAZ vehicle trip table from the TAZ vehicle trip table
  • Update the MAZ assignment to read from the newly created MAZ vehicle trip table instead of reading from OMX files
  • Save MAZ vehicle trip tables in EMME format directly, skip CSV and OMX
    • Note -> according to @inrokevin, it was best to save this output as a csv
  • Save TAZ vehicle trip tables in EMME format directly, skip OMX
  • Fix the background traffic setting code in highway_assign.py
  • Replace hardcoded integer travel modes in prepare_household_demand() with predefined ENUMs
  • Fix the person to vehicle trip conversion logic for Taxi and TNC in prepare_household_demand()
  • Simplify MAZ assignment by removing the 3-county grouping logic in prepare_highway_demand()
  • Implement a canonical crosswalk from sequential MAZ IDs to EMME node IDs for MAZ assignment
  • Add more comments and docstrings to prepare_highway_demand() for better code legibility
  • Perform a model run and confirm changes are working and not breaking anything

@lachlan-git will also create a PR to the tm2py-utils repo with associated config changes.

@i-am-sijia
Copy link
Copy Markdown
Collaborator

Update (11/17). Uncompleted items:

  1. MAZ assignment reports that there are OD pairs that are more than 0.5 miles apart (e.g., 10 miles), which should not happen because they should have been filtered out in:
    it_is_maz_trip = (
    (individual_trip_both_taz_and_maz_df["trip_dist"] < self.controller.config.highway.maz_drive_distance_threshold) &
    individual_trip_both_taz_and_maz_df["trip_mode"].isin(DRIVE_MODES)
    )
    it_maz_df = individual_trip_both_taz_and_maz_df[it_is_maz_trip]
    it = individual_trip_both_taz_and_maz_df[~it_is_maz_trip]
    jt_is_maz_trip = (
    (joint_trip_both_taz_and_maz_df["trip_dist"] < self.controller.config.highway.maz_drive_distance_threshold) &
    joint_trip_both_taz_and_maz_df["trip_mode"].isin(DRIVE_MODES)
    )
    jt_maz_df = joint_trip_both_taz_and_maz_df[jt_is_maz_trip]
    jt = joint_trip_both_taz_and_maz_df[~jt_is_maz_trip]
    maz_trips = pd.concat([it_maz_df, jt_maz_df])
    , based on the trip_dist column in the trip list from ct-ramp. This suggests either the sequential MAZ ID to EMME node ID correspondence is wrong (unlikely because it's recently updated in PR Add MAZ data model #216), or the trip_dist value in the trip list is wrong. To check the latter, we know that ct-ramp appends TAZ skims to the trip list https://github.com/BayAreaMetro/travel-model-two/blob/e941242fc43fce60beea24be6263f1cd1aae231b/src/java/com/pb/mtctm2/abm/reports/SkimBuilder.java#L234-L310, we therefore appended TAZ drive alone skims to the trip list to verify the trip_dist column. The snapshot below confirms that there are some large discrepancies between the trip_dist column and the appended TAZ drive alone skims for some MAZ pairs. We need to investigate why this is happening. Could it be because of the HOV and/or toll distance skims?

MAZ auto trip demand for MAZ assignment for Early Morning. Note the difference in trip_distance and taz_distance ("EA_DA_dist")
image

  1. This may be related to item No.1. When comparing the MAZ demand against the MAZ assigned volume, we found that the assigned volume is always less than the demand. We need to investigate why this is happening.
    Create initial logic for seperating maz table from taz table #215 (comment)

  2. We would like to get rid of writing out TAZ vehicle OD matrices to disk as OMX files, and instead write them directly to Emmebank. This would require modifying the demand preparation step to write directly to Emmebank instead of writing to OMX first, for resident demand, auxiliary demand models including airport, truck, IE/EI, etc., and then modifying the highway assignment step to read from Emmebank instead of OMX.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants