Disruptions#

Network.disrupt reroutes OD flows whose existing edge_path includes a failed edge. The result separates rerouted flows, isolated demand, updated edge totals, and rerouting losses.

Isolated demand#

If a failed edge removes the only available path, the affected demand appears in isolated_od and no rerouted flow is returned.

>>> import pandas as pd
>>> from transport_flow_model.model import Network, ODFlows
>>> network = Network(
...     pd.DataFrame(
...         {
...             "edge_from": ["A", "B"],
...             "edge_to": ["B", "C"],
...             "edge_id": ["AB", "BC"],
...             "cost": [1, 1],
...             "capacity": [100, 100],
...         }
...     )
... )
>>> existing_flows = ODFlows(
...     pd.DataFrame(
...         {
...             "origin_id": ["A"],
...             "destination_id": ["C"],
...             "flow": [10],
...             "edge_path": [["AB", "BC"]],
...             "cost": [2],
...         }
...     )
... )
>>> isolated = network.disrupt(existing_flows, ["AB"], directed=True)
>>> isolated.rerouted_flows.to_dataframe().empty
True
>>> isolated.isolated_od.to_dataframe().to_dict("records")
[{'origin_id': 'A', 'destination_id': 'C', 'flow': 10}]

Rerouting to an alternative path#

If an alternative path exists, the affected flow is rerouted. Comparing the original path cost with the disrupted path cost gives rerouting losses as result.losses.

>>> reroute_network = Network(
...     pd.DataFrame(
...         {
...             "edge_from": ["A", "B", "A"],
...             "edge_to": ["B", "C", "C"],
...             "edge_id": ["AB", "BC", "AC"],
...             "cost": [1, 1, 5],
...             "capacity": [100, 100, 100],
...         }
...     )
... )
>>> result = reroute_network.disrupt(existing_flows, ["AB"], directed=True)
>>> result.rerouted_flows.to_dataframe().to_dict("records")
[{'origin_id': 'A', 'destination_id': 'C', 'flow': 10, 'edge_path': ['AC'], 'cost': 5}]
>>> result.losses.to_dataframe().to_dict("records")
[{'origin_id': 'A', 'destination_id': 'C', 'flow': 10, 'initial_cost': 2, 'disrupted_cost': 5, 'rerouting_loss': 3}]
>>> result.network_flows.to_dataframe().set_index("edge_id")["flow"].to_dict()
{'AB': 0, 'BC': 0, 'AC': 10}

Unaffected flows stay on the network#

Only OD flows whose path includes a failed edge are rerouted. Other OD flows remain in the returned network_flows totals.

>>> partial_network = Network(
...     pd.DataFrame(
...         {
...             "edge_from": ["A", "B", "A", "C"],
...             "edge_to": ["B", "C", "C", "D"],
...             "edge_id": ["AB", "BC", "AC", "CD"],
...             "cost": [1, 1, 5, 1],
...             "capacity": [100, 100, 100, 100],
...         }
...     )
... )
>>> partial_flows = ODFlows(
...     pd.DataFrame(
...         {
...             "origin_id": ["A", "C"],
...             "destination_id": ["C", "D"],
...             "flow": [10, 4],
...             "edge_path": [["AB", "BC"], ["CD"]],
...             "cost": [2, 1],
...         }
...     )
... )
>>> partial = partial_network.disrupt(partial_flows, ["AB"], directed=True)
>>> partial.network_flows.to_dataframe().set_index("edge_id")["flow"].to_dict()
{'AB': 0, 'BC': 0, 'AC': 10, 'CD': 4}

Capacity-constrained disruption#

Disruption rerouting is capacity constrained by default. If the alternate path has only enough capacity for part of the affected flow, any residual demand is isolated.

>>> capacity_network = Network(
...     pd.DataFrame(
...         {
...             "edge_from": ["A", "B", "A", "D"],
...             "edge_to": ["B", "C", "D", "C"],
...             "edge_id": ["AB", "BC", "AD", "DC"],
...             "cost": [1, 1, 3, 3],
...             "capacity": [15, 15, 10, 10],
...         }
...     )
... )
>>> capacity_flows = ODFlows(
...     pd.DataFrame(
...         {
...             "origin_id": ["A"],
...             "destination_id": ["C"],
...             "flow": [15],
...             "edge_path": [["AB", "BC"]],
...             "cost": [2],
...         }
...     )
... )
>>> capacity_result = capacity_network.disrupt(
...     capacity_flows, ["AB"], directed=True
... )
>>> capacity_result.rerouted_flows.to_dataframe().to_dict("records")
[{'origin_id': 'A', 'destination_id': 'C', 'flow': 10, 'edge_path': ['AD', 'DC'], 'cost': 6}]
>>> capacity_result.isolated_od.to_dataframe().to_dict("records")
[{'origin_id': 'A', 'destination_id': 'C', 'flow': 5}]
>>> capacity_result.network_flows.to_dataframe().set_index("edge_id")["flow"].to_dict()
{'AB': 0, 'BC': 0, 'AD': 10, 'DC': 10}