xslope 0.1.12__py3-none-any.whl → 0.1.14__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
xslope/plot_seep.py CHANGED
@@ -10,7 +10,7 @@ def plot_seep_data(seep_data, figsize=(14, 6), show_nodes=False, show_bc=False,
10
10
  Supports both triangular and quadrilateral elements.
11
11
 
12
12
  Args:
13
- seep_data: Dictionary containing seepage data from import_seep2d
13
+ seep_data: Dictionary containing seep data from import_seep2d
14
14
  show_nodes: If True, plot node points
15
15
  show_bc: If True, plot boundary condition nodes
16
16
  label_elements: If True, label each element with its number at its centroid
@@ -206,11 +206,11 @@ def plot_seep_data(seep_data, figsize=(14, 6), show_nodes=False, show_bc=False,
206
206
  num_triangles = np.sum(element_types == 3)
207
207
  num_quads = np.sum(element_types == 4)
208
208
  if num_triangles > 0 and num_quads > 0:
209
- title = f"SEEP2D Mesh with Material Zones ({num_triangles} triangles, {num_quads} quads)"
209
+ title = f"Finite Element Mesh with Material Zones ({num_triangles} triangles, {num_quads} quads)"
210
210
  elif num_quads > 0:
211
- title = f"SEEP2D Mesh with Material Zones ({num_quads} quadrilaterals)"
211
+ title = f"Finite Element Mesh with Material Zones ({num_quads} quadrilaterals)"
212
212
  else:
213
- title = f"SEEP2D Mesh with Material Zones ({num_triangles} triangles)"
213
+ title = f"Finite Element Mesh with Material Zones ({num_triangles} triangles)"
214
214
 
215
215
  ax.set_title(title)
216
216
  # plt.subplots_adjust(bottom=0.2) # Add vertical cushion
@@ -225,9 +225,9 @@ def plot_seep_data(seep_data, figsize=(14, 6), show_nodes=False, show_bc=False,
225
225
 
226
226
  def plot_seep_solution(seep_data, solution, figsize=(14, 6), levels=20, base_mat=1, fill_contours=True, phreatic=True, alpha=0.4, pad_frac=0.05, mesh=True, variable="head", vectors=False, vector_scale=0.05, flowlines=True, save_png=False, dpi=300):
227
227
  """
228
- Plot seepage analysis results including head contours, flowlines, and phreatic surface.
228
+ Plot seep analysis results including head contours, flowlines, and phreatic surface.
229
229
 
230
- This function visualizes the results of a seepage analysis by plotting contours of various
230
+ This function visualizes the results of a seep analysis by plotting contours of various
231
231
  nodal variables (head, pore pressure, velocity magnitude, or gradient magnitude). When
232
232
  plotting head, flowlines are also overlaid. The plot properly handles mesh aspect ratios
233
233
  and supports both linear and quadratic triangular and quadrilateral elements.
@@ -235,7 +235,7 @@ def plot_seep_solution(seep_data, solution, figsize=(14, 6), levels=20, base_mat
235
235
  Parameters:
236
236
  -----------
237
237
  seep_data : dict
238
- Dictionary containing seepage mesh data from import_seep2d. Required keys include:
238
+ Dictionary containing seep mesh data from import_seep2d. Required keys include:
239
239
  'nodes', 'elements', 'element_materials', 'element_types' (optional), and
240
240
  'k1_by_mat' (optional, for flowline calculation).
241
241
  solution : dict
xslope/seep.py CHANGED
@@ -24,13 +24,13 @@ def build_seep_data(mesh, slope_data):
24
24
  Build a seep_data dictionary from a mesh and data dictionary.
25
25
 
26
26
  This function takes a mesh dictionary (from build_mesh_from_polygons) and a data dictionary
27
- (from load_slope_data) and constructs a seep_data dictionary suitable for seepage analysis.
27
+ (from load_slope_data) and constructs a seep_data dictionary suitable for seep analysis.
28
28
 
29
29
  The function:
30
30
  1. Extracts mesh information (nodes, elements, element types, element materials)
31
31
  2. Builds material property arrays (k1, k2, alpha, kr0, h0) from the materials table
32
32
  3. Constructs boundary conditions by finding nodes that intersect with specified head
33
- and seepage face lines from the data dictionary
33
+ and seep face lines from the data dictionary
34
34
 
35
35
  Parameters:
36
36
  mesh (dict): Mesh dictionary from build_mesh_from_polygons containing:
@@ -122,7 +122,7 @@ def build_seep_data(mesh, slope_data):
122
122
  bc_type[i] = 1 # Fixed head
123
123
  bc_values[i] = head_value
124
124
 
125
- # Process seepage face (exit face) boundary conditions
125
+ # Process seep face (exit face) boundary conditions
126
126
  exit_face_coords = seepage_bc.get("exit_face", [])
127
127
  if len(exit_face_coords) >= 2:
128
128
  # Create LineString from exit face coordinates
@@ -286,7 +286,7 @@ def import_seep2d(filepath):
286
286
 
287
287
  def solve_confined(nodes, elements, bc_type, dirichlet_bcs, k1_vals, k2_vals, angles=None, element_types=None):
288
288
  """
289
- FEM solver for confined seepage with anisotropic conductivity.
289
+ FEM solver for confined seep with anisotropic conductivity.
290
290
  Supports triangular and quadrilateral elements with both linear and quadratic shape functions.
291
291
 
292
292
  Parameters:
@@ -1989,10 +1989,10 @@ def quad4_stiffness_matrix(nodes_elem, Kmat):
1989
1989
 
1990
1990
  def run_seepage_analysis(seep_data, tol=1e-6):
1991
1991
  """
1992
- Standalone function to run seepage analysis.
1992
+ Standalone function to run seep analysis.
1993
1993
 
1994
1994
  Args:
1995
- seep_data: Dictionary containing all the seepage data
1995
+ seep_data: Dictionary containing all the seep data
1996
1996
 
1997
1997
  Returns:
1998
1998
  Dictionary containing solution results with the following keys:
@@ -2023,7 +2023,7 @@ def run_seepage_analysis(seep_data, tol=1e-6):
2023
2023
  # Determine if unconfined flow
2024
2024
  is_unconfined = np.any(bc_type == 2)
2025
2025
  flow_type = "unconfined" if is_unconfined else "confined"
2026
- print(f"Solving {flow_type.upper()} seepage problem...")
2026
+ print(f"Solving {flow_type.upper()} seep problem...")
2027
2027
  print("Number of fixed-head nodes:", np.sum(bc_type == 1))
2028
2028
  print("Number of exit face nodes:", np.sum(bc_type == 2))
2029
2029
 
@@ -2110,7 +2110,7 @@ def export_seep_solution(seep_data, solution, filename):
2110
2110
 
2111
2111
  Args:
2112
2112
  filename: Path to the output CSV file
2113
- seep_data: Dictionary containing seepage data
2113
+ seep_data: Dictionary containing seep data
2114
2114
  solution: Dictionary containing solution results from run_seepage_analysis
2115
2115
  """
2116
2116
  import pandas as pd
@@ -2140,7 +2140,7 @@ def print_seep_data_diagnostics(seep_data):
2140
2140
  Diagnostic function to print out the contents of seep_data after loading.
2141
2141
 
2142
2142
  Args:
2143
- seep_data: Dictionary containing seepage data
2143
+ seep_data: Dictionary containing seep data
2144
2144
  """
2145
2145
  print("\n" + "="*60)
2146
2146
  print("SEEP DATA DIAGNOSTICS")
xslope/slice.py CHANGED
@@ -122,7 +122,7 @@ def get_profile_layer_y_coordinates(x_coords, profile_lines):
122
122
 
123
123
  Parameters:
124
124
  x_coords (array-like): X-coordinates to evaluate
125
- profile_lines (list): List of profile layer lines
125
+ profile_lines (list): List of profile line dicts, each with 'coords' key containing coordinate tuples
126
126
 
127
127
  Returns:
128
128
  list: List of arrays, each containing y-coordinates for a profile layer
@@ -131,7 +131,7 @@ def get_profile_layer_y_coordinates(x_coords, profile_lines):
131
131
  layer_y_coords = []
132
132
 
133
133
  for line in profile_lines:
134
- line_coords = np.array(line)
134
+ line_coords = np.array(line['coords'])
135
135
  line_x = line_coords[:, 0]
136
136
  line_y = line_coords[:, 1]
137
137
 
@@ -534,7 +534,7 @@ def generate_slices(slope_data, circle=None, non_circ=None, num_slices=40, debug
534
534
 
535
535
  # Vectorized approach for profile line points
536
536
  for line in profile_lines:
537
- line_coords = np.array(line)
537
+ line_coords = np.array(line['coords'])
538
538
  x_coords = line_coords[:, 0]
539
539
  y_coords = line_coords[:, 1]
540
540
 
@@ -591,7 +591,7 @@ def generate_slices(slope_data, circle=None, non_circ=None, num_slices=40, debug
591
591
  # For circular failure surfaces, we can use a more efficient approach
592
592
  # by creating a dense circle representation and finding intersections
593
593
  for line in profile_lines:
594
- line_geom = LineString(line)
594
+ line_geom = LineString(line['coords'])
595
595
  # Create a dense circle representation for intersection
596
596
  theta_range = np.linspace(np.pi, 2 * np.pi, 200)
597
597
  circle_coords = [(Xo + R * np.cos(t), Yo + R * np.sin(t)) for t in theta_range]
@@ -608,7 +608,7 @@ def generate_slices(slope_data, circle=None, non_circ=None, num_slices=40, debug
608
608
  else:
609
609
  # For non-circular failure surfaces, use the original approach
610
610
  for i in range(len(profile_lines)):
611
- intersection = LineString(profile_lines[i]).intersection(clipped_surface)
611
+ intersection = LineString(profile_lines[i]['coords']).intersection(clipped_surface)
612
612
  if not intersection.is_empty:
613
613
  if hasattr(intersection, 'x'):
614
614
  fixed_xs.add(intersection.x)
@@ -775,12 +775,12 @@ def generate_slices(slope_data, circle=None, non_circ=None, num_slices=40, debug
775
775
  sum_gam_h_y = 0 # for calculating center of gravity of slice
776
776
  sum_gam_h = 0 # ditto
777
777
 
778
- for mat_index, layer_y in enumerate(profile_y_coords):
778
+ for profile_idx, layer_y in enumerate(profile_y_coords):
779
779
  layer_top_y = layer_y[i]
780
780
 
781
781
  # Bottom: highest of all other profile lines at x, or failure surface
782
782
  layer_bot_y = y_cb # Start with failure surface as default bottom
783
- for j in range(mat_index + 1, len(profile_y_coords)):
783
+ for j in range(profile_idx + 1, len(profile_y_coords)):
784
784
  next_y = profile_y_coords[j][i]
785
785
  if not np.isnan(next_y) and next_y > layer_bot_y:
786
786
  # Take the highest of the lower profile lines
@@ -792,14 +792,36 @@ def generate_slices(slope_data, circle=None, non_circ=None, num_slices=40, debug
792
792
  overlap_top = min(y_ct, layer_top_y)
793
793
  overlap_bot = max(y_cb, layer_bot_y)
794
794
  h = max(0, overlap_top - overlap_bot)
795
+
796
+ # Get material index from mat_id in profile line (already 0-based)
797
+ mat_id = profile_lines[profile_idx].get('mat_id')
798
+ if mat_id is not None and 0 <= mat_id < len(materials):
799
+ mat_index = mat_id
800
+ else:
801
+ # Fallback to profile index if no mat_id or out of range
802
+ mat_index = profile_idx
803
+
795
804
  sum_gam_h_y += h * materials[mat_index]['gamma'] * (overlap_top + overlap_bot) / 2
796
805
  sum_gam_h += h * materials[mat_index]['gamma']
797
806
 
798
807
  heights.append(h)
808
+
809
+ # Get material index for soil weight calculation (already 0-based)
810
+ mat_id = profile_lines[profile_idx].get('mat_id')
811
+ if mat_id is not None and 0 <= mat_id < len(materials):
812
+ mat_index = mat_id
813
+ else:
814
+ mat_index = profile_idx
815
+
799
816
  soil_weight += h * materials[mat_index]['gamma'] * dx
800
817
 
801
818
  if h > 0:
802
- base_material_idx = mat_index
819
+ # Get material index for base material (already 0-based)
820
+ mat_id = profile_lines[profile_idx].get('mat_id')
821
+ if mat_id is not None and 0 <= mat_id < len(materials):
822
+ base_material_idx = mat_id
823
+ else:
824
+ base_material_idx = profile_idx
803
825
 
804
826
  # Center of gravity
805
827
  y_cg = (sum_gam_h_y) / sum_gam_h if sum_gam_h > 0 else None
@@ -926,7 +948,7 @@ def generate_slices(slope_data, circle=None, non_circ=None, num_slices=40, debug
926
948
  else:
927
949
  u = 0
928
950
 
929
- # Check for second seepage solution (rapid drawdown)
951
+ # Check for second seep solution (rapid drawdown)
930
952
  if 'seep_u2' in data:
931
953
  seep_mesh = data['seep_mesh']
932
954
  seep_u2 = data['seep_u2']
xslope/solve.py CHANGED
@@ -768,13 +768,17 @@ def spencer(slice_df, tol=1e-4, max_iter = 100, debug_level=0):
768
768
  """Compute Q and y_Q for given F and theta values."""
769
769
  # Equation (24): m_alpha
770
770
  ma = 1 / (np.cos(alpha - theta_rad) + np.sin(alpha - theta_rad) * tan_p / F)
771
-
771
+
772
772
  # Equation (23): Q
773
773
  Q = (- Fv * sin_a - Fh * cos_a - (c / F) * dl + (Fv * cos_a - Fh * sin_a + u * dl) * tan_p / F) * ma
774
-
775
- # Equation (26): y_Q
776
- y_q = y_b + Mo / (Q * np.cos(theta_rad))
777
-
774
+
775
+ # Equation (26): y_Q with numerical safeguard
776
+ # Add small epsilon to prevent divide-by-zero when Q * cos(theta) is very small
777
+ Q_cos_theta = Q * np.cos(theta_rad)
778
+ eps = 1e-10
779
+ safe_denom = np.where(np.abs(Q_cos_theta) < eps, eps * np.sign(Q_cos_theta + eps), Q_cos_theta)
780
+ y_q = y_b + Mo / safe_denom
781
+
778
782
  return Q, y_q
779
783
 
780
784
  def compute_residuals(F, theta_rad):
@@ -814,10 +818,18 @@ def spencer(slice_df, tol=1e-4, max_iter = 100, debug_level=0):
814
818
  dC3_dtheta = sin_alpha_theta # Equation (55)
815
819
  dC4_dtheta = -cos_alpha_theta * tan_p # Equation (56)
816
820
  dQ_dtheta = (-1 / denom_Q**2) * (C1 + C2 / F) * (dC3_dtheta + dC4_dtheta / F)
817
-
821
+
818
822
  # Partial derivatives of y_Q (Equations 59-60)
819
- dyQ_dF = (-1 / (Q * cos_theta)**2) * Mo * dQ_dF * cos_theta
820
- dyQ_dtheta = (-1 / (Q * cos_theta)**2) * Mo * (dQ_dtheta * cos_theta - Q * sin_theta)
823
+ # Add numerical safeguard to prevent divide-by-zero when Q * cos_theta is very small
824
+ Q_cos_theta = Q * cos_theta
825
+ eps = 1e-10 # Small epsilon to prevent exact zero division
826
+
827
+ # Use np.where to handle element-wise operations safely
828
+ # Where |Q * cos_theta| < eps, set derivatives to a large value to signal ill-conditioning
829
+ safe_denom = np.where(np.abs(Q_cos_theta) < eps, eps * np.sign(Q_cos_theta + eps), Q_cos_theta)
830
+
831
+ dyQ_dF = (-1 / safe_denom**2) * Mo * dQ_dF * cos_theta
832
+ dyQ_dtheta = (-1 / safe_denom**2) * Mo * (dQ_dtheta * cos_theta - Q * sin_theta)
821
833
 
822
834
  # First-order partial derivatives of R1 (Equations 35-36)
823
835
  dR1_dF = np.sum(dQ_dF)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: xslope
3
- Version: 0.1.12
3
+ Version: 0.1.14
4
4
  Summary: Slope stability analysis (limit equilibrium and FEM) in Python.
5
5
  Author: Norman L. Jones
6
6
  Project-URL: Homepage, https://github.com/njones61/xslope
@@ -0,0 +1,21 @@
1
+ xslope/__init__.py,sha256=ZygAIkX6Nbjag1czWdQa-yP-GM1mBE_9ss21Xh__JFc,34
2
+ xslope/_version.py,sha256=ixr9Migpjqmt6Ouahz7Nl34FCcoYvrOjP-g-zkweVyw,51
3
+ xslope/advanced.py,sha256=-eL4-I36j6DfsheaGbOtclUHTFkkSn1k5k4YF9tOCvU,18278
4
+ xslope/fem.py,sha256=x3BWHqxqJd-K78NzLquqr8eLXWVdFMzyA2cQXTNqLTk,115719
5
+ xslope/fileio.py,sha256=BajiNljdwMMF2u2SqLpObZ-rimhgm3XyaFo14N6vZJI,38180
6
+ xslope/global_config.py,sha256=Cj8mbPidIuj5Ty-5cZM-c8H12kNvyHsk5_ofNGez-3M,2253
7
+ xslope/mesh copy.py,sha256=qtMH1yKFgHM4kNuIrxco7tKV86R3Dbf7Nok5j6MtlLY,131013
8
+ xslope/mesh.py,sha256=Kn2Um1wz50EA4xd5xpxgxxegxWOlpL3Brks0_8JLWFQ,139382
9
+ xslope/plot.py,sha256=35_qwMqA-qDn_GKB24ff5tYBYDvcR605FUJ5IZJnIYc,86534
10
+ xslope/plot_fem.py,sha256=z2FLPbIx6yNIKYcgC-LcZz2jfx0WLYFL3xJgNvQ1t-c,69488
11
+ xslope/plot_seep.py,sha256=0s8R76c_34sUwA-L-_71Kt5BZSaIFbs-xV56M69XRU4,32960
12
+ xslope/search.py,sha256=dvgKn8JCobuvyD7fClF5lcbeHESCvV8gZ_U_lQnYRok,16867
13
+ xslope/seep.py,sha256=AlZvp1kSPBfbNkpSZUNXqaefIRuT6BZPxhadmb5DFsk,93270
14
+ xslope/slice.py,sha256=5zUl3qSF4PMt2d9y6jAX881aTcUWVMeVN87wt-BbIao,46380
15
+ xslope/solve.py,sha256=_whlUuSsDqqe0wtgLowucc9dXmC8Q6J9zbViTQOmo-I,49337
16
+ xslope-0.1.14.dist-info/LICENSE,sha256=NU5J88FUai_4Ixu5DqOQukA-gcXAyX8pXLhIg8OB3AM,10969
17
+ xslope-0.1.14.dist-info/METADATA,sha256=gbzPTLbcQRkZwFIcYajPmG6JKVaH-k_JJe0iKfgJn58,2028
18
+ xslope-0.1.14.dist-info/NOTICE,sha256=E-sbN0MWwvJC27Z-2_G4VUHIx4IsfvLDTmOstvY4-OQ,530
19
+ xslope-0.1.14.dist-info/WHEEL,sha256=beeZ86-EfXScwlR_HKu4SllMC9wUEj_8Z_4FJ3egI2w,91
20
+ xslope-0.1.14.dist-info/top_level.txt,sha256=5qHbWJ1R2pdTNIainFyrVtFk8R1tRcwIn0kzTuwuV1Q,7
21
+ xslope-0.1.14.dist-info/RECORD,,
@@ -1,21 +0,0 @@
1
- xslope/__init__.py,sha256=ZygAIkX6Nbjag1czWdQa-yP-GM1mBE_9ss21Xh__JFc,34
2
- xslope/_version.py,sha256=q5MF9vedBklo3vj52_gwvihlwfAGwilLmjxYlgKI-zM,51
3
- xslope/advanced.py,sha256=35k_ekQJ-mQJ1wHnoIHMc5TVfYn-2EPCJyHz827Cpbc,18281
4
- xslope/fem.py,sha256=K4_Pq06HFBpRkN3JwtXF2zw_AMnCkXvj0ggrlRFMQQw,115731
5
- xslope/fileio.py,sha256=nxpD5dnkWjSVnby9jsQgHrxikfAsL3KOUehiw8sMb-Q,31783
6
- xslope/global_config.py,sha256=Cj8mbPidIuj5Ty-5cZM-c8H12kNvyHsk5_ofNGez-3M,2253
7
- xslope/mesh copy.py,sha256=qtMH1yKFgHM4kNuIrxco7tKV86R3Dbf7Nok5j6MtlLY,131013
8
- xslope/mesh.py,sha256=dY_G7ctgOroUSv2oUuM-ZQOnKAEJ1dIq6FTU_JLWEqs,136816
9
- xslope/plot.py,sha256=SuwC6quvkdQ66sVzSam3USRoEUC99s-iS1_0j-V7k8A,81401
10
- xslope/plot_fem.py,sha256=z2FLPbIx6yNIKYcgC-LcZz2jfx0WLYFL3xJgNvQ1t-c,69488
11
- xslope/plot_seep.py,sha256=wEmK3FCYjkqtnNqy1nhFLe72GBVrdhcR0CkgxlLaAjc,32948
12
- xslope/search.py,sha256=dvgKn8JCobuvyD7fClF5lcbeHESCvV8gZ_U_lQnYRok,16867
13
- xslope/seep.py,sha256=Xc3C3HcAOXBdsVStJTPo2AxY4QjQKjMcfTuJGYCazMY,93300
14
- xslope/slice.py,sha256=QHawTk7XPLziHoN_ZS0jrEPYKlhPT62caUc_q-_EGjs,45236
15
- xslope/solve.py,sha256=u_sBjtsj1EeZtgSEmo80tEWoRrrMgvRjmi6P0ODO1lU,48645
16
- xslope-0.1.12.dist-info/LICENSE,sha256=NU5J88FUai_4Ixu5DqOQukA-gcXAyX8pXLhIg8OB3AM,10969
17
- xslope-0.1.12.dist-info/METADATA,sha256=BPz97uzKVuFiS9_RqS5r6iYd4O0W4IacO-CyZEsUaHY,2028
18
- xslope-0.1.12.dist-info/NOTICE,sha256=E-sbN0MWwvJC27Z-2_G4VUHIx4IsfvLDTmOstvY4-OQ,530
19
- xslope-0.1.12.dist-info/WHEEL,sha256=beeZ86-EfXScwlR_HKu4SllMC9wUEj_8Z_4FJ3egI2w,91
20
- xslope-0.1.12.dist-info/top_level.txt,sha256=5qHbWJ1R2pdTNIainFyrVtFk8R1tRcwIn0kzTuwuV1Q,7
21
- xslope-0.1.12.dist-info/RECORD,,