0
# Grid and Discretization Classes
1
2
This module provides grid classes for structured, unstructured, and vertex-based discretizations with coordinate transformations, intersection utilities, and export capabilities. These classes form the spatial foundation for all FloPy models, handling geometric calculations, coordinate systems, and spatial data management.
3
4
## Base Grid Class
5
6
### Grid
7
8
Abstract base class that provides common functionality for all grid types.
9
10
```python { .api }
11
class Grid:
12
"""Base class for structured or unstructured model grids"""
13
def __init__(
14
self,
15
grid_type: str = None,
16
top: ArrayData = None,
17
botm: ArrayData = None,
18
idomain: ArrayData = None,
19
lenuni: int = 2,
20
epsg: int = None,
21
proj4: str = None,
22
prj: str = None,
23
xoff: float = 0.0,
24
yoff: float = 0.0,
25
angrot: float = 0.0,
26
**kwargs
27
): ...
28
29
def is_valid(self) -> bool:
30
"""Check if grid is valid and complete"""
31
...
32
33
def is_complete(self) -> bool:
34
"""Check if all required grid data is present"""
35
...
36
37
@property
38
def grid_type(self) -> str:
39
"""Grid type identifier"""
40
...
41
42
@property
43
def extent(self) -> tuple[float, float, float, float]:
44
"""Grid spatial extent (xmin, xmax, ymin, ymax)"""
45
...
46
47
@property
48
def shape(self) -> tuple[int, ...]:
49
"""Grid dimensions"""
50
...
51
52
@property
53
def size(self) -> int:
54
"""Total number of cells"""
55
...
56
57
@property
58
def xcellcenters(self) -> np.ndarray:
59
"""X-coordinates of cell centers"""
60
...
61
62
@property
63
def ycellcenters(self) -> np.ndarray:
64
"""Y-coordinates of cell centers"""
65
...
66
67
@property
68
def zcellcenters(self) -> np.ndarray:
69
"""Z-coordinates of cell centers"""
70
...
71
72
@property
73
def xvertices(self) -> np.ndarray:
74
"""X-coordinates of cell vertices"""
75
...
76
77
@property
78
def yvertices(self) -> np.ndarray:
79
"""Y-coordinates of cell vertices"""
80
...
81
82
@property
83
def zvertices(self) -> np.ndarray:
84
"""Z-coordinates of cell vertices"""
85
...
86
87
def get_coords(self, x: float, y: float) -> tuple[float, float]:
88
"""Transform coordinates to model coordinate system"""
89
...
90
91
def get_local_coords(self, x: float, y: float) -> tuple[float, float]:
92
"""Transform model coordinates to local coordinate system"""
93
...
94
95
def intersect(
96
self,
97
x: ArrayData,
98
y: ArrayData,
99
z: ArrayData = None,
100
local: bool = False,
101
forgive: bool = False
102
) -> np.ndarray:
103
"""Find grid cells intersected by points"""
104
...
105
106
def cross_section_vertices(
107
self,
108
line: dict,
109
local: bool = True
110
) -> np.ndarray:
111
"""Extract vertices for cross-section along line"""
112
...
113
114
def write_shapefile(
115
self,
116
filename: str,
117
array_dict: dict = None,
118
**kwargs
119
) -> None:
120
"""Export grid geometry to shapefile"""
121
...
122
123
@classmethod
124
def from_binary_grid_file(
125
cls,
126
file_path: str,
127
**kwargs
128
) -> 'Grid':
129
"""Load grid from binary grid file"""
130
...
131
132
def plot(self, **kwargs) -> object:
133
"""Plot the grid"""
134
...
135
```
136
137
## Structured Grid
138
139
### StructuredGrid
140
141
Regular rectangular grid discretization for traditional MODFLOW models.
142
143
```python { .api }
144
class StructuredGrid(Grid):
145
"""Regular rectangular grid discretization"""
146
def __init__(
147
self,
148
delc: ArrayData = None,
149
delr: ArrayData = None,
150
top: ArrayData = None,
151
botm: ArrayData = None,
152
idomain: ArrayData = None,
153
lenuni: int = 2,
154
epsg: int = None,
155
proj4: str = None,
156
prj: str = None,
157
xoff: float = 0.0,
158
yoff: float = 0.0,
159
angrot: float = 0.0,
160
nlay: int = None,
161
nrow: int = None,
162
ncol: int = None,
163
**kwargs
164
): ...
165
166
@property
167
def nlay(self) -> int:
168
"""Number of layers"""
169
...
170
171
@property
172
def nrow(self) -> int:
173
"""Number of rows"""
174
...
175
176
@property
177
def ncol(self) -> int:
178
"""Number of columns"""
179
...
180
181
@property
182
def delr(self) -> np.ndarray:
183
"""Column spacing (along rows)"""
184
...
185
186
@property
187
def delc(self) -> np.ndarray:
188
"""Row spacing (along columns)"""
189
...
190
191
@property
192
def delz(self) -> np.ndarray:
193
"""Layer thickness"""
194
...
195
196
@property
197
def cell_thickness(self) -> np.ndarray:
198
"""Cell thickness array"""
199
...
200
201
@property
202
def saturated_thickness(self, array: ArrayData) -> np.ndarray:
203
"""Calculate saturated thickness given head array"""
204
...
205
206
def is_regular_x(self, rtol: float = 1e-5) -> bool:
207
"""Check if column spacing is regular"""
208
...
209
210
def is_regular_y(self, rtol: float = 1e-5) -> bool:
211
"""Check if row spacing is regular"""
212
...
213
214
def is_regular_z(self, rtol: float = 1e-5) -> bool:
215
"""Check if layer spacing is regular"""
216
...
217
218
def get_lrc(self, nodes: list[int]) -> list[tuple[int, int, int]]:
219
"""Convert node numbers to layer-row-column indices"""
220
...
221
222
def get_node(self, lrc: list[tuple[int, int, int]]) -> list[int]:
223
"""Convert layer-row-column indices to node numbers"""
224
...
225
226
def get_vertices(self, i: int, j: int) -> list[tuple[float, float]]:
227
"""Get vertices for cell at row i, column j"""
228
...
229
230
def get_cellcenters(self) -> tuple[np.ndarray, np.ndarray]:
231
"""Get cell center coordinates"""
232
...
233
```
234
235
**Parameters:**
236
- `delr` (ArrayData): Column spacing array (length ncol)
237
- `delc` (ArrayData): Row spacing array (length nrow)
238
- `top` (ArrayData): Top elevation of layer 1
239
- `botm` (ArrayData): Bottom elevations of each layer
240
- `nlay` (int): Number of layers
241
- `nrow` (int): Number of rows
242
- `ncol` (int): Number of columns
243
244
## Unstructured Grid
245
246
### UnstructuredGrid
247
248
Flexible unstructured mesh discretization for complex geometries.
249
250
```python { .api }
251
class UnstructuredGrid(Grid):
252
"""Flexible unstructured mesh discretization"""
253
def __init__(
254
self,
255
vertices: ArrayData = None,
256
iverts: ArrayData = None,
257
xcenters: ArrayData = None,
258
ycenters: ArrayData = None,
259
top: ArrayData = None,
260
botm: ArrayData = None,
261
idomain: ArrayData = None,
262
lenuni: int = 2,
263
epsg: int = None,
264
proj4: str = None,
265
prj: str = None,
266
xoff: float = 0.0,
267
yoff: float = 0.0,
268
angrot: float = 0.0,
269
ncpl: ArrayData = None,
270
**kwargs
271
): ...
272
273
@property
274
def nnodes(self) -> int:
275
"""Number of nodes"""
276
...
277
278
@property
279
def nvertices(self) -> int:
280
"""Number of vertices"""
281
...
282
283
@property
284
def ncpl(self) -> np.ndarray:
285
"""Number of cells per layer"""
286
...
287
288
@property
289
def vertices(self) -> np.ndarray:
290
"""Vertex coordinates array"""
291
...
292
293
@property
294
def iverts(self) -> list[list[int]]:
295
"""Vertex indices for each cell"""
296
...
297
298
def get_vertices(self, cellid: int) -> np.ndarray:
299
"""Get vertices for specified cell"""
300
...
301
302
def get_cell_vertices(self, cellid: int) -> list[tuple[float, float]]:
303
"""Get vertex coordinates for cell"""
304
...
305
306
def get_cellcenters(self) -> tuple[np.ndarray, np.ndarray]:
307
"""Get cell center coordinates"""
308
...
309
```
310
311
**Parameters:**
312
- `vertices` (ArrayData): Vertex coordinate array
313
- `iverts` (ArrayData): Cell-to-vertex connectivity
314
- `xcenters` (ArrayData): Cell center x-coordinates
315
- `ycenters` (ArrayData): Cell center y-coordinates
316
- `ncpl` (ArrayData): Number of cells per layer
317
318
## Vertex Grid
319
320
### VertexGrid
321
322
DISV-type vertex-based grid discretization combining structured layers with unstructured horizontal discretization.
323
324
```python { .api }
325
class VertexGrid(Grid):
326
"""DISV-type vertex-based grid discretization"""
327
def __init__(
328
self,
329
vertices: ArrayData = None,
330
cell2d: ArrayData = None,
331
top: ArrayData = None,
332
botm: ArrayData = None,
333
idomain: ArrayData = None,
334
lenuni: int = 2,
335
epsg: int = None,
336
proj4: str = None,
337
prj: str = None,
338
xoff: float = 0.0,
339
yoff: float = 0.0,
340
angrot: float = 0.0,
341
nlay: int = None,
342
ncpl: int = None,
343
nvert: int = None,
344
**kwargs
345
): ...
346
347
@property
348
def nlay(self) -> int:
349
"""Number of layers"""
350
...
351
352
@property
353
def ncpl(self) -> int:
354
"""Number of cells per layer"""
355
...
356
357
@property
358
def nvert(self) -> int:
359
"""Number of vertices"""
360
...
361
362
@property
363
def vertices(self) -> np.ndarray:
364
"""Vertex coordinates"""
365
...
366
367
@property
368
def cell2d(self) -> np.ndarray:
369
"""2D cell definition array"""
370
...
371
372
def get_vertices(self, cellid: int) -> np.ndarray:
373
"""Get vertices for cell"""
374
...
375
376
def get_cell_vertices(self, cellid: int) -> list[tuple[float, float]]:
377
"""Get vertex coordinates for cell"""
378
...
379
```
380
381
**Parameters:**
382
- `vertices` (ArrayData): Vertex coordinate pairs
383
- `cell2d` (ArrayData): Cell definition with vertex indices
384
- `nlay` (int): Number of layers
385
- `ncpl` (int): Number of cells per layer
386
- `nvert` (int): Number of vertices
387
388
## Temporal Discretization
389
390
### ModelTime
391
392
Temporal discretization management for transient simulations.
393
394
```python { .api }
395
class ModelTime:
396
"""Temporal discretization management"""
397
def __init__(
398
self,
399
perioddata: list[tuple] = None,
400
time_units: str = 'days',
401
start_datetime: str = None,
402
**kwargs
403
): ...
404
405
@property
406
def nper(self) -> int:
407
"""Number of stress periods"""
408
...
409
410
@property
411
def perlen(self) -> list[float]:
412
"""Length of each stress period"""
413
...
414
415
@property
416
def nstp(self) -> list[int]:
417
"""Number of time steps in each stress period"""
418
...
419
420
@property
421
def tsmult(self) -> list[float]:
422
"""Time step multipliers"""
423
...
424
425
@property
426
def steady(self) -> list[bool]:
427
"""Steady state flags for each stress period"""
428
...
429
430
@property
431
def totim(self) -> list[float]:
432
"""Total elapsed times"""
433
...
434
435
def get_totim(self, kstp: int, kper: int) -> float:
436
"""Get total time for time step and stress period"""
437
...
438
439
def get_kstpkper(self, totim: float) -> tuple[int, int]:
440
"""Get time step and stress period for total time"""
441
...
442
```
443
444
**Parameters:**
445
- `perioddata` (list[tuple]): List of (perlen, nstp, tsmult) tuples
446
- `time_units` (str): Time units ('days', 'hours', 'minutes', 'seconds', 'years')
447
- `start_datetime` (str): Starting date/time in ISO format
448
449
## Caching and Performance
450
451
### CachedData
452
453
Caching mechanism for expensive grid calculations.
454
455
```python { .api }
456
class CachedData:
457
"""Caching mechanism for grid data"""
458
def __init__(
459
self,
460
data_func: callable,
461
cache_size: int = 128,
462
**kwargs
463
): ...
464
465
def get_data(self, *args, **kwargs) -> object:
466
"""Get cached or computed data"""
467
...
468
469
def clear_cache(self) -> None:
470
"""Clear the cache"""
471
...
472
473
@property
474
def cache_info(self) -> dict:
475
"""Cache statistics"""
476
...
477
```
478
479
## Grid Utility Functions
480
481
```python { .api }
482
def array_at_verts_basic2d(
483
array: ArrayData,
484
grid: StructuredGrid
485
) -> np.ndarray:
486
"""Convert cell-centered arrays to vertex-based for 2D structured grids"""
487
...
488
489
def array_at_faces_1d(
490
array: ArrayData,
491
grid: StructuredGrid,
492
direction: str = 'x'
493
) -> np.ndarray:
494
"""Convert cell-centered arrays to face-centered values"""
495
...
496
497
def create_structured_grid(
498
delr: ArrayData,
499
delc: ArrayData,
500
top: ArrayData = None,
501
botm: ArrayData = None,
502
**kwargs
503
) -> StructuredGrid:
504
"""Factory function for creating structured grids"""
505
...
506
507
def create_unstructured_grid(
508
vertices: ArrayData,
509
iverts: ArrayData,
510
**kwargs
511
) -> UnstructuredGrid:
512
"""Factory function for creating unstructured grids"""
513
...
514
515
def create_vertex_grid(
516
vertices: ArrayData,
517
cell2d: ArrayData,
518
**kwargs
519
) -> VertexGrid:
520
"""Factory function for creating vertex grids"""
521
...
522
```
523
524
## Usage Examples
525
526
### Basic Structured Grid
527
528
```python
529
import flopy
530
import numpy as np
531
532
# Create simple structured grid
533
nlay, nrow, ncol = 3, 50, 100
534
delr = np.ones(ncol) * 100.0 # 100m columns
535
delc = np.ones(nrow) * 100.0 # 100m rows
536
top = 50.0
537
botm = [30.0, 10.0, -10.0]
538
539
# Create grid
540
grid = flopy.discretization.StructuredGrid(
541
delr=delr,
542
delc=delc,
543
top=top,
544
botm=botm,
545
nlay=nlay,
546
nrow=nrow,
547
ncol=ncol
548
)
549
550
# Grid properties
551
print(f"Grid shape: {grid.shape}")
552
print(f"Grid extent: {grid.extent}")
553
print(f"Total cells: {grid.size}")
554
555
# Cell centers
556
xc, yc = grid.get_cellcenters()
557
print(f"Cell centers shape: {xc.shape}")
558
559
# Check regularity
560
print(f"Regular X spacing: {grid.is_regular_x()}")
561
print(f"Regular Y spacing: {grid.is_regular_y()}")
562
```
563
564
### Advanced Structured Grid with Coordinate System
565
566
```python
567
import flopy
568
import numpy as np
569
570
# Create grid with coordinate system and rotation
571
nlay, nrow, ncol = 5, 100, 200
572
573
# Variable column spacing (finer near pumping well)
574
delr = np.ones(ncol) * 50.0
575
delr[80:120] = 25.0 # Finer spacing in middle
576
577
# Variable row spacing
578
delc = np.ones(nrow) * 50.0
579
delc[40:60] = 25.0 # Finer spacing in middle
580
581
# Complex layer structure
582
top = 100.0
583
layer_thicks = [10.0, 15.0, 20.0, 25.0, 30.0]
584
botm = [top - sum(layer_thicks[:i+1]) for i in range(nlay)]
585
586
# Create grid with UTM coordinates and rotation
587
grid = flopy.discretization.StructuredGrid(
588
delr=delr,
589
delc=delc,
590
top=top,
591
botm=botm,
592
nlay=nlay,
593
nrow=nrow,
594
ncol=ncol,
595
xoff=500000.0, # UTM easting offset
596
yoff=4000000.0, # UTM northing offset
597
angrot=15.0, # 15 degree rotation
598
epsg=32633 # UTM Zone 33N
599
)
600
601
# Coordinate transformations
602
local_x, local_y = 2500.0, 2500.0 # Local coordinates
603
model_x, model_y = grid.get_coords(local_x, local_y)
604
print(f"Model coordinates: {model_x:.1f}, {model_y:.1f}")
605
606
# Find intersecting cells
607
points_x = [2000.0, 3000.0, 4000.0]
608
points_y = [2000.0, 3000.0, 4000.0]
609
cells = grid.intersect(points_x, points_y)
610
print(f"Intersecting cells: {cells}")
611
612
# Export to shapefile
613
grid.write_shapefile(
614
'model_grid.shp',
615
array_dict={'layer_thickness': grid.delz}
616
)
617
```
618
619
### Unstructured Grid Creation
620
621
```python
622
import flopy
623
import numpy as np
624
625
# Create triangular mesh vertices
626
nvert = 100
627
vertices = []
628
629
# Generate random vertices in domain
630
np.random.seed(42)
631
x_coords = np.random.uniform(0, 5000, nvert)
632
y_coords = np.random.uniform(0, 5000, nvert)
633
vertices = list(zip(x_coords, y_coords))
634
635
# Define cell connectivity (simplified - would use mesh generator)
636
# Each cell defined by vertex indices
637
iverts = []
638
ncells = 150
639
for i in range(ncells):
640
# Triangular cells with random vertices
641
vert_indices = np.random.choice(nvert, 3, replace=False)
642
iverts.append(list(vert_indices))
643
644
# Cell centers (computed from vertices)
645
xcenters = []
646
ycenters = []
647
for cell_verts in iverts:
648
x_cell = np.mean([vertices[i][0] for i in cell_verts])
649
y_cell = np.mean([vertices[i][1] for i in cell_verts])
650
xcenters.append(x_cell)
651
ycenters.append(y_cell)
652
653
# Layer structure
654
nlay = 4
655
top = np.ones(ncells) * 50.0
656
botm = np.array([30.0, 10.0, -10.0, -30.0])
657
botm_3d = np.repeat(botm.reshape(nlay, 1), ncells, axis=1)
658
659
# Create unstructured grid
660
grid = flopy.discretization.UnstructuredGrid(
661
vertices=vertices,
662
iverts=iverts,
663
xcenters=xcenters,
664
ycenters=ycenters,
665
top=top,
666
botm=botm_3d,
667
ncpl=[ncells] * nlay
668
)
669
670
print(f"Unstructured grid nodes: {grid.nnodes}")
671
print(f"Grid extent: {grid.extent}")
672
673
# Get vertices for specific cell
674
cell_vertices = grid.get_cell_vertices(0)
675
print(f"Cell 0 vertices: {cell_vertices}")
676
```
677
678
### Vertex Grid for MODFLOW 6 DISV
679
680
```python
681
import flopy
682
import numpy as np
683
684
# Create vertex grid for DISV package
685
nlay = 3
686
ncpl = 50 # cells per layer
687
nvert = 75 # vertices
688
689
# Hexagonal grid vertices (simplified)
690
vertices = []
691
for i in range(nvert):
692
angle = 2 * np.pi * i / nvert
693
radius = 1000.0 + 500.0 * (i % 5) / 4 # Variable radius
694
x = radius * np.cos(angle)
695
y = radius * np.sin(angle)
696
vertices.append((x, y))
697
698
# Cell definitions (cell center x, y, vertex indices)
699
cell2d = []
700
for i in range(ncpl):
701
# Cell center
702
angle = 2 * np.pi * i / ncpl
703
x_center = 800.0 * np.cos(angle)
704
y_center = 800.0 * np.sin(angle)
705
706
# Connect to nearest vertices (simplified)
707
vert_indices = [(i + j) % nvert for j in range(6)]
708
cell_data = [i, x_center, y_center] + vert_indices
709
cell2d.append(cell_data)
710
711
# Layer elevations
712
top = np.ones(ncpl) * 50.0
713
botm_layer1 = np.ones(ncpl) * 30.0
714
botm_layer2 = np.ones(ncpl) * 10.0
715
botm_layer3 = np.ones(ncpl) * -10.0
716
botm = [botm_layer1, botm_layer2, botm_layer3]
717
718
# Create vertex grid
719
grid = flopy.discretization.VertexGrid(
720
vertices=vertices,
721
cell2d=cell2d,
722
top=top,
723
botm=botm,
724
nlay=nlay,
725
ncpl=ncpl,
726
nvert=nvert
727
)
728
729
print(f"Vertex grid shape: {grid.shape}")
730
print(f"Cells per layer: {grid.ncpl}")
731
print(f"Total vertices: {grid.nvert}")
732
733
# Export for visualization
734
grid.write_shapefile(
735
'vertex_grid.shp',
736
array_dict={'top_elev': grid.top}
737
)
738
```
739
740
### Time Discretization
741
742
```python
743
import flopy
744
from datetime import datetime
745
746
# Create complex temporal discretization
747
# Stress periods: steady state, then monthly for 2 years
748
perioddata = []
749
750
# Initial steady state period
751
perioddata.append((1.0, 1, 1.0))
752
753
# Monthly periods with increasing time steps
754
import calendar
755
for year in [2023, 2024]:
756
for month in range(1, 13):
757
days_in_month = calendar.monthrange(year, month)[1]
758
# More time steps in first few months
759
if len(perioddata) <= 6:
760
nstp = 10
761
tsmult = 1.2
762
else:
763
nstp = 5
764
tsmult = 1.1
765
766
perioddata.append((days_in_month, nstp, tsmult))
767
768
# Create model time object
769
model_time = flopy.discretization.ModelTime(
770
perioddata=perioddata,
771
time_units='days',
772
start_datetime='2023-01-01T00:00:00'
773
)
774
775
print(f"Number of stress periods: {model_time.nper}")
776
print(f"Total simulation time: {model_time.totim[-1]} days")
777
778
# Get specific time information
779
totim_6months = model_time.get_totim(4, 6) # 5th time step, 7th period
780
print(f"Time at 6 months: {totim_6months} days")
781
782
# Find time step for specific time
783
kstpkper = model_time.get_kstpkper(365.0) # One year
784
print(f"Time step/period for 1 year: {kstpkper}")
785
786
# Steady state periods
787
steady_periods = [i for i, steady in enumerate(model_time.steady) if steady]
788
print(f"Steady state periods: {steady_periods}")
789
```
790
791
## Common Types
792
793
```python { .api }
794
# Grid and discretization types
795
ArrayData = Union[int, float, np.ndarray, list]
796
GridType = Literal['structured', 'unstructured', 'vertex']
797
CoordinateSystem = Union[int, str] # EPSG code or proj4 string
798
799
# Vertex and connectivity data
800
VertexData = list[tuple[float, float]]
801
CellConnectivity = list[list[int]]
802
Cell2DData = list[list[Union[int, float]]]
803
804
# Temporal discretization
805
PeriodData = list[tuple[float, int, float]] # (perlen, nstp, tsmult)
806
TimeUnits = Literal['days', 'hours', 'minutes', 'seconds', 'years']
807
808
# Spatial arrays
809
ElevationArray = Union[float, list[float], np.ndarray]
810
ThicknessArray = Union[float, list[float], np.ndarray]
811
SpacingArray = Union[float, list[float], np.ndarray]
812
DomainArray = Union[int, list[int], np.ndarray]
813
814
# Coordinate types
815
Coordinates = tuple[float, float]
816
Extent = tuple[float, float, float, float] # xmin, xmax, ymin, ymax
817
CellIndex = Union[int, tuple[int, ...]]
818
```
819
820
This comprehensive documentation covers the complete discretization API including structured, unstructured, and vertex grids, temporal discretization, and utility functions. The examples demonstrate basic to advanced grid creation scenarios with coordinate systems, variable spacing, and complex geometries.