Automatic azimuth set detection

Detect axial azimuth set centers and ranges with fractopo.analysis.automatic_azimuth_sets and (optionally) trim them with trim_azimuth_set_ranges. The example compares the detected centers with a rose plot of the same network.

The definition of azimuth sets based only on the orientation and lengths of fractures in an area might not reflect how they have actually been formed geologically. Consequently, any automatic set detection algorithm result should be critically evaluated during deeper analysis of fracturing in an area.

The example here tries to help with a case when fractures seem to have been, within the whole target area, clustered in orientation to few specific directions. However, not all fractures follow these specific directions and they are, consequently, considered “background” fractures. This is only one interpretation.

Initializing

from pprint import pprint
from textwrap import fill

import matplotlib.pyplot as plt
import numpy as np

# Load the KB11 network from examples/example_data.py
from example_data import KB11_NETWORK

from fractopo import Network
from fractopo.analysis.automatic_azimuth_sets import (
    automatic_azimuth_sets,
    trim_azimuth_set_ranges,
)

Input azimuths

The KB11 trace azimuths are axial values in the range [0, 180). Thus, 0° and 180° represent the same direction.

azimuths = KB11_NETWORK.trace_azimuth_array
lengths = KB11_NETWORK.trace_length_array
print(f"Number of trace azimuths: {azimuths.size}")
pprint(azimuths[:10])
Number of trace azimuths: 709
array([147.29097308,  49.07138633,  51.40545212, 149.17608927,
        57.16578588, 147.55474179, 154.23270791,  34.42233604,
        80.57016745,  42.86524189])

Detect sets automatically

Set the number of groups to find. The clustering weights each azimuth by fracture length, so longer traces have more influence on the centers. random_state keeps the example output reproducible; omit it otherwise.

n_sets = 2
centers, ranges = automatic_azimuth_sets(
    azimuths,
    lengths,
    n_sets=n_sets,
    random_state=0,
)

print(f"Detected {n_sets} sets")
print("Detected center azimuths (degrees):")
pprint(np.round(np.sort(centers), 1))

print("Detected set ranges (degrees):")
pprint(tuple(tuple(np.round(range_tuple, 1)) for range_tuple in ranges))
Detected 2 sets
Detected center azimuths (degrees):
array([ 71.5, 154.9])
Detected set ranges (degrees):
((np.float64(114.5), np.float64(21.2)), (np.float64(23.2), np.float64(113.3)))

Plot the detected centers on a rose plot

_, fig, ax = KB11_NETWORK.plot_trace_azimuth()
for center in centers:
    radians = np.deg2rad(center)
    ax.plot([radians, radians], [0, ax.get_ylim()[1]], linestyle="--", linewidth=2)
    ax.plot(
        [radians + np.pi, radians + np.pi],
        [0, ax.get_ylim()[1]],
        linestyle="--",
        linewidth=2,
    )

ax.set_title(
    fill("KB11 trace azimuths with automatically detected set centers", 30),
)
plt.show()
KB11 trace azimuths with automatically detected set centers

Narrow the detected ranges and label the remaining fractures as background

trimmed_ranges, trimmed_labels = trim_azimuth_set_ranges(
    azimuths,
    lengths,
    ranges,
    retained_length_fraction=0.8,
)
trimmed_set_names = tuple(f"{start:.0f}-{end:.0f}" for start, end in trimmed_ranges)

print("Trimmed set ranges (degrees):")
pprint(tuple(tuple(np.round(range_tuple, 1)) for range_tuple in trimmed_ranges))
print("Classified trace counts, including background fractures:")
pprint(
    {
        str(key): int(val)
        for key, val in zip(*np.unique(trimmed_labels, return_counts=True), strict=True)
    }
)
Trimmed set ranges (degrees):
((np.float64(139.9), np.float64(164.8)), (np.float64(53.2), np.float64(96.0)))
Classified trace counts, including background fractures:
{'0': 236, '1': 232, 'background': 241}

Build a new Network with the trimmed set ranges

kb11_network_automatic_sets = Network(
    trace_gdf=KB11_NETWORK.trace_gdf[["geometry"]],
    area_gdf=KB11_NETWORK.area_gdf,
    name="KB11 automatic sets",
    truncate_traces=KB11_NETWORK.truncate_traces,
    circular_target_area=KB11_NETWORK.circular_target_area,
    determine_branches_nodes=KB11_NETWORK.determine_branches_nodes,
    snap_threshold=KB11_NETWORK.snap_threshold,
    azimuth_set_names=trimmed_set_names,
    azimuth_set_ranges=trimmed_ranges,
)

pprint(kb11_network_automatic_sets.trace_azimuth_set_counts)

kb11_network_automatic_sets.plot_trace_azimuth(
    visualize_sets=True, add_abundance_order=True, append_azimuth_set_text=True
)
KB11 automatic sets
{'140-165': 236, '53-96': 240}

(AzimuthBins(bin_width=10.0, bin_locs=array([  5.,  15.,  25.,  35.,  45.,  55.,  65.,  75.,  85.,  95., 105.,
       115., 125., 135., 145., 155., 165., 175.]), bin_heights=array([ 24.65251191,   9.55228874,  24.80249385,  28.43888846,
        25.07495018,  61.0915944 , 176.85972845, 274.24150465,
        85.0267333 ,  41.60359186,  33.59424712,  21.44456621,
         9.24622342,  64.65547302, 160.87857165, 384.27467555,
       107.62579801,  30.51269213])), <Figure size 650x510 with 1 Axes>, <PolarAxes: title={'center': 'KB11\nautomatic\nsets'}>)

Total running time of the script: (0 minutes 4.228 seconds)

Gallery generated by Sphinx-Gallery