xslope 0.1.11__py3-none-any.whl → 0.1.13__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/_version.py +1 -1
- xslope/advanced.py +3 -3
- xslope/fem.py +3 -3
- xslope/fileio.py +463 -206
- xslope/mesh.py +189 -31
- xslope/plot.py +775 -75
- xslope/plot_fem.py +1 -69
- xslope/plot_seep.py +17 -75
- xslope/seep.py +20 -15
- xslope/slice.py +31 -9
- xslope/solve.py +20 -8
- {xslope-0.1.11.dist-info → xslope-0.1.13.dist-info}/METADATA +1 -1
- xslope-0.1.13.dist-info/RECORD +21 -0
- xslope-0.1.11.dist-info/RECORD +0 -21
- {xslope-0.1.11.dist-info → xslope-0.1.13.dist-info}/LICENSE +0 -0
- {xslope-0.1.11.dist-info → xslope-0.1.13.dist-info}/NOTICE +0 -0
- {xslope-0.1.11.dist-info → xslope-0.1.13.dist-info}/WHEEL +0 -0
- {xslope-0.1.11.dist-info → xslope-0.1.13.dist-info}/top_level.txt +0 -0
xslope/plot_fem.py
CHANGED
|
@@ -22,7 +22,7 @@ from matplotlib.colors import LinearSegmentedColormap
|
|
|
22
22
|
from matplotlib.patches import Polygon
|
|
23
23
|
|
|
24
24
|
|
|
25
|
-
def plot_fem_data(fem_data, figsize=(14, 6), show_nodes=False, show_bc=True,
|
|
25
|
+
def plot_fem_data(fem_data, figsize=(14, 6), show_nodes=False, show_bc=True,
|
|
26
26
|
label_elements=False, label_nodes=False, alpha=0.4, bc_symbol_size=0.03, save_png=False, dpi=300):
|
|
27
27
|
"""
|
|
28
28
|
Plots a FEM mesh colored by material zone with boundary conditions displayed.
|
|
@@ -32,7 +32,6 @@ def plot_fem_data(fem_data, figsize=(14, 6), show_nodes=False, show_bc=True, mat
|
|
|
32
32
|
figsize: Figure size
|
|
33
33
|
show_nodes: If True, plot node points
|
|
34
34
|
show_bc: If True, plot boundary condition symbols
|
|
35
|
-
material_table: If True, show material table
|
|
36
35
|
label_elements: If True, label each element with its number at its centroid
|
|
37
36
|
label_nodes: If True, label each node with its number just above and to the right
|
|
38
37
|
alpha: Transparency for element faces
|
|
@@ -245,10 +244,6 @@ def plot_fem_data(fem_data, figsize=(14, 6), show_nodes=False, show_bc=True, mat
|
|
|
245
244
|
else:
|
|
246
245
|
title = f"FEM Mesh with Material Zones ({num_triangles} triangles)"
|
|
247
246
|
|
|
248
|
-
# Place the table in the upper left
|
|
249
|
-
if material_table:
|
|
250
|
-
_plot_fem_material_table(ax, fem_data, xloc=0.3, yloc=1.1) # upper left
|
|
251
|
-
|
|
252
247
|
ax.set_title(title)
|
|
253
248
|
plt.tight_layout()
|
|
254
249
|
|
|
@@ -366,69 +361,6 @@ def _plot_boundary_conditions(ax, nodes, bc_type, bc_values, legend_handles, bc_
|
|
|
366
361
|
markersize=8, label='Applied Force (bc_type=4)')
|
|
367
362
|
)
|
|
368
363
|
|
|
369
|
-
|
|
370
|
-
def _plot_fem_material_table(ax, fem_data, xloc=0.6, yloc=0.7):
|
|
371
|
-
"""
|
|
372
|
-
Adds a FEM material properties table to the plot.
|
|
373
|
-
|
|
374
|
-
Parameters:
|
|
375
|
-
ax: matplotlib Axes object
|
|
376
|
-
fem_data: Dictionary containing FEM data with material properties
|
|
377
|
-
xloc: x-location of table (0-1)
|
|
378
|
-
yloc: y-location of table (0-1)
|
|
379
|
-
|
|
380
|
-
Returns:
|
|
381
|
-
None
|
|
382
|
-
"""
|
|
383
|
-
# Extract material properties from fem_data
|
|
384
|
-
c_by_mat = fem_data.get("c_by_mat")
|
|
385
|
-
phi_by_mat = fem_data.get("phi_by_mat")
|
|
386
|
-
E_by_mat = fem_data.get("E_by_mat")
|
|
387
|
-
nu_by_mat = fem_data.get("nu_by_mat")
|
|
388
|
-
gamma_by_mat = fem_data.get("gamma_by_mat")
|
|
389
|
-
material_names = fem_data.get("material_names", [])
|
|
390
|
-
|
|
391
|
-
if c_by_mat is None or len(c_by_mat) == 0:
|
|
392
|
-
return
|
|
393
|
-
|
|
394
|
-
# Column headers for FEM properties
|
|
395
|
-
col_labels = ["Mat", "Name", "γ", "c", "φ", "E", "ν"]
|
|
396
|
-
|
|
397
|
-
# Build table rows
|
|
398
|
-
table_data = []
|
|
399
|
-
for idx in range(len(c_by_mat)):
|
|
400
|
-
c = c_by_mat[idx]
|
|
401
|
-
phi = phi_by_mat[idx] if phi_by_mat is not None else 0.0
|
|
402
|
-
E = E_by_mat[idx] if E_by_mat is not None else 0.0
|
|
403
|
-
nu = nu_by_mat[idx] if nu_by_mat is not None else 0.0
|
|
404
|
-
gamma = gamma_by_mat[idx] if gamma_by_mat is not None else 0.0
|
|
405
|
-
|
|
406
|
-
# Get material name, use default if not available
|
|
407
|
-
material_name = material_names[idx] if idx < len(material_names) else f"Material {idx+1}"
|
|
408
|
-
|
|
409
|
-
# Format values with appropriate precision
|
|
410
|
-
row = [
|
|
411
|
-
idx + 1, # Material number (1-based)
|
|
412
|
-
material_name, # Material name
|
|
413
|
-
f"{gamma:.1f}", # unit weight
|
|
414
|
-
f"{c:.1f}", # cohesion
|
|
415
|
-
f"{phi:.1f}", # friction angle
|
|
416
|
-
f"{E:.0f}", # Young's modulus
|
|
417
|
-
f"{nu:.2f}" # Poisson's ratio
|
|
418
|
-
]
|
|
419
|
-
table_data.append(row)
|
|
420
|
-
|
|
421
|
-
# Add the table
|
|
422
|
-
table = ax.table(cellText=table_data,
|
|
423
|
-
colLabels=col_labels,
|
|
424
|
-
loc='upper right',
|
|
425
|
-
colLoc='center',
|
|
426
|
-
cellLoc='center',
|
|
427
|
-
bbox=[xloc, yloc, 0.45, 0.25]) # Increased width to accommodate name column
|
|
428
|
-
table.auto_set_font_size(False)
|
|
429
|
-
table.set_fontsize(8)
|
|
430
|
-
|
|
431
|
-
|
|
432
364
|
def plot_fem_results(fem_data, solution, plot_type='displacement', deform_scale=None,
|
|
433
365
|
show_mesh=True, show_reinforcement=True, figsize=(12, 8), label_elements=False,
|
|
434
366
|
plot_nodes=False, plot_elements=False, plot_boundary=True, displacement_tolerance=0.5,
|
xslope/plot_seep.py
CHANGED
|
@@ -4,16 +4,15 @@ from matplotlib.ticker import MaxNLocator
|
|
|
4
4
|
import numpy as np
|
|
5
5
|
|
|
6
6
|
|
|
7
|
-
def plot_seep_data(seep_data, figsize=(14, 6), show_nodes=False, show_bc=False,
|
|
7
|
+
def plot_seep_data(seep_data, figsize=(14, 6), show_nodes=False, show_bc=False, label_elements=False, label_nodes=False, alpha=0.4, save_png=False, dpi=300):
|
|
8
8
|
"""
|
|
9
9
|
Plots a mesh colored by material zone.
|
|
10
10
|
Supports both triangular and quadrilateral elements.
|
|
11
11
|
|
|
12
12
|
Args:
|
|
13
|
-
seep_data: Dictionary containing
|
|
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
|
-
material_table: If True, show material table
|
|
17
16
|
label_elements: If True, label each element with its number at its centroid
|
|
18
17
|
label_nodes: If True, label each node with its number just above and to the right
|
|
19
18
|
"""
|
|
@@ -181,10 +180,10 @@ def plot_seep_data(seep_data, figsize=(14, 6), show_nodes=False, show_bc=False,
|
|
|
181
180
|
bc1 = nodes[bc_type == 1]
|
|
182
181
|
bc2 = nodes[bc_type == 2]
|
|
183
182
|
if len(bc1) > 0:
|
|
184
|
-
h1, = ax.plot(bc1[:, 0], bc1[:, 1], '
|
|
183
|
+
h1, = ax.plot(bc1[:, 0], bc1[:, 1], 'bs', label="Fixed Head (bc_type=1)")
|
|
185
184
|
legend_handles.append(h1)
|
|
186
185
|
if len(bc2) > 0:
|
|
187
|
-
h2, = ax.plot(bc2[:, 0], bc2[:, 1], '
|
|
186
|
+
h2, = ax.plot(bc2[:, 0], bc2[:, 1], 'ro', label="Exit Face (bc_type=2)")
|
|
188
187
|
legend_handles.append(h2)
|
|
189
188
|
|
|
190
189
|
# Single combined legend outside the plot
|
|
@@ -196,20 +195,22 @@ def plot_seep_data(seep_data, figsize=(14, 6), show_nodes=False, show_bc=False,
|
|
|
196
195
|
frameon=False
|
|
197
196
|
)
|
|
198
197
|
ax.set_aspect("equal")
|
|
198
|
+
|
|
199
|
+
# Add a bit of headroom so the mesh/BC markers don't touch the top border
|
|
200
|
+
y0, y1 = ax.get_ylim()
|
|
201
|
+
if y1 > y0:
|
|
202
|
+
pad = 0.05 * (y1 - y0)
|
|
203
|
+
ax.set_ylim(y0, y1 + pad)
|
|
199
204
|
|
|
200
205
|
# Count element types for title
|
|
201
206
|
num_triangles = np.sum(element_types == 3)
|
|
202
207
|
num_quads = np.sum(element_types == 4)
|
|
203
208
|
if num_triangles > 0 and num_quads > 0:
|
|
204
|
-
title = f"
|
|
209
|
+
title = f"Finite Element Mesh with Material Zones ({num_triangles} triangles, {num_quads} quads)"
|
|
205
210
|
elif num_quads > 0:
|
|
206
|
-
title = f"
|
|
211
|
+
title = f"Finite Element Mesh with Material Zones ({num_quads} quadrilaterals)"
|
|
207
212
|
else:
|
|
208
|
-
title = f"
|
|
209
|
-
|
|
210
|
-
# Place the table in the upper left
|
|
211
|
-
if material_table:
|
|
212
|
-
plot_seep_material_table(ax, seep_data, xloc=0.3, yloc=1.1) # upper left
|
|
213
|
+
title = f"Finite Element Mesh with Material Zones ({num_triangles} triangles)"
|
|
213
214
|
|
|
214
215
|
ax.set_title(title)
|
|
215
216
|
# plt.subplots_adjust(bottom=0.2) # Add vertical cushion
|
|
@@ -224,9 +225,9 @@ def plot_seep_data(seep_data, figsize=(14, 6), show_nodes=False, show_bc=False,
|
|
|
224
225
|
|
|
225
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):
|
|
226
227
|
"""
|
|
227
|
-
Plot
|
|
228
|
+
Plot seep analysis results including head contours, flowlines, and phreatic surface.
|
|
228
229
|
|
|
229
|
-
This function visualizes the results of a
|
|
230
|
+
This function visualizes the results of a seep analysis by plotting contours of various
|
|
230
231
|
nodal variables (head, pore pressure, velocity magnitude, or gradient magnitude). When
|
|
231
232
|
plotting head, flowlines are also overlaid. The plot properly handles mesh aspect ratios
|
|
232
233
|
and supports both linear and quadratic triangular and quadrilateral elements.
|
|
@@ -234,7 +235,7 @@ def plot_seep_solution(seep_data, solution, figsize=(14, 6), levels=20, base_mat
|
|
|
234
235
|
Parameters:
|
|
235
236
|
-----------
|
|
236
237
|
seep_data : dict
|
|
237
|
-
Dictionary containing
|
|
238
|
+
Dictionary containing seep mesh data from import_seep2d. Required keys include:
|
|
238
239
|
'nodes', 'elements', 'element_materials', 'element_types' (optional), and
|
|
239
240
|
'k1_by_mat' (optional, for flowline calculation).
|
|
240
241
|
solution : dict
|
|
@@ -592,66 +593,7 @@ def plot_seep_solution(seep_data, solution, figsize=(14, 6), levels=20, base_mat
|
|
|
592
593
|
plt.show()
|
|
593
594
|
|
|
594
595
|
|
|
595
|
-
|
|
596
|
-
"""
|
|
597
|
-
Adds a seepage material properties table to the plot.
|
|
598
|
-
|
|
599
|
-
Parameters:
|
|
600
|
-
ax: matplotlib Axes object
|
|
601
|
-
seep_data: Dictionary containing seepage data with material properties
|
|
602
|
-
xloc: x-location of table (0-1)
|
|
603
|
-
yloc: y-location of table (0-1)
|
|
604
|
-
|
|
605
|
-
Returns:
|
|
606
|
-
None
|
|
607
|
-
"""
|
|
608
|
-
# Extract material properties from seep_data
|
|
609
|
-
k1_by_mat = seep_data.get("k1_by_mat")
|
|
610
|
-
k2_by_mat = seep_data.get("k2_by_mat")
|
|
611
|
-
angle_by_mat = seep_data.get("angle_by_mat")
|
|
612
|
-
kr0_by_mat = seep_data.get("kr0_by_mat")
|
|
613
|
-
h0_by_mat = seep_data.get("h0_by_mat")
|
|
614
|
-
material_names = seep_data.get("material_names", [])
|
|
615
|
-
|
|
616
|
-
if k1_by_mat is None or len(k1_by_mat) == 0:
|
|
617
|
-
return
|
|
618
|
-
|
|
619
|
-
# Column headers for seepage properties
|
|
620
|
-
col_labels = ["Mat", "Name", "k₁", "k₂", "Angle", "kr₀", "h₀"]
|
|
621
|
-
|
|
622
|
-
# Build table rows
|
|
623
|
-
table_data = []
|
|
624
|
-
for idx in range(len(k1_by_mat)):
|
|
625
|
-
k1 = k1_by_mat[idx]
|
|
626
|
-
k2 = k2_by_mat[idx] if k2_by_mat is not None else 0.0
|
|
627
|
-
angle = angle_by_mat[idx] if angle_by_mat is not None else 0.0
|
|
628
|
-
kr0 = kr0_by_mat[idx] if kr0_by_mat is not None else 0.0
|
|
629
|
-
h0 = h0_by_mat[idx] if h0_by_mat is not None else 0.0
|
|
630
|
-
|
|
631
|
-
# Get material name, use default if not available
|
|
632
|
-
material_name = material_names[idx] if idx < len(material_names) else f"Material {idx+1}"
|
|
633
|
-
|
|
634
|
-
# Format values with appropriate precision
|
|
635
|
-
row = [
|
|
636
|
-
idx + 1, # Material number (1-based)
|
|
637
|
-
material_name, # Material name
|
|
638
|
-
f"{k1:.3f}", # k1 in scientific notation
|
|
639
|
-
f"{k2:.3f}", # k2 in scientific notation
|
|
640
|
-
f"{angle:.1f}", # angle in degrees
|
|
641
|
-
f"{kr0:.4f}", # kr0
|
|
642
|
-
f"{h0:.2f}" # h0
|
|
643
|
-
]
|
|
644
|
-
table_data.append(row)
|
|
645
|
-
|
|
646
|
-
# Add the table
|
|
647
|
-
table = ax.table(cellText=table_data,
|
|
648
|
-
colLabels=col_labels,
|
|
649
|
-
loc='upper right',
|
|
650
|
-
colLoc='center',
|
|
651
|
-
cellLoc='center',
|
|
652
|
-
bbox=[xloc, yloc, 0.45, 0.25]) # Increased width to accommodate name column
|
|
653
|
-
table.auto_set_font_size(False)
|
|
654
|
-
table.set_fontsize(8)
|
|
596
|
+
# plot_seep_material_table has been moved to xslope/plot.py
|
|
655
597
|
|
|
656
598
|
|
|
657
599
|
def get_ordered_mesh_boundary(nodes, elements, element_types=None):
|
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
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
@@ -404,7 +404,7 @@ def solve_confined(nodes, elements, bc_type, dirichlet_bcs, k1_vals, k2_vals, an
|
|
|
404
404
|
|
|
405
405
|
def solve_unsaturated(nodes, elements, bc_type, bc_values, kr0=0.001, h0=-1.0,
|
|
406
406
|
k1_vals=1.0, k2_vals=1.0, angles=0.0,
|
|
407
|
-
max_iter=200, tol=1e-
|
|
407
|
+
max_iter=200, tol=1e-6, element_types=None):
|
|
408
408
|
"""
|
|
409
409
|
Iterative FEM solver for unconfined flow using linear kr frontal function.
|
|
410
410
|
Supports triangular and quadrilateral elements with both linear and quadratic shape functions.
|
|
@@ -716,7 +716,7 @@ def solve_unsaturated(nodes, elements, bc_type, bc_values, kr0=0.001, h0=-1.0,
|
|
|
716
716
|
print(" - Non-conservative flow field")
|
|
717
717
|
print(" - Incorrect boundary identification")
|
|
718
718
|
print(" - Numerical issues in the flow solution")
|
|
719
|
-
|
|
719
|
+
print(f"Try reducing the tolerance (tol) parameter. Current value: {tol:.6e}")
|
|
720
720
|
|
|
721
721
|
return h, A, q_final, total_inflow
|
|
722
722
|
|
|
@@ -969,10 +969,14 @@ def create_flow_potential_bc(nodes, elements, q, debug=False, element_types=None
|
|
|
969
969
|
starting_phi = phi[ordered_nodes[start_idx]]
|
|
970
970
|
closure_error = phi_val - starting_phi
|
|
971
971
|
|
|
972
|
-
|
|
972
|
+
# Use a relative threshold based on total positive boundary flow
|
|
973
|
+
rel_tol = 1e-2 # 1%
|
|
974
|
+
scale = max(total_q, 1e-12)
|
|
975
|
+
|
|
976
|
+
if debug or abs(closure_error) > rel_tol * scale:
|
|
973
977
|
print(f"Flow potential closure check: error = {closure_error:.6e}")
|
|
974
978
|
|
|
975
|
-
if abs(closure_error) >
|
|
979
|
+
if abs(closure_error) > rel_tol * scale:
|
|
976
980
|
print(f"Warning: Large flow potential closure error = {closure_error:.6e}")
|
|
977
981
|
print("This may indicate:")
|
|
978
982
|
print(" - Non-conservative flow field")
|
|
@@ -1983,12 +1987,12 @@ def quad4_stiffness_matrix(nodes_elem, Kmat):
|
|
|
1983
1987
|
|
|
1984
1988
|
return ke
|
|
1985
1989
|
|
|
1986
|
-
def run_seepage_analysis(seep_data):
|
|
1990
|
+
def run_seepage_analysis(seep_data, tol=1e-6):
|
|
1987
1991
|
"""
|
|
1988
|
-
Standalone function to run
|
|
1992
|
+
Standalone function to run seep analysis.
|
|
1989
1993
|
|
|
1990
1994
|
Args:
|
|
1991
|
-
seep_data: Dictionary containing all the
|
|
1995
|
+
seep_data: Dictionary containing all the seep data
|
|
1992
1996
|
|
|
1993
1997
|
Returns:
|
|
1994
1998
|
Dictionary containing solution results with the following keys:
|
|
@@ -2019,7 +2023,7 @@ def run_seepage_analysis(seep_data):
|
|
|
2019
2023
|
# Determine if unconfined flow
|
|
2020
2024
|
is_unconfined = np.any(bc_type == 2)
|
|
2021
2025
|
flow_type = "unconfined" if is_unconfined else "confined"
|
|
2022
|
-
print(f"Solving {flow_type.upper()}
|
|
2026
|
+
print(f"Solving {flow_type.upper()} seep problem...")
|
|
2023
2027
|
print("Number of fixed-head nodes:", np.sum(bc_type == 1))
|
|
2024
2028
|
print("Number of exit face nodes:", np.sum(bc_type == 2))
|
|
2025
2029
|
|
|
@@ -2048,7 +2052,8 @@ def run_seepage_analysis(seep_data):
|
|
|
2048
2052
|
k1_vals=k1,
|
|
2049
2053
|
k2_vals=k2,
|
|
2050
2054
|
angles=angle,
|
|
2051
|
-
element_types=element_types
|
|
2055
|
+
element_types=element_types,
|
|
2056
|
+
tol=tol
|
|
2052
2057
|
)
|
|
2053
2058
|
# Solve for potential function φ for flow lines
|
|
2054
2059
|
dirichlet_phi_bcs = create_flow_potential_bc(nodes, elements, q, element_types=element_types)
|
|
@@ -2105,7 +2110,7 @@ def export_seep_solution(seep_data, solution, filename):
|
|
|
2105
2110
|
|
|
2106
2111
|
Args:
|
|
2107
2112
|
filename: Path to the output CSV file
|
|
2108
|
-
seep_data: Dictionary containing
|
|
2113
|
+
seep_data: Dictionary containing seep data
|
|
2109
2114
|
solution: Dictionary containing solution results from run_seepage_analysis
|
|
2110
2115
|
"""
|
|
2111
2116
|
import pandas as pd
|
|
@@ -2135,7 +2140,7 @@ def print_seep_data_diagnostics(seep_data):
|
|
|
2135
2140
|
Diagnostic function to print out the contents of seep_data after loading.
|
|
2136
2141
|
|
|
2137
2142
|
Args:
|
|
2138
|
-
seep_data: Dictionary containing
|
|
2143
|
+
seep_data: Dictionary containing seep data
|
|
2139
2144
|
"""
|
|
2140
2145
|
print("\n" + "="*60)
|
|
2141
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
|
|
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
|
|
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(
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
820
|
-
|
|
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)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
xslope/__init__.py,sha256=ZygAIkX6Nbjag1czWdQa-yP-GM1mBE_9ss21Xh__JFc,34
|
|
2
|
+
xslope/_version.py,sha256=XLyb183vl7CU3KttGJB7AeLEWBwd9veAWre8ysdY9ww,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=VkItm3kASiNL3hha-W6PbjdJ7w2_yi2_4LcjH7GqokM,86265
|
|
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.13.dist-info/LICENSE,sha256=NU5J88FUai_4Ixu5DqOQukA-gcXAyX8pXLhIg8OB3AM,10969
|
|
17
|
+
xslope-0.1.13.dist-info/METADATA,sha256=gnmFMyTdek5oNE5MDHkb_WX6hiMKBJ9dP2DdG4OhHYI,2028
|
|
18
|
+
xslope-0.1.13.dist-info/NOTICE,sha256=E-sbN0MWwvJC27Z-2_G4VUHIx4IsfvLDTmOstvY4-OQ,530
|
|
19
|
+
xslope-0.1.13.dist-info/WHEEL,sha256=beeZ86-EfXScwlR_HKu4SllMC9wUEj_8Z_4FJ3egI2w,91
|
|
20
|
+
xslope-0.1.13.dist-info/top_level.txt,sha256=5qHbWJ1R2pdTNIainFyrVtFk8R1tRcwIn0kzTuwuV1Q,7
|
|
21
|
+
xslope-0.1.13.dist-info/RECORD,,
|
xslope-0.1.11.dist-info/RECORD
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
xslope/__init__.py,sha256=ZygAIkX6Nbjag1czWdQa-yP-GM1mBE_9ss21Xh__JFc,34
|
|
2
|
-
xslope/_version.py,sha256=EuS8ourwvf_O1n08GOG1eXlKDKt7dguYQ663MaapOxQ,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=DnFUYmJedjKXOuVPZUfTRxGfTjiIz8KyHkRDK7ddQg0,28840
|
|
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=qtMH1yKFgHM4kNuIrxco7tKV86R3Dbf7Nok5j6MtlLY,131013
|
|
9
|
-
xslope/plot.py,sha256=MexmrZJ1CVSnG0ZN3lNz67DyQdDTdO6hLlbTTBYz4Zw,56016
|
|
10
|
-
xslope/plot_fem.py,sha256=WiFFt9cUIRwX1uCiJrPaMtmhz5LfhZnzNVJwhp0fMP8,71902
|
|
11
|
-
xslope/plot_seep.py,sha256=ep9spY2DiMvOJELccBAj77p9HTnBRgoyqQ_brQphWHw,35166
|
|
12
|
-
xslope/search.py,sha256=dvgKn8JCobuvyD7fClF5lcbeHESCvV8gZ_U_lQnYRok,16867
|
|
13
|
-
xslope/seep.py,sha256=tLvme-eL0R5SGGTH2Y6z4Rrfe36UuSWWLlL_1kglee8,93030
|
|
14
|
-
xslope/slice.py,sha256=QHawTk7XPLziHoN_ZS0jrEPYKlhPT62caUc_q-_EGjs,45236
|
|
15
|
-
xslope/solve.py,sha256=u_sBjtsj1EeZtgSEmo80tEWoRrrMgvRjmi6P0ODO1lU,48645
|
|
16
|
-
xslope-0.1.11.dist-info/LICENSE,sha256=NU5J88FUai_4Ixu5DqOQukA-gcXAyX8pXLhIg8OB3AM,10969
|
|
17
|
-
xslope-0.1.11.dist-info/METADATA,sha256=ZbDxeZ2g25EwvsZDdbQIhwRB5eOu-LSpeFYmcFn0nUk,2028
|
|
18
|
-
xslope-0.1.11.dist-info/NOTICE,sha256=E-sbN0MWwvJC27Z-2_G4VUHIx4IsfvLDTmOstvY4-OQ,530
|
|
19
|
-
xslope-0.1.11.dist-info/WHEEL,sha256=beeZ86-EfXScwlR_HKu4SllMC9wUEj_8Z_4FJ3egI2w,91
|
|
20
|
-
xslope-0.1.11.dist-info/top_level.txt,sha256=5qHbWJ1R2pdTNIainFyrVtFk8R1tRcwIn0kzTuwuV1Q,7
|
|
21
|
-
xslope-0.1.11.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|