Reading and Writing Files#
This guide covers reading and writing IWFM model files in various formats.
Supported Formats#
pyiwfm supports multiple file formats:
ASCII: Human-readable text files (IWFM’s native format)
Binary: Fortran unformatted binary files (faster I/O)
HDF5: Hierarchical data format (efficient for large datasets)
HEC-DSS 7: Time series data format (optional, requires external library)
SMP: IWFM observation file format (bore ID, date/time, value, exclusion flag)
SimulationMessages.out: Parser for IWFM simulation diagnostic output
Complete Model I/O#
The easiest way to work with IWFM models is to use the complete model I/O functions.
Loading a Complete Model#
from pyiwfm.io import load_complete_model
# Load model from simulation main file
model = load_complete_model("Simulation/Simulation.in")
# Access model components
print(f"Grid: {model.grid.n_nodes} nodes, {model.grid.n_elements} elements")
print(f"Stratigraphy layers: {model.stratigraphy.n_layers}")
# Access component data (if loaded)
if model.groundwater:
print(f"Wells: {len(model.groundwater.wells)}")
if model.stream:
print(f"Stream nodes: {len(model.stream.nodes)}")
Saving a Complete Model#
from pathlib import Path
from pyiwfm.io import save_complete_model
# Save model to a new directory
files_written = save_complete_model(
model,
output_dir=Path("output_model"),
timeseries_format="ascii", # or "dss"
)
print(f"Written files: {list(files_written.keys())}")
ASCII Files#
Reading ASCII Node Files#
from pyiwfm.io import read_nodes
# Read nodes from IWFM node file
nodes = read_nodes("Preprocessor/Nodal.dat")
print(f"Read {len(nodes)} nodes")
for node_id, node in list(nodes.items())[:5]:
print(f" Node {node_id}: ({node.x}, {node.y})")
Writing ASCII Node Files#
from pyiwfm.io import write_nodes
from pyiwfm.core.mesh import Node
# Prepare nodes
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),
}
# Write to file
write_nodes("output/nodes.dat", nodes)
Reading and Writing Elements#
from pyiwfm.io import read_elements, write_elements
from pyiwfm.core.mesh import Element
# Read elements from IWFM element file
elements = read_elements("Preprocessor/Element.dat")
# Write elements
write_elements("output/elements.dat", elements)
Reading and Writing Stratigraphy#
from pyiwfm.io import read_stratigraphy, write_stratigraphy
# Read stratigraphy
stratigraphy = read_stratigraphy("Preprocessor/Stratigraphy.dat")
print(f"Layers: {stratigraphy.n_layers}")
# Write stratigraphy
write_stratigraphy("output/stratigraphy.dat", stratigraphy)
Binary Files#
Binary files are faster for large datasets but not human-readable.
Reading Native IWFM Binary Files#
from pyiwfm.core.model import IWFMModel
# Load model from IWFM PreProcessor binary output (ACCESS='STREAM' format)
model = IWFMModel.from_preprocessor_binary("PreprocessorOut.bin")
print(f"Read mesh: {model.n_nodes} nodes, {model.n_elements} elements")
HDF5 Files#
HDF5 is recommended for large models and result storage.
Writing Complete Model to HDF5#
from pyiwfm.io import write_model_hdf5
# Write complete model to HDF5
write_model_hdf5("model.h5", model)
Reading Model from HDF5#
from pyiwfm.io import read_model_hdf5
# Read complete model from HDF5
model = read_model_hdf5("model.h5")
HDF5 File Structure#
The HDF5 file structure follows this hierarchy:
model.h5
├── mesh/
│ ├── nodes/
│ │ ├── id
│ │ ├── x
│ │ ├── y
│ │ └── is_boundary
│ └── elements/
│ ├── id
│ ├── vertices
│ └── subregion
├── stratigraphy/
│ ├── n_layers
│ ├── gs_elev
│ ├── top_elev
│ ├── bottom_elev
│ └── active_node
└── timeseries/
└── heads/
├── times
└── values
Component File Writers#
pyiwfm provides specialized writers for each IWFM component.
Groundwater Component#
from pathlib import Path
from pyiwfm.io.groundwater import GroundwaterWriter, GWFileConfig
# Configure output files
config = GWFileConfig(
output_dir=Path("output/Groundwater"),
wells_file="Wells.dat",
pumping_file="Pumping.dat",
)
# Write groundwater files
with GroundwaterWriter(config) as writer:
files = writer.write(model.groundwater)
print(f"Written: {files}")
Stream Component#
from pathlib import Path
from pyiwfm.io.streams import StreamWriter, StreamFileConfig
config = StreamFileConfig(
output_dir=Path("output/Streams"),
stream_nodes_file="StreamNodes.dat",
reaches_file="Reaches.dat",
)
with StreamWriter(config) as writer:
files = writer.write(model.stream)
Lake Component#
from pathlib import Path
from pyiwfm.io.lakes import LakeWriter, LakeFileConfig
config = LakeFileConfig(
output_dir=Path("output/Lakes"),
)
with LakeWriter(config) as writer:
files = writer.write(model.lakes)
Root Zone Component#
from pathlib import Path
from pyiwfm.io.rootzone import RootZoneWriter, RootZoneFileConfig
config = RootZoneFileConfig(
output_dir=Path("output/RootZone"),
)
with RootZoneWriter(config) as writer:
files = writer.write(model.rootzone)
Simulation Control File#
from pathlib import Path
from pyiwfm.io.simulation import SimulationWriter, SimulationConfig
config = SimulationConfig(
title="My IWFM Model",
start_date="10/01/1990",
end_date="09/30/2020",
time_step="1DAY",
)
writer = SimulationWriter(Path("output"))
writer.write(config)
ASCII Time Series#
IWFM uses a specific 21-character timestamp format for time series files.
Writing Time Series#
import numpy as np
from datetime import datetime
from pyiwfm.io import TimeSeriesWriter, format_iwfm_timestamp
# Create times and values
times = np.array([datetime(2020, 1, i) for i in range(1, 32)], dtype="datetime64[s]")
values = np.random.rand(31)
# Write to file
with TimeSeriesWriter("pumping.dat") as writer:
writer.write(times, {"WELL_1": values})
# Or use the convenience function
from pyiwfm.io import write_timeseries
write_timeseries("pumping.dat", times, {"WELL_1": values, "WELL_2": values * 0.5})
Reading Time Series#
from pyiwfm.io import TimeSeriesReader, read_timeseries
# Read time series from file
with TimeSeriesReader("pumping.dat") as reader:
times, values_dict = reader.read()
# Or use the convenience function
times, values_dict = read_timeseries("pumping.dat")
print(f"Locations: {list(values_dict.keys())}")
Timestamp Format#
IWFM uses a 21-character timestamp format:
from pyiwfm.io import format_iwfm_timestamp, parse_iwfm_timestamp
from datetime import datetime
dt = datetime(2020, 6, 15, 12, 30, 0)
# Format to IWFM string: "06/15/2020_12:30:00"
ts_str = format_iwfm_timestamp(dt)
# Parse back to datetime
dt_parsed = parse_iwfm_timestamp(ts_str)
HEC-DSS Support#
pyiwfm supports reading and writing HEC-DSS 7 files for time series data. This requires the HEC-DSS C library to be installed.
Note
HEC-DSS support is optional. Set the HECDSS_LIB environment variable
to point to the HEC-DSS library location.
Checking DSS Availability#
from pyiwfm.io.dss import HAS_DSS_LIBRARY
if HAS_DSS_LIBRARY:
print("HEC-DSS library is available")
else:
print("HEC-DSS library not found")
DSS Pathnames#
HEC-DSS uses a 6-part pathname: /A/B/C/D/E/F/
from pyiwfm.io.dss import DSSPathname, DSSPathnameTemplate
# Create a pathname directly
pathname = DSSPathname(
a_part="PROJECT",
b_part="LOCATION",
c_part="FLOW",
d_part="01JAN2020-31DEC2020",
e_part="1DAY",
f_part="SIM",
)
print(str(pathname)) # /PROJECT/LOCATION/FLOW/01JAN2020-31DEC2020/1DAY/SIM/
# Use a template for multiple locations
template = DSSPathnameTemplate(
a_part="IWFM_MODEL",
c_part="HEAD",
e_part="1DAY",
f_part="BASELINE",
)
# Create pathnames for different locations
path1 = template.make_pathname(location="WELL_001")
path2 = template.make_pathname(location="WELL_002")
Writing Time Series to DSS#
from pyiwfm.io.dss import (
DSSTimeSeriesWriter,
DSSPathnameTemplate,
write_timeseries_to_dss,
write_collection_to_dss,
)
from pyiwfm.core.timeseries import TimeSeries
# Write a single time series
ts = TimeSeries(times=times, values=values, name="HEAD", location="WELL_1", units="ft")
template = DSSPathnameTemplate(a_part="MODEL", c_part="HEAD", e_part="1DAY")
result = write_timeseries_to_dss(
"output.dss",
ts,
template.make_pathname(location="WELL_1"),
)
print(f"Success: {result.success}")
# Write a collection of time series
from pyiwfm.core.timeseries import TimeSeriesCollection
collection = TimeSeriesCollection(variable="HEAD")
collection.add(ts)
result = write_collection_to_dss("output.dss", collection, template, units="ft")
Reading Time Series from DSS#
from pyiwfm.io.dss import DSSTimeSeriesReader, read_timeseries_from_dss
# Read a single time series
ts = read_timeseries_from_dss(
"output.dss",
"/MODEL/WELL_1/HEAD/01JAN2020-31DEC2020/1DAY//"
)
print(f"Values: {ts.values}")
# Read multiple time series
with DSSTimeSeriesReader("output.dss") as reader:
collection = reader.read_collection([
"/MODEL/WELL_1/HEAD//1DAY//",
"/MODEL/WELL_2/HEAD//1DAY//",
])
Budget Excel Export#
pyiwfm can parse IWFM budget and zone budget control files and export the results to formatted Excel workbooks – one sheet per location (or zone) with title lines, bold headers, unit conversion, and auto-fitted column widths.
Control File Workflow#
The standard IWFM workflow uses a control file that references one or more HDF5 budget files with unit conversion factors and output paths:
from pyiwfm.io import read_budget_control, budget_control_to_excel
# Parse the control file
config = read_budget_control("C2VSimFG_Budget_xlsx.in")
# Export all budgets to Excel (one .xlsx per budget spec)
created_files = budget_control_to_excel(config)
for f in created_files:
print(f"Created: {f}")
Zone budget control files follow the same pattern:
from pyiwfm.io import read_zbudget_control, zbudget_control_to_excel
config = read_zbudget_control("C2VSimFG_ZBudget_xlsx.in")
created_files = zbudget_control_to_excel(config)
Direct Export#
You can also export directly from a BudgetReader or ZBudgetReader
without a control file:
from pyiwfm.io import BudgetReader, budget_to_excel
reader = BudgetReader("GW_Budget.hdf")
budget_to_excel(
reader,
"GW_Budget.xlsx",
volume_factor=2.29568e-05,
volume_unit="AC.FT.",
area_factor=2.29568e-05,
area_unit="AC",
)
Unit-Converted DataFrames#
The get_dataframe() method on both BudgetReader and ZBudgetReader
accepts optional conversion factors for direct use in scripts and notebooks:
from pyiwfm.io import BudgetReader
reader = BudgetReader("GW_Budget.hdf")
# Raw data (no conversion)
df_raw = reader.get_dataframe(0)
# With unit conversion applied
df_converted = reader.get_dataframe(
0,
length_factor=1.0,
area_factor=2.29568e-05,
volume_factor=2.29568e-05,
)
CLI Commands#
Budget export is also available from the command line:
# Export budgets from a control file
pyiwfm budget C2VSimFG_Budget_xlsx.in
# Export zone budgets from a control file
pyiwfm zbudget C2VSimFG_ZBudget_xlsx.in
# Override output directory
pyiwfm budget C2VSimFG_Budget_xlsx.in --output-dir /path/to/output
Model Packaging#
pyiwfm can package an entire IWFM model directory into a distributable ZIP archive, preserving the directory structure and generating a manifest.
Packaging a Model#
from pathlib import Path
from pyiwfm.io import package_model, collect_model_files
model_dir = Path("C2VSimCG")
# Inspect which files will be included
files = collect_model_files(model_dir)
print(f"Found {len(files)} model files")
# Create the ZIP archive
result = package_model(model_dir, output_path=Path("C2VSimCG.zip"))
print(f"Packaged {len(result.files_included)} files ({result.total_size_bytes / 1e6:.1f} MB)")
By default, output files (Results/, .hdf, .h5) and executables
(.exe, .dll, .so) are excluded. Pass include_executables=True
or include_results=True to include them.
Generating Run Scripts#
Generate platform-appropriate run scripts to execute the IWFM preprocessor, simulation, and optional post-processors:
from pyiwfm.roundtrip.script_generator import generate_run_scripts
scripts = generate_run_scripts(
model_dir=Path("C2VSimCG"),
preprocessor_main="Preprocessor/Preprocessor.in",
simulation_main="Simulation/Simulation.in",
budget_exe="Budget_x64.exe",
formats=["bat", "ps1", "sh"],
)
for s in scripts:
print(f"Generated: {s.name}")
This creates run_preprocessor, run_simulation, run_budget, and
run_all scripts in each requested format.
CLI Commands#
Both operations are available from the command line:
# Package a model
pyiwfm package --model-dir ./C2VSimCG --output C2VSimCG.zip
# Generate run scripts
pyiwfm run --model-dir ./C2VSimCG --scripts-only
# Download executables and generate scripts
pyiwfm run --model-dir ./C2VSimCG --download-executables --scripts-only
See Tutorial: Packaging and Running IWFM Models for a detailed tutorial.
PreProcessor Integration#
Loading Models from PreProcessor Files#
from pyiwfm.core.model import IWFMModel
from pyiwfm.io import load_complete_model
# Load just mesh and stratigraphy from preprocessor
model = IWFMModel.from_preprocessor("Preprocessor/Preprocessor.in")
# Load complete model including all components
model = load_complete_model("Simulation/Simulation.in")
Saving Models to PreProcessor Format#
from pathlib import Path
from pyiwfm.io import save_model_to_preprocessor, save_complete_model
# Save mesh and stratigraphy
save_model_to_preprocessor(model, Path("output/Preprocessor"))
# Save complete model with all components
files = save_complete_model(model, Path("output"))
print(f"Written: {list(files.keys())}")