pydfnWorks
python wrapper for dfnWorks
plot_fracture_orientations.py
Go to the documentation of this file.
1 """
2  :filename: plot_fracture_orientations.py
3  :synopsis: Make plots of fracture orientations for the entire network and by family
4  :version: 1.0
5  :maintainer: Jeffrey Hyman and Matthew Sweeney
6  :moduleauthor: Jeffrey Hyman <jhyman@lanl.gov>, Matthew Sweeney <msweeney2796@lanl.gov>
7 """
8 
9 import numpy as np
10 import math as m
11 import mplstereonet
12 import matplotlib.pyplot as plt
13 import random
14 
15 
16 def convert_to_trend_and_plunge(normal_vectors):
17  """ Convert Fracture normal vectors to trend and plunge
18 
19  Parameters
20  -----------
21  normal_vectors : numpy array
22  Array of fracture normal vectors (n-fractures x 3)
23 
24  Returns
25  ----------
26  trends : numpy array
27  Array of fracture trends
28  plunge : numpy array
29  Array of fracture plunge
30 
31  Notes
32  -------
33  Conversion is based on the information found at http://www.geo.cornell.edu/geology/faculty/RWA/structure-lab-manual/chapter-2.pdf
34 
35  """
36 
37  # break up normal vector components
38  n_1 = normal_vectors[:, 0]
39  n_2 = normal_vectors[:, 1]
40  n_3 = normal_vectors[:, 2]
41 
42  # calculate trends and plunges
43  trends = np.zeros(len(n_1))
44  plunges = np.zeros(len(n_1))
45  for i in range(len(n_1)):
46  plunges[i] = m.degrees(m.asin(n_3[i]))
47  if n_1[i] > 0.0:
48  trends[i] = m.degrees(m.atan(n_2[i] / n_1[i]))
49  elif n_1[i] < 0.0:
50  trends[i] = 180.0 + m.degrees(m.atan(n_2[i] / n_1[i]))
51  elif n_1[i] == 0.0 and n_2[i] >= 0.0:
52  trends[i] = 90.0
53  elif n_1[i] == 0.0 and n_2[i] < 0.0:
54  trends[i] = 270.0
55 
56  return trends, plunges
57 
58 
59 def plot_fracture_orientations(params, families, fractures):
60  """ Creates Stereonets and Rose Diagrams for Fractures by family and the entire network
61 
62  Parameters
63  ------------
64  params : dictionary
65  General dictionary of output analysis code. Contains information on number of families, number of fractures, and colors.
66 
67  families: list of fracture family dictionaries
68  Created by get_family_information
69 
70  fractures: list of fracture dictionaries
71  Created by get_fracture_information
72 
73  Returns
74  --------
75  None
76 
77  Notes
78  -------
79  PDF files are dumped into dfnGen_output_report/figures. There is one figure for all fractures (all_fracture_centers.pdf) and one per family family_{family_id}_fracture_centers.pdf.
80  """
81 
82  print("--> Plotting Rose Diagrams and Stereonets")
83 
84  # plot all families
85  # build stereonets
86  fig = plt.figure(figsize=(16, 8))
87  ax1 = fig.add_subplot(121, projection='stereonet')
88  ax1.grid()
89  ax2 = fig.add_subplot(122, projection='polar')
90 
91  if params["verbose"]:
92  print("--> Plotting Entire Network Stereonet")
93  for fam in families:
94  family_id = fam["Global Family"]
95  # Gather Fracture information
96  normal_vectors = np.zeros((fam["final_number_of_fractures"], 3))
97  for i, j in enumerate(fam["fracture list - final"]):
98  normal_vectors[i, :] = fractures[j]["normal"]
99 
100 
101  trends, plunges = convert_to_trend_and_plunge(normal_vectors)
102  # stuff for rose diagram
103  bin_edges = np.arange(-5, 366, 10)
104  num_of_trends, bin_edges = np.histogram(trends, bin_edges)
105  num_of_trends[0] += num_of_trends[-1]
106 
107  half = np.sum(np.split(num_of_trends[:-1], 2), 0)
108  two_halves = np.concatenate([half, half])
109 
110  ax1.line(plunges[0],
111  trends[0],
112  'o',
113  color=fam["color"],
114  markersize=5,
115  alpha=1.0,
116  label=f"Family \# {family_id}")
117 
118  if len(plunges) > 500:
119  print("Too Many Fractures")
120  idx = random.choices(range(len(plunges)), k=500)
121  ax1.line(plunges[idx],
122  trends[idx],
123  'o',
124  color=fam["color"],
125  markersize=5,
126  alpha=0.5)
127  else:
128  ax1.line(plunges,
129  trends,
130  'o',
131  color=fam["color"],
132  markersize=5,
133  alpha=0.5)
134  if params["verbose"]:
135  print("--> Plotting Densities")
136 
137  total_fractures = 0
138  for fam in families:
139  total_fractures += fam["final_number_of_fractures"]
140 
141  normal_vectors = np.zeros((total_fractures, 3))
142  accepted_cnt = 0
143 
144  for f in fractures:
145  if not f["removed"] and f["family"] > 0:
146  normal_vectors[accepted_cnt, :] = f["normal"]
147  accepted_cnt += 1
148 
149  trends, plunges = convert_to_trend_and_plunge(normal_vectors)
150  ax1.density_contourf(plunges, trends, measurement='lines', cmap='Greys')
151  ax1.set_title('Density contour of trends and plunges', y=1.10, fontsize=24)
152  #ax1.legend(loc="lower left", fontsize=14)
153 
154  # plot all families
155  # build Rose Diagrams
156  if params["verbose"]:
157  print("--> Plotting Entire Network Rose Diagram")
158  for fam in families:
159  family_id = fam["Global Family"]
160  # Gather Fracture information
161  normal_vectors = np.zeros((fam["final_number_of_fractures"], 3))
162  for i, j in enumerate(fam["fracture list - final"]):
163  normal_vectors[i, :] = fractures[j]["normal"]
164 
165  trends, plunges = convert_to_trend_and_plunge(normal_vectors)
166 
167  # stuff for rose diagram
168  bin_edges = np.arange(-5, 366, 10)
169  num_of_trends, bin_edges = np.histogram(trends, bin_edges)
170  num_of_trends[0] += num_of_trends[-1]
171 
172  half = np.sum(np.split(num_of_trends[:-1], 2), 0)
173  two_halves = np.concatenate([half, half])
174 
175  ax2.bar(np.deg2rad(np.arange(0, 360,10)), two_halves, \
176  width=np.deg2rad(10), bottom = 0.0, color=fam["color"], edgecolor='k')
177 
178  ax2.set_theta_zero_location('N')
179  ax2.set_theta_direction(-1)
180  ax2.set_thetagrids(np.arange(0, 360, 10), labels=np.arange(0, 360, 10))
181  ax2.set_title('Rose diagram of trends', y=1.10, fontsize=24)
182 
183  fileout = f"network_orientations.png"
184  if params["verbose"]:
185  print(f"--> Saving File {fileout}")
186 
187  plt.savefig(f"{params['output_dir']}/network/{fileout}")
188  plt.clf()
189  plt.close()
190 
191  # Plot individual families
192  for fam in families:
193  family_id = fam["Global Family"]
194  if params["verbose"]:
195  print(f"--> Working on fracture family {family_id}")
196 
197  # Gather Fracture information
198  normal_vectors = np.zeros((fam["final_number_of_fractures"], 3))
199  for i, j in enumerate(fam["fracture list - final"]):
200  normal_vectors[i, :] = fractures[j]["normal"]
201 
202  trends, plunges = convert_to_trend_and_plunge(normal_vectors)
203 
204  # stuff for rose diagram
205  bin_edges = np.arange(-5, 366, 10)
206  num_of_trends, bin_edges = np.histogram(trends, bin_edges)
207  num_of_trends[0] += num_of_trends[-1]
208 
209  half = np.sum(np.split(num_of_trends[:-1], 2), 0)
210  two_halves = np.concatenate([half, half])
211 
212  # build stereonets
213  fig = plt.figure(figsize=(16, 8))
214 
215  ax = fig.add_subplot(121, projection='stereonet')
216  ax.grid()
217  ax.line(plunges,
218  trends,
219  'o',
220  color=params["final_color"],
221  markersize=10,
222  alpha=0.7)
223  ax.density_contourf(plunges, trends, measurement='lines', cmap='Greys')
224  ax.set_title('Density contour of trends and plunges',
225  y=1.10,
226  fontsize=24)
227  #ax.grid()
228 
229  ax = fig.add_subplot(122, projection='polar')
230  ax.bar(np.deg2rad(np.arange(0, 360, 10)),
231  two_halves,
232  width=np.deg2rad(10),
233  bottom=0.0,
234  color=params["final_color"],
235  edgecolor='k')
236  ax.set_theta_zero_location('N')
237  ax.set_theta_direction(-1)
238  ax.set_thetagrids(np.arange(0, 360, 10), labels=np.arange(0, 360, 10))
239  #ax.set_rgrids(np.arange(1, two_halves.max() + 1, two_halves.max()/15), angle=0, weight='black')
240  ax.set_title('Rose diagram of trends', y=1.10, fontsize=24)
241 
242  if family_id > 0:
243  fileout = f"family_{family_id}_orienations.png"
244  else:
245  tmp = fam["Distribution"]
246  fileout = f"family_{family_id}_orienations.png"
247 
248  if params["verbose"]:
249  print(f"--> Saving File {fileout}")
250 
251  plt.savefig(f"{params['output_dir']}/family_{family_id}/{fileout}")
252  plt.clf()
253  plt.close()