Core Modules#

The core modules provide the fundamental data structures for representing IWFM models, including meshes, stratigraphy, and time series data.

Mesh Module#

The mesh module contains classes for representing finite element meshes, including Node, Element, Face, and AppGrid.

Mesh classes for IWFM model representation.

This module provides the core mesh data structures including:

  • Node: Mesh vertices with coordinates and connectivity

  • Element: Triangular or quadrilateral finite elements

  • Face: Element edges for flux calculations

  • Subregion: Named groups of elements

  • AppGrid: The complete mesh container (mirrors IWFM’s Class_AppGrid)

Example

Create a simple triangular mesh:

>>> from pyiwfm.core.mesh import AppGrid, Node, Element
>>> nodes = {
...     1: Node(id=1, x=0.0, y=0.0),
...     2: Node(id=2, x=100.0, y=0.0),
...     3: Node(id=3, x=50.0, y=100.0),
... }
>>> elements = {
...     1: Element(id=1, vertices=(1, 2, 3), subregion=1),
... }
>>> grid = AppGrid(nodes=nodes, elements=elements)
>>> grid.compute_connectivity()
>>> print(f"Mesh: {grid.n_nodes} nodes, {grid.n_elements} elements")
Mesh: 3 nodes, 1 elements
class pyiwfm.core.mesh.Node(id, x, y, area=0.0, is_boundary=False, connected_nodes=<factory>, surrounding_elements=<factory>)[source]#

Bases: object

A mesh node (vertex) with coordinates and connectivity information.

Parameters:
  • id (int) – Unique node identifier (1-based in IWFM).

  • x (float) – X coordinate in model units.

  • y (float) – Y coordinate in model units.

  • area (float, optional) – Node area for water budget calculations (Voronoi-like area). Default is 0.0, computed by AppGrid.compute_areas().

  • is_boundary (bool, optional) – True if node is on the model boundary. Default is False, computed by AppGrid.compute_connectivity().

  • connected_nodes (list of int, optional) – IDs of directly connected nodes. Computed automatically.

  • surrounding_elements (list of int, optional) – IDs of elements containing this node. Computed automatically.

Examples

Create a simple node:

>>> node = Node(id=1, x=100.0, y=200.0)
>>> print(f"Node {node.id} at ({node.x}, {node.y})")
Node 1 at (100.0, 200.0)

Create a boundary node:

>>> boundary_node = Node(id=2, x=0.0, y=0.0, is_boundary=True)
>>> boundary_node.is_boundary
True

Calculate distance between nodes:

>>> n1 = Node(id=1, x=0.0, y=0.0)
>>> n2 = Node(id=2, x=3.0, y=4.0)
>>> n1.distance_to(n2)
5.0
id: int#
x: float#
y: float#
area: float = 0.0#
is_boundary: bool = False#
connected_nodes: list[int]#
surrounding_elements: list[int]#
property coordinates: tuple[float, float]#

Return (x, y) coordinate tuple.

distance_to(other)[source]#

Calculate Euclidean distance to another node.

__init__(id, x, y, area=0.0, is_boundary=False, connected_nodes=<factory>, surrounding_elements=<factory>)#
class pyiwfm.core.mesh.Element(id, vertices, subregion=0, area=0.0)[source]#

Bases: object

A finite element (triangle or quadrilateral).

IWFM supports both triangular (3 vertices) and quadrilateral (4 vertices) elements. Vertices must be ordered counter-clockwise.

Parameters:
  • id (int) – Unique element identifier (1-based in IWFM).

  • vertices (tuple of int) – Node IDs defining the element (3 or 4 nodes, counter-clockwise order).

  • subregion (int, optional) – Subregion ID this element belongs to. Default is 0 (unassigned).

  • area (float, optional) – Element area in coordinate units squared. Default is 0.0, computed by AppGrid.compute_areas().

Raises:

MeshError – If number of vertices is not 3 or 4.

Examples

Create a triangular element:

>>> tri = Element(id=1, vertices=(1, 2, 3), subregion=1)
>>> tri.is_triangle
True
>>> tri.n_vertices
3

Create a quadrilateral element:

>>> quad = Element(id=2, vertices=(1, 2, 5, 4), subregion=1)
>>> quad.is_quad
True
>>> quad.n_vertices
4

Get element edges:

>>> tri = Element(id=1, vertices=(1, 2, 3))
>>> tri.edges
[(1, 2), (2, 3), (3, 1)]
id: int#
vertices: tuple[int, ...]#
subregion: int = 0#
area: float = 0.0#
__post_init__()[source]#

Validate element after initialization.

property is_triangle: bool#

Return True if element is a triangle.

property is_quad: bool#

Return True if element is a quadrilateral.

property n_vertices: int#

Return number of vertices.

property edges: list[tuple[int, int]]#

Return list of edge tuples (node1_id, node2_id).

Edges are returned in order around the element, closing back to first vertex.

__init__(id, vertices, subregion=0, area=0.0)#
class pyiwfm.core.mesh.Face(id, nodes, elements)[source]#

Bases: object

An element face (edge) shared between elements.

Faces are used for flux calculations between elements.

Variables:
  • id (int) – Unique face identifier

  • nodes (tuple[int, int]) – Tuple of two node IDs defining the face

  • elements (tuple[int, int | None]) – Tuple of (element1_id, element2_id). Second element is None for boundary.

id: int#
nodes: tuple[int, int]#
elements: tuple[int, int | None]#
property is_boundary: bool#

Return True if this face is on the model boundary.

__init__(id, nodes, elements)#
class pyiwfm.core.mesh.Subregion(id, name='')[source]#

Bases: object

A named group of elements for reporting purposes.

Variables:
  • id (int) – Unique subregion identifier (1-based)

  • name (str) – Descriptive name for the subregion

id: int#
name: str = ''#
__init__(id, name='')#
class pyiwfm.core.mesh.AppGrid(nodes, elements=<factory>, faces=<factory>, subregions=<factory>, _x_cache=None, _y_cache=None, _vertex_cache=None, _node_id_to_idx=None)[source]#

Bases: object

The complete finite element mesh for an IWFM model.

This class mirrors IWFM’s Class_AppGrid and contains all mesh geometry, topology, and connectivity information.

Parameters:
  • nodes (dict) – Dictionary mapping node ID to Node object.

  • elements (dict, optional) – Dictionary mapping element ID to Element object.

  • faces (dict, optional) – Dictionary mapping face ID to Face object. Built automatically by build_faces().

  • subregions (dict, optional) – Dictionary mapping subregion ID to Subregion object.

Examples

Create a 2x2 quad mesh:

>>> from pyiwfm.core.mesh import AppGrid, Node, Element
>>> nodes = {
...     1: Node(id=1, x=0.0, y=0.0),
...     2: Node(id=2, x=100.0, y=0.0),
...     3: Node(id=3, x=200.0, y=0.0),
...     4: Node(id=4, x=0.0, y=100.0),
...     5: Node(id=5, x=100.0, y=100.0),
...     6: Node(id=6, x=200.0, y=100.0),
... }
>>> elements = {
...     1: Element(id=1, vertices=(1, 2, 5, 4), subregion=1),
...     2: Element(id=2, vertices=(2, 3, 6, 5), subregion=1),
... }
>>> grid = AppGrid(nodes=nodes, elements=elements)
>>> grid.compute_connectivity()
>>> grid.n_nodes
6
>>> grid.n_elements
2

Iterate over nodes:

>>> for node in grid.iter_nodes():
...     print(f"Node {node.id}: ({node.x}, {node.y})")
Node 1: (0.0, 0.0)
...

Get bounding box:

>>> xmin, ymin, xmax, ymax = grid.bounding_box
>>> print(f"Extent: ({xmin}, {ymin}) to ({xmax}, {ymax})")
Extent: (0.0, 0.0) to (200.0, 100.0)
nodes: dict[int, Node]#
elements: dict[int, Element]#
faces: dict[int, Face]#
subregions: dict[int, Subregion]#
__post_init__()[source]#

Initialize internal mappings.

property n_nodes: int#

Return number of nodes in the mesh.

property n_elements: int#

Return number of elements in the mesh.

property n_faces: int#

Return number of faces in the mesh.

property n_subregions: int#

Return number of subregions.

property x: ndarray[tuple[Any, ...], dtype[float64]]#

Return x coordinates as numpy array, sorted by node ID.

property y: ndarray[tuple[Any, ...], dtype[float64]]#

Return y coordinates as numpy array, sorted by node ID.

property vertex: ndarray[tuple[Any, ...], dtype[int32]]#

Return vertex connectivity array.

Returns array of shape (n_elements, max_vertices) where max_vertices=4. For triangles, the 4th vertex is 0 (IWFM convention). Values are 0-based indices into the node arrays.

property bounding_box: tuple[float, float, float, float]#

Return bounding box as (xmin, ymin, xmax, ymax).

get_node(node_id)[source]#

Get a node by ID. Raises KeyError if not found.

get_element(element_id)[source]#

Get an element by ID. Raises KeyError if not found.

get_face(face_id)[source]#

Get a face by ID. Raises KeyError if not found.

get_element_centroid(element_id)[source]#

Calculate centroid of an element.

get_elements_in_subregion(subregion_id)[source]#

Return list of element IDs in a subregion.

get_elements_by_subregion()[source]#

Group all elements by their subregion.

Returns:

Dictionary mapping subregion ID to list of element IDs. Elements with subregion=0 are grouped under key 0.

Return type:

dict[int, list[int]]

get_subregion_areas()[source]#

Compute total area for each subregion.

Returns:

Dictionary mapping subregion ID to total area.

Return type:

dict[int, float]

get_element_areas_array()[source]#

Get array of element areas in element ID order.

Returns:

Array of areas where index i corresponds to element (i+1).

Return type:

NDArray

get_boundary_node_ids()[source]#

Return list of boundary node IDs.

iter_nodes()[source]#

Iterate over nodes in ID order.

iter_elements()[source]#

Iterate over elements in ID order.

compute_connectivity()[source]#

Compute node-to-element and node-to-node connectivity.

This populates: - Node.surrounding_elements: Elements containing each node - Node.connected_nodes: Nodes directly connected via element edges - Node.is_boundary: True if node is on boundary

build_faces()[source]#

Build face (edge) data structure from elements.

Each unique edge becomes a Face with references to adjacent elements.

compute_areas()[source]#

Compute element areas and node areas.

Element areas are computed using the shoelace formula. Node areas are computed by distributing element area equally to vertices.

validate()[source]#

Validate mesh integrity.

Raises:

MeshError – If mesh is invalid

__init__(nodes, elements=<factory>, faces=<factory>, subregions=<factory>, _x_cache=None, _y_cache=None, _vertex_cache=None, _node_id_to_idx=None)#

Stratigraphy Module#

The stratigraphy module contains classes for representing model layer structure.

Stratigraphy class for IWFM model representation.

This module provides the Stratigraphy class which represents the vertical layering structure of an IWFM groundwater model. It mirrors IWFM’s Class_Stratigraphy.

class pyiwfm.core.stratigraphy.Stratigraphy(n_layers, n_nodes, gs_elev, top_elev, bottom_elev, active_node)[source]#

Bases: object

Vertical layering structure for an IWFM groundwater model.

This class stores the elevation data for each node at each layer, including ground surface elevation and active/inactive flags.

Variables:
  • n_layers (int) – Number of aquifer layers

  • n_nodes (int) – Number of nodes in the mesh

  • gs_elev (NDArray[np.float64]) – Ground surface elevation at each node (n_nodes,)

  • top_elev (NDArray[np.float64]) – Top elevation of each layer at each node (n_nodes, n_layers)

  • bottom_elev (NDArray[np.float64]) – Bottom elevation of each layer at each node (n_nodes, n_layers)

  • active_node (NDArray[np.bool_]) – Whether each node is active in each layer (n_nodes, n_layers)

n_layers: int#
n_nodes: int#
gs_elev: NDArray[np.float64]#
top_elev: NDArray[np.float64]#
bottom_elev: NDArray[np.float64]#
active_node: NDArray[np.bool_]#
__post_init__()[source]#

Validate array dimensions after initialization.

get_layer_thickness(layer)[source]#

Get thickness of a layer at all nodes.

Parameters:

layer (int) – Layer index (0-based)

Returns:

Array of thickness values at each node

Return type:

NDArray[np.float64]

get_total_thickness()[source]#

Get total thickness of all layers at each node.

Returns:

Array of total thickness values at each node

Return type:

NDArray[np.float64]

get_layer_top(layer)[source]#

Get top elevation of a layer at all nodes.

Parameters:

layer (int) – Layer index (0-based)

Returns:

Array of top elevation values at each node

Return type:

NDArray[np.float64]

get_layer_bottom(layer)[source]#

Get bottom elevation of a layer at all nodes.

Parameters:

layer (int) – Layer index (0-based)

Returns:

Array of bottom elevation values at each node

Return type:

NDArray[np.float64]

get_node_elevations(node_idx)[source]#

Get all elevations for a specific node.

Parameters:

node_idx (int) – Node index (0-based)

Returns:

Tuple of (ground_surface_elev, layer_tops, layer_bottoms)

Return type:

tuple[float, list[float], list[float]]

is_node_active(node_idx, layer)[source]#

Check if a node is active in a specific layer.

Parameters:
  • node_idx (int) – Node index (0-based)

  • layer (int) – Layer index (0-based)

Returns:

True if node is active in the layer

Return type:

bool

get_n_active_nodes(layer)[source]#

Count number of active nodes in a layer.

Parameters:

layer (int) – Layer index (0-based)

Returns:

Number of active nodes

Return type:

int

get_elevation_at_depth(node_idx, depth)[source]#

Get elevation at a given depth below ground surface.

Parameters:
  • node_idx (int) – Node index (0-based)

  • depth (float) – Depth below ground surface (positive value)

Returns:

Elevation at the given depth

Return type:

float

get_layer_at_elevation(node_idx, elevation)[source]#

Find which layer contains a given elevation.

Parameters:
  • node_idx (int) – Node index (0-based)

  • elevation (float) – Elevation to locate

Returns:

  • -1 if elevation is above ground surface

  • n_layers if elevation is below all layers

Return type:

Layer index, or

validate()[source]#

Validate stratigraphy data.

Returns:

List of warning messages (empty if valid)

Raises:

StratigraphyError – If critical validation fails

Return type:

list[str]

copy()[source]#

Create a deep copy of the stratigraphy.

Returns:

New Stratigraphy object with copied data

Return type:

Stratigraphy

__init__(n_layers, n_nodes, gs_elev, top_elev, bottom_elev, active_node)#

Time Series Module#

The time series module provides classes for working with temporal data.

Time series classes for IWFM model data.

This module provides classes for representing time steps and time series data used throughout IWFM models.

class pyiwfm.core.timeseries.TimeUnit(*values)[source]#

Bases: Enum

Time units supported by IWFM.

MINUTE = 'MIN'#
HOUR = 'HOUR'#
DAY = 'DAY'#
WEEK = 'WEEK'#
MONTH = 'MON'#
YEAR = 'YEAR'#
classmethod from_string(s)[source]#

Parse a time unit from string.

Accepts plain unit names (DAY, MON) as well as IWFM-style count+unit tokens like 1MON, 1DAY, 1HOUR.

to_timedelta(n=1)[source]#

Convert to a timedelta.

Note: Month and year are approximated.

class pyiwfm.core.timeseries.TimeStep(start, end, index=0)[source]#

Bases: object

A single time step in an IWFM simulation.

Variables:
start: datetime#
end: datetime#
index: int = 0#
property duration: timedelta#

Return the duration of this time step.

property midpoint: datetime#

Return the midpoint datetime of this time step.

__init__(start, end, index=0)#
class pyiwfm.core.timeseries.SimulationPeriod(start, end, time_step_length, time_step_unit)[source]#

Bases: object

Defines the simulation time period for an IWFM model.

Variables:
start: datetime#
end: datetime#
time_step_length: int#
time_step_unit: TimeUnit#
property duration: timedelta#

Return total simulation duration.

property time_step_delta: timedelta#

Return the time step as a timedelta.

property n_time_steps: int#

Return approximate number of time steps.

iter_time_steps()[source]#

Iterate over all time steps in the simulation period.

get_time_step(index)[source]#

Get a specific time step by index.

__init__(start, end, time_step_length, time_step_unit)#
class pyiwfm.core.timeseries.TimeSeries(times, values, name='', units='', location='', metadata=<factory>)[source]#

Bases: object

A time series of values.

Variables:
times: ndarray[tuple[Any, ...], dtype[datetime64]]#
values: ndarray[tuple[Any, ...], dtype[float64]]#
name: str = ''#
units: str = ''#
location: str = ''#
metadata: dict[str, Any]#
__post_init__()[source]#

Validate time series data.

property n_times: int#

Return number of time points.

property start_time: datetime64#

Return start time.

property end_time: datetime64#

Return end time.

classmethod from_datetimes(times, values, **kwargs)[source]#

Create a TimeSeries from Python datetime objects.

Parameters:
  • times (Sequence[datetime]) – Sequence of datetime objects

  • values (ndarray[tuple[Any, ...], dtype[float64]]) – Array of values

  • **kwargs (Any) – Additional attributes (name, units, location, metadata)

to_dataframe()[source]#

Convert to a pandas DataFrame.

Returns:

DataFrame with times as index

Return type:

Any

resample(freq)[source]#

Resample the time series to a new frequency.

Parameters:

freq (str) – Pandas frequency string (e.g., ‘D’ for daily, ‘M’ for monthly)

Returns:

New resampled TimeSeries

Return type:

TimeSeries

slice_time(start=None, end=None)[source]#

Slice the time series to a time range.

Parameters:
  • start (datetime | None) – Start datetime (inclusive)

  • end (datetime | None) – End datetime (inclusive)

Returns:

New sliced TimeSeries

Return type:

TimeSeries

__getitem__(key)[source]#

Get values by index.

__init__(times, values, name='', units='', location='', metadata=<factory>)#
class pyiwfm.core.timeseries.TimeSeriesCollection(series=<factory>, name='', variable='')[source]#

Bases: object

A collection of related time series (e.g., heads at multiple nodes).

Variables:
series: dict[str, TimeSeries]#
name: str = ''#
variable: str = ''#
add(ts)[source]#

Add a time series to the collection.

get(location)[source]#

Get a time series by location.

property locations: list[str]#

Return list of all locations.

__init__(series=<factory>, name='', variable='')#

Base Component#

Abstract base class that all model components (groundwater, streams, lakes, root zone, small watersheds, unsaturated zone) inherit from, providing a consistent interface for validation and item counting.

Abstract base class for IWFM model components.

All major IWFM simulation components (groundwater, streams, lakes, root zone, small watersheds, unsaturated zone) share a common interface defined by BaseComponent. This enables generic model validation, iteration, and serialization.

class pyiwfm.core.base_component.BaseComponent[source]#

Bases: ABC

Abstract base class for IWFM model components.

Every component must implement:

  • validate() – return a list of validation error strings (empty list means valid).

  • n_items – number of primary entities managed by the component (e.g., number of wells, stream nodes, lakes).

abstractmethod validate()[source]#

Validate the component state.

Raises:

ValidationError – If the component state is invalid.

abstract property n_items: int#

Return the number of primary entities in this component.

Model Module#

The model module provides the central IWFMModel class that orchestrates all model components.

Loading Models:

  • IWFMModel.from_preprocessor(pp_file) - Load from preprocessor input files (mesh, stratigraphy, geometry)

  • IWFMModel.from_preprocessor_binary(binary_file) - Load from preprocessor binary output

  • IWFMModel.from_simulation(sim_file) - Load complete model from simulation input file

  • IWFMModel.from_simulation_with_preprocessor(sim_file, pp_file) - Load using both files

  • IWFMModel.from_hdf5(hdf5_file) - Load from HDF5 file

Saving Models:

  • model.to_preprocessor(output_dir) - Save to preprocessor input files

  • model.to_simulation(output_dir) - Save complete model to simulation files

  • model.to_hdf5(output_file) - Save to HDF5 format

  • model.to_binary(output_file) - Save mesh/stratigraphy to binary

IWFMModel class - main orchestrator for IWFM model components.

This module provides the central IWFMModel class that orchestrates all model components including mesh, stratigraphy, groundwater, streams, lakes, and root zone.

class pyiwfm.core.model.IWFMModel(name, mesh=None, stratigraphy=None, groundwater=None, streams=None, lakes=None, rootzone=None, small_watersheds=None, unsaturated_zone=None, supply_adjustment=None, metadata=<factory>, source_files=<factory>)[source]#

Bases: object

The main IWFM model container class.

This class orchestrates all model components and provides methods for reading, writing, and validating IWFM models. It mirrors the structure of IWFM’s Package_Model.

Variables:
  • name (str) – Model name/identifier

  • mesh (AppGrid | None) – Finite element mesh (AppGrid)

  • stratigraphy (Stratigraphy | None) – Vertical layering structure

  • groundwater (AppGW | None) – Groundwater component (AppGW) - wells, pumping, BCs, aquifer params

  • streams (AppStream | None) – Stream network component (AppStream) - nodes, reaches, diversions, bypasses

  • lakes (AppLake | None) – Lake component (AppLake) - lake definitions, elements, rating curves

  • rootzone (RootZone | None) – Root zone component (RootZone) - crop types, soil params, land use

  • small_watersheds (AppSmallWatershed | None) – Small Watershed component (AppSmallWatershed)

  • unsaturated_zone (AppUnsatZone | None) – Unsaturated Zone component (AppUnsatZone)

  • supply_adjustment (SupplyAdjustment | None) – Parsed supply adjustment specification data

  • metadata (dict[str, Any]) – Additional model metadata

name: str#
mesh: AppGrid | None = None#
stratigraphy: Stratigraphy | None = None#
groundwater: AppGW | None = None#
streams: AppStream | None = None#
lakes: AppLake | None = None#
rootzone: RootZone | None = None#
small_watersheds: AppSmallWatershed | None = None#
unsaturated_zone: AppUnsatZone | None = None#
supply_adjustment: SupplyAdjustment | None = None#
metadata: dict[str, Any]#
source_files: dict[str, Path]#
classmethod from_preprocessor(pp_file, load_streams=True, load_lakes=True)[source]#

Load a model from PreProcessor input files.

This loads the model structure (mesh, stratigraphy) and optionally the stream and lake geometry from the preprocessor input file and all files referenced by it.

Note: This creates a “partial” model with only the static geometry defined in the preprocessor. It does not include dynamic components like groundwater parameters, pumping, or root zone data which are defined in the simulation input files.

Parameters:
  • pp_file (Path | str) – Path to the main PreProcessor input file

  • load_streams (bool) – If True, load stream network geometry

  • load_lakes (bool) – If True, load lake geometry

Returns:

IWFMModel instance with mesh, stratigraphy, and optionally streams/lakes geometry loaded

Return type:

IWFMModel

Example

>>> model = IWFMModel.from_preprocessor("Preprocessor/Preprocessor.in")
>>> print(f"Loaded {model.n_nodes} nodes, {model.n_elements} elements")
classmethod from_preprocessor_binary(binary_file, name='')[source]#

Load a model from the native IWFM PreProcessor binary output.

The preprocessor binary file (ACCESS='STREAM') contains mesh, stratigraphy, stream/lake connectors, and component data compiled by the IWFM PreProcessor.

Parameters:
  • binary_file (Path | str) – Path to the preprocessor binary output file

  • name (str) – Model name (optional, defaults to file stem)

Returns:

IWFMModel with mesh, stratigraphy, streams, and lakes loaded

Return type:

IWFMModel

Example

>>> model = IWFMModel.from_preprocessor_binary("PreprocessorOut.bin")
>>> print(f"Loaded {model.n_nodes} nodes, {model.n_layers} layers")
classmethod from_simulation(simulation_file)[source]#

Load a complete IWFM model from a simulation main input file.

Delegates to CompleteModelLoader which auto-detects the simulation file format and loads all components (mesh, stratigraphy, groundwater, streams, lakes, root zone, etc.).

Parameters:

simulation_file (Path | str) – Path to the simulation main input file

Returns:

IWFMModel instance with all components loaded

Return type:

IWFMModel

Example

>>> model = IWFMModel.from_simulation("Simulation/Simulation.in")
>>> print(f"Stream nodes: {len(model.streams.nodes)}")
classmethod from_simulation_with_preprocessor(simulation_file, preprocessor_file, load_timeseries=False)[source]#

Load a complete IWFM model using both simulation and preprocessor files.

This method first loads the mesh and stratigraphy from the preprocessor input file (ASCII format), then loads all dynamic components from the simulation input file and its referenced component files.

Use this method when: - You have both preprocessor input files and simulation input files - You want to load from ASCII preprocessor files rather than binary - The binary file path in the simulation file is incorrect or missing

Parameters:
  • simulation_file (Path | str) – Path to the simulation main input file

  • preprocessor_file (Path | str) – Path to the preprocessor main input file

  • load_timeseries (bool) – If True, also load time series data (slower)

Returns:

IWFMModel instance with all components loaded

Return type:

IWFMModel

Example

>>> model = IWFMModel.from_simulation_with_preprocessor(
...     "Simulation/Simulation.in",
...     "Preprocessor/Preprocessor.in"
... )
classmethod from_hdf5(hdf5_file)[source]#

Load a model from HDF5 output file.

This loads a complete model that was previously saved to HDF5 format using the to_hdf5() method or write_model_hdf5() function.

Parameters:

hdf5_file (Path | str) – Path to the HDF5 file

Returns:

Loaded IWFMModel instance

Return type:

IWFMModel

Example

>>> model = IWFMModel.from_hdf5("model.h5")
to_preprocessor(output_dir)[source]#

Write model to PreProcessor input files.

Creates all preprocessor input files (nodes, elements, stratigraphy) in the specified output directory.

Parameters:

output_dir (Path | str) – Directory to write output files

Returns:

Dictionary mapping file type to output path

Return type:

dict[str, Path]

to_simulation(output_dir, file_paths=None, ts_format='text')[source]#

Write complete model to simulation input files.

Creates all input files required for an IWFM simulation, including preprocessor files, component files, and the simulation control file.

Parameters:
  • output_dir (Path | str) – Directory to write output files

  • file_paths (dict[str, str] | None) – Optional dict of {file_key: relative_path} overrides for custom directory layouts. If None, uses default nested layout.

  • ts_format (str) – Time series format - “text” or “dss”

Returns:

Dictionary mapping file type to output path

Return type:

dict[str, Path]

to_hdf5(output_file)[source]#

Write model to HDF5 file.

Saves the complete model (mesh, stratigraphy, and all components) to a single HDF5 file for efficient storage and later loading.

Parameters:

output_file (Path | str) – Path to the output HDF5 file

Example

>>> model.to_hdf5("model.h5")
to_binary(output_file)[source]#

Write model mesh and stratigraphy to binary files.

Parameters:

output_file (Path | str) – Base path for output files (will create .bin and .strat.bin)

validate()[source]#

Validate model structure and data.

Returns:

List of validation errors (empty if valid)

Raises:

ValidationError – If critical validation fails

Return type:

list[str]

property n_nodes: int#

Return number of nodes in the mesh.

property n_elements: int#

Return number of elements in the mesh.

property n_layers: int#

Return number of layers in the stratigraphy.

property grid: AppGrid | None#

Alias for mesh property for compatibility.

property n_wells: int#

Return number of wells in the groundwater component.

property n_stream_nodes: int#

Return number of stream nodes.

property n_stream_reaches: int#

Return number of stream reaches.

property n_diversions: int#

Return number of diversions.

property n_lakes: int#

Return number of lakes.

property n_crop_types: int#

Return number of crop types in the root zone.

property has_groundwater: bool#

Return True if groundwater component is loaded.

property has_streams: bool#

Return True if stream component is loaded.

property has_lakes: bool#

Return True if lake component is loaded.

property has_rootzone: bool#

Return True if root zone component is loaded.

property has_small_watersheds: bool#

Return True if small watershed component is loaded.

property has_unsaturated_zone: bool#

Return True if unsaturated zone component is loaded.

summary()[source]#

Return a summary string of the model.

Returns:

Multi-line summary of model components

Return type:

str

validate_components()[source]#

Validate all model components.

Returns:

List of validation warnings/errors from components

Return type:

list[str]

__init__(name, mesh=None, stratigraphy=None, groundwater=None, streams=None, lakes=None, rootzone=None, small_watersheds=None, unsaturated_zone=None, supply_adjustment=None, metadata=<factory>, source_files=<factory>)#

Model Factory#

Helper functions extracted from IWFMModel for model construction: reach building from node reach IDs, stream node coordinate resolution, parametric grid application, KH anomaly application, subsidence parameters, and binary-to-model conversion. IWFMModel classmethods delegate to these functions.

Factory helper functions for constructing IWFMModel instances.

This module contains the model-building helper functions extracted from model.py to keep the IWFMModel class focused on data and properties. The classmethods on IWFMModel delegate to these functions.

pyiwfm.core.model_factory.build_reaches_from_node_reach_ids(stream)[source]#

Build StrmReach objects by grouping stream nodes by their reach_id.

Only populates stream.reaches when it is currently empty and at least some nodes carry a non-zero reach_id.

Parameters:

stream (AppStream) – Stream network whose nodes are inspected.

pyiwfm.core.model_factory.apply_kh_anomalies(params, anomalies, mesh)[source]#

Apply Kh anomaly overwrites from element-level data to node arrays.

For each anomaly element, sets Kh at all vertex nodes to the anomaly value. This matches IWFM Fortran behavior in ReadAquiferParameters (Class_AppGW.f90:4433–4442).

Parameters:
  • params (AquiferParameters) – Aquifer parameters whose kh array will be modified in-place.

  • anomalies (list[KhAnomalyEntry]) – Parsed anomaly entries from the GW main file.

  • mesh (AppGrid) – Model mesh providing element-to-node connectivity.

Returns:

Number of anomalies successfully applied.

Return type:

int

pyiwfm.core.model_factory.apply_parametric_grids(gw, parametric_grids, mesh)[source]#

Interpolate aquifer parameters from parametric grids onto model nodes.

Builds a ParametricGrid for each group, interpolates all 5 parameters (Kh, Ss, Sy, AquitardKv, Kv) at every model node, and assigns the result as aquifer parameters.

Returns True if interpolation was performed.

pyiwfm.core.model_factory.apply_parametric_subsidence(subs_config, mesh, n_nodes, n_layers)[source]#

Interpolate subsidence parameters from parametric grids onto model nodes.

Returns a list of SubsidenceNodeParams objects.

pyiwfm.core.model_factory.binary_data_to_model(data, name='')[source]#

Convert PreprocessorBinaryData to an IWFMModel.

Builds Node, Element, Subregion, Stratigraphy, AppStream, and AppLake objects from the raw arrays in data.

pyiwfm.core.model_factory.resolve_stream_node_coordinates(model)[source]#

Resolve stream node (0,0) coordinates from associated GW nodes.

Many IWFM loaders create stream nodes with placeholder (0, 0) coordinates. When the stream node has a gw_node reference we can look up the real coordinates from the mesh.

Returns the number of nodes updated.

Zones Module#

The zones module provides data structures for defining spatial zones (subregions, custom zones for ZBudget analysis) and mapping elements to zones.

Zone definitions for multi-scale data viewing.

This module provides zone data structures for spatial aggregation:

  • Zone: A named group of elements with computed area

  • ZoneDefinition: Collection of zones with element-to-zone mapping

Zone definitions support multiple spatial scales: - Subregions (predefined groupings from model input files) - User-specified zones (custom groupings for ZBudget analysis)

Example

Create a simple zone definition:

>>> from pyiwfm.core.zones import Zone, ZoneDefinition
>>> import numpy as np
>>> zones = {
...     1: Zone(id=1, name="North Basin", elements=[1, 2, 3], area=1500.0),
...     2: Zone(id=2, name="South Basin", elements=[4, 5, 6], area=1200.0),
... }
>>> element_zones = np.array([1, 1, 1, 2, 2, 2])  # element_id-1 -> zone_id
>>> zone_def = ZoneDefinition(zones=zones, extent="horizontal", element_zones=element_zones)
>>> zone_def.get_zone_for_element(3)
1
>>> zone_def.get_elements_in_zone(2)
[4, 5, 6]
class pyiwfm.core.zones.Zone(id, name, elements=<factory>, area=0.0)[source]#

Bases: object

A named zone containing a group of elements.

Parameters:
  • id (int) – Unique zone identifier (1-based).

  • name (str) – Descriptive name for the zone (e.g., “Sacramento Valley”).

  • elements (list of int) – List of element IDs belonging to this zone.

  • area (float, optional) – Total zone area in model units squared. Default is 0.0, can be computed from element areas.

Examples

Create a zone:

>>> zone = Zone(id=1, name="Central Basin", elements=[10, 11, 12, 13], area=5000.0)
>>> print(f"Zone {zone.id}: {zone.name} ({len(zone.elements)} elements)")
Zone 1: Central Basin (4 elements)

Check if element is in zone:

>>> 11 in zone.elements
True
id: int#
name: str#
elements: list[int]#
area: float = 0.0#
property n_elements: int#

Return the number of elements in this zone.

__init__(id, name, elements=<factory>, area=0.0)#
class pyiwfm.core.zones.ZoneDefinition(zones=<factory>, extent='horizontal', element_zones=None, name='', description='')[source]#

Bases: object

A complete zone definition with element-to-zone mapping.

Parameters:
  • zones (dict[int, Zone]) – Dictionary mapping zone ID to Zone object.

  • extent (str, optional) – Zone extent type: “horizontal” (default) or “vertical”. Horizontal zones span all layers; vertical zones are layer-specific.

  • element_zones (NDArray, optional) – Array mapping element index (0-based) to zone ID. Shape: (n_elements,). Elements with no zone have value 0.

  • name (str, optional) – Name for this zone definition set.

  • description (str, optional) – Description of the zone definition.

Examples

Create a zone definition from scratch:

>>> zones = {
...     1: Zone(id=1, name="Zone A", elements=[1, 2, 3]),
...     2: Zone(id=2, name="Zone B", elements=[4, 5]),
... }
>>> elem_zones = np.array([1, 1, 1, 2, 2])
>>> zone_def = ZoneDefinition(zones=zones, element_zones=elem_zones)
>>> zone_def.n_zones
2

Create from subregions:

>>> from pyiwfm.core.mesh import AppGrid, Subregion
>>> grid = ...  # Load from model
>>> zone_def = ZoneDefinition.from_subregions(grid)
zones: dict[int, Zone]#
extent: str = 'horizontal'#
element_zones: ndarray[tuple[Any, ...], dtype[int32]] | None = None#
name: str = ''#
description: str = ''#
__post_init__()[source]#

Validate and normalize zone definition.

property n_zones: int#

Return the total number of zones.

property n_elements: int#

Return the total number of elements with zone assignments.

property zone_ids: list[int]#

Return sorted list of zone IDs.

get_zone_for_element(element_id)[source]#

Get the zone ID for a given element.

Parameters:

element_id (int) – Element ID (1-based).

Returns:

Zone ID, or 0 if element has no zone assignment.

Return type:

int

get_elements_in_zone(zone_id)[source]#

Get all element IDs in a zone.

Parameters:

zone_id (int) – Zone ID.

Returns:

Element IDs (1-based) in the zone.

Return type:

list of int

get_zone(zone_id)[source]#

Get a Zone by ID.

Parameters:

zone_id (int) – Zone ID.

Returns:

The Zone object, or None if not found.

Return type:

Zone or None

iter_zones()[source]#

Iterate over all zones in ID order.

classmethod from_subregions(grid)[source]#

Create a ZoneDefinition from model subregions.

Parameters:

grid (AppGrid) – The model grid with subregion assignments.

Returns:

Zone definition with one zone per subregion.

Return type:

ZoneDefinition

classmethod from_element_list(element_zone_pairs, zone_names=None, element_areas=None, name='', description='')[source]#

Create a ZoneDefinition from a list of (element_id, zone_id) pairs.

Parameters:
  • element_zone_pairs (list of (int, int)) – List of (element_id, zone_id) pairs.

  • zone_names (dict[int, str], optional) – Mapping of zone ID to zone name.

  • element_areas (dict[int, float], optional) – Mapping of element ID to element area.

  • name (str, optional) – Name for this zone definition.

  • description (str, optional) – Description of the zone definition.

Returns:

The constructed zone definition.

Return type:

ZoneDefinition

add_zone(zone)[source]#

Add a new zone to the definition.

Parameters:

zone (Zone) – The zone to add.

remove_zone(zone_id)[source]#

Remove a zone from the definition.

Parameters:

zone_id (int) – ID of the zone to remove.

Returns:

The removed zone, or None if not found.

Return type:

Zone or None

compute_areas(grid)[source]#

Recompute zone areas from element areas.

Parameters:

grid (AppGrid) – The model grid with computed element areas.

validate(n_elements)[source]#

Validate the zone definition.

Parameters:

n_elements (int) – Total number of elements in the model.

Returns:

List of validation error messages (empty if valid).

Return type:

list of str

__init__(zones=<factory>, extent='horizontal', element_zones=None, name='', description='')#

Aggregation Module#

The aggregation module provides spatial data aggregation from element-level values to zone-level statistics using configurable methods.

Spatial data aggregation for multi-scale viewing.

This module provides aggregation utilities for computing zone-level values from element-level data:

Supported aggregation methods: - sum: Total value across zone - mean: Simple average - area_weighted_mean: Area-weighted average (default) - min: Minimum value - max: Maximum value - median: Median value

Example

Aggregate element values to zones:

>>> from pyiwfm.core.aggregation import DataAggregator
>>> from pyiwfm.core.zones import ZoneDefinition
>>> import numpy as np
>>> aggregator = DataAggregator()
>>> element_values = np.array([10.0, 12.0, 11.0, 20.0, 22.0])  # 5 elements
>>> zone_def = ...  # ZoneDefinition with 2 zones
>>> zone_values = aggregator.aggregate(element_values, zone_def, method="mean")
>>> print(zone_values)
{1: 11.0, 2: 21.0}
class pyiwfm.core.aggregation.AggregationMethod(*values)[source]#

Bases: Enum

Available aggregation methods.

SUM = 'sum'#
MEAN = 'mean'#
AREA_WEIGHTED_MEAN = 'area_weighted_mean'#
MIN = 'min'#
MAX = 'max'#
MEDIAN = 'median'#
class pyiwfm.core.aggregation.DataAggregator(element_areas=None)[source]#

Bases: object

Aggregates element-level data to zone-level values.

Parameters:

element_areas (NDArray, optional) – Array of element areas for area-weighted calculations. Shape: (n_elements,). Required for area_weighted_mean.

Examples

Create an aggregator with element areas:

>>> areas = np.array([100.0, 150.0, 200.0, 125.0, 175.0])
>>> aggregator = DataAggregator(element_areas=areas)

Aggregate hydraulic head values:

>>> head_values = np.array([50.0, 52.0, 48.0, 60.0, 62.0])
>>> zone_def = ...  # Zone 1: elements 1-3, Zone 2: elements 4-5
>>> zone_heads = aggregator.aggregate(head_values, zone_def, method="area_weighted_mean")
METHODS: dict[str, Callable[[...], float]] = {}#
__init__(element_areas=None)[source]#
aggregate(values, zone_def, method='area_weighted_mean')[source]#

Aggregate element values to zone-level values.

Parameters:
  • values (NDArray) – Element-level values. Shape: (n_elements,).

  • zone_def (ZoneDefinition) – Zone definition with element-to-zone mapping.

  • method (str, optional) – Aggregation method. One of: “sum”, “mean”, “area_weighted_mean”, “min”, “max”, “median”. Default is “area_weighted_mean”.

Returns:

Dictionary mapping zone ID to aggregated value.

Return type:

dict[int, float]

Raises:

ValueError – If method is not recognized or areas not provided for area_weighted_mean.

Examples

>>> aggregator = DataAggregator(element_areas=areas)
>>> result = aggregator.aggregate(heads, zone_def, method="mean")
>>> print(f"Zone 1 mean head: {result[1]:.2f}")
aggregate_to_array(values, zone_def, method='area_weighted_mean')[source]#

Aggregate and expand zone values back to element array.

Each element gets its zone’s aggregated value. Useful for visualization where you want to color elements by their zone’s aggregate value.

Parameters:
  • values (NDArray) – Element-level values. Shape: (n_elements,).

  • zone_def (ZoneDefinition) – Zone definition with element-to-zone mapping.

  • method (str, optional) – Aggregation method. Default is “area_weighted_mean”.

Returns:

Array of same shape as input, with each element’s value replaced by its zone’s aggregated value. Elements with no zone get NaN.

Return type:

NDArray

Examples

>>> expanded = aggregator.aggregate_to_array(heads, zone_def)
>>> # expanded[0:3] all have same value (Zone 1's aggregate)
aggregate_timeseries(timeseries_values, zone_def, method='area_weighted_mean')[source]#

Aggregate a time series of element values to zone-level time series.

Parameters:
  • timeseries_values (list of NDArray) – List of element-level value arrays, one per timestep.

  • zone_def (ZoneDefinition) – Zone definition with element-to-zone mapping.

  • method (str, optional) – Aggregation method. Default is “area_weighted_mean”.

Returns:

Dictionary mapping zone ID to list of aggregated values over time.

Return type:

dict[int, list[float]]

Examples

>>> head_series = [heads_t0, heads_t1, heads_t2]  # 3 timesteps
>>> zone_series = aggregator.aggregate_timeseries(head_series, zone_def)
>>> print(f"Zone 1 heads over time: {zone_series[1]}")
set_element_areas(areas)[source]#

Set or update element areas for area-weighted aggregation.

Parameters:

areas (NDArray) – Array of element areas. Shape: (n_elements,).

property available_methods: list[str]#

Return list of available aggregation method names.

pyiwfm.core.aggregation.create_aggregator_from_grid(grid)[source]#

Create a DataAggregator with element areas from an AppGrid.

Parameters:

grid (AppGrid) – The model grid with computed element areas.

Returns:

Configured aggregator ready for area-weighted calculations.

Return type:

DataAggregator

Examples

>>> from pyiwfm.core.mesh import AppGrid
>>> grid = ...  # Load from model
>>> aggregator = create_aggregator_from_grid(grid)
>>> zone_values = aggregator.aggregate(heads, zone_def, "area_weighted_mean")

Mesh Quality#

Compute element-level quality metrics (aspect ratio, skewness, min/max angle) and aggregate statistics for the entire mesh.

Mesh quality metrics for IWFM finite element meshes.

This module computes per-element quality metrics (area, aspect ratio, skewness, interior angles) and aggregate statistics for an entire mesh. Useful for identifying poorly shaped elements that may cause numerical issues.

Example

Compute quality for a single triangle:

>>> verts = [(0.0, 0.0), (1.0, 0.0), (0.5, 0.866)]
>>> q = compute_element_quality(verts, element_id=1)
>>> q.n_vertices
3
>>> q.min_angle > 59.0
True

Compute mesh-wide quality report:

>>> from pyiwfm.core.mesh import AppGrid, Node, Element
>>> nodes = {1: Node(1, 0, 0), 2: Node(2, 1, 0), 3: Node(3, 1, 1), 4: Node(4, 0, 1)}
>>> elements = {1: Element(1, (1, 2, 3, 4))}
>>> grid = AppGrid(nodes=nodes, elements=elements)
>>> report = compute_mesh_quality(grid)
>>> report.n_elements
1
class pyiwfm.core.mesh_quality.ElementQuality(element_id, area, aspect_ratio, skewness, min_angle, max_angle, n_vertices)[source]#

Bases: object

Quality metrics for a single mesh element.

Parameters:
  • element_id (int) – Element identifier (1-based).

  • area (float) – Element area computed via the shoelace formula.

  • aspect_ratio (float) – Ratio of the longest edge to the shortest edge. A value of 1.0 indicates all edges are equal length.

  • skewness (float) – Deviation from an ideal shape, ranging from 0.0 (perfect) to 1.0 (degenerate). For triangles, compares to equilateral; for quads, compares diagonal lengths.

  • min_angle (float) – Smallest interior angle in degrees.

  • max_angle (float) – Largest interior angle in degrees.

  • n_vertices (int) – Number of vertices (3 for triangle, 4 for quad).

element_id: int#
area: float#
aspect_ratio: float#
skewness: float#
min_angle: float#
max_angle: float#
n_vertices: int#
__init__(element_id, area, aspect_ratio, skewness, min_angle, max_angle, n_vertices)#
class pyiwfm.core.mesh_quality.MeshQualityReport(n_elements, n_triangles, n_quads, area_min, area_max, area_mean, area_std, aspect_ratio_min, aspect_ratio_max, aspect_ratio_mean, skewness_mean, min_angle_global, max_angle_global, poor_quality_count, element_qualities=<factory>)[source]#

Bases: object

Aggregate mesh quality statistics.

Parameters:
  • n_elements (int) – Total number of elements.

  • n_triangles (int) – Number of triangular elements.

  • n_quads (int) – Number of quadrilateral elements.

  • area_min (float) – Minimum element area.

  • area_max (float) – Maximum element area.

  • area_mean (float) – Mean element area.

  • area_std (float) – Standard deviation of element areas.

  • aspect_ratio_min (float) – Minimum aspect ratio across all elements.

  • aspect_ratio_max (float) – Maximum aspect ratio across all elements.

  • aspect_ratio_mean (float) – Mean aspect ratio.

  • skewness_mean (float) – Mean skewness across all elements.

  • min_angle_global (float) – Smallest interior angle in the entire mesh (degrees).

  • max_angle_global (float) – Largest interior angle in the entire mesh (degrees).

  • poor_quality_count (int) – Number of elements with aspect_ratio > 10 or skewness > 0.8.

  • element_qualities (list[ElementQuality]) – Per-element quality metrics.

n_elements: int#
n_triangles: int#
n_quads: int#
area_min: float#
area_max: float#
area_mean: float#
area_std: float#
aspect_ratio_min: float#
aspect_ratio_max: float#
aspect_ratio_mean: float#
skewness_mean: float#
min_angle_global: float#
max_angle_global: float#
poor_quality_count: int#
element_qualities: list[ElementQuality]#
to_dict()[source]#

Serialize for API response (without per-element details).

Returns:

Dictionary with aggregate statistics only.

Return type:

dict[str, Any]

__init__(n_elements, n_triangles, n_quads, area_min, area_max, area_mean, area_std, aspect_ratio_min, aspect_ratio_max, aspect_ratio_mean, skewness_mean, min_angle_global, max_angle_global, poor_quality_count, element_qualities=<factory>)#
pyiwfm.core.mesh_quality.compute_element_quality(vertices, element_id=0)[source]#

Compute quality metrics for a single element given its vertex coordinates.

Parameters:
  • vertices (list[tuple[float, float]]) – Ordered vertex coordinates (3 for triangle, 4 for quad).

  • element_id (int, optional) – Element identifier for the returned dataclass. Default is 0.

Returns:

Quality metrics for the element.

Return type:

ElementQuality

Raises:

ValueError – If the number of vertices is not 3 or 4.

pyiwfm.core.mesh_quality.compute_mesh_quality(grid)[source]#

Compute quality metrics for the entire mesh.

Iterates over all elements, computes per-element quality metrics, and aggregates them into summary statistics.

Parameters:

grid (AppGrid) – The mesh to analyze.

Returns:

Aggregate and per-element quality metrics.

Return type:

MeshQualityReport

Query Module#

The query module provides a high-level API for accessing model data at multiple spatial scales with aggregation and export capabilities.

High-level query API for multi-scale data access.

This module provides a unified interface for querying model data at different spatial scales:

Example

Query and export model data:

>>> from pyiwfm.core.query import ModelQueryAPI
>>> api = ModelQueryAPI(model)
>>> # Get zone-aggregated values
>>> zone_heads = api.get_values("head", scale="subregion", layer=1)
>>> # Export to DataFrame
>>> df = api.export_to_dataframe(["head", "kh"], scale="subregion")
>>> df.to_csv("subregion_data.csv")
class pyiwfm.core.query.TimeSeries(times, values, variable, location_id, location_type, units='')[source]#

Bases: object

Time series data for a single location or zone.

Parameters:
  • times (list of datetime) – Timestamps for each value.

  • values (NDArray) – Array of values at each timestamp.

  • variable (str) – Variable name (e.g., “head”, “pumping”).

  • location_id (int) – Zone, element, or node ID.

  • location_type (str) – Type of location: “zone”, “element”, “node”.

  • units (str, optional) – Units of the values.

times: list[datetime]#
values: ndarray[tuple[Any, ...], dtype[float64]]#
variable: str#
location_id: int#
location_type: str#
units: str = ''#
property n_timesteps: int#

Return number of timesteps.

to_dict()[source]#

Convert to dictionary for DataFrame creation.

__init__(times, values, variable, location_id, location_type, units='')#
class pyiwfm.core.query.ModelQueryAPI(model)[source]#

Bases: object

High-level API for querying model data at multiple spatial scales.

Parameters:

model (IWFMModel) – The IWFM model instance.

Variables:
  • model (IWFMModel) – The underlying model.

  • aggregator (DataAggregator) – Aggregator for zone-level calculations.

Examples

Create API and query data:

>>> from pyiwfm.core.model import IWFMModel
>>> model = IWFMModel.from_preprocessor("Preprocessor_MAIN.IN")
>>> api = ModelQueryAPI(model)

Get element-level values:

>>> heads = api.get_values("head", scale="element", layer=1)
>>> print(f"Head at element 1: {heads[1]:.2f} ft")

Get zone-aggregated values:

>>> zone_heads = api.get_values("head", scale="subregion", layer=1)
>>> for zone_id, head in zone_heads.items():
...     print(f"Zone {zone_id}: {head:.2f} ft")

Export to DataFrame:

>>> df = api.export_to_dataframe(["head", "kh"], scale="subregion")
>>> df.to_csv("zone_data.csv", index=False)
PROPERTY_INFO: dict[str, dict[str, Any]] = {'area': {'name': 'Element Area', 'source': 'mesh', 'units': 'sq ft'}, 'bottom_elev': {'name': 'Bottom Elevation', 'source': 'stratigraphy', 'units': 'ft'}, 'head': {'name': 'Hydraulic Head', 'source': 'results', 'units': 'ft'}, 'kh': {'name': 'Horizontal K', 'source': 'params', 'units': 'ft/d'}, 'kv': {'name': 'Vertical K', 'source': 'params', 'units': 'ft/d'}, 'ss': {'name': 'Specific Storage', 'source': 'params', 'units': '1/ft'}, 'subregion': {'name': 'Subregion ID', 'source': 'mesh', 'units': ''}, 'sy': {'name': 'Specific Yield', 'source': 'params', 'units': ''}, 'thickness': {'name': 'Layer Thickness', 'source': 'stratigraphy', 'units': 'ft'}, 'top_elev': {'name': 'Top Elevation', 'source': 'stratigraphy', 'units': 'ft'}}#
__init__(model)[source]#
property aggregator: DataAggregator#

Get or create the data aggregator.

property subregion_zones: ZoneDefinition#

Get zone definition from model subregions.

register_zones(name, zone_def)[source]#

Register a custom zone definition.

Parameters:
  • name (str) – Name to identify this zone definition.

  • zone_def (ZoneDefinition) – The zone definition to register.

get_zone_definition(scale)[source]#

Get a zone definition by scale name.

Parameters:

scale (str) – Scale name: “element” (no zones), “subregion”, or custom name.

Returns:

The zone definition, or None for element scale.

Return type:

ZoneDefinition or None

get_values(variable, scale='element', layer=None, time_index=None, aggregation='area_weighted_mean')[source]#

Get property values at specified scale.

Parameters:
  • variable (str) – Property name (e.g., “head”, “kh”, “area”).

  • scale (str, optional) – Spatial scale: “element”, “subregion”, or custom zone name. Default is “element”.

  • layer (int, optional) – Model layer (1-based). None for 2D properties.

  • time_index (int, optional) – Time index for time-varying properties.

  • aggregation (str, optional) – Aggregation method for zone scales. Default is “area_weighted_mean”.

Returns:

Dictionary mapping element/zone ID to value.

Return type:

dict[int, float]

Examples

Get element-level heads:

>>> heads = api.get_values("head", scale="element", layer=1)

Get subregion-average heads:

>>> zone_heads = api.get_values("head", scale="subregion", layer=1)
get_timeseries(variable, location_id, scale='element', layer=None, aggregation='area_weighted_mean')[source]#

Get time series for a location at specified scale.

Parameters:
  • variable (str) – Property name (e.g., “head”).

  • location_id (int) – Element or zone ID.

  • scale (str, optional) – Spatial scale. Default is “element”.

  • layer (int, optional) – Model layer (1-based).

  • aggregation (str, optional) – Aggregation method for zone scales.

Returns:

Time series data, or None if not available.

Return type:

TimeSeries or None

Notes

Requires model to have time-varying data loaded.

export_to_dataframe(variables, scale='element', layer=None, time_index=None, aggregation='area_weighted_mean')[source]#

Export values to a pandas DataFrame.

Parameters:
  • variables (list of str) – Property names to include.

  • scale (str, optional) – Spatial scale. Default is “element”.

  • layer (int, optional) – Model layer (1-based).

  • time_index (int, optional) – Time index for time-varying properties.

  • aggregation (str, optional) – Aggregation method for zone scales.

Returns:

DataFrame with columns for location ID and each variable.

Return type:

pd.DataFrame

Examples

>>> df = api.export_to_dataframe(["head", "kh", "area"], scale="subregion")
>>> print(df.head())
export_to_csv(variables, filepath, scale='element', layer=None, time_index=None, aggregation='area_weighted_mean')[source]#

Export values to a CSV file.

Parameters:
  • variables (list of str) – Property names to include.

  • filepath (Path or str) – Output file path.

  • scale (str, optional) – Spatial scale. Default is “element”.

  • layer (int, optional) – Model layer (1-based).

  • time_index (int, optional) – Time index for time-varying properties.

  • aggregation (str, optional) – Aggregation method for zone scales.

Examples

>>> api.export_to_csv(["head", "kh"], "zone_data.csv", scale="subregion")
export_timeseries_to_csv(variable, location_ids, filepath, scale='element', layer=None, aggregation='area_weighted_mean')[source]#

Export time series for multiple locations to CSV.

Parameters:
  • variable (str) – Property name.

  • location_ids (list of int) – List of element or zone IDs.

  • filepath (Path or str) – Output file path.

  • scale (str, optional) – Spatial scale. Default is “element”.

  • layer (int, optional) – Model layer (1-based).

  • aggregation (str, optional) – Aggregation method for zone scales.

get_available_variables()[source]#

Get list of available variables for this model.

Returns:

Variable names that can be queried.

Return type:

list of str

get_available_scales()[source]#

Get list of available spatial scales.

Returns:

Scale names that can be used.

Return type:

list of str