Skip to content
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

extract turning movements #358

Open
wants to merge 42 commits into
base: develop
Choose a base branch
from

Conversation

ziocolaandrea
Copy link

No description provided.

@ziocolaandrea ziocolaandrea marked this pull request as draft November 12, 2022 06:58
@ziocolaandrea ziocolaandrea marked this pull request as ready for review January 3, 2023 00:34
@Jake-Moss
Copy link
Contributor

TurnVolumesResults also needs to be add to the results/__init__.py

diff --git a/aequilibrae/paths/results/__init__.py b/aequilibrae/paths/results/__init__.py
index c7ac6c4..3fb353c 100644
--- a/aequilibrae/paths/results/__init__.py
+++ b/aequilibrae/paths/results/__init__.py
@@ -14,3 +14,4 @@ __date__ = "$Date: 2016-07-02$"
 from .assignment_results import AssignmentResults
 from .path_results import PathResults
 from .skim_results import SkimResults
+from .turn_volumes_results import TurnVolumesResults
-- 
2.40.0

def turning_volumes(
self,
turns_df: pd.DataFrame,
classes: Optional[list[str]] = None,
Copy link
Contributor

Choose a reason for hiding this comment

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

This type hint indicates that a str is ok, but the TurnVolumesResults.from_traffic_class expects a traffic class object

-         classes: Optional[list[str]] = None,
+         classes: Optional[list[TrafficClass]] = None,

@Jake-Moss
Copy link
Contributor

Thank you both for your work on this. I've been attempting to get a mwe with the sioux_falls_2020_02_15, and just running into a few issues.

https://github.com/ziocolaandrea/aequilibrae/blob/0a569e8fe2e11dd4be2594dbcad9fe93445ed39d/aequilibrae/paths/results/turn_volumes_results.py#L286-L299

    def get_turns_ods(
        self, turns_w_links: pd.DataFrame, formatted_paths: pd.DataFrame, node_to_index_df
    ) -> pd.DataFrame:
        index_to_node = node_to_index_df.reset_index()
        turns_w_od_idx = formatted_paths.merge(turns_w_links, on=["id", "id_next"])
        turns_w_od = turns_w_od_idx.merge(index_to_node, left_on="origin_idx", right_on="node_index", how="left").merge(
            index_to_node,
            left_on="destination_idx",
            right_on="node_index",
            how="left",
            suffixes=("_origin", "_destination"),
        )
        turns_w_od.rename(columns={"index_origin": "origin", "index_destination": "destination"}, inplace=True)
        return turns_w_od[TURNING_VOLUME_OD_COLUMNS]

The line turns_w_od_idx = formatted_paths.merge(turns_w_links, on=["id", "id_next"]) seems to be creating an empty Dataframe which is causing issues later on. Both Dataframes that are being merged are non-empty and have the required columns however there is no 11 or 11.0 entry in formatted_paths.id_next
image

This dataframe is also indexed by entirely ones? Additionally should the destination_idx and id_next columns be floating points?
image

Heres the setup code I am working with, sioux_falls_2020_02_15 refers to the example project available at https://www.aequilibrae.com/python/latest/usageexamples.html#sample-data

from os.path import join
import pandas as pd
from aequilibrae.project import Project
from aequilibrae.paths import TurnVolumesResults
from aequilibrae.paths.traffic_assignment import TrafficAssignment, TrafficClass
from aequilibrae.matrix import AequilibraeData, AequilibraeMatrix

fldr = './models/sioux_falls_2020_02_15/'
dt_fldr = '0_tntp_data'
prj_fldr = '1_project'
project = Project()
project.load(join(fldr, prj_fldr))
# project.load(fldr)
project.network.build_graphs()
graph = project.network.graphs['c']

assig = TrafficAssignment()

# Creates the assignment class
demand = AequilibraeMatrix()
demand.load(join(fldr, dt_fldr, 'demand.omx'))
#demand = project.matrices.get_matrix("demand_omx")
demand.computational_view(["matrix"])
assigclass = TrafficClass("car", graph, demand)

# The first thing to do is to add at list of traffic classes to be assigned
assig.set_classes([assigclass])

assig.set_vdf("BPR")  # This is not case-sensitive # Then we set the volume delay function

assig.set_vdf_parameters({"alpha": "b", "beta": "power"})  # And its parameters

assig.set_capacity_field("capacity")  # The capacity and free flow travel times as they exist in the graph
assig.set_time_field("distance")

# And the algorithm we want to use to assign
assig.set_algorithm('bfw')

# since I haven't checked the parameters file, let's make sure convergence criteria is good
assig.max_iter = 2
assig.rgap_target = 0.00001

assig.set_save_path_files(True)

assig.execute()  # we then execute the assignment


turn_abc = pd.DataFrame(
    [
        [1, 2, 3],
        [4, 5, 6],
    ],
    columns=["a", "b", "c"]
)
turning_volumes = assig.turning_volumes(turn_abc, classes=[assigclass])

@ziocolaandrea
Copy link
Author

ziocolaandrea commented Apr 28, 2023 via email

@ziocolaandrea
Copy link
Author

ziocolaandrea commented May 6, 2023

Hi Jake,
Not sure if you did it on purpose, but you managed to get 2 exceptions:

  1. an impossible turn, links 1-2 and 2-3 are not consecutive
  2. a turn without volumes, links 4-5 and 5-6 are not used in any paths.

I introduced some warnings (not errors) so that the process continues to the end for valid turns.

For case scenario 2, I'm removing the turns from the DataFrame, however another solution could be to have a record with 0 volume.
Happy to get your ideas on this.

It looks like I can't solve the conflicts in the documentation. Could you or Pedro help?

I looks like the project you used was created with an older version and I couldn't get it to run. However, I had another Sioux Falls project with a consistent network as yours.

Cheers,
Andrea

@ziocolaandrea
Copy link
Author

@Jake-Moss just checking if you saw my last commits that should solve the issues you found.
Cheers,
Andrea

@Jake-Moss
Copy link
Contributor

Hi Andrea, very sorry for the delay, I have seen your changes (thank you) and I've been meaning to meet with @pedrocamargo to discuss these changes. I'll try to schedule something today.

As for the merge conflicts we'll be able to resolve those manually

class TestTurnVolumes(TestCase):
def setUp(self) -> None:
ensure_spatialite_binaries()
os.environ["PATH"] = os.path.join(gettempdir(), "temp_data") + ";" + os.environ["PATH"]
Copy link
Contributor

Choose a reason for hiding this comment

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

This is probably not needed after ensure_spatialite_binaries()

self.assignment.set_save_path_files(True)
self.assignment.execute()
turning_movements = self.assignment.turning_volumes(TURNS_DF)
self.assertEqual(turning_movements.at[0, "volume"], 150)
Copy link
Contributor

Choose a reason for hiding this comment

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

All test results seem to indicate the same values, which maybe tells me that not everything is being tested. Is this assignment converging after 1 iteration and that's why the results are like this? Maybe we need something with a minimum of congestion to show that it picks up the right results

@Jake-Moss
Copy link
Contributor

Hi Andrea, I've constructed another demo script based on the tests you've implemented but I've not sure what going wrong here, could you please take a look?

It doesn't seem to be converging, but ignoring that the turning_volumes dataframe is turning up empty.
Thanks,

import zipfile
import os
import pandas as pd
from aequilibrae.project import Project
from aequilibrae.utils import create_example
from aequilibrae.paths import TurnVolumesResults
from aequilibrae.paths.traffic_assignment import TrafficAssignment, TrafficClass
from aequilibrae.matrix import AequilibraeData, AequilibraeMatrix
import numpy as np
from tempfile import gettempdir
import uuid


def _create_assign_matrix():
    zones = 24
    mat_file = join(gettempdir(), f"Aequilibrae_matrix_{uuid.uuid4()}.aem")
    args = {
        "file_name": mat_file,
        "zones": zones,
        "matrix_names": ["mat1", "mat2"],
        "index_names": ["my_indices"],
        "memory_only": False,
    }
    matrix = AequilibraeMatrix()
    matrix.create_empty(**args)
    trips = np.zeros((zones, zones))
    trips[0, 5] = 100
    trips[0, 7] = 50

    matrix.index[:] = np.arange(matrix.zones) + 1
    matrix.matrices[:, :, 0] = trips
    matrix.matrices[:, :, 1] = matrix.mat1 * 2
    matrix.setName("test_turn_volumes")
    matrix.setDescription("test turn movements")
    matrix.matrix_hash = {zone: idx for idx, zone in enumerate(np.arange(matrix.zones) + 1)}
    return matrix


proj_path = os.path.join(gettempdir(), "test_traffic_assignment_path_files" + uuid.uuid4().hex)
os.mkdir(proj_path)
zipfile.ZipFile("/home/jake/Software/aequilibrae/tests/data/sioux_falls_single_class.zip").extractall(proj_path)
project = Project()
project.load(proj_path)
project.network.build_graphs()
graph = project.network.graphs['c']

assig = TrafficAssignment()

matrix_1 = _create_assign_matrix()
matrix_1.computational_view(["mat1"])

matrix_2 = _create_assign_matrix()
matrix_2.computational_view(["mat2"])

assigclass_1 = TrafficClass("car", graph, matrix_1)
assigclass_2 = TrafficClass("truck", graph, matrix_2)
assig.set_classes([assigclass_1, assigclass_2])
assig.set_vdf("BPR")
assig.set_vdf_parameters({"alpha": "b", "beta": "power"})
assig.set_capacity_field("capacity")
assig.set_time_field("free_flow_time")

assig.set_algorithm('bfw')
assig.max_iter = 5
assig.rgap_target = 0.001
assig.set_save_path_files(True)
assig.execute()  # we then execute the assignment
assig.save_results("demo")

turn_abc = pd.DataFrame([[1, 2, 6]], columns=["a", "b", "c"])

turning_volumes = assig.turning_volumes(turn_abc)

class_to_matrix = {
    "car": matrix_1,
    "truck": matrix_2
}

turning_movements = TurnVolumesResults.calculate_from_result_table(
    project=project,
    turns_df=turn_abc,
    asgn_result_table_name="demo",
    class_to_matrix=class_to_matrix,
)

@colaandrea
Copy link

colaandrea commented May 27, 2023

Hi @Jake-Moss, thank you for looking into this again.
You missed a critical setting for the graph around line 46 in your script:
graph.set_graph("free_flow_time")
graph.set_blocked_centroid_flows(False)

@pedrocamargo I'll look into improving the bfw test

@colaandrea
Copy link

@pedrocamargo @Jake-Moss I've improved the test by building a matrix with 0 trips to and from centroid 2. This way the turning volumes for turn 1,2,6 should be the same as the volumes on links 1-2 and 2-6.
Unfortunately the test fails for the BFW, which is quite disappointing...I'm looking into the issue.

@colaandrea
Copy link

@Jake-Moss @pedrocamargo thank you very much for your reviews. There was indeed an error in how the volume by iteration was calculated.
Can you please double check if this condition is correct (line 366 in turn_volumes_results.py):
if (betas_for_it != -1).all():

Thanks again!

@janzill janzill mentioned this pull request Nov 7, 2023
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.

4 participants