xslope 0.1.7__tar.gz → 0.1.9__tar.gz
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-0.1.7/xslope.egg-info → xslope-0.1.9}/PKG-INFO +1 -1
- {xslope-0.1.7 → xslope-0.1.9}/xslope/_version.py +1 -1
- {xslope-0.1.7 → xslope-0.1.9}/xslope/plot_seep.py +154 -34
- {xslope-0.1.7 → xslope-0.1.9}/xslope/seep.py +172 -3
- {xslope-0.1.7 → xslope-0.1.9/xslope.egg-info}/PKG-INFO +1 -1
- {xslope-0.1.7 → xslope-0.1.9}/LICENSE +0 -0
- {xslope-0.1.7 → xslope-0.1.9}/MANIFEST.in +0 -0
- {xslope-0.1.7 → xslope-0.1.9}/NOTICE +0 -0
- {xslope-0.1.7 → xslope-0.1.9}/README.md +0 -0
- {xslope-0.1.7 → xslope-0.1.9}/pyproject.toml +0 -0
- {xslope-0.1.7 → xslope-0.1.9}/setup.cfg +0 -0
- {xslope-0.1.7 → xslope-0.1.9}/xslope/__init__.py +0 -0
- {xslope-0.1.7 → xslope-0.1.9}/xslope/advanced.py +0 -0
- {xslope-0.1.7 → xslope-0.1.9}/xslope/fem.py +0 -0
- {xslope-0.1.7 → xslope-0.1.9}/xslope/fileio.py +0 -0
- {xslope-0.1.7 → xslope-0.1.9}/xslope/global_config.py +0 -0
- {xslope-0.1.7 → xslope-0.1.9}/xslope/mesh copy.py +0 -0
- {xslope-0.1.7 → xslope-0.1.9}/xslope/mesh.py +0 -0
- {xslope-0.1.7 → xslope-0.1.9}/xslope/plot.py +0 -0
- {xslope-0.1.7 → xslope-0.1.9}/xslope/plot_fem.py +0 -0
- {xslope-0.1.7 → xslope-0.1.9}/xslope/search.py +0 -0
- {xslope-0.1.7 → xslope-0.1.9}/xslope/slice.py +0 -0
- {xslope-0.1.7 → xslope-0.1.9}/xslope/solve.py +0 -0
- {xslope-0.1.7 → xslope-0.1.9}/xslope.egg-info/SOURCES.txt +0 -0
- {xslope-0.1.7 → xslope-0.1.9}/xslope.egg-info/dependency_links.txt +0 -0
- {xslope-0.1.7 → xslope-0.1.9}/xslope.egg-info/requires.txt +0 -0
- {xslope-0.1.7 → xslope-0.1.9}/xslope.egg-info/top_level.txt +0 -0
|
@@ -217,20 +217,73 @@ def plot_seep_data(seep_data, figsize=(14, 6), show_nodes=False, show_bc=False,
|
|
|
217
217
|
plt.show()
|
|
218
218
|
|
|
219
219
|
|
|
220
|
-
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,
|
|
220
|
+
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):
|
|
221
221
|
"""
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
222
|
+
Plot seepage analysis results including head contours, flowlines, and phreatic surface.
|
|
223
|
+
|
|
224
|
+
This function visualizes the results of a seepage analysis by plotting contours of various
|
|
225
|
+
nodal variables (head, pore pressure, velocity magnitude, or gradient magnitude). When
|
|
226
|
+
plotting head, flowlines are also overlaid. The plot properly handles mesh aspect ratios
|
|
227
|
+
and supports both linear and quadratic triangular and quadrilateral elements.
|
|
228
|
+
|
|
229
|
+
Parameters:
|
|
230
|
+
-----------
|
|
231
|
+
seep_data : dict
|
|
232
|
+
Dictionary containing seepage mesh data from import_seep2d. Required keys include:
|
|
233
|
+
'nodes', 'elements', 'element_materials', 'element_types' (optional), and
|
|
234
|
+
'k1_by_mat' (optional, for flowline calculation).
|
|
235
|
+
solution : dict
|
|
236
|
+
Dictionary containing solution results from run_seepage_analysis. Required keys include:
|
|
237
|
+
'head' (array of total head values at nodes), 'velocity' (array of velocity vectors),
|
|
238
|
+
'gradient' (array of hydraulic gradient vectors). Optional keys: 'phi' (stream function),
|
|
239
|
+
'flowrate' (total flow rate), 'u' (pore pressure), 'v_mag' (velocity magnitude),
|
|
240
|
+
'i_mag' (gradient magnitude).
|
|
241
|
+
figsize : tuple of float, optional
|
|
242
|
+
Figure size in inches (width, height). Default is (14, 6).
|
|
243
|
+
levels : int, optional
|
|
244
|
+
Number of contour levels to plot. Default is 20.
|
|
245
|
+
base_mat : int, optional
|
|
246
|
+
Material ID (1-based) used to compute hydraulic conductivity for flow function
|
|
247
|
+
calculation. Default is 1. Only used when variable="head".
|
|
248
|
+
fill_contours : bool, optional
|
|
249
|
+
If True, shows filled contours with color map. If False, only black solid
|
|
250
|
+
contour lines are shown. Default is True.
|
|
251
|
+
phreatic : bool, optional
|
|
252
|
+
If True, plots the phreatic surface (where pressure head = 0) as a thick red line.
|
|
253
|
+
Default is True. Only plotted if pore pressure is negative somewhere in the domain.
|
|
254
|
+
alpha : float, optional
|
|
255
|
+
Transparency level (0-1) for material zone fill colors. Default is 0.4.
|
|
256
|
+
pad_frac : float, optional
|
|
257
|
+
Fraction of mesh extent to add as padding around the plot boundaries. Default is 0.05.
|
|
258
|
+
mesh : bool, optional
|
|
259
|
+
If True, overlays element edges in light gray. Default is True.
|
|
260
|
+
variable : str, optional
|
|
261
|
+
Nodal variable to contour. Options: "head" (default), "u" (pore pressure),
|
|
262
|
+
"v_mag" (velocity magnitude), "i_mag" (gradient magnitude). When "head" is selected,
|
|
263
|
+
flowlines can be overlaid if flowlines=True. Other variables do not include flowlines.
|
|
264
|
+
vectors : bool, optional
|
|
265
|
+
If True, plots velocity vectors as arrows at each node. Default is False.
|
|
266
|
+
vector_scale : float, optional
|
|
267
|
+
Scale factor for vector lengths. Maximum vector length will be x_range * vector_scale,
|
|
268
|
+
where x_range is the x-extent of the mesh. Default is 0.05.
|
|
269
|
+
flowlines : bool, optional
|
|
270
|
+
If True and variable="head", overlays flowlines (stream function contours) on the plot.
|
|
271
|
+
Default is True. Only applicable when variable="head".
|
|
272
|
+
|
|
273
|
+
Returns:
|
|
274
|
+
--------
|
|
275
|
+
None
|
|
276
|
+
Displays the plot using matplotlib.pyplot.show().
|
|
277
|
+
|
|
278
|
+
Notes:
|
|
279
|
+
------
|
|
280
|
+
- The function automatically subdivides quadratic elements (tri6, quad8, quad9) for
|
|
281
|
+
proper visualization and contouring.
|
|
282
|
+
- Flowlines are only plotted when variable="head" and if 'phi' and 'flowrate' are present
|
|
283
|
+
in solution and 'k1_by_mat' is present in seep_data.
|
|
284
|
+
- The plot includes a colorbar for contours when fill_contours=True.
|
|
285
|
+
- The title includes flowrate information if available in the solution dictionary and
|
|
286
|
+
variable="head".
|
|
234
287
|
"""
|
|
235
288
|
import matplotlib.pyplot as plt
|
|
236
289
|
import matplotlib.tri as tri
|
|
@@ -238,15 +291,30 @@ def plot_seep_solution(seep_data, solution, figsize=(14, 6), levels=20, base_mat
|
|
|
238
291
|
from matplotlib.patches import Polygon
|
|
239
292
|
import numpy as np
|
|
240
293
|
|
|
294
|
+
# Validate variable parameter
|
|
295
|
+
valid_variables = ["head", "u", "v_mag", "i_mag"]
|
|
296
|
+
if variable not in valid_variables:
|
|
297
|
+
raise ValueError(f"variable must be one of {valid_variables}, got '{variable}'")
|
|
298
|
+
|
|
241
299
|
# Extract data from seep_data and solution
|
|
242
300
|
nodes = seep_data["nodes"]
|
|
243
301
|
elements = seep_data["elements"]
|
|
244
302
|
element_materials = seep_data["element_materials"]
|
|
245
303
|
element_types = seep_data.get("element_types", None) # New field for element types
|
|
246
304
|
k1_by_mat = seep_data.get("k1_by_mat") # Use .get() in case it's not present
|
|
305
|
+
|
|
306
|
+
# Extract the variable to plot
|
|
307
|
+
if variable not in solution:
|
|
308
|
+
raise ValueError(f"Variable '{variable}' not found in solution dictionary. Available keys: {list(solution.keys())}")
|
|
309
|
+
contour_data = solution[variable]
|
|
310
|
+
|
|
311
|
+
# Extract head and flowline-related data (only needed for head plots)
|
|
247
312
|
head = solution["head"]
|
|
248
313
|
phi = solution.get("phi")
|
|
249
314
|
flowrate = solution.get("flowrate")
|
|
315
|
+
|
|
316
|
+
# Determine if we should plot flowlines (only for head and if flowlines=True)
|
|
317
|
+
plot_flowlines = (variable == "head" and flowlines)
|
|
250
318
|
|
|
251
319
|
|
|
252
320
|
# Use constrained_layout for best layout
|
|
@@ -347,9 +415,9 @@ def plot_seep_solution(seep_data, solution, figsize=(14, 6), levels=20, base_mat
|
|
|
347
415
|
for sub_quad in sub_quads:
|
|
348
416
|
ax.fill(*zip(*sub_quad), edgecolor='none', facecolor=color, alpha=alpha)
|
|
349
417
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
418
|
+
# Set up contour levels
|
|
419
|
+
vmin = np.min(contour_data)
|
|
420
|
+
vmax = np.max(contour_data)
|
|
353
421
|
contour_levels = np.linspace(vmin, vmax, levels)
|
|
354
422
|
|
|
355
423
|
# For contouring, subdivide tri6 elements into 4 subtriangles
|
|
@@ -374,24 +442,41 @@ def plot_seep_solution(seep_data, solution, figsize=(14, 6), levels=20, base_mat
|
|
|
374
442
|
all_triangles_for_contouring.extend([tri1, tri2])
|
|
375
443
|
triang = tri.Triangulation(nodes[:, 0], nodes[:, 1], all_triangles_for_contouring)
|
|
376
444
|
|
|
445
|
+
# Variable labels for colorbar and title
|
|
446
|
+
variable_labels = {
|
|
447
|
+
"head": "Total Head",
|
|
448
|
+
"u": "Pore Pressure",
|
|
449
|
+
"v_mag": "Velocity Magnitude",
|
|
450
|
+
"i_mag": "Hydraulic Gradient Magnitude"
|
|
451
|
+
}
|
|
452
|
+
variable_label = variable_labels[variable]
|
|
453
|
+
|
|
377
454
|
# Filled contours (only if fill_contours=True)
|
|
378
455
|
if fill_contours:
|
|
379
|
-
contourf = ax.tricontourf(triang,
|
|
380
|
-
cbar = plt.colorbar(contourf, ax=ax, label=
|
|
456
|
+
contourf = ax.tricontourf(triang, contour_data, levels=contour_levels, cmap="Spectral_r", vmin=vmin, vmax=vmax, alpha=0.5)
|
|
457
|
+
cbar = plt.colorbar(contourf, ax=ax, label=variable_label, shrink=0.8, pad=0.02)
|
|
381
458
|
cbar.locator = MaxNLocator(nbins=10, steps=[1, 2, 5])
|
|
382
459
|
cbar.update_ticks()
|
|
383
460
|
|
|
384
|
-
# Solid lines for
|
|
385
|
-
ax.tricontour(triang,
|
|
461
|
+
# Solid lines for contours
|
|
462
|
+
ax.tricontour(triang, contour_data, levels=contour_levels, colors="k", linewidths=0.5)
|
|
386
463
|
|
|
387
464
|
# Phreatic surface (pressure head = 0)
|
|
465
|
+
# Check if phreatic surface exists (pore pressure must be negative somewhere)
|
|
466
|
+
has_phreatic = False
|
|
388
467
|
if phreatic:
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
468
|
+
# Check if pore pressure goes negative (indicating a phreatic surface exists)
|
|
469
|
+
u = solution.get("u")
|
|
470
|
+
if u is not None and np.min(u) < 0:
|
|
471
|
+
elevation = nodes[:, 1] # y-coordinate is elevation
|
|
472
|
+
pressure_head = head - elevation
|
|
473
|
+
ax.tricontour(triang, pressure_head, levels=[0], colors="black", linewidths=2.0)
|
|
474
|
+
has_phreatic = True
|
|
475
|
+
|
|
476
|
+
# Overlay flowlines if variable is head and phi is available
|
|
477
|
+
if plot_flowlines and phi is not None and flowrate is not None and k1_by_mat is not None:
|
|
478
|
+
# Compute head drop for flowline calculation
|
|
479
|
+
hdrop = vmax - vmin
|
|
395
480
|
if base_mat > len(k1_by_mat):
|
|
396
481
|
print(f"Warning: base_mat={base_mat} is larger than number of materials ({len(k1_by_mat)}). Using material 1.")
|
|
397
482
|
base_mat = 1
|
|
@@ -406,8 +491,39 @@ def plot_seep_solution(seep_data, solution, figsize=(14, 6), levels=20, base_mat
|
|
|
406
491
|
phi_contours = np.linspace(np.min(phi), np.max(phi), phi_levels)
|
|
407
492
|
ax.tricontour(triang, phi, levels=phi_contours, colors="blue", linewidths=0.7, linestyles="solid")
|
|
408
493
|
|
|
494
|
+
# Plot velocity vectors if requested
|
|
495
|
+
if vectors:
|
|
496
|
+
velocity = solution.get("velocity")
|
|
497
|
+
if velocity is not None:
|
|
498
|
+
# Calculate x_range for scaling
|
|
499
|
+
x_min_vec = nodes[:, 0].min()
|
|
500
|
+
x_max_vec = nodes[:, 0].max()
|
|
501
|
+
x_range = x_max_vec - x_min_vec
|
|
502
|
+
max_vector_length = x_range * vector_scale
|
|
503
|
+
|
|
504
|
+
# Get velocity magnitude
|
|
505
|
+
v_mag = solution.get("v_mag")
|
|
506
|
+
if v_mag is None:
|
|
507
|
+
# Calculate v_mag if not available
|
|
508
|
+
v_mag = np.linalg.norm(velocity, axis=1)
|
|
509
|
+
|
|
510
|
+
# Find maximum velocity magnitude
|
|
511
|
+
max_v_mag = np.max(v_mag)
|
|
512
|
+
|
|
513
|
+
# Scale vectors: if max_v_mag > 0, scale so max vector has length max_vector_length
|
|
514
|
+
if max_v_mag > 0:
|
|
515
|
+
scale_factor = max_vector_length / max_v_mag
|
|
516
|
+
velocity_scaled = velocity * scale_factor
|
|
517
|
+
else:
|
|
518
|
+
velocity_scaled = velocity
|
|
519
|
+
|
|
520
|
+
# Plot vectors using quiver
|
|
521
|
+
ax.quiver(nodes[:, 0], nodes[:, 1], velocity_scaled[:, 0], velocity_scaled[:, 1],
|
|
522
|
+
angles='xy', scale_units='xy', scale=1, width=0.002, headwidth=2.5,
|
|
523
|
+
headlength=3, headaxislength=2.5, color='black', alpha=0.7)
|
|
524
|
+
|
|
409
525
|
# Plot element edges if requested
|
|
410
|
-
if
|
|
526
|
+
if mesh:
|
|
411
527
|
# Draw all element edges
|
|
412
528
|
for element, elem_type in zip(elements, element_types if element_types is not None else [3]*len(elements)):
|
|
413
529
|
if elem_type == 3:
|
|
@@ -444,13 +560,17 @@ def plot_seep_solution(seep_data, solution, figsize=(14, 6), levels=20, base_mat
|
|
|
444
560
|
ax.set_xlim(x_min - x_pad, x_max + x_pad)
|
|
445
561
|
ax.set_ylim(y_min - y_pad, y_max + y_pad)
|
|
446
562
|
|
|
447
|
-
|
|
448
|
-
if
|
|
449
|
-
title
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
563
|
+
# Build title based on variable
|
|
564
|
+
if variable == "head":
|
|
565
|
+
title = f"Flow Net: {variable_label} Contours"
|
|
566
|
+
if plot_flowlines and phi is not None:
|
|
567
|
+
title += " and Flowlines"
|
|
568
|
+
if has_phreatic:
|
|
569
|
+
title += " with Phreatic Surface"
|
|
570
|
+
if flowrate is not None:
|
|
571
|
+
title += f" — Total Flowrate: {flowrate:.3f}"
|
|
572
|
+
else:
|
|
573
|
+
title = f"{variable_label} Contours"
|
|
454
574
|
ax.set_title(title)
|
|
455
575
|
|
|
456
576
|
# Set equal aspect ratio AFTER setting limits
|
|
@@ -1515,6 +1515,142 @@ def compute_velocity(nodes, elements, head, k1_vals, k2_vals, angles, kr0=None,
|
|
|
1515
1515
|
velocity /= count[:, None]
|
|
1516
1516
|
return velocity
|
|
1517
1517
|
|
|
1518
|
+
def compute_gradient(nodes, elements, head, element_types=None):
|
|
1519
|
+
"""
|
|
1520
|
+
Compute nodal hydraulic gradient by averaging element-wise head gradients.
|
|
1521
|
+
The hydraulic gradient i = -grad(h), where grad(h) is the gradient of head.
|
|
1522
|
+
Supports both triangular and quadrilateral elements.
|
|
1523
|
+
|
|
1524
|
+
Parameters:
|
|
1525
|
+
nodes : (n_nodes, 2) array of node coordinates
|
|
1526
|
+
elements : (n_elements, 3 or 4) triangle or quad node indices
|
|
1527
|
+
head : (n_nodes,) nodal head solution
|
|
1528
|
+
element_types : (n_elements,) array indicating 3 for triangles, 4 for quads
|
|
1529
|
+
|
|
1530
|
+
Returns:
|
|
1531
|
+
gradient : (n_nodes, 2) array of nodal hydraulic gradient vectors [ix, iy]
|
|
1532
|
+
"""
|
|
1533
|
+
# If element_types is not provided, assume all triangles (backward compatibility)
|
|
1534
|
+
if element_types is None:
|
|
1535
|
+
element_types = np.full(len(elements), 3)
|
|
1536
|
+
|
|
1537
|
+
n_nodes = nodes.shape[0]
|
|
1538
|
+
gradient = np.zeros((n_nodes, 2))
|
|
1539
|
+
count = np.zeros(n_nodes)
|
|
1540
|
+
|
|
1541
|
+
for idx, element_nodes in enumerate(elements):
|
|
1542
|
+
element_type = element_types[idx]
|
|
1543
|
+
|
|
1544
|
+
if element_type == 3:
|
|
1545
|
+
# Triangle: use first 3 nodes
|
|
1546
|
+
i, j, k = element_nodes[:3]
|
|
1547
|
+
xi, yi = nodes[i]
|
|
1548
|
+
xj, yj = nodes[j]
|
|
1549
|
+
xk, yk = nodes[k]
|
|
1550
|
+
|
|
1551
|
+
area = 0.5 * np.linalg.det([[1, xi, yi], [1, xj, yj], [1, xk, yk]])
|
|
1552
|
+
if area <= 0:
|
|
1553
|
+
continue
|
|
1554
|
+
|
|
1555
|
+
beta = np.array([yj - yk, yk - yi, yi - yj])
|
|
1556
|
+
gamma = np.array([xk - xj, xi - xk, xj - xi])
|
|
1557
|
+
grad = np.array([beta, gamma]) / (2 * area)
|
|
1558
|
+
|
|
1559
|
+
h_vals = head[[i, j, k]]
|
|
1560
|
+
grad_h = grad @ h_vals
|
|
1561
|
+
# Hydraulic gradient i = -grad(h)
|
|
1562
|
+
i_elem = -grad_h
|
|
1563
|
+
|
|
1564
|
+
for node in element_nodes[:3]:
|
|
1565
|
+
gradient[node] += i_elem
|
|
1566
|
+
count[node] += 1
|
|
1567
|
+
elif element_type == 4:
|
|
1568
|
+
# Quadrilateral: use first 4 nodes
|
|
1569
|
+
i, j, k, l = element_nodes[:4]
|
|
1570
|
+
nodes_elem = nodes[[i, j, k, l], :]
|
|
1571
|
+
h_elem = head[[i, j, k, l]]
|
|
1572
|
+
|
|
1573
|
+
# 2x2 Gauss points and weights
|
|
1574
|
+
gauss_pts = [(-1/np.sqrt(3), -1/np.sqrt(3)),
|
|
1575
|
+
(1/np.sqrt(3), -1/np.sqrt(3)),
|
|
1576
|
+
(1/np.sqrt(3), 1/np.sqrt(3)),
|
|
1577
|
+
(-1/np.sqrt(3), 1/np.sqrt(3))]
|
|
1578
|
+
|
|
1579
|
+
for (xi, eta) in gauss_pts:
|
|
1580
|
+
# Shape function derivatives w.r.t. natural coords
|
|
1581
|
+
dN_dxi = np.array([-(1-eta), (1-eta), (1+eta), -(1+eta)]) * 0.25
|
|
1582
|
+
dN_deta = np.array([-(1-xi), -(1+xi), (1+xi), (1-xi)]) * 0.25
|
|
1583
|
+
# Jacobian
|
|
1584
|
+
J = np.zeros((2,2))
|
|
1585
|
+
for a in range(4):
|
|
1586
|
+
J[0,0] += dN_dxi[a] * nodes_elem[a,0]
|
|
1587
|
+
J[0,1] += dN_dxi[a] * nodes_elem[a,1]
|
|
1588
|
+
J[1,0] += dN_deta[a] * nodes_elem[a,0]
|
|
1589
|
+
J[1,1] += dN_deta[a] * nodes_elem[a,1]
|
|
1590
|
+
detJ = np.linalg.det(J)
|
|
1591
|
+
if detJ <= 0:
|
|
1592
|
+
continue
|
|
1593
|
+
Jinv = np.linalg.inv(J)
|
|
1594
|
+
# Shape function derivatives w.r.t. x,y
|
|
1595
|
+
dN_dx = Jinv[0,0]*dN_dxi + Jinv[0,1]*dN_deta
|
|
1596
|
+
dN_dy = Jinv[1,0]*dN_dxi + Jinv[1,1]*dN_deta
|
|
1597
|
+
gradN = np.vstack((dN_dx, dN_dy)) # shape (2,4)
|
|
1598
|
+
# Compute grad(h) at this Gauss point
|
|
1599
|
+
grad_h = gradN @ h_elem
|
|
1600
|
+
# Hydraulic gradient i = -grad(h)
|
|
1601
|
+
i_gp = -grad_h
|
|
1602
|
+
# Distribute/average to nodes
|
|
1603
|
+
for node in element_nodes[:4]:
|
|
1604
|
+
gradient[node] += i_gp
|
|
1605
|
+
count[node] += 1
|
|
1606
|
+
elif element_type == 6:
|
|
1607
|
+
# 6-node triangle (quadratic): compute gradient using 3-point Gauss quadrature
|
|
1608
|
+
nodes_elem = nodes[element_nodes[:6], :]
|
|
1609
|
+
h_elem = head[element_nodes[:6]]
|
|
1610
|
+
|
|
1611
|
+
# 3-point Gauss quadrature for triangles
|
|
1612
|
+
gauss_pts = [(1/6, 1/6, 2/3), (1/6, 2/3, 1/6), (2/3, 1/6, 1/6)]
|
|
1613
|
+
weights = [1/3, 1/3, 1/3]
|
|
1614
|
+
|
|
1615
|
+
for (L1, L2, L3), w in zip(gauss_pts, weights):
|
|
1616
|
+
# Shape function derivatives w.r.t. area coordinates
|
|
1617
|
+
dN_dL1 = np.array([4*L1-1, 0, 0, 4*L2, 0, 4*L3])
|
|
1618
|
+
dN_dL2 = np.array([0, 4*L2-1, 0, 4*L1, 4*L3, 0])
|
|
1619
|
+
dN_dL3 = np.array([0, 0, 4*L3-1, 0, 4*L2, 4*L1])
|
|
1620
|
+
|
|
1621
|
+
# Jacobian transformation
|
|
1622
|
+
x0, y0 = nodes_elem[0]
|
|
1623
|
+
x1, y1 = nodes_elem[1]
|
|
1624
|
+
x2, y2 = nodes_elem[2]
|
|
1625
|
+
|
|
1626
|
+
J = np.array([[x0 - x2, x1 - x2],
|
|
1627
|
+
[y0 - y2, y1 - y2]])
|
|
1628
|
+
|
|
1629
|
+
detJ = np.linalg.det(J)
|
|
1630
|
+
if abs(detJ) < 1e-10:
|
|
1631
|
+
continue
|
|
1632
|
+
|
|
1633
|
+
Jinv = np.linalg.inv(J)
|
|
1634
|
+
|
|
1635
|
+
# Transform derivatives to global coordinates
|
|
1636
|
+
dN_dx = Jinv[0,0] * (dN_dL1 - dN_dL3) + Jinv[0,1] * (dN_dL2 - dN_dL3)
|
|
1637
|
+
dN_dy = Jinv[1,0] * (dN_dL1 - dN_dL3) + Jinv[1,1] * (dN_dL2 - dN_dL3)
|
|
1638
|
+
gradN = np.vstack((dN_dx, dN_dy)) # shape (2,6)
|
|
1639
|
+
|
|
1640
|
+
# Compute grad(h) at this Gauss point
|
|
1641
|
+
grad_h = gradN @ h_elem
|
|
1642
|
+
# Hydraulic gradient i = -grad(h)
|
|
1643
|
+
i_gp = -grad_h
|
|
1644
|
+
|
|
1645
|
+
# Distribute gradient to all 6 nodes of tri6 element
|
|
1646
|
+
for node in element_nodes[:6]:
|
|
1647
|
+
gradient[node] += i_gp * w # Weight by Gauss weight
|
|
1648
|
+
count[node] += w
|
|
1649
|
+
|
|
1650
|
+
count[count == 0] = 1 # Avoid division by zero
|
|
1651
|
+
gradient /= count[:, None]
|
|
1652
|
+
return gradient
|
|
1653
|
+
|
|
1518
1654
|
def tri3_stiffness_matrix(nodes_elem, Kmat):
|
|
1519
1655
|
"""
|
|
1520
1656
|
Compute the 3x3 local stiffness matrix for a 3-node triangular element.
|
|
@@ -1855,7 +1991,16 @@ def run_seepage_analysis(seep_data):
|
|
|
1855
1991
|
seep_data: Dictionary containing all the seepage data
|
|
1856
1992
|
|
|
1857
1993
|
Returns:
|
|
1858
|
-
Dictionary containing solution results
|
|
1994
|
+
Dictionary containing solution results with the following keys:
|
|
1995
|
+
- 'head': numpy array of hydraulic head values at each node
|
|
1996
|
+
- 'u': numpy array of pore pressure values at each node
|
|
1997
|
+
- 'velocity': numpy array of shape (n_nodes, 2) containing velocity vectors [vx, vy] at each node
|
|
1998
|
+
- 'gradient': numpy array of shape (n_nodes, 2) containing hydraulic gradient vectors [ix, iy] at each node
|
|
1999
|
+
- 'v_mag': numpy array of velocity magnitude at each node
|
|
2000
|
+
- 'i_mag': numpy array of hydraulic gradient magnitude at each node
|
|
2001
|
+
- 'q': numpy array of nodal flow vector
|
|
2002
|
+
- 'phi': numpy array of stream function/flow potential values at each node
|
|
2003
|
+
- 'flowrate': scalar total flow rate
|
|
1859
2004
|
"""
|
|
1860
2005
|
# Extract data from seep_data
|
|
1861
2006
|
nodes = seep_data["nodes"]
|
|
@@ -1920,6 +2065,13 @@ def run_seepage_analysis(seep_data):
|
|
|
1920
2065
|
# Compute velocity, don't pass kr0 and h0
|
|
1921
2066
|
velocity = compute_velocity(nodes, elements, head, k1, k2, angle, element_types=element_types)
|
|
1922
2067
|
|
|
2068
|
+
# Compute hydraulic gradient i = -grad(h)
|
|
2069
|
+
gradient = compute_gradient(nodes, elements, head, element_types)
|
|
2070
|
+
|
|
2071
|
+
# Compute velocity and gradient magnitudes
|
|
2072
|
+
v_mag = np.linalg.norm(velocity, axis=1)
|
|
2073
|
+
i_mag = np.linalg.norm(gradient, axis=1)
|
|
2074
|
+
|
|
1923
2075
|
gamma_w = unit_weight
|
|
1924
2076
|
u = gamma_w * (head - nodes[:, 1])
|
|
1925
2077
|
|
|
@@ -1927,6 +2079,9 @@ def run_seepage_analysis(seep_data):
|
|
|
1927
2079
|
"head": head,
|
|
1928
2080
|
"u": u,
|
|
1929
2081
|
"velocity": velocity,
|
|
2082
|
+
"gradient": gradient,
|
|
2083
|
+
"v_mag": v_mag,
|
|
2084
|
+
"i_mag": i_mag,
|
|
1930
2085
|
"q": q,
|
|
1931
2086
|
"phi": phi,
|
|
1932
2087
|
"flowrate": total_flow
|
|
@@ -1937,10 +2092,21 @@ def run_seepage_analysis(seep_data):
|
|
|
1937
2092
|
def export_seep_solution(seep_data, solution, filename):
|
|
1938
2093
|
"""Exports nodal results to a CSV file.
|
|
1939
2094
|
|
|
2095
|
+
The exported CSV file contains the following columns:
|
|
2096
|
+
- node_id: Node identifier (1-based)
|
|
2097
|
+
- head: Hydraulic head at each node
|
|
2098
|
+
- u: Pore pressure at each node
|
|
2099
|
+
- v_x, v_y: Velocity vector components
|
|
2100
|
+
- v_mag: Velocity magnitude
|
|
2101
|
+
- i_x, i_y: Hydraulic gradient vector components
|
|
2102
|
+
- i_mag: Hydraulic gradient magnitude
|
|
2103
|
+
- q: Nodal flow vector
|
|
2104
|
+
- phi: Stream function/flow potential
|
|
2105
|
+
|
|
1940
2106
|
Args:
|
|
1941
2107
|
filename: Path to the output CSV file
|
|
1942
2108
|
seep_data: Dictionary containing seepage data
|
|
1943
|
-
solution: Dictionary containing solution results from
|
|
2109
|
+
solution: Dictionary containing solution results from run_seepage_analysis
|
|
1944
2110
|
"""
|
|
1945
2111
|
import pandas as pd
|
|
1946
2112
|
n_nodes = len(seep_data["nodes"])
|
|
@@ -1950,7 +2116,10 @@ def export_seep_solution(seep_data, solution, filename):
|
|
|
1950
2116
|
"u": solution["u"],
|
|
1951
2117
|
"v_x": solution["velocity"][:, 0],
|
|
1952
2118
|
"v_y": solution["velocity"][:, 1],
|
|
1953
|
-
"v_mag":
|
|
2119
|
+
"v_mag": solution["v_mag"],
|
|
2120
|
+
"i_x": solution["gradient"][:, 0],
|
|
2121
|
+
"i_y": solution["gradient"][:, 1],
|
|
2122
|
+
"i_mag": solution["i_mag"],
|
|
1954
2123
|
"q": solution["q"],
|
|
1955
2124
|
"phi": solution["phi"]
|
|
1956
2125
|
})
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|