|
11 | 11 | from matplotlib import pyplot as plt |
12 | 12 | from modflow_devtools.markers import requires_exe, requires_pkg |
13 | 13 | from modflow_devtools.misc import has_pkg |
| 14 | +from scipy.spatial import Delaunay |
14 | 15 |
|
15 | 16 | from autotest.test_dis_cases import case_dis, case_disv |
16 | 17 | from autotest.test_grid_cases import GridCases |
@@ -77,6 +78,17 @@ def minimal_vertex_grid_info(minimal_unstructured_grid_info): |
77 | 78 | return d |
78 | 79 |
|
79 | 80 |
|
| 81 | +@pytest.fixture |
| 82 | +def simple_structured_grid(): |
| 83 | + """Create a simple 10x10 structured grid for testing.""" |
| 84 | + nrow, ncol = 10, 10 |
| 85 | + delr = np.ones(ncol) * 10.0 |
| 86 | + delc = np.ones(nrow) * 10.0 |
| 87 | + top = np.ones((nrow, ncol)) * 10.0 |
| 88 | + botm = np.zeros((1, nrow, ncol)) |
| 89 | + return StructuredGrid(delr=delr, delc=delc, top=top, botm=botm) |
| 90 | + |
| 91 | + |
80 | 92 | def test_rotation(): |
81 | 93 | m = Modflow(rotation=20.0) |
82 | 94 | dis = ModflowDis( |
@@ -419,6 +431,127 @@ def test_unstructured_xyz_intersect(example_data_path): |
419 | 431 | raise AssertionError("Unstructured grid intersection failed") |
420 | 432 |
|
421 | 433 |
|
| 434 | +def test_structured_grid_intersect_array(simple_structured_grid): |
| 435 | + """Test StructuredGrid.intersect() with array inputs.""" |
| 436 | + grid = simple_structured_grid |
| 437 | + |
| 438 | + # Test array input |
| 439 | + x = np.array([25.0, 50.0, 75.0]) |
| 440 | + y = np.array([25.0, 50.0, 75.0]) |
| 441 | + rows, cols = grid.intersect(x, y, forgive=True) |
| 442 | + |
| 443 | + # Verify array output |
| 444 | + assert isinstance(rows, np.ndarray) |
| 445 | + assert isinstance(cols, np.ndarray) |
| 446 | + assert len(rows) == 3 |
| 447 | + assert len(cols) == 3 |
| 448 | + |
| 449 | + # Verify equivalence with scalar calls |
| 450 | + for i in range(len(x)): |
| 451 | + row_scalar, col_scalar = grid.intersect(x[i], y[i], forgive=True) |
| 452 | + assert rows[i] == row_scalar |
| 453 | + assert cols[i] == col_scalar |
| 454 | + |
| 455 | + # Test out-of-bounds with forgive |
| 456 | + x_mixed = np.array([50.0, 150.0]) |
| 457 | + y_mixed = np.array([50.0, 50.0]) |
| 458 | + rows_mixed, cols_mixed = grid.intersect(x_mixed, y_mixed, forgive=True) |
| 459 | + assert not np.isnan(rows_mixed[0]) # First point valid |
| 460 | + assert np.isnan(rows_mixed[1]) # Second point out of bounds |
| 461 | + |
| 462 | + |
| 463 | +def test_vertex_grid_intersect_array(): |
| 464 | + """Test VertexGrid.intersect() with array inputs.""" |
| 465 | + # Create a simple vertex grid using Delaunay triangulation |
| 466 | + np.random.seed(42) |
| 467 | + n_points = 50 |
| 468 | + x_verts = np.random.uniform(0, 100, n_points) |
| 469 | + y_verts = np.random.uniform(0, 100, n_points) |
| 470 | + points = np.column_stack([x_verts, y_verts]) |
| 471 | + |
| 472 | + tri = Delaunay(points) |
| 473 | + vertices = [[i, x_verts[i], y_verts[i]] for i in range(len(x_verts))] |
| 474 | + cell2d = [[i] + list(tri.simplices[i]) for i in range(len(tri.simplices))] |
| 475 | + |
| 476 | + ncells = len(cell2d) |
| 477 | + top = np.ones(ncells) * 10.0 |
| 478 | + botm = np.zeros(ncells) |
| 479 | + grid = VertexGrid(vertices=vertices, cell2d=cell2d, top=top, botm=botm) |
| 480 | + |
| 481 | + # Test array input |
| 482 | + x = np.array([25.0, 50.0, 75.0]) |
| 483 | + y = np.array([25.0, 50.0, 75.0]) |
| 484 | + results = grid.intersect(x, y, forgive=True) |
| 485 | + |
| 486 | + # Verify array output |
| 487 | + assert isinstance(results, np.ndarray) |
| 488 | + assert len(results) == 3 |
| 489 | + |
| 490 | + # Verify equivalence with scalar calls |
| 491 | + for i in range(len(x)): |
| 492 | + result_scalar = grid.intersect(x[i], y[i], forgive=True) |
| 493 | + if np.isnan(results[i]) and np.isnan(result_scalar): |
| 494 | + continue |
| 495 | + assert results[i] == result_scalar |
| 496 | + |
| 497 | + # Test out-of-bounds with forgive |
| 498 | + x_mixed = np.array([50.0, 200.0]) |
| 499 | + y_mixed = np.array([50.0, 50.0]) |
| 500 | + results_mixed = grid.intersect(x_mixed, y_mixed, forgive=True) |
| 501 | + assert np.isnan(results_mixed[1]) # Second point out of bounds |
| 502 | + |
| 503 | + |
| 504 | +def test_unstructured_grid_intersect_array(): |
| 505 | + """Test UnstructuredGrid.intersect() with array inputs.""" |
| 506 | + # Create a simple unstructured grid using Delaunay triangulation |
| 507 | + np.random.seed(42) |
| 508 | + n_points = 50 |
| 509 | + x_verts = np.random.uniform(0, 100, n_points) |
| 510 | + y_verts = np.random.uniform(0, 100, n_points) |
| 511 | + points = np.column_stack([x_verts, y_verts]) |
| 512 | + |
| 513 | + tri = Delaunay(points) |
| 514 | + vertices = [[i, x_verts[i], y_verts[i]] for i in range(len(x_verts))] |
| 515 | + iverts = tri.simplices.tolist() |
| 516 | + |
| 517 | + xcenters = np.mean(points[tri.simplices], axis=1)[:, 0] |
| 518 | + ycenters = np.mean(points[tri.simplices], axis=1)[:, 1] |
| 519 | + |
| 520 | + ncells = len(iverts) |
| 521 | + top = np.ones(ncells) * 10.0 |
| 522 | + botm = np.zeros(ncells) |
| 523 | + grid = UnstructuredGrid( |
| 524 | + vertices=vertices, |
| 525 | + iverts=iverts, |
| 526 | + xcenters=xcenters, |
| 527 | + ycenters=ycenters, |
| 528 | + top=top, |
| 529 | + botm=botm, |
| 530 | + ) |
| 531 | + |
| 532 | + # Test array input |
| 533 | + x = np.array([25.0, 50.0, 75.0]) |
| 534 | + y = np.array([25.0, 50.0, 75.0]) |
| 535 | + results = grid.intersect(x, y, forgive=True) |
| 536 | + |
| 537 | + # Verify array output |
| 538 | + assert isinstance(results, np.ndarray) |
| 539 | + assert len(results) == 3 |
| 540 | + |
| 541 | + # Verify equivalence with scalar calls |
| 542 | + for i in range(len(x)): |
| 543 | + result_scalar = grid.intersect(x[i], y[i], forgive=True) |
| 544 | + if np.isnan(results[i]) and np.isnan(result_scalar): |
| 545 | + continue |
| 546 | + assert results[i] == result_scalar |
| 547 | + |
| 548 | + # Test out-of-bounds with forgive |
| 549 | + x_mixed = np.array([50.0, 200.0]) |
| 550 | + y_mixed = np.array([50.0, 50.0]) |
| 551 | + results_mixed = grid.intersect(x_mixed, y_mixed, forgive=True) |
| 552 | + assert np.isnan(results_mixed[1]) # Second point out of bounds |
| 553 | + |
| 554 | + |
422 | 555 | @pytest.mark.parametrize("spc_file", ["grd.spc", "grdrot.spc"]) |
423 | 556 | def test_structured_from_gridspec(example_data_path, spc_file): |
424 | 557 | fn = example_data_path / "specfile" / spc_file |
@@ -1522,3 +1655,20 @@ def test_unstructured_iverts_cleanup(): |
1522 | 1655 |
|
1523 | 1656 | if clean_ugrid.nvert != cleaned_vert_num: |
1524 | 1657 | raise AssertionError("Improper number of vertices for cleaned 'shared' iverts") |
| 1658 | + |
| 1659 | + |
| 1660 | +def test_structured_grid_intersect_edge_case(simple_structured_grid): |
| 1661 | + """Test StructuredGrid.intersect() edge case: out-of-bounds x,y with z. |
| 1662 | +
|
| 1663 | + This tests the specific case where x,y are out of bounds and z is provided. |
| 1664 | + The expected behavior is to return (None, nan, nan). |
| 1665 | + """ |
| 1666 | + grid = simple_structured_grid |
| 1667 | + |
| 1668 | + # Test out-of-bounds x,y with z coordinate |
| 1669 | + lay, row, col = grid.intersect(-50.0, -50.0, z=5.0, forgive=True) |
| 1670 | + |
| 1671 | + # Expected behavior: lay=None, row=nan, col=nan |
| 1672 | + assert lay is None, f"Expected lay=None, got {lay}" |
| 1673 | + assert np.isnan(row), f"Expected row=nan, got {row}" |
| 1674 | + assert np.isnan(col), f"Expected col=nan, got {col}" |
0 commit comments