-
Notifications
You must be signed in to change notification settings - Fork 51
update fault builder for building faults from surface vertices/ 3D points #282
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
f409ad8
71c6f17
4d60155
5d9e04b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -295,29 +295,69 @@ def create_data_from_geometry( | |
| self.fault_dip = fault_dip | ||
| if fault_normal_vector is None: | ||
| if fault_frame_data.loc[ | ||
| np.logical_and(fault_frame_data["coord"] == 0, fault_frame_data["gx"].notna())].shape[0]>0: | ||
| fault_normal_vector = fault_frame_data.loc[ | ||
| np.logical_and(fault_frame_data["coord"] == 0, fault_frame_data["gx"].notna()), | ||
| ["gx", "gy", "gz"], | ||
| ].to_numpy().mean(axis=0) | ||
| elif fault_frame_data.loc[ | ||
| np.logical_and(fault_frame_data["coord"] == 0, fault_frame_data["nx"].notna())].shape[0]>0: | ||
| fault_normal_vector = fault_frame_data.loc[ | ||
| np.logical_and(fault_frame_data["coord"] == 0, fault_frame_data["nx"].notna()), | ||
| ["nx", "ny", "nz"], | ||
| ].to_numpy().mean(axis=0) | ||
|
|
||
| else: | ||
| fault_surface_pts = fault_frame_data.loc[ | ||
| np.logical_and( | ||
| fault_frame_data["coord"] == 0, | ||
| fault_frame_data["val"] == 0, | ||
| ), | ||
| ["X", "Y", "Z"], | ||
| ].to_numpy() | ||
| fault_surface_pts = fault_surface_pts[ | ||
| ~np.isnan(fault_surface_pts).any(axis=1) | ||
| ] | ||
|
|
||
| # Calculate fault strike using eigenvectors | ||
| pts = fault_trace - fault_trace.mean(axis=0) | ||
| # Calculate covariance matrix | ||
| cov_matrix = pts.T @ pts | ||
| # Get eigenvectors and eigenvalues | ||
| eigenvalues, eigenvectors = np.linalg.eigh(cov_matrix) | ||
| # Use eigenvector with largest eigenvalue as strike direction | ||
| strike_vector = eigenvectors[:, np.argmax(eigenvalues)] | ||
| strike_vector = np.append(strike_vector, 0) # Add z component | ||
| strike_vector /= np.linalg.norm(strike_vector) | ||
|
|
||
| fault_normal_vector = np.cross(strike_vector, [0, 0, 1]) | ||
| # Rotate the fault normal vector according to the fault dip | ||
| rotation_matrix = rotation(strike_vector[None, :], np.array([90 - fault_dip])) | ||
| fault_normal_vector = np.einsum("ijk,ik->ij", rotation_matrix, fault_normal_vector[None, :])[0] | ||
| if fault_surface_pts.shape[0] >= 3: | ||
| pts_3d = fault_surface_pts - fault_surface_pts.mean(axis=0) | ||
| cov_matrix = pts_3d.T @ pts_3d | ||
| eigenvalues, eigenvectors = np.linalg.eigh(cov_matrix) | ||
| # If points span a surface, use the smallest eigenvector as plane normal | ||
| if eigenvalues[-1] > 0 and (eigenvalues[1] / eigenvalues[-1]) > 0.05: | ||
| fault_normal_vector = eigenvectors[:, 0] | ||
|
Comment on lines
+325
to
+327
|
||
| else: | ||
|
||
| # Fall back to line-on-map strike logic | ||
| pts = fault_trace - fault_trace.mean(axis=0) | ||
| cov_matrix = pts.T @ pts | ||
| eigenvalues, eigenvectors = np.linalg.eigh(cov_matrix) | ||
| strike_vector = eigenvectors[:, np.argmax(eigenvalues)] | ||
| strike_vector = np.append(strike_vector, 0) # Add z component | ||
| strike_vector /= np.linalg.norm(strike_vector) | ||
|
|
||
| fault_normal_vector = np.cross(strike_vector, [0, 0, 1]) | ||
| rotation_matrix = rotation( | ||
| strike_vector[None, :], np.array([90 - fault_dip]) | ||
| ) | ||
| fault_normal_vector = np.einsum( | ||
| "ijk,ik->ij", rotation_matrix, fault_normal_vector[None, :] | ||
| )[0] | ||
|
||
| else: | ||
| # Fall back to line-on-map strike logic | ||
| pts = fault_trace - fault_trace.mean(axis=0) | ||
| cov_matrix = pts.T @ pts | ||
| eigenvalues, eigenvectors = np.linalg.eigh(cov_matrix) | ||
| strike_vector = eigenvectors[:, np.argmax(eigenvalues)] | ||
| strike_vector = np.append(strike_vector, 0) # Add z component | ||
| strike_vector /= np.linalg.norm(strike_vector) | ||
|
|
||
| fault_normal_vector = np.cross(strike_vector, [0, 0, 1]) | ||
| rotation_matrix = rotation( | ||
| strike_vector[None, :], np.array([90 - fault_dip]) | ||
| ) | ||
| fault_normal_vector = np.einsum( | ||
| "ijk,ik->ij", rotation_matrix, fault_normal_vector[None, :] | ||
| )[0] | ||
|
|
||
| if not isinstance(fault_normal_vector, np.ndarray): | ||
| fault_normal_vector = np.array(fault_normal_vector) | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new
.loc[...]existence checks are hard to read and currently formatted in a way that’s easy to misinterpret (line breaks inside the indexer, no spaces around operators, and the closing brackets are not visually aligned). Please reformat these conditions (e.g., assign the mask to a variable and use.any()/.emptychecks) to improve maintainability and reduce the chance of subtle bracket/precedence mistakes later.