xslope 0.1.5__py3-none-any.whl → 0.1.7__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/mesh.py CHANGED
@@ -1235,14 +1235,8 @@ def build_polygons(slope_data, reinf_lines=None, debug=False):
1235
1235
  if max_depth is None:
1236
1236
  raise ValueError("When using only 1 profile line, max_depth must be specified")
1237
1237
 
1238
- def get_avg_y(line):
1239
- return sum(y for _, y in line) / len(line)
1240
-
1241
- # Sort profile lines from top to bottom by average y
1242
- sorted_lines = sorted(profile_lines, key=get_avg_y, reverse=True)
1243
- n = len(sorted_lines)
1244
- # Deep copy so we can insert points
1245
- lines = [list(line) for line in copy.deepcopy(sorted_lines)]
1238
+ n = len(profile_lines)
1239
+ lines = [list(line) for line in copy.deepcopy(profile_lines)]
1246
1240
  tol = 1e-8
1247
1241
 
1248
1242
  for i in range(n - 1):
@@ -1282,7 +1276,7 @@ def build_polygons(slope_data, reinf_lines=None, debug=False):
1282
1276
  if not found:
1283
1277
  insert_idx = np.searchsorted(xs_lower, x_top)
1284
1278
  lower.insert(insert_idx, (round(x_top, 6), round(y_proj, 6)))
1285
-
1279
+
1286
1280
  def clean_polygon(poly, tol=1e-8):
1287
1281
  # Remove consecutive duplicate points (except for closing point)
1288
1282
  if not poly:
@@ -1304,38 +1298,267 @@ def build_polygons(slope_data, reinf_lines=None, debug=False):
1304
1298
  ys_top = np.array(ys_top)
1305
1299
  left_x, left_y = xs_top[0], ys_top[0]
1306
1300
  right_x, right_y = xs_top[-1], ys_top[-1]
1301
+
1302
+ # Initialize variables for debug output
1303
+ lower_left_x = None
1304
+ lower_right_x = None
1305
+ proj_left_x = None
1306
+ proj_right_x = None
1307
+ bottom_cleaned = []
1308
+
1309
+ # Initialize vertical edge points (used for intermediate points on vertical edges)
1310
+ left_vertical_points = [] # Intermediate points on left vertical edge (bottom to top)
1311
+ right_vertical_points = [] # Intermediate points on right vertical edge (top to bottom)
1312
+ left_y_bot = -np.inf
1313
+ right_y_bot = -np.inf
1307
1314
 
1308
1315
  if i < n - 1:
1316
+ # Use the immediate next line as the lower boundary
1309
1317
  lower_line = lines[i + 1]
1310
1318
  xs_bot, ys_bot = zip(*lower_line)
1311
1319
  xs_bot = np.array(xs_bot)
1312
1320
  ys_bot = np.array(ys_bot)
1313
- # Project left and right endpoints vertically to lower profile
1314
- left_y_bot = np.interp(left_x, xs_bot, ys_bot)
1315
- right_y_bot = np.interp(right_x, xs_bot, ys_bot)
1316
- # Find all lower profile points between left_x and right_x (exclusive)
1317
- mask = (xs_bot > left_x) & (xs_bot < right_x)
1318
- xs_bot_in = xs_bot[mask]
1319
- ys_bot_in = ys_bot[mask]
1320
- # Build bottom boundary: right projection, lower profile points (right to left), left projection
1321
+ lower_left_x = xs_bot[0]
1322
+ lower_right_x = xs_bot[-1]
1323
+
1324
+ # Collect actual points from all lower lines within the top line's x-range
1325
+ # But only include a point if it's actually on the highest lower profile at that x
1326
+ bottom_points = [] # List of (x, y, line_idx) tuples
1327
+
1328
+ for j in range(i + 1, n):
1329
+ lower_candidate = lines[j]
1330
+ xs_cand = np.array([x for x, y in lower_candidate])
1331
+ ys_cand = np.array([y for x, y in lower_candidate])
1332
+
1333
+ # Only include points that are within the top line's x-range
1334
+ mask = (xs_cand >= left_x - tol) & (xs_cand <= right_x + tol)
1335
+ for x, y in zip(xs_cand[mask], ys_cand[mask]):
1336
+ # Check if this point is actually on the highest lower profile at this x
1337
+ # Compare with all other lower lines at this x-coordinate
1338
+ is_highest = True
1339
+ for k in range(i + 1, n):
1340
+ if k == j:
1341
+ continue
1342
+ other_line = lines[k]
1343
+ xs_other = np.array([x_o for x_o, y_o in other_line])
1344
+ ys_other = np.array([y_o for x_o, y_o in other_line])
1345
+ if xs_other[0] - tol <= x <= xs_other[-1] + tol:
1346
+ y_other = np.interp(x, xs_other, ys_other)
1347
+ if y_other > y + tol: # Other line is higher
1348
+ is_highest = False
1349
+ break
1350
+
1351
+ if is_highest:
1352
+ bottom_points.append((x, y, j))
1353
+
1354
+ # Group by x-coordinate (within tolerance) and keep only the highest y at each x
1355
+ # This handles cases where multiple lines have points at the same x
1356
+ bottom_dict = {} # x_key -> (y, line_idx, orig_x, orig_y)
1357
+ for x, y, line_idx in bottom_points:
1358
+ x_key = round(x / tol) * tol # Round to tolerance to group nearby points
1359
+ if x_key not in bottom_dict or y > bottom_dict[x_key][0]:
1360
+ bottom_dict[x_key] = (y, line_idx, x, y)
1361
+
1362
+ # Convert to sorted list
1363
+ bottom_cleaned = sorted([(orig_x, orig_y) for _, _, orig_x, orig_y in bottom_dict.values()])
1364
+
1365
+ # Helper function to check if a point already exists in a list
1366
+ def point_exists(point_list, x, y, tol=1e-8):
1367
+ """Check if a point (x, y) already exists in the point list within tolerance."""
1368
+ for px, py in point_list:
1369
+ if abs(px - x) < tol and abs(py - y) < tol:
1370
+ return True
1371
+ return False
1372
+
1373
+ # Helper function to find the lowest y value at a given x by checking all segments
1374
+ def find_lowest_y_at_x(line_points, x_query, tol=1e-8):
1375
+ """
1376
+ Find the lowest y value at x_query by checking all segments of the line.
1377
+ Handles vertical segments properly by finding all y values at that x and returning the minimum.
1378
+
1379
+ Returns:
1380
+ tuple: (y_value, is_at_endpoint) where is_at_endpoint indicates if x_query is at an endpoint
1381
+ """
1382
+ if not line_points:
1383
+ return None, False
1384
+
1385
+ xs = np.array([x for x, y in line_points])
1386
+ ys = np.array([y for x, y in line_points])
1387
+
1388
+ # Check if x_query is within the line's x-range
1389
+ if xs[0] - tol > x_query or xs[-1] + tol < x_query:
1390
+ return None, False
1391
+
1392
+ # Check if x_query is at an endpoint
1393
+ is_at_left_endpoint = abs(x_query - xs[0]) < tol
1394
+ is_at_right_endpoint = abs(x_query - xs[-1]) < tol
1395
+ is_at_endpoint = is_at_left_endpoint or is_at_right_endpoint
1396
+
1397
+ # Find all y values at x_query by checking all segments
1398
+ y_values = []
1399
+
1400
+ # Check all points that are exactly at x_query
1401
+ for k in range(len(line_points)):
1402
+ if abs(xs[k] - x_query) < tol:
1403
+ y_values.append(ys[k])
1404
+
1405
+ # Check all segments that contain x_query
1406
+ for k in range(len(line_points) - 1):
1407
+ x1, y1 = line_points[k]
1408
+ x2, y2 = line_points[k + 1]
1409
+
1410
+ # Check if segment is vertical and contains x_query
1411
+ if abs(x1 - x_query) < tol and abs(x2 - x_query) < tol:
1412
+ # Vertical segment - include both y values
1413
+ y_values.append(y1)
1414
+ y_values.append(y2)
1415
+ # Check if segment is horizontal or sloped and contains x_query
1416
+ elif min(x1, x2) - tol <= x_query <= max(x1, x2) + tol:
1417
+ # Interpolate y value
1418
+ if abs(x2 - x1) < tol:
1419
+ # Segment is vertical (should have been caught above, but just in case)
1420
+ y_values.append(y1)
1421
+ y_values.append(y2)
1422
+ else:
1423
+ # Linear interpolation
1424
+ t = (x_query - x1) / (x2 - x1)
1425
+ if 0 <= t <= 1:
1426
+ y_interp = y1 + t * (y2 - y1)
1427
+ y_values.append(y_interp)
1428
+
1429
+ if not y_values:
1430
+ return None, False
1431
+
1432
+ # Return the lowest y value
1433
+ y_min = min(y_values)
1434
+ return y_min, is_at_endpoint
1435
+
1436
+ # Project endpoints - find highest lower profile or use max_depth
1437
+ # When projecting right side: if intersection is at left end of lower line,
1438
+ # add that point but continue projecting down
1439
+ # When projecting left side: if intersection is at right end of lower line,
1440
+ # add that point but continue projecting down
1441
+ for j in range(i + 1, n):
1442
+ lower_candidate = lines[j]
1443
+ xs_cand = np.array([x for x, y in lower_candidate])
1444
+ ys_cand = np.array([y for x, y in lower_candidate])
1445
+
1446
+ # Check left endpoint projection
1447
+ if xs_cand[0] - tol <= left_x <= xs_cand[-1] + tol:
1448
+ y_cand, is_at_endpoint = find_lowest_y_at_x(lower_candidate, left_x, tol)
1449
+ if y_cand is not None:
1450
+ # If intersection is at the right end of the lower line, add point but continue
1451
+ if is_at_endpoint and abs(left_x - xs_cand[-1]) < tol: # At right endpoint
1452
+ # Only add if not duplicate of the endpoint being projected and not already in list
1453
+ if abs(y_cand - left_y) > tol and not point_exists(left_vertical_points, left_x, y_cand, tol):
1454
+ left_vertical_points.append((left_x, y_cand))
1455
+ else: # Not at endpoint, use as stopping point
1456
+ if y_cand > left_y_bot:
1457
+ left_y_bot = y_cand
1458
+
1459
+ # Check right endpoint projection
1460
+ if xs_cand[0] - tol <= right_x <= xs_cand[-1] + tol:
1461
+ y_cand, is_at_endpoint = find_lowest_y_at_x(lower_candidate, right_x, tol)
1462
+ if y_cand is not None:
1463
+ # If intersection is at the left end of the lower line, add point but continue
1464
+ if is_at_endpoint and abs(right_x - xs_cand[0]) < tol: # At left endpoint
1465
+ # Only add if not duplicate of the endpoint being projected and not already in list
1466
+ if abs(y_cand - right_y) > tol and not point_exists(right_vertical_points, right_x, y_cand, tol):
1467
+ right_vertical_points.append((right_x, y_cand))
1468
+ else: # Not at endpoint, use as stopping point
1469
+ if y_cand > right_y_bot:
1470
+ right_y_bot = y_cand
1471
+
1472
+ # If no lower profile at endpoints, use max_depth
1473
+ if left_y_bot == -np.inf:
1474
+ left_y_bot = max_depth if max_depth is not None else -np.inf
1475
+ if right_y_bot == -np.inf:
1476
+ right_y_bot = max_depth if max_depth is not None else -np.inf
1477
+
1478
+ # Deduplicate vertical points (remove points that are too close to each other)
1479
+ def deduplicate_points(points, tol=1e-8):
1480
+ """Remove duplicate points within tolerance."""
1481
+ if not points:
1482
+ return []
1483
+ unique_points = [points[0]]
1484
+ for p in points[1:]:
1485
+ # Check if this point is too close to any existing unique point
1486
+ is_duplicate = False
1487
+ for up in unique_points:
1488
+ if abs(p[0] - up[0]) < tol and abs(p[1] - up[1]) < tol:
1489
+ is_duplicate = True
1490
+ break
1491
+ if not is_duplicate:
1492
+ unique_points.append(p)
1493
+ return unique_points
1494
+
1495
+ right_vertical_points = deduplicate_points(right_vertical_points, tol)
1496
+ left_vertical_points = deduplicate_points(left_vertical_points, tol)
1497
+
1498
+ # Sort vertical points: right edge top to bottom, left edge bottom to top
1499
+ right_vertical_points.sort(key=lambda p: -p[1]) # Sort by y descending (top to bottom)
1500
+ left_vertical_points.sort(key=lambda p: p[1]) # Sort by y ascending (bottom to top)
1501
+
1502
+ # Build bottom boundary: right projection, intermediate points (right to left), left projection
1503
+ # The bottom should go from right to left to close the polygon
1321
1504
  bottom = []
1322
- bottom.append((right_x, right_y_bot))
1323
- for x, y in zip(xs_bot_in[::-1], ys_bot_in[::-1]):
1324
- bottom.append((x, y))
1325
- bottom.append((left_x, left_y_bot))
1505
+
1506
+ # Start with right endpoint
1507
+ if right_y_bot != -np.inf:
1508
+ bottom.append((right_x, right_y_bot))
1509
+
1510
+ # Add intermediate points in reverse order (right to left)
1511
+ # Filter out points too close to endpoints
1512
+ for x, y in reversed(bottom_cleaned):
1513
+ if abs(x - left_x) > tol and abs(x - right_x) > tol:
1514
+ bottom.append((x, y))
1515
+
1516
+ # End with left endpoint
1517
+ if left_y_bot != -np.inf:
1518
+ bottom.append((left_x, left_y_bot))
1519
+
1520
+ # Store for debug output
1521
+ proj_left_x = left_x
1522
+ proj_right_x = right_x
1326
1523
  else:
1327
1524
  # For the lowest polygon, bottom is at max_depth
1328
1525
  # Only need endpoints - no intermediate points
1526
+ left_y_bot = max_depth if max_depth is not None else -np.inf
1527
+ right_y_bot = max_depth if max_depth is not None else -np.inf
1329
1528
  bottom = []
1330
1529
  bottom.append((right_x, max_depth))
1331
1530
  bottom.append((left_x, max_depth))
1332
1531
 
1333
- # Build polygon: top left-to-right, bottom right-to-left
1532
+ # Build polygon: top left-to-right, right vertical edge (with intermediate points),
1533
+ # bottom right-to-left, left vertical edge (with intermediate points)
1334
1534
  poly = []
1535
+
1536
+ # Top edge: left to right along profile line
1335
1537
  for x, y in zip(xs_top, ys_top):
1336
1538
  poly.append((round(x, 6), round(y, 6)))
1539
+
1540
+ # Right vertical edge: from (right_x, right_y) down to (right_x, right_y_bot)
1541
+ # Include intermediate points where we intersect left endpoints of lower lines
1542
+ # Note: (right_x, right_y_bot) will be added as part of the bottom edge, so don't add it here
1543
+ if i < n - 1:
1544
+ for x, y in right_vertical_points:
1545
+ # Only add if it's between top and bottom (not duplicate of endpoints)
1546
+ if abs(y - right_y) > tol and abs(y - right_y_bot) > tol:
1547
+ poly.append((round(x, 6), round(y, 6)))
1548
+
1549
+ # Bottom edge: right to left (already includes (right_x, right_y_bot) and (left_x, left_y_bot))
1337
1550
  for x, y in bottom:
1338
1551
  poly.append((round(x, 6), round(y, 6)))
1552
+
1553
+ # Left vertical edge: from (left_x, left_y_bot) up to (left_x, left_y)
1554
+ # Include intermediate points where we intersect right endpoints of lower lines
1555
+ # Note: (left_x, left_y_bot) was already added as part of the bottom edge
1556
+ if i < n - 1:
1557
+ for x, y in reversed(left_vertical_points): # Reverse to go bottom to top
1558
+ # Only add if it's between bottom and top (not duplicate of endpoints)
1559
+ if abs(y - left_y_bot) > tol and abs(y - left_y) > tol:
1560
+ poly.append((round(x, 6), round(y, 6)))
1561
+
1339
1562
  # Clean up polygon (should rarely do anything)
1340
1563
  poly = clean_polygon(poly)
1341
1564
  polygons.append(poly)
xslope/plot.py CHANGED
@@ -86,8 +86,10 @@ def plot_max_depth(ax, profile_lines, max_depth):
86
86
  x_max = max(x_vals)
87
87
  ax.hlines(max_depth, x_min, x_max, colors='black', linewidth=1.5, label='Max Depth')
88
88
 
89
- spacing = 5
90
- length = 4
89
+ x_diff = x_max - x_min
90
+ spacing = x_diff / 100
91
+ length = x_diff / 80
92
+
91
93
  angle_rad = np.radians(60)
92
94
  dx = length * np.cos(angle_rad)
93
95
  dy = length * np.sin(angle_rad)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: xslope
3
- Version: 0.1.5
3
+ Version: 0.1.7
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
@@ -1,20 +1,21 @@
1
1
  xslope/__init__.py,sha256=ZygAIkX6Nbjag1czWdQa-yP-GM1mBE_9ss21Xh__JFc,34
2
- xslope/_version.py,sha256=m--aui80RX-2veIS6xYj8NeTcDQvEfbRibez2Th5nKQ,50
2
+ xslope/_version.py,sha256=Lc9xqzzg7cCgDTKs34dvk6NG0CArmowG_5qV27pZmSs,50
3
3
  xslope/advanced.py,sha256=35k_ekQJ-mQJ1wHnoIHMc5TVfYn-2EPCJyHz827Cpbc,18281
4
4
  xslope/fem.py,sha256=K4_Pq06HFBpRkN3JwtXF2zw_AMnCkXvj0ggrlRFMQQw,115731
5
5
  xslope/fileio.py,sha256=DnFUYmJedjKXOuVPZUfTRxGfTjiIz8KyHkRDK7ddQg0,28840
6
6
  xslope/global_config.py,sha256=Cj8mbPidIuj5Ty-5cZM-c8H12kNvyHsk5_ofNGez-3M,2253
7
- xslope/mesh.py,sha256=t3rD3H59epXeH-7RR_PDK7QUg13DRwl3qMRVERAhuv0,118744
8
- xslope/plot.py,sha256=rr1D-6KzYyJtq51o4yLyaFkowBa1LtygIk9rtA3GxgQ,54592
7
+ xslope/mesh copy.py,sha256=qtMH1yKFgHM4kNuIrxco7tKV86R3Dbf7Nok5j6MtlLY,131013
8
+ xslope/mesh.py,sha256=qtMH1yKFgHM4kNuIrxco7tKV86R3Dbf7Nok5j6MtlLY,131013
9
+ xslope/plot.py,sha256=OW2eZyrT5h3Y95qjNFgGMnC_Am7TurUB2S7b_BgnO6E,54641
9
10
  xslope/plot_fem.py,sha256=al9zjqjxWKooLl4SAds77Zz-j9cjD4TJGygyU9QK5vo,71111
10
11
  xslope/plot_seep.py,sha256=3wbKp85cmK_6EniFnK9x-FaZtYBZ3KZbRn9U1GsInmk,28775
11
12
  xslope/search.py,sha256=dvgKn8JCobuvyD7fClF5lcbeHESCvV8gZ_U_lQnYRok,16867
12
13
  xslope/seep.py,sha256=zVX8NJOp8e6VJ6y99YLkmOh_RPQFly9Z9UI3tka6yQQ,85765
13
14
  xslope/slice.py,sha256=QHawTk7XPLziHoN_ZS0jrEPYKlhPT62caUc_q-_EGjs,45236
14
15
  xslope/solve.py,sha256=j7N66QBjBpDAo-aizTiP8auwd5Ey1SiYAYeySaMVzkw,48554
15
- xslope-0.1.5.dist-info/LICENSE,sha256=NU5J88FUai_4Ixu5DqOQukA-gcXAyX8pXLhIg8OB3AM,10969
16
- xslope-0.1.5.dist-info/METADATA,sha256=-s6Lcc-tEGy2KJhBcP5oZTOlRXjnLO9bq4D3Mx0uLIA,2027
17
- xslope-0.1.5.dist-info/NOTICE,sha256=E-sbN0MWwvJC27Z-2_G4VUHIx4IsfvLDTmOstvY4-OQ,530
18
- xslope-0.1.5.dist-info/WHEEL,sha256=beeZ86-EfXScwlR_HKu4SllMC9wUEj_8Z_4FJ3egI2w,91
19
- xslope-0.1.5.dist-info/top_level.txt,sha256=5qHbWJ1R2pdTNIainFyrVtFk8R1tRcwIn0kzTuwuV1Q,7
20
- xslope-0.1.5.dist-info/RECORD,,
16
+ xslope-0.1.7.dist-info/LICENSE,sha256=NU5J88FUai_4Ixu5DqOQukA-gcXAyX8pXLhIg8OB3AM,10969
17
+ xslope-0.1.7.dist-info/METADATA,sha256=9oE9ygroQ0pg5sj1iz3KZ4GxKCUYg0N2oNDh9Kks6jI,2027
18
+ xslope-0.1.7.dist-info/NOTICE,sha256=E-sbN0MWwvJC27Z-2_G4VUHIx4IsfvLDTmOstvY4-OQ,530
19
+ xslope-0.1.7.dist-info/WHEEL,sha256=beeZ86-EfXScwlR_HKu4SllMC9wUEj_8Z_4FJ3egI2w,91
20
+ xslope-0.1.7.dist-info/top_level.txt,sha256=5qHbWJ1R2pdTNIainFyrVtFk8R1tRcwIn0kzTuwuV1Q,7
21
+ xslope-0.1.7.dist-info/RECORD,,
File without changes