or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

discretization.mdexport.mdfile-io.mdindex.mdmodflow2005.mdmodflow6.mdparticle-tracking.mdplotting.mdtransport.mdutilities.md

discretization.mddocs/

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.