Skip to content

Commit

Permalink
Add features (#598)
Browse files Browse the repository at this point in the history
  • Loading branch information
r-akemii authored Dec 20, 2024
1 parent 5aa3e4f commit 0e1c845
Show file tree
Hide file tree
Showing 4 changed files with 178 additions and 2 deletions.
50 changes: 49 additions & 1 deletion aequilibrae/paths/graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from abc import ABC
from datetime import datetime
from os.path import join
from typing import List, Tuple, Optional
from typing import List, Tuple, Optional, Union
import dataclasses

import numpy as np
Expand Down Expand Up @@ -274,6 +274,54 @@ def _build_directed_graph(self, network: pd.DataFrame, centroids: np.ndarray):

return all_nodes, num_nodes, nodes_to_indices, fs, df

def compute_path(
self,
origin: int,
destination: int,
early_exit: bool = False,
a_star: bool = False,
heuristic: Union[str, None] = None,
):
"""
Returns the results from path computation result holder.
:Arguments:
**origin** (:obj:`int`): origin for the path
**destination** (:obj:`int`): destination for the path
**early_exit** (:obj:`bool`): stop constructing the shortest path tree once the destination is found.
Doing so may cause subsequent calls to ``update_trace`` to recompute the tree. Default is ``False``.
**a_star** (:obj:`bool`): whether or not to use A* over Dijkstra's algorithm.
When ``True``, ``early_exit`` is always ``True``. Default is ``False``.
**heuristic** (:obj:`str`): heuristic to use if ``a_star`` is enabled. Default is ``None``.
"""
from aequilibrae.paths import PathResults

res = PathResults()
res.prepare(self)
res.compute_path(origin, destination, early_exit, a_star, heuristic)

return res

def compute_skims(self, cores: Union[int, None] = None):
"""
Returns the results from network skimming result holder.
:Arguments:
**cores** (:obj:`Union[int, None]`): number of cores (threads) to be used in computation
"""
from aequilibrae.paths import NetworkSkimming

skimmer = NetworkSkimming(self)
if cores:
skimmer.set_cores(cores)
skimmer.execute()

return skimmer.results

def exclude_links(self, links: list) -> None:
"""
Excludes a list of links from a graph by setting their B node equal to their A node
Expand Down
2 changes: 1 addition & 1 deletion aequilibrae/paths/results/path_results.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ def set_heuristic(self, heuristic: str) -> None:
**heuristic** (:obj:`str`): Heuristic to use in A*.
"""
if heuristic not in HEURISTIC_MAP.keys():
raise ValueError(f"heruistic must be one of {self.get_heuristics()}")
raise ValueError(f"heuristic must be one of {self.get_heuristics()}")

self._heuristic = heuristic

Expand Down
106 changes: 106 additions & 0 deletions docs/source/graph_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
"""
.. _plot_graph_from_arbitrary_data:
Graph from arbitrary data
=========================
In this example, we demonstrate how to create an AequilibraE Graph from an arbitrary network.
We are using
`Sioux Falls data <https://github.com/bstabler/TransportationNetworks/tree/master/SiouxFalls>`_,
from TNTP.
"""

# %%
# .. seealso::
# Several functions, methods, classes and modules are used in this example:
#
# * :func:`aequilibrae.paths.Graph`

# %%

# Imports
import numpy as np
import pandas as pd

from aequilibrae.paths import Graph
# sphinx_gallery_thumbnail_path = '../source/_images/graph_network.png'

# %%
# We start by adding the path to load our arbitrary network.
net_file = "https://raw.githubusercontent.com/bstabler/TransportationNetworks/master/SiouxFalls/SiouxFalls_net.tntp"

# %%
# Let's read our data! We'll be using Sioux Falls transportation network data, but without
# geometric information. The data will be stored in a Pandas DataFrame containing information
# about initial and final nodes, link distances, travel times, etc.
net = pd.read_csv(net_file, skiprows=8, sep="\t", lineterminator="\n", usecols=np.arange(1, 11))

# %%
# The Graph object requires several default fields: link_id, a_node, b_node, and direction.
# We'll need to manipulate the data to add the missing fields (link_id and direction) and
# rename the node columns accordingly.
net.insert(0, "link_id", np.arange(1, net.shape[0] + 1))
net = net.assign(direction=1)
net.rename(columns={"init_node": "a_node", "term_node": "b_node"}, inplace=True)

# %%
# Now we can take a look in our network file
net.head()

# %%
# Building an AequilibraE graph from our network is pretty straightforward. We assign
# our network to be the graph's network ...
graph = Graph()
graph.network = net

# %%
# ... and then set the graph's configurations.

graph.prepare_graph(np.arange(1, 25)) # sets the centroids for which we will perform computation

graph.set_graph("length") # sets the cost field for path computation

graph.set_skimming(["length", "free_flow_time"]) # sets the skims to be computed

graph.set_blocked_centroid_flows(False) # we don't block flows through centroids because all nodes
# in the Sioux Falls network are centroids

# %%
# Two of AequilibraE's new features consist in directly computing path or skims.
#
# Let's compute the path between nodes 1 and 17...
res = graph.compute_path(1, 17)

# %%
# ... and print the corresponding nodes...
res.path_nodes

# %%
# ... and the path links.
res.path
# %%
# For path computation, when we call the method `graph.compute_path(1, 17)`, we are calling the class
# `PathComputation <aequilibrae.paths.PathComputation>` and storing its results into a variable.
#
# Notice that other methods related to path computation, such as `milepost` can also be used with
# `res`.

# %%
# For skim computation, the process is quite similar. When calligng the method `graph.compute_skims()`
# we are actually calling the class `NetworkSkimming <aequilibrae.paths.NetworkSkimming>`, and storing
# its results into `skm`.

skm = graph.compute_skims()

# %%
# Let's get the values for 'free_flow_time' matrix.
skims = skm.skims
skims.get_matrix("free_flow_time")

# %%
# Now we're all set!

# %%
# Graph image credits to
# `Behance-network icons created by Sumitsaengtong - Flaticon <https://www.flaticon.com/free-icons/behance-network>`_
22 changes: 22 additions & 0 deletions tests/aequilibrae/paths/test_graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,28 @@ def test_available_skims(self):
if i not in avail:
self.fail("Skim availability with problems")

def test_compute_path(self):
graph = self.project.network.graphs["c"]
graph.prepare_graph()
graph.set_graph("distance")
graph.set_blocked_centroid_flows(False)

res = graph.compute_path(1, 6)
self.assertEqual(list(res.path), [1, 4], "Number of path links is not correct")
self.assertEqual(list(res.path_nodes), [1, 2, 6], "Number of path nodes is not correct")

def test_compute_skims(self):
graph = self.project.network.graphs["c"]
graph.prepare_graph()
graph.set_graph("distance")
graph.set_skimming(["distance", "free_flow_time"])
graph.set_blocked_centroid_flows(False)

skm = graph.compute_skims()
skims = skm.skims
self.assertEqual(skims.cores, 2, "Number of cores is not correct")
self.assertEqual(skims.names, ["distance", "free_flow_time"], "Matrices names are not correct")

def test_exclude_links(self):
# excludes a link before any setting or preparation
self.graph.set_blocked_centroid_flows(False)
Expand Down

0 comments on commit 0e1c845

Please sign in to comment.