xslope 0.1.12__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 +427 -240
- xslope/mesh.py +80 -27
- xslope/plot.py +182 -40
- xslope/plot_seep.py +7 -7
- xslope/seep.py +9 -9
- xslope/slice.py +31 -9
- xslope/solve.py +20 -8
- {xslope-0.1.12.dist-info → xslope-0.1.13.dist-info}/METADATA +1 -1
- xslope-0.1.13.dist-info/RECORD +21 -0
- xslope-0.1.12.dist-info/RECORD +0 -21
- {xslope-0.1.12.dist-info → xslope-0.1.13.dist-info}/LICENSE +0 -0
- {xslope-0.1.12.dist-info → xslope-0.1.13.dist-info}/NOTICE +0 -0
- {xslope-0.1.12.dist-info → xslope-0.1.13.dist-info}/WHEEL +0 -0
- {xslope-0.1.12.dist-info → xslope-0.1.13.dist-info}/top_level.txt +0 -0
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:
|
|
@@ -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
|
|
1992
|
+
Standalone function to run seep analysis.
|
|
1993
1993
|
|
|
1994
1994
|
Args:
|
|
1995
|
-
seep_data: Dictionary containing all the
|
|
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()}
|
|
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
|
|
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
|
|
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
|
|
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.12.dist-info/RECORD
DELETED
|
@@ -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,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|