Validation layers
epanetparser validates a network at three distinct points. The layers are
independent but converge into a single
ValidationReport so that callers
see one, uniformly structured result regardless of which checks produced the
issues.
The three layers
Layer |
When it runs |
What it checks |
|---|---|---|
Structural |
While parsing the file, before a network object exists |
The file is well-formed EPANET: valid section headers, required
fields, numeric values, and references that resolve
(e.g. |
Static |
After the |
Simulator-agnostic rules from the core rule set ( |
Simulation |
After a hydraulic simulation, on the results object |
Post-simulation output such as pressures, flows, velocities, and
quality. Rules are declared with |
Each layer is described in turn below. Simulation validation covers the third layer in more depth.
Layer 1: structural
When it runs. While the input document is read, before a
WNTREPANETNetwork exists. An INP document is read by
read_inp_to_dict; a WNTR JSON document is read by the WNTR JSON parser.
What it checks. That the document is well-formed and internally consistent: section headers are recognised, data lines have the right number of fields, numeric values parse, and names referenced within the document resolve. The parser does not validate the model’s hydraulics or its EPANET conformance — those are the static layer’s job.
Error taxonomy. Failures are raised as InpParseError subclasses, defined
in epanetparser.core.io.inp_errors:
Exception |
EPANET codes |
Meaning |
|---|---|---|
|
201, 202, 213 |
Malformed section headers, wrong field counts, invalid values. Defaults to code 201. |
|
203, 205, 209, 211, 217, 225 |
Valid syntax but unresolved references or invalid values. Defaults to code 203. |
|
200, 204 |
Missing required sections or fundamental file problems. Defaults to code 204. |
EPANET_ERROR_MAP maps an EPANET code to the matching subclass, and
create_inp_error uses it to construct the right exception from a code.
Each exception carries the offending line_number, the section, the
error_code and a list of suggestions.
Into the report. When a document cannot be parsed, the problem is presented
to callers as a ValidationIssue rather than only as an exception:
InpParseError.to_validation_issue() maps the parser’s fields onto the
shared issue shape, with code E_PARSE_FAILED, validation_layer set to
"structural", rule_id="parse", ruleset_key="parser" and the line,
section, error code and suggestions preserved in context. This is what
lets a malformed file and a model-level rule failure be reported side by
side. Passing --raise-on-error re-raises the InpParseError instead.
Layer 2: static
When it runs. After the WNTREPANETNetwork is built, with no simulator
involved. network.validate() delegates to the validation engine.
What it checks. Simulator-agnostic rules from the core rule set
(epanet_core) and from any custom rule sets the caller selects. Component
rules run collection by collection; network rules run afterwards against the
whole model with the name index available for resolving references. Rules
assert required fields, valid types and values, well-formed curves, unique
names, and references between components.
Codes and metadata. Every rule declares a stable code (generated from
the rule id when not given explicitly) and a category. Severity follows
the function name — rule_* reports ERROR and warn_* reports
WARNING. Issues carry fix_suggestion, the validated component’s
component_data, and a context mapping that records the ruleset,
component subtype and any failing fields. A rule that raises anything other
than AssertionError is a defect in the rule and raises
RuleExecutionError; it is not a finding about the model. Static issues use
the ValidationIssue default, validation_layer="static".
Layer 3: simulation
When it runs. After a hydraulic simulation, on the WNTR results object rather than the model. The layer only runs when simulation validation is requested, via the CLI or programmatically.
What it checks. Post-simulation output: pressures, flows, velocities,
headloss, demand and energy, plus the structure and dtypes of the result
tables. The built-in hydraulic rule set provides the reference rules.
How rules are declared. Rules use the
simulation_rule() decorator and take
the simulation results as their single positional argument. The decorator
attaches a SimulationRuleSpec carrying the rule id, severity, stable
code, attribute, category, fix_suggestion and the
required_outputs the rule needs (for example "node.pressure").
Validator.validate_simulation(results, report) inspects the outputs the
simulator produced and skips any rule whose required_outputs are absent,
so a rule set stays meaningful across simulators with different output.
Codes and metadata. Simulation issues use the decorator’s stable code
and are tagged validation_layer="simulation". Their
simulation_metadata records the rule’s required_outputs, and their
context carries the rule’s static context. The
core_rules.core_dynamic.hydraulic.schemas helpers
(validate_node_results, validate_link_results) additionally check
whole result tables against Pandera schemas, failing when a present field
has the wrong dtype or violates its check. The schemas are optional
(epanetparser[schemas]); without Pandera the schema checks are skipped
and a failure is reported as E_SIMULATION_SCHEMA only when they run.
Unified report
Every issue carries a validation_layer so that layered checks can be
combined, filtered, and reported together:
ValidationIssuehas avalidation_layerfield (one of"structural","static", or"simulation") and asimulation_metadatadictionary for post-simulation context. It also carriescategory,fix_suggestion,component_dataandcontext.ValidationReportaggregates issues from every layer. Itsschema_versiondefaults to"2.0", and its JSON serialisation includes the layer fields so downstream consumers can reproduce the layered result. OnlySeverity.ERRORmakes a report invalid; warnings and informational issues do not.
CLI switches
The validate command exposes one switch per layer combination:
# Static validation, then a simulation run and result validation
$ epanetparser validate -f model.inp --simulate \
--simulation-ruleset hydraulic
# Only the simulation validation (skip static rules)
$ epanetparser validate -f model.inp --simulation-only \
--simulation-ruleset hydraulic
--simulateruns the static rules first, then a hydraulic simulation, then the simulation rules.--simulation-onlyskips the static rules and runs only the simulation layer.--simulation-ruleset <key>selects the simulation rule sets and may be repeated (for example--simulation-ruleset hydraulic).--raise-on-errorchanges how the structural layer reports: a parsing failure is re-raised as an exception (anInpParseErrorfor an INP file) instead of becoming aValidationIssue.
See Basic Usage for the full flag reference.
Why three layers
The layers answer different questions and fail at different stages:
Can the file be read at all? (structural)
Is the model a sound EPANET model? (static)
Does the model behave acceptably under hydraulics? (simulation)
Separating them means a structurally valid model can be rejected by an
application rule set, and a statically valid model can still fail at
simulation time — without any of those outcomes contaminating the others.
Because all three converge into one ValidationReport, a caller can still
present a single, layer-tagged result.