source_modelling.rupture_propagation

Rupture Propagation Module

This module provides functions for computing likely rupture paths from information about the distances between faults.

Functions: - shaw_dieterich_distance_model: Compute fault jump probabilities using the Shaw-Dieterich distance model. - prune_distance_graph: Prune the distance graph based on a cutoff value. - probability_graph: Convert a graph of distances between faults into a graph of jump probabilities using the Shaw-Dieterich model. - probabilistic_minimum_spanning_tree: Generate a probabilistic minimum spanning tree.

Typing Aliases: - DistanceGraph: A graph representing distances between faults. - ProbabilityGraph: A graph representing jump probabilities between faults. - RuptureCausalityTree: A tree representing the causality of ruptures between faults.

  1"""
  2Rupture Propagation Module
  3
  4This module provides functions for computing likely rupture paths from
  5information about the distances between faults.
  6
  7Functions:
  8    - shaw_dieterich_distance_model:
  9      Compute fault jump probabilities using the Shaw-Dieterich distance model.
 10    - prune_distance_graph: Prune the distance graph based on a cutoff value.
 11    - probability_graph:
 12      Convert a graph of distances between faults into a graph of jump
 13      probabilities using the Shaw-Dieterich model.
 14    - probabilistic_minimum_spanning_tree: Generate a probabilistic minimum spanning tree.
 15
 16Typing Aliases:
 17    - DistanceGraph: A graph representing distances between faults.
 18    - ProbabilityGraph: A graph representing jump probabilities between faults.
 19    - RuptureCausalityTree: A tree representing the causality of ruptures between faults.
 20"""
 21
 22from collections import defaultdict, namedtuple
 23from typing import Generator, Optional
 24
 25import numpy as np
 26from qcore import coordinates
 27
 28from source_modelling import sources
 29
 30DistanceGraph = dict[str, dict[str, int]]
 31ProbabilityGraph = defaultdict[str, dict[str, float]]
 32RuptureCausalityTree = dict[str, Optional[str]]
 33
 34JumpPair = namedtuple("JumpPair", ["from_point", "to_point"])
 35
 36
 37def shaw_dieterich_distance_model(distance: float, d0: float, delta: float) -> float:
 38    """
 39    Compute fault jump probabilities using the Shaw-Dieterich distance model[0].
 40
 41    Parameters
 42    ----------
 43    distance : float
 44        The distance between two faults.
 45    d0 : float
 46        The characteristic distance parameter.
 47    delta : float
 48        The characteristic slip distance parameter.
 49
 50    Returns
 51    -------
 52    float
 53        The calculated probability.
 54
 55    References
 56    ----------
 57    [0]: Shaw, B. E., & Dieterich, J. H. (2007). Probabilities for jumping fault
 58         segment stepovers. Geophysical Research Letters, 34(1).
 59    """
 60    return min(1, np.exp(-(distance - delta) / d0))
 61
 62
 63def prune_distance_graph(distances: DistanceGraph, cutoff: int) -> DistanceGraph:
 64    """
 65    Prune the distance graph based on a cutoff value.
 66
 67    Parameters
 68    ----------
 69    distances : DistanceGraph
 70        The graph of distances between faults.
 71    cutoff : int
 72        The cutoff distance in metres.
 73
 74    Returns
 75    -------
 76    DistanceGraph
 77        A copy of the input distance graph, keeping only edges that are less
 78        than the cutoff.
 79    """
 80    return {
 81        fault_u: {
 82            fault_v: distance
 83            for fault_v, distance in neighbours_fault_u.items()
 84            if distance < cutoff
 85        }
 86        for fault_u, neighbours_fault_u in distances.items()
 87    }
 88
 89
 90def probability_graph(
 91    distances: DistanceGraph, d0: float = 3, delta: float = 1
 92) -> ProbabilityGraph:
 93    """
 94    Convert a graph of distances between faults into a graph of jump
 95    probablities using the Shaw-Dieterich model.
 96
 97    Parameters
 98    ----------
 99    distances : DistanceGraph
100        The distance graph between faults.
101    d0 : float, optional
102        The d0 parameter for the Shaw_Dieterich model. See `shaw_dieterich_distance_model`.
103    delta : float, optional
104        The delta parameter for the Shaw_Dieterich model. See `shaw_dieterich_distance_model`.
105
106    Returns
107    -------
108    ProbabilityGraph
109        The graph with faults as vertices. Each edge (fault_u, fault_v)
110        has a log-probability -p as a weight. The log-probability -p here
111        is the negative of the log-probability a rupture propogates from
112        fault_u to fault_v, relative to the probability it propogates to
113        any of the other neighbours of fault_u.
114    """
115    probabilities_raw = {
116        fault_u: {
117            fault_v: shaw_dieterich_distance_model(distance / 1000, d0, delta)
118            for fault_v, distance in neighbours_fault_u.items()
119        }
120        for fault_u, neighbours_fault_u in distances.items()
121    }
122    probabilities_log = defaultdict(dict)
123    for fault_u, neighbours_fault_u in probabilities_raw.items():
124        normalising_constant = sum(neighbours_fault_u.values())
125        if normalising_constant == 0:
126            for fault_v, _ in neighbours_fault_u.items():
127                probabilities_log[fault_u][fault_v] = -np.log(
128                    1 / len(neighbours_fault_u)
129                )
130        for fault_v, prob in neighbours_fault_u.items():
131            probabilities_log[fault_u][fault_v] = -np.log(prob / normalising_constant)
132    return probabilities_log
133
134
135def probabilistic_minimum_spanning_tree(
136    probability_graph: ProbabilityGraph, initial_fault: str
137) -> RuptureCausalityTree:
138    """
139    Generate a probabilistic minimum spanning tree.
140
141    The minimum spanning tree of the probability graph represents rupture
142    causality tree with the highest likelihood of occuring assuming that
143    rupture jumps are independent.
144
145    NOTE: While the overall probability of the minimum spanning tree is high,
146    the paths from the initial fault may not be the most likely.
147
148    Parameters
149    ----------
150    probability_graph : ProbabilityGraph
151        The probability graph.
152    initial_fault : str
153        The initial fault.
154
155    Returns
156    -------
157    RuptureCausalityTree
158        The probabilistic minimum spanning tree.
159    """
160    rupture_causality_tree = {initial_fault: None}
161    path_probabilities = defaultdict(lambda: np.inf)
162    path_probabilities[initial_fault] = 0
163    processed_faults = set()
164    for _ in range(len(probability_graph)):
165        current_fault = min(
166            (fault for fault in probability_graph if fault not in processed_faults),
167            key=lambda fault: path_probabilities[fault],
168        )
169        processed_faults.add(current_fault)
170        for fault_neighbour, probability in probability_graph[current_fault].items():
171            if (
172                fault_neighbour not in processed_faults
173                and probability < path_probabilities[fault_neighbour]
174            ):
175                path_probabilities[fault_neighbour] = probability
176                rupture_causality_tree[fault_neighbour] = current_fault
177    return rupture_causality_tree
178
179
180def distance_between(
181    source_a: sources.IsSource,
182    source_b: sources.IsSource,
183    source_a_point: np.ndarray,
184    source_b_point: np.ndarray,
185) -> float:
186    global_point_a = source_a.fault_coordinates_to_wgs_depth_coordinates(source_a_point)
187    global_point_b = source_b.fault_coordinates_to_wgs_depth_coordinates(source_b_point)
188    return coordinates.distance_between_wgs_depth_coordinates(
189        global_point_a, global_point_b
190    )
191
192
193def estimate_most_likely_rupture_propagation(
194    sources_map: dict[str, sources.IsSource],
195    initial_source: str,
196    jump_impossibility_limit_distance: int = 15000,
197) -> RuptureCausalityTree:
198    distance_graph = {
199        source_a_name: {
200            source_b_name: distance_between(
201                source_a,
202                source_b,
203                *sources.closest_point_between_sources(source_a, source_b),
204            )
205            for source_b_name, source_b in sources_map.items()
206            if source_a_name != source_b_name
207        }
208        for source_a_name, source_a in sources_map.items()
209    }
210    distance_graph = prune_distance_graph(
211        distance_graph, jump_impossibility_limit_distance
212    )
213    jump_probability_graph = probability_graph(distance_graph)
214    return probabilistic_minimum_spanning_tree(
215        jump_probability_graph, initial_fault=initial_source
216    )
217
218
219def jump_points_from_rupture_tree(
220    source_map: dict[str, sources.IsSource],
221    rupture_causality_tree: RuptureCausalityTree,
222) -> dict[str, JumpPair]:
223    jump_points = {}
224    for source, parent in rupture_causality_tree.items():
225        if parent is None:
226            continue
227        source_point, parent_point = sources.closest_point_between_sources(
228            source_map[source], source_map[parent]
229        )
230        jump_points[source] = JumpPair(parent_point, source_point)
231    return jump_points
232
233
234def tree_nodes_in_order(
235    tree: dict[str, str],
236) -> Generator[str, None, None]:
237    """Generate faults in topologically sorted order.
238
239    Parameters
240    ----------
241    faults : list[RealisationFault]
242        List of RealisationFault objects.
243
244    Yields
245    ------
246    RealisationFault
247        The next fault in the topologically sorted order.
248    """
249    tree_child_map = defaultdict(list)
250    for cur, parent in tree.items():
251        if parent:
252            tree_child_map[parent].append(cur)
253
254    def in_order_traversal(
255        node: str,
256    ) -> Generator[str, None, None]:
257        yield node
258        for child in tree_child_map[node]:
259            yield from in_order_traversal(child)
260
261    initial_fault = next(cur for cur, parent in tree.items() if not parent)
262    yield from in_order_traversal(initial_fault)
DistanceGraph = dict[str, dict[str, int]]
ProbabilityGraph = collections.defaultdict[str, dict[str, float]]
RuptureCausalityTree = dict[str, typing.Optional[str]]
class JumpPair(builtins.tuple):

JumpPair(from_point, to_point)

JumpPair(from_point, to_point)

Create new instance of JumpPair(from_point, to_point)

from_point

Alias for field number 0

to_point

Alias for field number 1

def shaw_dieterich_distance_model(distance: float, d0: float, delta: float) -> float:
38def shaw_dieterich_distance_model(distance: float, d0: float, delta: float) -> float:
39    """
40    Compute fault jump probabilities using the Shaw-Dieterich distance model[0].
41
42    Parameters
43    ----------
44    distance : float
45        The distance between two faults.
46    d0 : float
47        The characteristic distance parameter.
48    delta : float
49        The characteristic slip distance parameter.
50
51    Returns
52    -------
53    float
54        The calculated probability.
55
56    References
57    ----------
58    [0]: Shaw, B. E., & Dieterich, J. H. (2007). Probabilities for jumping fault
59         segment stepovers. Geophysical Research Letters, 34(1).
60    """
61    return min(1, np.exp(-(distance - delta) / d0))

Compute fault jump probabilities using the Shaw-Dieterich distance model[0].

Parameters
  • distance (float): The distance between two faults.
  • d0 (float): The characteristic distance parameter.
  • delta (float): The characteristic slip distance parameter.
Returns
  • float: The calculated probability.
References
 segment stepovers. Geophysical Research Letters, 34(1).
def prune_distance_graph( distances: dict[str, dict[str, int]], cutoff: int) -> dict[str, dict[str, int]]:
64def prune_distance_graph(distances: DistanceGraph, cutoff: int) -> DistanceGraph:
65    """
66    Prune the distance graph based on a cutoff value.
67
68    Parameters
69    ----------
70    distances : DistanceGraph
71        The graph of distances between faults.
72    cutoff : int
73        The cutoff distance in metres.
74
75    Returns
76    -------
77    DistanceGraph
78        A copy of the input distance graph, keeping only edges that are less
79        than the cutoff.
80    """
81    return {
82        fault_u: {
83            fault_v: distance
84            for fault_v, distance in neighbours_fault_u.items()
85            if distance < cutoff
86        }
87        for fault_u, neighbours_fault_u in distances.items()
88    }

Prune the distance graph based on a cutoff value.

Parameters
  • distances (DistanceGraph): The graph of distances between faults.
  • cutoff (int): The cutoff distance in metres.
Returns
  • DistanceGraph: A copy of the input distance graph, keeping only edges that are less than the cutoff.
def probability_graph( distances: dict[str, dict[str, int]], d0: float = 3, delta: float = 1) -> collections.defaultdict[str, dict[str, float]]:
 91def probability_graph(
 92    distances: DistanceGraph, d0: float = 3, delta: float = 1
 93) -> ProbabilityGraph:
 94    """
 95    Convert a graph of distances between faults into a graph of jump
 96    probablities using the Shaw-Dieterich model.
 97
 98    Parameters
 99    ----------
100    distances : DistanceGraph
101        The distance graph between faults.
102    d0 : float, optional
103        The d0 parameter for the Shaw_Dieterich model. See `shaw_dieterich_distance_model`.
104    delta : float, optional
105        The delta parameter for the Shaw_Dieterich model. See `shaw_dieterich_distance_model`.
106
107    Returns
108    -------
109    ProbabilityGraph
110        The graph with faults as vertices. Each edge (fault_u, fault_v)
111        has a log-probability -p as a weight. The log-probability -p here
112        is the negative of the log-probability a rupture propogates from
113        fault_u to fault_v, relative to the probability it propogates to
114        any of the other neighbours of fault_u.
115    """
116    probabilities_raw = {
117        fault_u: {
118            fault_v: shaw_dieterich_distance_model(distance / 1000, d0, delta)
119            for fault_v, distance in neighbours_fault_u.items()
120        }
121        for fault_u, neighbours_fault_u in distances.items()
122    }
123    probabilities_log = defaultdict(dict)
124    for fault_u, neighbours_fault_u in probabilities_raw.items():
125        normalising_constant = sum(neighbours_fault_u.values())
126        if normalising_constant == 0:
127            for fault_v, _ in neighbours_fault_u.items():
128                probabilities_log[fault_u][fault_v] = -np.log(
129                    1 / len(neighbours_fault_u)
130                )
131        for fault_v, prob in neighbours_fault_u.items():
132            probabilities_log[fault_u][fault_v] = -np.log(prob / normalising_constant)
133    return probabilities_log

Convert a graph of distances between faults into a graph of jump probablities using the Shaw-Dieterich model.

Parameters
Returns
  • ProbabilityGraph: The graph with faults as vertices. Each edge (fault_u, fault_v) has a log-probability -p as a weight. The log-probability -p here is the negative of the log-probability a rupture propogates from fault_u to fault_v, relative to the probability it propogates to any of the other neighbours of fault_u.
def probabilistic_minimum_spanning_tree( probability_graph: collections.defaultdict[str, dict[str, float]], initial_fault: str) -> dict[str, typing.Optional[str]]:
136def probabilistic_minimum_spanning_tree(
137    probability_graph: ProbabilityGraph, initial_fault: str
138) -> RuptureCausalityTree:
139    """
140    Generate a probabilistic minimum spanning tree.
141
142    The minimum spanning tree of the probability graph represents rupture
143    causality tree with the highest likelihood of occuring assuming that
144    rupture jumps are independent.
145
146    NOTE: While the overall probability of the minimum spanning tree is high,
147    the paths from the initial fault may not be the most likely.
148
149    Parameters
150    ----------
151    probability_graph : ProbabilityGraph
152        The probability graph.
153    initial_fault : str
154        The initial fault.
155
156    Returns
157    -------
158    RuptureCausalityTree
159        The probabilistic minimum spanning tree.
160    """
161    rupture_causality_tree = {initial_fault: None}
162    path_probabilities = defaultdict(lambda: np.inf)
163    path_probabilities[initial_fault] = 0
164    processed_faults = set()
165    for _ in range(len(probability_graph)):
166        current_fault = min(
167            (fault for fault in probability_graph if fault not in processed_faults),
168            key=lambda fault: path_probabilities[fault],
169        )
170        processed_faults.add(current_fault)
171        for fault_neighbour, probability in probability_graph[current_fault].items():
172            if (
173                fault_neighbour not in processed_faults
174                and probability < path_probabilities[fault_neighbour]
175            ):
176                path_probabilities[fault_neighbour] = probability
177                rupture_causality_tree[fault_neighbour] = current_fault
178    return rupture_causality_tree

Generate a probabilistic minimum spanning tree.

The minimum spanning tree of the probability graph represents rupture causality tree with the highest likelihood of occuring assuming that rupture jumps are independent.

NOTE: While the overall probability of the minimum spanning tree is high, the paths from the initial fault may not be the most likely.

Parameters
  • probability_graph (ProbabilityGraph): The probability graph.
  • initial_fault (str): The initial fault.
Returns
  • RuptureCausalityTree: The probabilistic minimum spanning tree.
def distance_between( source_a: source_modelling.sources.IsSource, source_b: source_modelling.sources.IsSource, source_a_point: numpy.ndarray, source_b_point: numpy.ndarray) -> float:
181def distance_between(
182    source_a: sources.IsSource,
183    source_b: sources.IsSource,
184    source_a_point: np.ndarray,
185    source_b_point: np.ndarray,
186) -> float:
187    global_point_a = source_a.fault_coordinates_to_wgs_depth_coordinates(source_a_point)
188    global_point_b = source_b.fault_coordinates_to_wgs_depth_coordinates(source_b_point)
189    return coordinates.distance_between_wgs_depth_coordinates(
190        global_point_a, global_point_b
191    )
def estimate_most_likely_rupture_propagation( sources_map: dict[str, source_modelling.sources.IsSource], initial_source: str, jump_impossibility_limit_distance: int = 15000) -> dict[str, typing.Optional[str]]:
194def estimate_most_likely_rupture_propagation(
195    sources_map: dict[str, sources.IsSource],
196    initial_source: str,
197    jump_impossibility_limit_distance: int = 15000,
198) -> RuptureCausalityTree:
199    distance_graph = {
200        source_a_name: {
201            source_b_name: distance_between(
202                source_a,
203                source_b,
204                *sources.closest_point_between_sources(source_a, source_b),
205            )
206            for source_b_name, source_b in sources_map.items()
207            if source_a_name != source_b_name
208        }
209        for source_a_name, source_a in sources_map.items()
210    }
211    distance_graph = prune_distance_graph(
212        distance_graph, jump_impossibility_limit_distance
213    )
214    jump_probability_graph = probability_graph(distance_graph)
215    return probabilistic_minimum_spanning_tree(
216        jump_probability_graph, initial_fault=initial_source
217    )
def jump_points_from_rupture_tree( source_map: dict[str, source_modelling.sources.IsSource], rupture_causality_tree: dict[str, typing.Optional[str]]) -> dict[str, JumpPair]:
220def jump_points_from_rupture_tree(
221    source_map: dict[str, sources.IsSource],
222    rupture_causality_tree: RuptureCausalityTree,
223) -> dict[str, JumpPair]:
224    jump_points = {}
225    for source, parent in rupture_causality_tree.items():
226        if parent is None:
227            continue
228        source_point, parent_point = sources.closest_point_between_sources(
229            source_map[source], source_map[parent]
230        )
231        jump_points[source] = JumpPair(parent_point, source_point)
232    return jump_points
def tree_nodes_in_order(tree: dict[str, str]) -> Generator[str, NoneType, NoneType]:
235def tree_nodes_in_order(
236    tree: dict[str, str],
237) -> Generator[str, None, None]:
238    """Generate faults in topologically sorted order.
239
240    Parameters
241    ----------
242    faults : list[RealisationFault]
243        List of RealisationFault objects.
244
245    Yields
246    ------
247    RealisationFault
248        The next fault in the topologically sorted order.
249    """
250    tree_child_map = defaultdict(list)
251    for cur, parent in tree.items():
252        if parent:
253            tree_child_map[parent].append(cur)
254
255    def in_order_traversal(
256        node: str,
257    ) -> Generator[str, None, None]:
258        yield node
259        for child in tree_child_map[node]:
260            yield from in_order_traversal(child)
261
262    initial_fault = next(cur for cur, parent in tree.items() if not parent)
263    yield from in_order_traversal(initial_fault)

Generate faults in topologically sorted order.

Parameters
  • faults (list[RealisationFault]): List of RealisationFault objects.
Yields
  • RealisationFault: The next fault in the topologically sorted order.