Error Codes
Case loading reports errors in two groups: validation report kinds
(diagnostic categories collected while the case is validated) and
load-failure kinds (returned when a case fails to load). The page also
lists the policy-load errors a checkpoint refusal raises and the
runtime errors of a running novomodelo run.
How these errors reach you
Section titled “How these errors reach you”Novomodelo surfaces errors through four user-facing channels:
-
novomodelo validatehuman report — errors print aserror:prefixed lines; warnings (after a successful validation) print aswarning:prefixed lines. -
novomodelo validate --json— on success, one JSON object on stdout:{ configured, boundary_date, report }, pluspolicy_load: { mode, unused_stored_bases }when the run would load a policy (modewarm_start,resumeorsimulation_only), with noerrorkey;configuredisfalsewhen nopolicy.boundaryis set. On failure, those three keys arenulland anerrorkey holds{ phase, message }. Thephasefield is the name of the failingLoadErrorvariant, one of the six preparation-phase kinds, or a policy-load kind (WarmStartIncompatible,ResumeIncompatible, orIoErrorfor a checkpoint the OS refuses to read); it names aLoadErrorvariant, a preparation kind or a policy-load kind, never theErrorKindof a collected diagnostic. A case-load failure reportsConstraintErrorin almost every case, because every per-file read, parse and schema failure and every validation rule is collected into one report; itsmessageholds one[<ErrorKind>] <file>[ (<entity>)]: <message>line per collected error, joined by newlines (the[ (<entity>)]part appears only when the error names an entity). Warnings are not in the JSON. Only a missing case directory prints no JSON object; it reports on stderr and exits2.A case with no
policy.boundarysucceeds with:{"configured": false,"boundary_date": null,"report": null}A hydro unit group that names a bus that does not exist fails with:
{"configured": null,"boundary_date": null,"report": null,"error": {"phase": "ConstraintError","message": "[InvalidReference] system/hydros.json (Hydro 0 unit group 0): Hydro 0 unit group 0 references non-existent Bus 99 via field 'bus_id'"}} -
novomodelo.io.validate(...)(Python) — returns{ valid, errors: [{ kind, message }], warnings: [{ kind, message, file, entity }] }.errorsholds at most one entry: none whenvalidis true, one on failure. Itskindis the same string as the CLIphase, and itsmessageis the error’s full text, which for a case-load failure startsconstraint violation:. For a missing case directory, where the CLI prints no JSON object, Python still returns anIoErrorentry. Each warning’skindis theErrorKindname. -
novomodelo.errorsPython exception classes — case loading raisesValidationErrororCaseIoError; a policy-load refusal raises the class of its error:PolicyIncompatibleErrorfor a refused checkpoint or one that is missing or cannot be decoded,CaseIoErrorfor a checkpoint read the operating system refuses, andValidationErrorfor a missing policy directory or a refused boundary reconciliation (see Policy-load errors). Seenovomodelo.errorsfor the full hierarchy and Python Quickstart — Error Handling for catching these classes.
Policy-load failures use different exit codes and exception classes; see How a policy-load failure reaches you. For the cause and fix of the failures a study commonly meets, see Troubleshooting.
Kind index
Section titled “Kind index”Find the kind named on your error line (the word in square brackets, or the phase value of novomodelo validate --json) in the first column; its row gives how the kind is reported, its severity, and the exit code novomodelo returns for it.
| Kind | Reported as | Severity | Exit code |
|---|---|---|---|
FileNotFound | [FileNotFound] report line | Error | 1 |
ParseError | [ParseError] report line; phase value | Error | 1 as a report line; as a phase value, 4 (novomodelo validate) or 1 (novomodelo run) |
SchemaViolation | [SchemaViolation] report line | Error | 1 |
InvalidReference | [InvalidReference] report line | Error | 1 |
DuplicateId | [DuplicateId] report line | Error | 1 |
InvalidValue | [InvalidValue] report line | Error | 1 |
CycleDetected | [CycleDetected] report line | Error | 1 |
DimensionMismatch | [DimensionMismatch] report line | Error | 1 |
BusinessRuleViolation | [BusinessRuleViolation] report line | Error | 1 |
WarmStartIncompatible | phase value (refused policy load); warning: line | Error; Warning | 1 (2 under phase IoError); 0 for a warning |
ResumeIncompatible | phase value (refused policy load); warning: line | Error; Warning | 1 (2 under phase IoError); 0 for a warning |
NotImplemented | [NotImplemented] report line | Error | 1 |
UnusedEntity | warning: line | Warning | 0 |
ModelQuality | warning: line | Warning | 0 |
SemanticAmbiguity | warning: line | Warning | 0 |
IoError | phase value | Error | 2, including an OS-refused policy read |
SchemaError | phase value | Error | 1, including a policy.path refusal; 4 under novomodelo validate only for the post-report AR-coefficient count mismatch (novomodelo run: 1) |
ConstraintError | phase value | Error | 1 |
ConfigValidationError | phase value | Error | 1 |
StochasticPreparationError | phase value | Error | 1, or 2 for a stochastic-input read such as a declared opening tree that is absent (both subcommands) |
StudySetupError | phase value | Error | 1 |
HydroModelsPreparationError | phase value | Error | 1 |
GenericConstraintValidationError | phase value | Error | 1 |
BoundaryReconciliationError | phase value | Error | 1 |
PolicySoftwareMismatch | policy-load refusal | Error | 1 |
| Stored bases not used | warning: line | Warning | 0 |
Runtime failures carry no kind; Runtime errors gives the exit code of each message.
A solver-profile rejection carries phase StudySetupError, or
BoundaryReconciliationError with policy.boundary configured;
Solver profile validation states its exit code.
Validation report kinds
Section titled “Validation report kinds”Each error a case load collects prints as one report line, [<Kind>] <file> (<entity>): <message>. The
(<entity>) part appears only when the error names an entity, and <Kind> is one of the values below.
novomodelo validate prints each error as error: followed by its report line, and exits 1 when any error is
present. novomodelo run prints error: constraint violation: followed by the same lines. After a successful
validation, novomodelo validate prints each warning as a warning: line of the form <file> (<entity>): <message>
after the Valid case: ... line, without the bracketed kind, and novomodelo.io.validate (Python) returns the
warnings, each with its kind, in its warnings array.
A warning of the policy load the run would apply prints after them as
warning: <output dir>/<policy.path>: <message>, names no entity, and counts in the Validation: line.
The three warning kinds (UnusedEntity, ModelQuality,
SemanticAmbiguity) never stop a run, and neither does a BusinessRuleViolation that novomodelo validate prints as a warning: line; every other report line is an error that must be resolved
before the case loads.
WarmStartIncompatible and ResumeIncompatible are not report-line kinds: each names a refused policy load,
as the phase value, and that load’s warnings, WarmStartIncompatible for a warm-start or simulation-only
load and ResumeIncompatible for a resume load; see their entries below.
FileNotFound
Section titled “FileNotFound”Severity: Error
When it occurs: A file that is required by the case structure is missing from the case directory. Emitted when the required files are checked, once for each of them that is not found on disk.
Example: A case directory without system/hydros.json:
[FileNotFound] system/hydros.json: required file 'system/hydros.json' not found in case directoryA case directory without system/buses.json gives two
[FileNotFound] lines: the first reports the missing required file, the second
the failed read of it:
[FileNotFound] system/buses.json: required file 'system/buses.json' not found in case directory[FileNotFound] system/buses.json: No such file or directory (os error 2)Resolution: Create the missing file in the correct subdirectory. The required files are: config.json, penalties.json, stages.json,
initial_conditions.json, system/buses.json, system/lines.json,
system/hydros.json, and system/thermals.json.
ParseError
Section titled “ParseError”Severity: Error
When it occurs: A file exists and was read but could not be parsed —
invalid JSON syntax, an unreadable Parquet header, a missing required field, or
an unknown enum variant in a tagged JSON union. Emitted when a file is parsed and the
parse fails. config.json and
system/hydros.json report a missing field or an unknown variant as a
SchemaViolation instead, and
system/hydro_production_models.json does so for an unknown variant.
A training.stopping_rules entry that carries a field that belongs to a different rule type — for example,
seconds (a time_limit field) on a bound_stalling rule — is also rejected
as a ParseError with an unknown field message.
Display format:
parse error in {path}: {message}This format reaches you only for a parse error raised after the validation report is complete (see Load-failure kinds); a parse failure in the report prints as the [ParseError] line shown in the examples below.
Fields:
| Field | Description |
|---|---|
path | Path to the file that failed to parse |
message | Human-readable description of the parse failure |
Example:
[ParseError] stages.json: parse error: EOF while parsing a list at line 2 column 0A missing required field, here a constraint entry without slack:
[ParseError] constraints/generic_constraints.json: parse error: missing field `slack` at line 1 column 69A bound_stalling rule that carries seconds, a time_limit field:
[ParseError] config.json: parse error: unknown field `seconds`, expected `iterations` or `tolerance` at line 19 column 5Resolution: Fix the syntax error in the indicated file. Use a JSON linter
or Parquet viewer to find the exact location. For JSON files, common causes are
trailing commas, missing quotation marks, or mismatched braces. For a
missing field message, add the field it names. For an unknown field message inside a stopping_rules entry, remove the field that does not
belong to that entry’s type, or move it to an entry whose type declares it.
See also: an anticipated_config object in system/thermals.json that matches neither accepted
lead shape is collected under this kind — see
Invalid anticipated-thermal lead.
Unknown field in a generic-constraint entry
Section titled “Unknown field in a generic-constraint entry”When it occurs: Each generic-constraint object accepts exactly five fields
(id, name, description, expression, slack); any other key is rejected
as a ParseError with an unknown field message.
Fields:
| Field | Description |
|---|---|
path | Always constraints/generic_constraints.json |
message | An “unknown field” message, naming the offending key and the five accepted field names |
Example: A constraint object with an extra type key:
[ParseError] constraints/generic_constraints.json: parse error: unknown field `type`, expected one of `id`, `name`, `description`, `expression`, `slack` at line 10 column 12Resolution: Remove the unknown field, leaving only the five accepted keys. See Generic Constraints for the grammar.
SchemaViolation
Section titled “SchemaViolation”Severity: Error
When it occurs: A file parses successfully but a field fails a schema
constraint: a required field is missing from config.json or system/hydros.json, or a required Parquet column is missing (a missing field in any other JSON file is a ParseError), a value is outside its valid range
(e.g., negative capacity, non-positive penalty cost, a reservoir whose
min_storage_hm3 exceeds max_storage_hm3, or a stage whose num_openings is
0), a field contains an unexpected type, or an entity id appears twice in a
registry file such as system/buses.json. Emitted when the fields of a parsed
file are checked.
Example:
A negative penalty cost:
[SchemaViolation] system/buses.json: field buses[0].deficit_segments[0].cost: penalty value must be > 0.0, got -100A reservoir whose minimum storage exceeds its maximum:
[SchemaViolation] system/hydros.json: field hydros[0].reservoir: min_storage_hm3 (800) must be <= max_storage_hm3 (500)A stage with no openings:
[SchemaViolation] stages.json: field stages[0].num_openings: num_openings must be > 0A config.json without training.selection:
[SchemaViolation] config.json: field training.selection: a forward-pass count is required via training.selectionTwo buses that share an id in system/buses.json:
[SchemaViolation] system/buses.json: field buses[1].id: duplicate id 0 in buses arrayResolution: Correct the value in the indicated field. Field paths use
dot-notation and zero-based array indices. Consult the
Case Format page for valid ranges and required fields.
For storage bounds, ensure min <= max. For opening counts, ensure num_openings >= 1.
For config.json, training.selection (for example
{ "method": "sampled", "forward_passes": 192 }) and training.stopping_rules
are mandatory keys with no default when absent. An absent training.selection is
reported with the message a forward-pass count is required via training.selection; add the key to config.json.
See also: a constraints/hydro_bounds.parquet spillage-band inversion or
non-negativity rejection is collected under this kind during a full case
load — see
Spillage-band inversion.
Two of the four failures of
Invalid anticipated-thermal lead print [SchemaViolation] lines; the
other two print [ParseError] and [BusinessRuleViolation] lines. A case load prints every schema failure
as a [SchemaViolation] line; the schema error in <path>, field <field>: <message> form is the display
format of SchemaError, which reaches you only for a load error raised after the report is
complete.
Configuration load rules
Section titled “Configuration load rules”config.json loading refuses these values; each prints a [SchemaViolation] config.json: field <field>: <message> line, and both commands exit 1.
| Rule | Field | Message |
|---|---|---|
No iteration_limit rule | training.stopping_rules | must contain an iteration_limit rule |
interval_iterations absent or below 1, checkpointing enabled | policy.checkpointing.interval_iterations | must be at least 1 when policy.checkpointing.enabled is true; got {found} |
policy.path naming no subdirectory ("", ., ..) | policy.path | "{value}" names the output directory or one of its ancestors, which a checkpoint write would replace; choose another directory, such as "./policy" |
{found} is no value when the key is absent and 0 otherwise. {value} is the configured policy.path.
Refused policy.path
Section titled “Refused policy.path”When it occurs: policy.path is resolved against --output, and a link is checked where it sits and where it points. The path is refused when it names the output directory or one of its ancestors, or names, contains or lies inside a directory a run rewrites or removes. Configuration — policy states the rule. In a case that trains, the path is refused too when it (for a link, its target) or its .staging or .previous sibling is not a directory or holds an entry no checkpoint writer leaves there; the message is refusing to write a checkpoint: …, quoted under Policy Management — Checkpoint Directory Contents.
"{value}" names the output directory or one of its ancestors, which a checkpoint write would replace; choose another directory, such as "./policy""{value}" {relation} {dir}, which a run removes whole before writing its outputs; choose a directory that neither contains nor lies inside it, such as "./policy""{value}" {relation} {dir}, which holds files a run writes and a checkpoint write would replace; choose a directory that neither names nor contains it, such as "./policy"{relation} is names, contains or lies inside, and {dir} is the output-relative directory, such as simulation/hydros. For a link, {value} reads {path} -> {target}. novomodelo validate reports phase SchemaError (exit 1), or IoError (exit 2) when the path cannot be inspected; novomodelo run prints error: schema error in {path}, field policy.path: {message} and exits 1.
Overlapping seasons in one resolution level
Section titled “Overlapping seasons in one resolution level”When it occurs: Seasons whose spans differ by at most 7 days form one resolution level, and two seasons of one level may not share a calendar day. The load refuses an overlapping pair of season_definitions with [SchemaViolation] stages.json: field season_definitions.seasons: <message>, where <message> is:
seasons {a} and {b} overlap within one resolution level; seasons whose spans differ by at most 7 days form one level and must not share a calendar day{a} and {b} are the ids of the two seasons. Stage Files — stages.json describes season_definitions.
Namespace collision between a scalar parameter and a named expression
Section titled “Namespace collision between a scalar parameter and a named expression”When it occurs: constraints/generic_constraints.json’s expressions
array declares named linear expressions that share the @name reference
namespace with constraints/generic_parameters.json’s scalar parameters. A
named-expression entry whose name matches an already-loaded scalar
parameter’s name is rejected — a @name token must resolve unambiguously to
exactly one kind of thing.
Fields:
| Field | Description |
|---|---|
path | Always constraints/generic_constraints.json |
field | expressions[i].name — the colliding entry’s index |
message | name "<name>" is declared as both a scalar parameter and a named expression; the "@name" namespace is shared |
Example:
[SchemaViolation] constraints/generic_constraints.json: field expressions[0].name: name "demanda" is declared as both a scalar parameter and a named expression; the "@name" namespace is sharedResolution: Rename the named expression (or the scalar parameter) so the
two @name tables do not overlap. See
Generic Constraints for the @name
grammar shared between the two files.
Named-expression reference cycle
Section titled “Named-expression reference cycle”When it occurs: expressions[] entries reference each other by @name;
a cycle in that reference graph (including a length-1 self-reference) is
rejected before any expression is inlined, even if no constraint ever uses it.
Fields:
| Field | Description |
|---|---|
path | Always constraints/generic_constraints.json |
field | Always expressions |
message | named-expression reference cycle detected: <a> -> <b> -> ... -> <a> |
Example:
[SchemaViolation] constraints/generic_constraints.json: field expressions: named-expression reference cycle detected: fnese -> fnese_margin -> fneseResolution: Break the cycle — rewrite one of the named expressions in the
reported chain so it no longer refers back to an ancestor. See
Generic Constraints for the @name
composition rules.
Named-expression inlining term budget exceeded
Section titled “Named-expression inlining term budget exceeded”When it occurs: Inlining substitutes every @name reference with its
referenced expression’s terms. An acyclic but exponentially-expanding
declaration — e.g. a doubling chain @e_k = @e_{k-1} + @e_{k-1} — is caught
once the flattened term count would exceed a hard cap of 100,000 terms, far
above any legitimately authored expression, aborting the blow-up before the
full vector materializes. The cap applies only when a constraint (or another
expression) actually references the runaway declaration — declaring it
without referencing it is a cheap existence check, not an expansion.
Fields:
| Field | Description |
|---|---|
path | Always constraints/generic_constraints.json |
field | constraints[i].expression (or the referencing expression’s own field) |
message | named-expression reference "@<name>" expands to more than 100000 inlined terms; this indicates an exponential reference pattern (e.g. a doubling chain "@e = @prev + @prev") |
Example:
[SchemaViolation] constraints/generic_constraints.json: field constraints[0].expression: named-expression reference "@e60" expands to more than 100000 inlined terms; this indicates an exponential reference pattern (e.g. a doubling chain "@e = @prev + @prev")Resolution: Rewrite the named-expression chain to grow linearly rather
than by repeated self-addition — e.g. accumulate into one running expression
instead of doubling a reference at each step. See
Generic Constraints for the @name
composition rules.
@parameter / @name reference misuse
Section titled “@parameter / @name reference misuse”When it occurs: The expression grammar accepts at most one @-prefixed
reference per term, and a parenthesized group takes only a literal
coefficient. Four distinct authoring mistakes are rejected at the same parse
site (constraints[i].expression, or expressions[i].expression for a named
expression’s own definition):
| Mistake | Example message |
|---|---|
Two @ references in one term (@param * @name, or two @params) | only one @parameter reference is allowed per term; found "@a" and "@b" |
@param scaling a parenthesized group (@param * (...)) | parameter "@a" cannot scale a parenthesized group: a group takes only a literal coefficient, not "@a * (...)" |
@name naming no loaded scalar parameter, in a coefficient position | unknown parameter "@a": no definition with this name was loaded |
@name naming no declared expression, in a reference position | undeclared named-expression reference "@a": no expression with this name was declared |
Fields:
| Field | Description |
|---|---|
path | Always constraints/generic_constraints.json |
field | constraints[i].expression or expressions[i].expression |
message | One of the four messages in the table above |
Resolution: A term carries at most one @ reference, and only a literal
number may scale a (...) group. Check that a bound @name matches a
parameter actually declared in constraints/generic_parameters.json, and
that a reference @name matches an entry actually declared in this file’s own
expressions array. See Generic Constraints
for the full grammar.
Repeated block argument
Section titled “Repeated block argument”When it occurs: A variable takes at most one positional block argument. A second one, equal or not, is rejected when the expression is parsed, for example hydro_turbined(5, 0, 1).
Fields:
| Field | Description |
|---|---|
path | Always constraints/generic_constraints.json |
field | constraints[<i>].expression or expressions[<i>].expression |
message | repeated block argument in variable "{name}", where {name} is the variable, such as hydro_turbined |
Resolution: Give the variable one block argument. See Generic Constraints — Expression grammar for the full grammar.
Missing required Parquet column
Section titled “Missing required Parquet column”When it occurs: A Parquet input that lacks a required column, under whatever other name the file carries it, is a schema failure naming the required column.
Fields:
| Field | Description |
|---|---|
path | The Parquet file |
field | The required column name |
message | missing required column "<name>" |
Example: A scenarios/inflow_seasonal_stats.parquet whose hydro_id column is named hydro:
[SchemaViolation] scenarios/inflow_seasonal_stats.parquet: field hydro_id: missing required column "hydro_id"Resolution: Rename the column to the name the file requires, keeping its data unchanged. The column tables of Case Format list the required columns of every file.
Scenario-source admission rules
Section titled “Scenario-source admission rules”When it occurs: A training.scenario_source or simulation.scenario_source
block of config.json breaks an admission rule. The rules are checked when
config.json loads, so novomodelo validate and novomodelo run both exit 1. In the
table, {section} stands for training or simulation, whichever block broke
the rule, and {from} and {to} stand for the first and last year of the range
as the case declares them. The printed message shows those values in place of
the placeholders.
| Rule | Field | Message |
|---|---|---|
openings under simulation | simulation.scenario_source.openings | openings is only valid under training.scenario_source, not simulation.scenario_source |
historical_years without a historical class | {section}.scenario_source.historical_years | historical_years is specified but no class uses the 'historical' scheme |
historical on load or ncs | {section}.scenario_source.load.scheme or {section}.scenario_source.ncs.scheme | historical scheme is only valid for the inflow class |
Missing seed | {section}.scenario_source.seed | seed is required when any class uses out_of_sample or external scheme |
Inverted historical_years range | {section}.scenario_source.historical_years | range 'from' ({from}) must be <= 'to' ({to}) |
Example: novomodelo validate on a case whose training.scenario_source sets the
inflow class to out_of_sample and no seed:
[SchemaViolation] config.json: field training.scenario_source.seed: seed is required when any class uses out_of_sample or external schemenovomodelo validate prints this line after an error: prefix. novomodelo run prints
it after error: constraint violation: .
Resolution: Configuration lists the
scenario_source keys. Find the message in the table above, then fix that rule:
openings: declare it undertraining.scenario_sourceonly.historical_years: remove it, or set the inflow class to thehistoricalscheme.loadorncs: use another scheme; only the inflow class takeshistorical.seed: add it to the block.- Inverted
historical_yearsrange: setfromno later thanto.
A declared opening-tree file that is missing is reported in the stochastic phase: Opening-tree file missing.
Invalid anticipated-thermal lead
Section titled “Invalid anticipated-thermal lead”When it occurs: A thermal’s anticipated_config in system/thermals.json
takes exactly one of two keys: lead_stages (an integer of at least 1) or
lead_time_hours (a finite number above 0). A lead longer than the whole study
horizon must also reach a declared post-study stage. Four failures each add one
line to the validation report:
lead_stagesis 0.lead_time_hoursis zero or negative.- The object matches neither accepted shape, for example because it has both keys, has neither key, or has a negative
lead_stages. - The lead is longer than the whole study horizon and reaches no declared post-study stage.
Messages: novomodelo validate prints one error: line for each failure. The first
three failures stop system/thermals.json at its first offending entry, so a file
with several bad leads reports only the first; the horizon rule reports every
offending thermal. The captures follow the order of the list above.
error: [SchemaViolation] system/thermals.json: field thermals[0].anticipated_config.lead_stages: lead_stages must be >= 1, got 0error: [SchemaViolation] system/thermals.json: field thermals[0].anticipated_config.lead_time_hours: lead_time_hours must be finite and > 0.0, got -1error: [ParseError] system/thermals.json: parse error: data did not match any variant of untagged enum RawAnticipatedConfig at line 18 column 5error: [BusinessRuleViolation] system/thermals.json (thermals[id=0].anticipated_config.lead_stages): Thermal 0: lead_stages exceeds study horizon (lead_stages=9, n_stages=4); the plant can never deliver within the study horizonA lead_time_hours lead that fails the horizon rule produces this message
instead of the lead_stages one above:
Thermal {thermal_id}: lead_time exceeds study horizon (lead_time={delta_hours}, total_horizon_hours={total_horizon_hours}); the plant can never deliver within the study horizon{thermal_id} is the thermal’s id, {delta_hours} is its lead in hours, and
{total_horizon_hours} is the length of the whole study horizon in hours. In
the SchemaViolation lines, the index in thermals[0] is the entry’s position in the thermals array, counting from 0; the horizon message names the thermal id
instead, as in thermals[id=0].
Resolution: Keep one of the two keys: give lead_stages an integer of at
least 1, or lead_time_hours a number above 0. For a lead longer than the study
horizon, shorten the lead, or declare, in post_study_stages.json, a post-study stage that the lead
reaches. See
System Element Modeling Overview §4 for the
anticipated-thermal model and
system/thermals.json
for the file’s fields.
Spillage-band inversion
Section titled “Spillage-band inversion”When it occurs: An inverted spillage band (min_spillage_m3s > max_spillage_m3s)
or a negative min_diversion_m3s, min_spillage_m3s, or max_spillage_m3s in
constraints/hydro_bounds.parquet is reported here (tagged [SchemaViolation])
when a full case is loaded, and surfaces as a standalone SchemaError when that
file is checked on its own.
Fields:
| Field | Description |
|---|---|
description | Contains a [SchemaViolation] constraints/hydro_bounds.parquet: field hydro_bounds[<i>].<column>: <message> line — see below |
Example:
[SchemaViolation] constraints/hydro_bounds.parquet: field hydro_bounds[0].max_spillage_m3s: max_spillage_m3s (5) must be >= min_spillage_m3s (10)[SchemaViolation] constraints/hydro_bounds.parquet: field hydro_bounds[0].min_diversion_m3s: value must be >= 0.0, got -5Resolution: Correct the offending row so min_spillage_m3s <= max_spillage_m3s, and so min_diversion_m3s, min_spillage_m3s, and
max_spillage_m3s are all non-negative. See
Case Format for
hydro_bounds.parquet’s column reference.
InvalidReference
Section titled “InvalidReference”Severity: Error
When it occurs: A cross-entity foreign-key reference points to an entity
that does not exist in the expected registry. For example, one of a hydro
plant’s unit groups’ bus_id references a bus that is not in
system/buses.json. Emitted when the cross-entity references are resolved.
Example:
[InvalidReference] system/hydros.json (Hydro 0 unit group 0): Hydro 0 unit group 0 references non-existent Bus 99 via field 'bus_id'Resolution: Either add the referenced entity to its registry file, or
correct the ID in the referencing file. Check all ID references:
unit_groups[].bus_id, thermals.bus_id, lines.source_bus_id,
lines.target_bus_id, hydros.downstream_id.
See also: a constraints/ncs_bounds.parquet row with an undeclared stage_id is reported under this
kind — see
Undeclared stage_id in bound-override parquet.
Generic-constraint bound reference with no activation rows
Section titled “Generic-constraint bound reference with no activation rows”When it occurs: constraints/generic_constraint_bounds.parquet is the
activation grid for generic constraints — a constraint applies at
(stage, block) if and only if a row exists for it there, independent of
whether the row supplies a numeric endpoint or the endpoint comes entirely
from the constraint’s own inline affine remainder (an inline <= @demanda,
say). A constraint whose only endpoints come from that affine remainder, but
which has zero rows in the bounds parquet, is collected as InvalidReference
— the reference would otherwise apply to nothing and be silently inert.
Fields:
| Field | Description |
|---|---|
description | Contains a [InvalidReference] constraints/generic_constraints.json (GenericConstraint <id>): GenericConstraint <id> declares a bound reference but has no activation rows in generic_constraint_bounds.parquet: the reference would apply to nothing line |
Example:
constraint violation: [InvalidReference] constraints/generic_constraints.json (GenericConstraint 3): GenericConstraint 3 declares a bound reference but has no activation rows in generic_constraint_bounds.parquet: the reference would apply to nothingResolution: Add at least one row for the constraint’s id to
generic_constraint_bounds.parquet at every (stage, block) where it should
apply — the row may leave both bound_lower/bound_upper null as long as the
constraint’s own affine remainder supplies the value; the row’s job is then
only to activate the cell. See
Generic Constraints — the activation grid
for the full model.
DuplicateId
Section titled “DuplicateId”Severity: Error
When it occurs: An id or key is declared twice where it must be unique.
Three cases emit this kind: a unit-group id declared twice in one hydro plant
(unit-group ids are unique within a plant, not globally); a policy-graph node
id declared twice in policy_graph.nodes[] of stages.json; and two rows of a
constraints/*_bounds.parquet override file that set the same column for the
same entity, stage and block. An entity id declared twice in a registry file,
such as two buses with the same id in system/buses.json, is a
SchemaViolation, not a DuplicateId; see the
two-buses example there.
Example:
[DuplicateId] system/hydros.json (Hydro 0): Hydro 0: unit group id 0 is declared more than once; unit group ids must be unique within a plantResolution: Rename the repeated unit-group or node id, or remove or merge
the bound-override rows that set the same column twice.
InvalidValue
Section titled “InvalidValue”Severity: Error
When it occurs: A value violates a domain rule that novomodelo validate
checks once the files have loaded. Examples: the outgoing transition
probabilities from a stage in stages.json do not sum to 1.0, or a study stage
has an empty blocks array. A reservoir whose min_storage_hm3 exceeds
max_storage_hm3, and a stage whose num_openings is 0, are reported as
SchemaViolation lines instead.
Example:
[InvalidValue] stages.json: outgoing transition probabilities from stage 0 sum to 0.50000000 (expected 1.0 ±0.000001); probability must sum to 1.0[InvalidValue] stages.json: stage 1 declares no blocks; every study stage must declare at least one blockResolution: Correct the value named in the message. Consult
the Case Format page for documented constraints. For transitions,
make the outgoing probabilities from each stage sum to 1.0. For stages, declare at least one
block in every study stage’s blocks array.
See also: a min_diversion_m3s floor on a hydro with no declared
diversion channel is reported under this kind — see
Diversion floor without a declared channel.
Generic-constraint bounds row with neither endpoint and no affine remainder
Section titled “Generic-constraint bounds row with neither endpoint and no affine remainder”When it occurs: A generic_constraint_bounds.parquet row with
bound_lower = null and bound_upper = null is legal only when the
constraint’s own bound_lower_affine/bound_upper_affine supplies at least
one endpoint (the row then only activates the cell, per above). A both-null
row on a constraint with no affine remainder on either side has no
endpoint at all and is collected as InvalidValue.
Fields:
| Field | Description |
|---|---|
description | Contains a [InvalidValue] constraints/generic_constraint_bounds.parquet (GenericConstraintBoundsRow[<i>]): GenericConstraintBoundsRow[<i>] on constraint <id> has neither bound_lower nor bound_upper: at least one endpoint is required line |
Example:
constraint violation: [InvalidValue] constraints/generic_constraint_bounds.parquet (GenericConstraintBoundsRow[4]): GenericConstraintBoundsRow[4] on constraint 3 has neither bound_lower nor bound_upper: at least one endpoint is requiredResolution: Either supply bound_lower and/or bound_upper on the row,
or add an inline relational operator (<=, >=, ==) to the constraint’s
expression so an affine remainder fills the missing endpoint. See
Generic Constraints — the activation grid.
Diversion floor without a declared channel
Section titled “Diversion floor without a declared channel”When it occurs: constraints/hydro_bounds.parquet may override a hydro’s
min_diversion_m3s floor per (hydro, stage). When the hydro declares no
diversion channel, its diversion flow is fixed at [0, 0] — a positive floor
is then the infeasible interval [min > 0, 0]. The business-rule check rejects
the override before it ever reaches the LP.
Fields:
| Field | Description |
|---|---|
description | Contains a [InvalidValue] constraints/hydro_bounds.parquet (Hydro <id>): Hydro <id>: hydro_bounds row at stage_id=<stage> sets min_diversion_m3s=<value>, but the hydro declares no diversion channel; diversion is pinned [0, 0] with no channel, making a positive floor infeasible line |
Example:
[InvalidValue] constraints/hydro_bounds.parquet (Hydro 0): Hydro 0: hydro_bounds row at stage_id=1 sets min_diversion_m3s=2, but the hydro declares no diversion channel; diversion is pinned [0, 0] with no channel, making a positive floor infeasibleResolution: Either declare a diversion channel on the hydro in
system/hydros.json (see System Element Modeling Overview), or remove
the min_diversion_m3s override for that (hydro, stage) in
constraints/hydro_bounds.parquet.
CycleDetected
Section titled “CycleDetected”Severity: Error
When it occurs: A directed graph that must be acyclic contains a cycle.
Two graphs are checked. The hydro cascade: the downstream_id links among hydro
plants must form a directed forest (no cycles), so a cycle would mean plant A
drains into plant B which drains back into plant A. The policy graph: the
policy_graph.nodes[] of stages.json, linked by transitions[], must be
acyclic; this check applies only when nodes[] is declared.
Messages:
[CycleDetected] system/hydros.json: hydro cascade contains a cycle involving hydro IDs: [0, 1]The policy-graph nodes message:
policy-graph nodes contain a cycle; the node graph must be acyclicnovomodelo validate prints it as [CycleDetected] stages.json: <message>, where
<message> is the text above.
Resolution: Review the downstream_id chain for the listed plants and remove
the cycle. Every hydro cascade must be a directed tree rooted at plants with no
downstream (tailwater discharge). For the policy graph, remove the
transitions[] entry that closes the cycle.
DimensionMismatch
Section titled “DimensionMismatch”Severity: Error
When it occurs: A cross-file coverage check fails. For example, when
scenarios/inflow_seasonal_stats.parquet is present, every hydro needs a row
for each study stage in which it is active, and each missing (hydro, stage)
pair is one error that names the hydro and the stage. A mismatch means an
optional per-entity file provides data for some entities but not all that
require it.
Example:
[DimensionMismatch] scenarios/inflow_seasonal_stats.parquet (Hydro 1): Hydro 1 missing inflow seasonal stats for stage 0Resolution: Add the missing rows to the Parquet file; the message names the hydro and the stage that lack one. When that file is present, every hydro needs a row for each study stage in which it is active.
BusinessRuleViolation
Section titled “BusinessRuleViolation”Severity: Error
When it occurs: A domain-specific business rule is violated that cannot
be expressed as a simple range constraint. Examples: a negative turbined_cost
on a hydro whose generation model is fpha, a filling schedule that cannot
reach the dead volume before the hydro enters operation, the anticipated-thermal
and post-study-boundary rules, a bound-override row that names an undeclared
stage_id, a pumping station in service at a stage where its source or destination hydro is not Operating, or a per-block generic-constraint reference that the stage’s blocks
cannot satisfy. One check reports a BusinessRuleViolation as a warning: when the inflow model is estimated from scenarios/inflow_history.parquet and a hydro has more than one observation in one season of a year, novomodelo validate prints a warning: line (exit 0) saying the observations are aggregated to season resolution.
Example:
error: [BusinessRuleViolation] penalties.json (Hydro 0): Hydro 0: turbined_cost (-0.05) must be non-negative (>= 0) for FPHA hydros; negative values distort LP dispatchResolution: Read the message carefully — it describes the specific rule that was violated and which entities are involved. For the two penalty rules, see Load-time penalty checks.
See also: the entries below hold the anticipated-thermal and post-study boundary rules
(Commitment on a non-anticipated thermal,
Missing commitment window on an anticipated thermal,
Post-study boundary unanchored or malformed and
Uncovered or over-covered past_anticipated_commitments window),
the undeclared-stage_id bound-override rule
(Undeclared stage_id in bound-override parquet) and
the per-block generic-constraint references
(Per-block generic-constraint references).
The pumping-station rule is under
Pumping station outside its endpoints’ Operating windows.
The horizon rule of Invalid anticipated-thermal lead also prints a
[BusinessRuleViolation] line; that entry sits under SchemaViolation, where two of its four lines print.
Deterministic external inflow column under an autoregressive model
Section titled “Deterministic external inflow column under an autoregressive model”When it occurs: Under the external inflow sampling scheme, an inflow’s
seasonal mean and standard deviation are replaced by the sample moments of its
external scenario file only for a hydro of autoregressive order 0 with no annual
component; a hydro whose files declare a lag coefficient or an annual component
keeps the statistics its own files declare. The substitution is per class: a
load or non-controllable-source class takes its moments from its external file
only when that class is itself external.
A constant (σ = 0) column is accepted for load, NCS, and a hydro of order 0 with no annual component, but rejected for a hydro whose files declare an autoregressive order above 0 or an annual component: a single deterministic value cannot stand in for such a model, since it would have to equal that model’s own stage-by-stage deterministic PAR output, which the loader does not reconstruct from a flat column.
Fields:
| Field | Description |
|---|---|
description | Contains a [BusinessRuleViolation] <external inflow file> (inflow entity <e> stage <stage_id>): <message> line |
Example:
constraint violation: [BusinessRuleViolation] scenarios/external_inflow_scenarios.parquet (inflow entity 7 stage 3): inflow external library at stage 3, entity 7: every scenario value is constant (σ = 0), but this hydro's inflow follows an autoregressive model of order > 0; a deterministic value here would have to equal that model's own deterministic PAR output at every stage, which this loader does not compute upstreamResolution: Give the external column genuine stage-to-stage variation, or
drop the plant’s autoregressive inflow model to order 0 (no lag coefficient, no
annual component) if its inflow really is deterministic. See
Scenario Generation §4.4 for the accept/reject
rule and scenario_source for the
scheme configuration.
Scalar parameters outside the constraints directory
Section titled “Scalar parameters outside the constraints directory”When it occurs: Scalar parameters are read from
constraints/generic_parameters.json only, beside the generic constraints that
reference its @name parameters. A file at system/scalar_parameters.json is
rejected whatever its content; the loader never reads it, so a well-formed file at that path is still refused.
Fields:
| Field | Description |
|---|---|
description | Contains a [BusinessRuleViolation] system/scalar_parameters.json: system/scalar_parameters.json is no longer read; scalar parameters are now read from constraints/generic_parameters.json, beside the constraints that reference them by @name. Move the file (its contents are unchanged). line |
Example:
[BusinessRuleViolation] system/scalar_parameters.json: system/scalar_parameters.json is no longer read; scalar parameters are now read from constraints/generic_parameters.json, beside the constraints that reference them by @name. Move the file (its contents are unchanged).Resolution: mkdir -p <case>/constraints && git mv <case>/system/scalar_parameters.json <case>/constraints/generic_parameters.json — the contents do not change. See
Generic Constraints — the five parameter kinds
for the current file’s schema.
Commitment on a non-anticipated thermal
Section titled “Commitment on a non-anticipated thermal”When it occurs: initial_conditions.json’s past_anticipated_commitments
entries declare an externally-decided commitment for an anticipated thermal,
keyed by thermal_id. Case validation rejects any entry whose thermal_id
names no thermal with an anticipated_config — either the thermal does not
exist, or it exists but is not configured as anticipated.
Fields:
| Field | Description |
|---|---|
description | Contains a [BusinessRuleViolation] initial_conditions.json (initial_conditions.past_anticipated_commitments[thermal_id=<id>]): Thermal <id>: referenced in past_anticipated_commitments but is not an anticipated thermal (anticipated_config is None or thermal does not exist) line |
Example:
constraint violation: [BusinessRuleViolation] initial_conditions.json (initial_conditions.past_anticipated_commitments[thermal_id=12]): Thermal 12: referenced in past_anticipated_commitments but is not an anticipated thermal (anticipated_config is None or thermal does not exist)Resolution: Either set anticipated_config on thermal <id> in
system/thermals.json, or remove/correct the thermal_id in the offending
past_anticipated_commitments entry. See
System Element Modeling Overview §4 for anticipated-thermal configuration and Case Format
for initial_conditions.json’s schema.
Missing commitment window on an anticipated thermal
Section titled “Missing commitment window on an anticipated thermal”When it occurs: Every thermal with an anticipated_config needs at least one
window in initial_conditions.json’s past_anticipated_commitments, keyed by
thermal_id. Case validation rejects a thermal that has none, for a lead given in
stages and for a lead given in hours alike.
Fields:
| Field | Description |
|---|---|
description | Contains a [BusinessRuleViolation] initial_conditions.json (initial_conditions.past_anticipated_commitments): Thermal <id>: missing entry in initial_conditions.past_anticipated_commitments; every anticipated thermal must have at least one commitment window line |
Example:
[BusinessRuleViolation] initial_conditions.json (initial_conditions.past_anticipated_commitments): Thermal 0: missing entry in initial_conditions.past_anticipated_commitments; every anticipated thermal must have at least one commitment windowResolution: Add a window for the thermal to past_anticipated_commitments (a
committed 0 MW is an explicit window), or remove the thermal’s anticipated_config.
See Uncovered or over-covered past_anticipated_commitments window
for the coverage the windows must reach and Case Format
for initial_conditions.json’s schema.
Post-study boundary unanchored or malformed
Section titled “Post-study boundary unanchored or malformed”When it occurs: The business-rule check validates post_study_stages.json — the
post-horizon boundary calendar — and the anticipated-thermal deliveries that
resolve onto it. Several distinct failures are collected under this one check.
(A plant whose lead is longer than the whole study horizon and reaches no
declared post-study stage is caught earlier, at anticipated-thermal validation,
with a
system/thermals.json … the plant can never deliver within the study horizon
message (see
Invalid anticipated-thermal lead); to clear
it, shorten the lead or declare a post-study stage the lead reaches — see
System Element Modeling Overview §4.)
The post-study failures, keyed to post_study_stages.json:
- The post-study calendar’s first stage does not start exactly at the study horizon end.
- The post-study stages are not date-contiguous (a gap or overlap between consecutive stages).
- A post-study stage’s
duration_hoursrounds to a non-positive whole-day span. - Rule 1 — a plant’s lead reaches a post-study stage with no
thermal_boundscell for its(thermal_id, post_study_stage_index). - V2 — a plant’s commitments decided before the study and delivered past the
horizon do not tile the post-study stages they cover at coverage
1.0. - V3 — a commitment window covers a post-study stage the study itself
decides (
carried), or one past the plant’s decision reach. - V5 — a non-zero fixed commitment covers a post-study stage outside the plant’s commissioning window.
Fields:
| Field | Description |
|---|---|
description | Contains one [BusinessRuleViolation] post_study_stages.json (...): <message> line per failure — see the messages below |
Example:
constraint violation: [BusinessRuleViolation] post_study_stages.json (stages[0].start_date): first post-study stage starts 2026-09-08 but the study horizon ends 2026-09-05; the post-study calendar must begin exactly at the study horizon end.constraint violation: [BusinessRuleViolation] post_study_stages.json (stages start_date=2026-10-01): post-study stages are not date-contiguous: the stage starting 2026-09-05 ends 2026-09-28 but the next stage starts 2026-10-01; each stage must end exactly where the next begins.constraint violation: [BusinessRuleViolation] post_study_stages.json (thermals[id=12].anticipated_config): Thermal 12: anticipated lead reaches post-study stage index 0, but post_study_stages.json has no thermal_bounds entry for (thermal_id 12, post_study_stage_index 0).constraint violation: [BusinessRuleViolation] post_study_stages.json (thermals[id=12].anticipated_config): Thermal 12: past_anticipated_commitments do not tile post-study stage index(es) [0] at coverage 1.0; declare the fixed commitment for each (a committed 0 MW is explicit).constraint violation: [BusinessRuleViolation] post_study_stages.json (thermals[id=12].anticipated_config): Thermal 12: past_anticipated_commitments cover post-study stage index(es) [1], which the study itself decides; a declared fixed value there contradicts the study's own decision. Remove the window, or lengthen anticipated_config's lead so the delivery becomes pre-study-decided.constraint violation: [BusinessRuleViolation] post_study_stages.json (thermals[id=12].anticipated_config): Thermal 12: past_anticipated_commitments cover post-study stage index(es) [2], which are past the plant's decision reach and cannot be represented. Remove the window, or shorten anticipated_config's lead so the delivery falls inside the reach.constraint violation: [BusinessRuleViolation] post_study_stages.json (thermals[id=12].anticipated_config): Thermal 12: past_anticipated_commitments window [2026-10-01, 2026-11-01) value_mw = 120 covers post-study stage index 0, which is outside the plant's commissioning window [entry=Some(0), exit=Some(6)); the plant is not in service for this stage and the fixed commitment cannot be delivered. Declare a zero commitment at this stage, or widen the commissioning window.Resolution: Declare post_study_stages.json with its first stage starting
exactly at the study horizon end and every stage date-contiguous with the next.
Add a thermal_bounds cell for every (thermal_id, post_study_stage_index) a
plant’s in-study-decided lead reaches. Tile every post-study stage that a
past_anticipated_commitments window for a commitment decided before the study
covers, at coverage 1.0, never covering a stage the study decides or one past the plant’s reach,
and keep a non-zero fixed commitment inside the plant’s commissioning window.
See Post-Study Boundary & Chained Studies for the boundary model and
Case Format for
post_study_stages.json’s schema.
Uncovered or over-covered past_anticipated_commitments window
Section titled “Uncovered or over-covered past_anticipated_commitments window”When it occurs: An anticipated thermal’s past_anticipated_commitments
windows must tile exactly the in-study delivery stages the plant decided
before the study — every such stage covered at fraction 1.0 — and never a
stage the study itself decides. The business-rule check reports a gap (an
uncovered leading stage), an over-reach (a window covering a study-decided
stage), and a horizon-straddle (a single window crossing the horizon end) as
separate diagnostics; each can fire independently. A window may legitimately
extend past the horizon (a commitment decided before the study and delivered
past the horizon; for the equivalent term in other planning tools, see the
Glossary) — that post-study coverage is validated
separately, under
Post-study boundary above.
Fields:
| Field | Description |
|---|---|
description | Contains a [BusinessRuleViolation] initial_conditions.json (thermals[id=<id>].anticipated_config): <message> line — see the messages below |
Example:
constraint violation: [BusinessRuleViolation] initial_conditions.json (thermals[id=7].anticipated_config): Thermal 7: past_anticipated_commitments do not tile the leading 2 delivery stage(s) at coverage 1.0; study stage id(s) [0] are not covered exactly once. Write a commitment window (a committed 0 MW is explicit) for every leading delivery stage.constraint violation: [BusinessRuleViolation] initial_conditions.json (thermals[id=7].anticipated_config): Thermal 7: past_anticipated_commitments cover study stage id(s) [3] beyond the leading 2 calendar-derived delivery stage(s); a commitment window may not cover a study stage the study itself decides. Shorten the plant's anticipated_config lead so its window stays within the leading 2 delivery stage(s).constraint violation: [BusinessRuleViolation] initial_conditions.json (thermals[id=7].anticipated_config): Thermal 7: past_anticipated_commitments window [2026-08-15, 2026-09-15) straddles the study horizon end (2026-09-01); a single window may not cover both in-study and post-study delivery. Declare two windows split at the horizon instead: one ending at 2026-09-01 for the in-study coverage, one starting at 2026-09-01 for the post-study coverage.Resolution: Write a commitment window for every in-study delivery stage the
plant decided before the study (a committed 0 MW is a legitimate, explicit
window — not an omission), never covering a stage the study itself decides (end the window earlier, or
lengthen the plant’s anticipated_config lead if that stage is in fact decided
before the study), and split any window at the horizon end rather than
straddling it. Those are the in-study delivery stages whose decision falls
before the first study stage: the first lead_stages stages, or, for
lead_time_hours, every stage whose decision instant — one lead before the
stage’s end — is at or before the study start. See
System Element Modeling Overview §4.
Undeclared stage_id in bound-override parquet
Section titled “Undeclared stage_id in bound-override parquet”When it occurs: A row in constraints/{thermal,hydro,line,pumping,contract,hydro_unit_group}_bounds.parquet
whose stage_id is not a declared study stage (membership in the declared ids of
stages.json, which may be gapped or start at 1) is rejected at novomodelo validate
as BusinessRuleViolation; a constraints/ncs_bounds.parquet
row is rejected as InvalidReference with
NcsBoundsRow[i] has invalid stage_id N (not a valid study stage). Rows in
constraints/generic_constraint_bounds.parquet and
constraints/penalty_overrides_*.parquet with an undeclared stage_id are
not rejected and have no effect.
Fields:
| Field | Description |
|---|---|
description | One [BusinessRuleViolation] constraints/{family}_bounds.parquet (...): ... line per rejected row for the six families, or one [InvalidReference] constraints/ncs_bounds.parquet: NcsBoundsRow[i] has invalid stage_id N ... line for NCS |
Example:
error: [BusinessRuleViolation] constraints/hydro_bounds.parquet (hydro_id=0, stage_id=15): Hydro 0: hydro_bounds override names stage_id=15, which is not a declared study stageResolution: Correct the stage_id value to match one of the declared stage
IDs in stages.json. For a typo (e.g., 15 instead of 5), fix the row; for a
stale override referencing a removed stage, delete the row or update the stages
declaration.
Per-block generic-constraint references
Section titled “Per-block generic-constraint references”When it occurs: A generic-constraint term names a block that its stage
cannot expose. The check runs only for a (constraint, stage) pair that has a row
in constraints/generic_constraint_bounds.parquet. An evaporation reference is a
hydro_evaporation term; a storage reference is a hydro_storage_initial,
hydro_storage_final, hydro_useful_volume_initial, or
hydro_useful_volume_final term. With K the number of blocks the stage
declares, the check rejects four conditions:
- A block index of
Kor more in an evaporation or storage reference, on any stage. - An evaporation reference to a block from 1 to
K - 1on a parallel stage withK > 1. - An evaporation reference with no block on a chronological stage with
K > 1. - A storage reference to an interior block boundary, a boundary between two
blocks, on a parallel stage with
K > 1.
The rules are stated under
hydro_evaporation block references.
Kind, file, entity: BusinessRuleViolation,
constraints/generic_constraints.json, and the entity constraint[id=N], where
N is the constraint’s id.
Messages: novomodelo validate prints one error: line per rejection. The five
lines below show one message per template, in this order:
- An evaporation block of
Kor more. - An evaporation block from 1 to
K - 1on a parallel stage. - An interior storage boundary on a parallel stage.
- A storage block of
Kor more. - An evaporation reference with no block on a chronological stage.
error: [BusinessRuleViolation] constraints/generic_constraints.json (constraint[id=0]): Constraint "evap_out_of_range": per-block evaporation reference `hydro_evaporation(0, 5)` at stage 0 references block 5 which does not exist at stage 0 (K = 2)error: [BusinessRuleViolation] constraints/generic_constraints.json (constraint[id=1]): Constraint "evap_parallel_block": per-block evaporation reference `hydro_evaporation(0, 1)` at stage 0 names block 1, past the stage-level evaporation, which requires chronological block mode (stage 0 is parallel with 2 blocks); use block 0 or no blockerror: [BusinessRuleViolation] constraints/generic_constraints.json (constraint[id=2]): Constraint "storage_interior": per-block storage reference `hydro_storage_final(0, 0)` at stage 0 resolves to an interior boundary, which requires chronological block mode (stage 0 is parallel with 2 blocks)error: [BusinessRuleViolation] constraints/generic_constraints.json (constraint[id=3]): Constraint "storage_out_of_range": per-block storage reference `hydro_storage_initial(0, 5)` at stage 0 references block 5 which does not exist at stage 0 (K = 2)error: [BusinessRuleViolation] constraints/generic_constraints.json (constraint[id=4]): Constraint "evap_chronological_bare": stage-level `hydro_evaporation(0)` at chronological stage 1 is ambiguous — evaporation is per-block there (K = 2); name a block, e.g. `hydro_evaporation(0, 0)`Each per-block message quotes the whole term accessor(hydro, block); the stage-level
ambiguity message quotes accessor(hydro) and suggests a block term.
Resolution: Name a block that exists on the stage: block indices run from 0
to K - 1. On a parallel stage, address evaporation with block 0 or with no
block, and address a storage reference only at the stage’s start or end: block 0
or no block for an initial reference, the last block or no block for a final
reference. To address per-block quantities, set block_mode to "chronological"
on the stage in stages.json; a
chronological stage requires a named block for evaporation, as in
hydro_evaporation(<hydro>, 0).
Pumping station outside its endpoints’ Operating windows
Section titled “Pumping station outside its endpoints’ Operating windows”When it occurs: A pumping station in system/pumping_stations.json is in service at a stage where its source or destination hydro is not Operating: before that hydro’s entry_stage_id, at or after its exit_stage_id, or while it is Filling. One line prints for each offending endpoint and names the earliest such stage.
PumpingStation {id}: active at stage {stage_id} while its {side} hydro {hid} is not Operating there (before entry_stage_id, at or after exit_stage_id, or Filling); a pumping station may operate only inside both endpoints' Operating windows{side} is source or destination. The line prints as [BusinessRuleViolation] system/pumping_stations.json (PumpingStation {id}): <message>.
Resolution: Keep the station in service only at stages where both hydros are Operating: adjust the entry_stage_id and exit_stage_id of the station, or those of the hydros.
WarmStartIncompatible
Section titled “WarmStartIncompatible”novomodelo validate reports a refused warm-start or simulation-only load as its phase, with the message labelled
<output dir>/<policy.path>: , and exits 1; a checkpoint the OS refuses to read reports IoError and exits 2.
The same kind carries the load’s warnings, which exit 0: Stored bases not used,
entity manifest absent (source slots: {n}, current slots: {m}); slot identity could not be verified, relying on state_dimension alone
and
slot {i} (entity_type={entity_type}, entity_id={entity_id}, subindex={subindex}) was dormant in the source policy but is active in the current study; loading its cut.
Policy-load errors gives the refusal messages.
ResumeIncompatible
Section titled “ResumeIncompatible”novomodelo validate reports a refused resume load as its phase, with the message labelled
<output dir>/<policy.path>: , and exits 1; a checkpoint the OS refuses to read reports IoError and exits 2.
The same kind carries the load’s warnings, which exit 0: Stored bases not used,
entity manifest absent (source slots: {n}, current slots: {m}); slot identity could not be verified, relying on state_dimension alone
and
slot {i} (entity_type={entity_type}, entity_id={entity_id}, subindex={subindex}) was dormant in the source policy but is active in the current study; loading its cut.
Policy-load errors gives the refusal messages.
Stored bases not used
Section titled “Stored bases not used”A full-FCF load uses a stored LP basis only when it fits its stage LP (Policy Management — Stored-basis gate states the rule); the others are left out with one warning, and the load proceeds:
warning: stored bases not used: {n} of {m} do not fit the current LP (first: node {node}, {reason}); a stored basis is used only when its column count equals the LP's, its row count equals the LP's template rows plus its recorded cut rows, and its basic count equals its row count; the policy was trained on a different LP{reason} is one of {found} columns, the LP has {expected}, {found} rows, the LP expects {expected} or
{found} basic entries for {expected} rows.
novomodelo run prints it as warning: {message} on stderr (--quiet hides it); novomodelo validate prints
warning: <output dir>/<policy.path>: {message}, and --json gives the count as
policy_load.unused_stored_bases. Python prints novomodelo-python: policy validation warning: {message}. The exit
code is unaffected.
NotImplemented
Section titled “NotImplemented”Severity: Error
When it occurs: Two or more hydros that declare a positive
travel_time_hours feed the same downstream plant with values that differ by
more than 1e-9, and at least one study stage in stages.json uses
"chronological" block_mode. The
check rejects the case whenever any study stage is chronological, not only the
stages where the arcs’ timing would differ. Emitted by the business-rule check,
with one line for each affected downstream plant, on system/hydros.json.
Example:
Hydro {downstream_id}: chronological confluence with heterogeneous travel times is unsupported in v1 — {n} declared arcs feed this downstream plant with differing travel_time_hours ({arcs}); align the arcs' travel_time_hours or keep every study stage in Parallel mode{downstream_id} is the id of the downstream plant, {n} is the number of arcs
that feed it, and {arcs} lists those arcs as comma-separated
hydro {id} (travel_time_hours={t}) items.
Resolution: Give every arc into that downstream plant the same
travel_time_hours in
system/hydros.json, or
set block_mode to "parallel" on every study stage in
stages.json.
UnusedEntity
Section titled “UnusedEntity”Severity: Warning (does not block execution)
When it occurs: A contract_import or contract_export term of a
generic constraint names a contract id that system/energy_contracts.json does
not declare. The term has no effect and the case still loads. The warning alerts
the user to a possible input error.
Example:
{label} references Contract {id} which is a stub entity with no LP effect{label} is GenericConstraint N term[M], naming the constraint id and the
zero-based position of the term in its expression; {id} is the undeclared
contract id.
Resolution: Correct the contract id in the term, or declare that contract in
system/energy_contracts.json. If the term is intentional, this warning can be
ignored.
ModelQuality
Section titled “ModelQuality”Severity: Warning (does not block execution)
When it occurs: A concern about the input model is detected that leaves
the case valid. Examples: an inverted penalty ordering, inflow-lag state
disabled on every study stage despite supplied autoregressive coefficients, a
negative value in scenarios/inflow_history.parquet, a season with no
observations in that file, a hydro-season with at least one, but fewer than
estimation.min_observations_per_season, fully covered stage occurrences in that
file, or an advisory on a hydro’s declared travel_time_hours. Each is reported as
a warning.
Example:
warning: penalties.json: Penalty ordering violation: max(deficit_segment_costs) (7500) should be > generation_violation_below_cost (10000) (both $/MWh) -- 1 hydro(s) affected, worst case: Hydro 0Every penalty-ordering warning starts Penalty ordering violation:; the three
messages are quoted under
Load-time penalty checks.
The inflow-lag message:
inflow lags are disabled on all study stages (state_variables.inflow_lags = false) despite a PAR(p>0) inflow model (AR order {max_order}), so the inflow-lag dimensions are omitted from the per-stage state. This is a valid configuration for external-solver interoperability; otherwise it is likely a misconfiguration{max_order} is replaced by the AR order when the warning is issued. When it
fires and what it changes:
stages[].state_variables — Cut Projection.
The observation-count message:
hydro {hid} season {sid} has {n} observations (minimum recommended: {min_obs}); estimation accuracy may be insufficient with so few observations{min_obs} is estimation.min_observations_per_season. novomodelo validate reports
the warning only when the inflow model is estimated from
scenarios/inflow_history.parquet (that is, the case does not supply both
scenarios/inflow_seasonal_stats.parquet and scenarios/inflow_ar_coefficients.parquet)
and stages.json declares season_definitions.
Resolution: Review the flagged input. For a penalty-ordering warning, see
Load-time penalty checks; for the
inflow-lag warning, see
stages[].state_variables — Cut Projection.
Warnings of this type do not prevent the solver from running.
SemanticAmbiguity
Section titled “SemanticAmbiguity”Severity: Warning (does not block execution)
When it occurs: A valid construct whose semantics are ambiguous or
stage-dependent in a way that is likely to surprise the user. Two constructs
trigger it, both emitted by the business-rule check in
constraints/generic_constraints.json:
-
thermal_generation(N)on an anticipated thermal. Usingthermal_generation(N)in a generic constraint when thermalNis an anticipated thermal.thermal_generationrefers to the per-block generation measured at the delivery stage (when the commitment matures), not the commitment decision made at the current stage. Users who intend to constrain the commitment itself should useanticipated_decision(N)instead.Example: A generic constraint
thermal_generation(0)on an anticipated thermal:warning: constraints/generic_constraints.json (constraint[id=0]): Constraint "peak_cap": thermal_generation(0) references an anticipated thermal. thermal_generation refers to the per-block generation at the delivery stage, not the forward commitment. If you intend to constrain the commitment itself, use anticipated_decision(0) instead.Resolution: Review the constraint expression. If you want to bound the generation dispatched at the delivery stage,
thermal_generation(N)is correct and the warning can be ignored. If you want to bound the advance commitment decision itself, replacethermal_generation(N)withanticipated_decision(N). -
max_stored_energy(h)paired withaccumulated_productivity(h). One constraint references both computed tags for the same hydro — in its expression coefficients or in its bounds (an@emaxon the right-hand side counts). The two are evaluated differently (accumulated_productivityat the plant’s reference point,max_stored_energyover the useful range), so a stored-energy comparison built from them does not cancel. One warning is emitted per such hydro; the case still validates.Example: A constraint that pairs the two tags for hydro 0:
warning: constraints/generic_constraints.json (sec_mismatch): Constraint "sec_mismatch": pairs max_stored_energy(0) with accumulated_productivity(0) for hydro 0; the two ride different evaluators and would not cancel. The coefficient matching max_stored_energy is integrated_accumulated_productivity, not accumulated_productivity.Resolution: Use
integrated_accumulated_productivityas the coefficient that matchesmax_stored_energy; see the security-curve example.
The warning is printed by novomodelo validate in its human-readable output; the
--json success output does not carry warnings.
Load-failure kinds
Section titled “Load-failure kinds”The phase value of novomodelo validate --json and the kind of an entry in the errors array of
novomodelo.io.validate (Python) name the failure that stopped the load. Almost every case-load failure reports
ConstraintError, because the load collects its problems into one report. The kinds are
listed below in the order of the load steps in which they typically occur: reading a file, parsing it,
checking its fields, then validating the whole case.
During a case load, a read, parse or schema failure in one file is collected
into the validation report rather than returned on its own. It reaches you as a
line of a ConstraintError: [FileNotFound] for a read
failure, [ParseError] <file>: parse error: <message> for a parse failure, and
[SchemaViolation] <file>: field <field>: <message> for a schema failure. The
display formats below reach you only for a load error raised after that report
is complete, such as a policy.path refusal raised once the case has loaded; see Exit Codes.
IoError
Section titled “IoError”Severity: Error
When it occurs: A required file exists in the file manifest but cannot be
read from disk — file not found, permission denied, or other OS-level I/O
failure. Occurs when a JSON or Parquet file is read for parsing and the read
returns an error. It is also returned when a policy.path cannot be inspected (exit 2).
Display format:
I/O error reading {path}: {source}Fields:
| Field | Description |
|---|---|
path | Path to the file that could not be read |
source | Underlying OS I/O error |
Resolution: Verify the file exists in the case directory. Check that the process has read permissions for the directory and file. For a full case load, the case root must contain all required files (see Case Format). A problem collected during a case load prints as a report line; see Validation report kinds.
ParseError — see ParseError; a parse failure collected in the report prints as a
[ParseError] line.
SchemaError
Section titled “SchemaError”Severity: Error
When it occurs: A file parses successfully but a field violates a schema
constraint: a required field is missing, a value is outside its valid range, or
an enum discriminator names an unknown variant. Occurs when the fields of a parsed
file are checked. Also returned when training.selection
(which carries the forward-pass count) or training.stopping_rules is absent
and the config is parsed or merged outside a case load (for example, Python
config overrides); a case load reports the same absence as a
SchemaViolation line. It is also returned for a policy.path that the run refuses after the case loads (exit 1; see
Configuration — policy).
Display format:
schema error in {path}, field {field}: {message}Fields:
| Field | Description |
|---|---|
path | Path to the file containing the invalid entry |
field | Dot-separated path to the offending field (e.g., "hydros[3].bus_id") |
message | Human-readable description of the violation |
Resolution: The field value identifies the exact location of the problem.
Check that required fields are present and that values fall within documented
ranges. A problem collected during a case load prints as a report line; see Validation report kinds.
ConstraintError
Section titled “ConstraintError”Severity: Error
When it occurs: A catch-all for all validation diagnostics collected
across any validation step, for system assembly rejections, and for guards
raised directly by loaders after assembly (for example, a non-finite
closure-derived innovation scale is rejected here naming the hydro and
season). The description field contains
every collected error message joined by newlines, each prefixed with its
[<Kind>], source file, optional entity identifier, and message text.
Display format:
constraint violation: {description}Fields:
| Field | Description |
|---|---|
description | All error messages joined by newlines |
Example: The --json failure capture under
How these errors reach you reports ConstraintError as its
phase, with the collected report line in message.
Resolution: Read every line in description — each line is a separate
problem. Address them all and re-run. The [<Kind>] prefix identifies the
category of each problem, and each kind section gives its resolution. A problem collected during a case load prints as a report line; see Validation report kinds.
Preparation-phase kinds
Section titled “Preparation-phase kinds”The six phase values below appear only in novomodelo validate’s pre-solver
validation checks and are not LoadError kinds. They surface in
novomodelo validate --json’s error phase field when a prep-phase check fails
before the solver is initialized.
ConfigValidationError
Section titled “ConfigValidationError”Severity: Error
When it occurs: A config.json validation failure in the study-setup
preparation phase, after the config has been parsed and before stochastic model
setup begins. File label: config.json.
Example: A gap stopping rule with neither tolerance nor
relative_tolerance:
config.json: configuration validation error: gap stopping rule requires at least one of tolerance / relative_tolerance to be presentStochasticPreparationError
Section titled “StochasticPreparationError”Severity: Error
When it occurs: A failure during stochastic model preparation — scenario
generation, inflow-model setup, or external scenario validation. File label:
scenarios/inflow_history.parquet for a stochastic-model error (after the label,
its message begins stochastic error:) and scenarios/ for every other failure
in this phase, such as a load error (after the label, its message begins
I/O error:). The label depends on how the error is classified, not on the file
it traces to.
Example: A historical library that holds no complete window, a non-stationary fitted inflow model (non-finite innovation scale), or an autoregressive stage whose standard deviation is zero, or a declared opening-tree file that is absent. Historical-library refusals, Inflow-model refusals and Opening-tree file missing quote the messages.
Historical-library refusals
Section titled “Historical-library refusals”When it occurs: Three refusals of the historical window pool are reachable
from a case. The pool serves the forward historical scheme and the stages that
use the historical_residuals sampling method (see
Historical Window Pool and
Historical-Residual Openings).
Each message is insufficient data: followed by the text below:
- No year is admissible, so the pool is empty:
no valid historical windows found: ensure that inflow history covers the required seasons for at least one starting year
- A study stage has no season:
V2.1: stage {stage_id} (index {index}) has season_id: None; all study stages must have a season_id assigned
- A residual of the pool is not finite:
V2.3: historical library contains non-finite eta (NEG_INFINITY or NaN) at window year {year}, stage id {stage_id}, hydro id {hydro_id} — sigma=0 with non-matching historical observation or numerical failure
V2.1 names the stage id, then the stage’s 0-based index. A stage has no season
when it declares none and the case either has no season definitions or has a
custom season map that leaves the stage’s start date uncovered. For example,
with an inflow model of order 1 and a custom season map that covers the first
stage and its lag season but leaves one later stage’s start date uncovered,
novomodelo validate reports V2.1.
V2.3 names the window’s year, the stage id and the hydro id.
Rendering under novomodelo validate: the phase is StochasticPreparationError
and the exit code is 1. The error: line carries the file label and the
stochastic error: prefix. This excerpt omits the Validation: summary line,
which carries the case path.
error: scenarios/inflow_history.parquet: stochastic error: insufficient data: no valid historical windows found: ensure that inflow history covers the required seasons for at least one starting yearnovomodelo run on the same case exits 1. The line has no file label, and the
validate hint follows it. This excerpt omits the banner, the Execution block and the
Loading case: line.
error: stochastic error: insufficient data: no valid historical windows found: ensure that inflow history covers the required seasons for at least one starting year -> run `novomodelo validate <CASE_DIR>` for a full diagnostic reportnovomodelo validate builds the study as novomodelo run does, so it reports a refusal
of the forward historical scheme’s library too: as
StudySetupError (scenarios/: stochastic error: …) without
policy.boundary, and as
BoundaryReconciliationError
(policy.boundary: …) with it; both commands exit 1.
When a stage also uses historical_residuals, the refusal is reported as rendered above, with or without
policy.boundary.
Inflow-model refusals
Section titled “Inflow-model refusals”When it occurs: The inflow model, declared by the case’s autoregressive and
statistics files or fitted from inflow_history.parquet, cannot support the
study. Two refusals carry a fixed message:
- An autoregressive stage has a standard deviation of zero. The message names
the hydro id and the stage id, and
ar_orderis the stage’s autoregressive order:This is a stochastic-model error.invalid PAR parameters for hydro {hydro_id} at stage {stage_id}: zero standard deviation with ar_order={ar_order}: AR model requires nonzero variance to normalize coefficientsnovomodelo validateprints it after thescenarios/inflow_history.parquet: stochastic error:prefix and exits1.novomodelo runprints it aserror: stochastic error: invalid PAR parameters …with the validate hint, and exits1. - The coefficients fitted from the history imply a non-stationary process, so a
derived residual standard-deviation ratio is not finite. The message names the
hydro id and the season id:
This is aI/O error: constraint violation: derived residual_std_ratio is non-finite for hydro_id={hydro_id} season={season}: the coefficients imply a non-stationary process (implied residual variance 1 - sum(psi*rho) is negative)
ConstraintErrorraised while the model is fitted, so it reaches the command as a load error.novomodelo validateprints it with thescenarios/file label and exits1.novomodelo runprints it without theI/O error:prefix, followed by a hint to runnovomodelo validate, and also exits1.
Opening-tree file missing
Section titled “Opening-tree file missing”When it occurs: training.scenario_source.openings is {"source": "file"}
and scenarios/noise_openings.parquet is absent. The stochastic preparation
phase of novomodelo validate stops with exit 2, as novomodelo run does, and under --json the error’s phase is
StochasticPreparationError. The message is:
openings source is 'file' but the conventional scenarios/noise_openings.parquet is absentExample: novomodelo validate prints the message inside an I/O error line. The
scenarios/ label and the I/O error: wording are how this phase reports a
missing file, and <case> stands for the path of your case directory:
error: scenarios/: I/O error: I/O error reading <case>/scenarios/noise_openings.parquet: openings source is 'file' but the conventional scenarios/noise_openings.parquet is absenterror: I/O error in <case>/scenarios/noise_openings.parquet: openings source is 'file' but the conventional scenarios/noise_openings.parquet is absent -> check that the path exists and you have read/write permissionsResolution: Supply scenarios/noise_openings.parquet, or declare openings
as {"source": "generated"}. Configuration
describes the openings key.
StudySetupError
Section titled “StudySetupError”Severity: Error
When it occurs: Study construction fails on a case without policy.boundary: a scenario library (the
forward historical scheme’s included), an external-library check, a solver-profile check, or the admission
gate of the gap rule and of dynamic cut selection. File label: scenarios/ for a stochastic-model error,
config.json otherwise. On a case with policy.boundary, the same failures report
BoundaryReconciliationError. novomodelo validate exits 1.
Example:
scenarios/: stochastic error: insufficient data: no valid historical windows found: ensure that inflow history covers the required seasons for at least one starting yearHydroModelsPreparationError
Section titled “HydroModelsPreparationError”Severity: Error
When it occurs: A failure during computed-FPHA fitting or hydro production
model preparation. File label: system/hydro_production_models.json.
Example: A plant whose generation.model is fpha and that has no entry in
system/hydro_production_models.json:
system/hydro_production_models.json: configuration validation error: hydro UHE1 (id=0) has generation_model: "fpha" in hydros.json but no entry in hydro_production_models.json. Add an entry with source: "precomputed" to specify the hyperplane source.GenericConstraintValidationError
Section titled “GenericConstraintValidationError”Severity: Error
When it occurs: A failure during generic-constraint preparation, including
expression inlining, parameter resolution, and slack setup. File label:
constraints/.
An unresolved @parameter scalar — a generic-constraint parameter
declared in constraints/generic_parameters.json that has no value for a
season, stage or block the study needs — fails here at novomodelo validate time.
An @name that no file declares is reported at load time; see
@parameter / @name reference misuse.
Example: A seasonal parameter whose only value is for season 5, in a case with
no season definitions:
constraints/: configuration validation error: parameter 'sea': no seasonal value for season_id=0 (needed by stage 0)This phase also reports energy-conversion failures of a hydro plant computed
at the same step — a forebay (VHA) table in system/hydro_geometry.parquet that
fails to build, or a non-positive useful-range mean equivalent head — for any
plant with VHA geometry and a specific productivity, independent of its
generation model and of whether the case declares generic constraints (see
Hydro Production Function Models §5.3).
A plant whose geometry and losses leave a negative head:
constraints/: configuration validation error: hydro EntityId(0) has non-positive equivalent head h_eq=-50BoundaryReconciliationError
Section titled “BoundaryReconciliationError”Severity: Error
When it occurs: A failure when reconciling an injected boundary policy with
the current study. File label: policy.boundary.
Example: A boundary checkpoint written by another software or version reports the message of
PolicySoftwareMismatch prefixed with the file label:
policy.boundary: policy was written by {writer}, but this is novomodelo {running}; a policy loads only in the software and version that wrote it: re-run the program that produced it with novomodelo {running}; for a converted boundary policy, convert it againThe phase reports a refusal from any of the boundary checks listed under
Compatibility requirements
once the checkpoint is read. A checkpoint that cannot be read reports in this phase too, as
policy.boundary: configuration validation error: failed to read boundary policy checkpoint at {path}: {e} (exit 1; --json prints the object).
On a case with policy.boundary, every study-construction failure reports this phase and label.
Policy-load errors
Section titled “Policy-load errors”A policy load refuses a checkpoint that does not fit the study loading it. Two
loads raise these errors: a full-FCF load of the checkpoint at policy.path
(warm-start, resume, and simulation-only runs) and a boundary load of the
checkpoint at policy.boundary.path. novomodelo run and the Python entry points
raise them before training or simulation starts. novomodelo validate runs both loads the run would apply, reading the full-FCF checkpoint from <output dir>/<policy.path> (--output, default <CASE_DIR>/output/), and exits 1 on every refusal novomodelo run makes. These errors are not
LoadError variants, and case loading does not return them.
A full-FCF load runs its checks in the order given under
Check order. The checks of a boundary
load are listed under
Compatibility requirements.
A manifest.bin whose format_version is not 3 fails before these checks:
failed to read policy checkpoint: serialization error for entity checkpoint_manifest: unsupported checkpoint manifest format_version {n}; expected 3; re-run the program that produced it with novomodelo {running}; for a converted boundary policy, convert it again, exit 1.
How a policy-load failure reaches you
Section titled “How a policy-load failure reaches you”| Channel | Refusal | Unreadable or missing checkpoint |
|---|---|---|
novomodelo run, full-FCF load | Exit 1. error: {message} and the validate hint on stderr. | Exit 1: error: Policy directory not found: {path}. {requirement} or error: failed to read policy checkpoint: {e}. Exit 2 for a checkpoint the OS refuses to read: error: I/O error in {path}: {source} and the permissions hint. |
novomodelo validate, full-FCF load | Exit 1. Validation: 1 errors, 0 warnings in <case> and error: <output dir>/<policy.path>: {message}; --json phase WarmStartIncompatible or ResumeIncompatible. | Exit 1 (phase as for a refusal), or exit 2 with phase IoError for a checkpoint the OS refuses to read. |
novomodelo run and novomodelo validate, boundary load | Exit 1. novomodelo run prints error: {message} and a hint line. novomodelo validate labels the message policy.boundary: ; under --json it reports phase BoundaryReconciliationError. | Exit 1. error: failed to read boundary policy checkpoint at {path}: {e} and a hint line on stderr. Under novomodelo validate --json, phase BoundaryReconciliationError. |
Python, full-FCF load (novomodelo.run.run, Study.train, Study.load_policy) | PolicyIncompatibleError. The message starts policy validation error: . | ValidationError for a missing directory, PolicyIncompatibleError for a read or decode failure, CaseIoError for a read the operating system refuses. The message is Policy directory not found: {path}. {requirement} or failed to read policy checkpoint: {e}, with no prefix. |
Python, boundary load (novomodelo.run.run, Study, Study.train) | novomodelo.run.run and Study.train raise PolicyIncompatibleError for a version mismatch, with the message prefix policy validation error: , and ValidationError for every other refusal, with the message prefix boundary cut error: . Study(...) constructs without error on a checkpoint it can read. | ValidationError. The message starts setup validation error: . It is raised when the study is built, before training. |
novomodelo.results.load_policy | No refusal: it reads a checkpoint of any novomodelo version without comparing it with a study. | FileNotFoundError for a missing directory or a missing manifest.bin. OutputError for a file whose content cannot be decoded. |
{requirement} names the run mode: Cannot warm-start without a prior policy.,
Cannot resume without a prior checkpoint., or
Cannot run simulation-only mode without a trained policy.
PolicySoftwareMismatch
Section titled “PolicySoftwareMismatch”Severity: Error
When it occurs: A full-FCF load or a boundary load reads a checkpoint whose
manifest.bin records another software or version than the running build: the
software and software_version compare exactly, so a different patch or
pre-release build is refused as well. A checkpoint whose format_version is not
3 is refused before this check
(How a policy-load failure reaches you).
Version gate states the rule.
Message:
policy was written by {writer}, but this is novomodelo {running}; a policy loads only in the software and version that wrote it: re-run the program that produced it with novomodelo {running}; for a converted boundary policy, convert it again{writer} is <software> <version>, <software>, which recorded no version,
software that recorded no name, version <version> or software that recorded no name or version;
{running} is the version of the novomodelo that loads it.
Python class: Python raises
PolicyIncompatibleError with the message prefix policy validation error: ,
for a full-FCF load and for a boundary load. Under novomodelo validate --json, a
boundary checkpoint written by another software or version reports phase
BoundaryReconciliationError and the message prefixed policy.boundary: .
Resolution: Re-run the program that produced the checkpoint with the running novomodelo version; for a converted boundary policy, convert it again. Version gate describes both routes.
Structural mismatch
Section titled “Structural mismatch”When it occurs: A full-FCF load finds that the checkpoint records no cost scale, or that it describes a different state space or study graph than the current study. The cost-scale check is step 2 of the check order and the state and graph checks are step 4. The entity-identity and graph checks run only when both sides carry the manifest.
Messages:
Cost scale (step 2):
policy checkpoint predates self-describing cuts (its resolved cuts/<pool>.bin carries no cost_scale_factor); re-run the program that produced it with novomodelo {running}; for a converted boundary policy, convert it againState and graph (step 4):
policy state_dimension mismatch: policy has {n}, current system has {n} (a lag-state depth mismatch is a common cause)policy num_stages mismatch: policy has {n}, current system has {n}policy n_pools mismatch: policy has {n}, current system has {n}entity manifest length mismatch: source has {n} slots, current study has {n}entity-identity mismatch at slot {i}: source (entity_type={t}, entity_id={id}, subindex={s}) != current (entity_type={t}, entity_id={id}, subindex={s}); the cut coefficient at this slot would attach to the wrong state variablegraph manifest n_pools mismatch: source has {n}, current study has {n}graph manifest node-count mismatch: source has {n} nodes, current study has {n}graph manifest node {i} mismatch: source (id={id}, stage_id={id}, pool_id={id}) != current (id={id}, stage_id={id}, pool_id={id})graph manifest edge-count mismatch: source has {n} edges, current study has {n}graph manifest edge {i} mismatch: source ({source_id} -> {target_id}) != current ({source_id} -> {target_id})A boundary load refuses a resolved pool without a cost_scale_factor with its
own message:
boundary policy checkpoint at {path} predates self-describing cuts (its resolved cuts/<pool>.bin carries no cost_scale_factor); re-run the program that produced it with novomodelo {running}; for a converted boundary policy, convert it againPython class: Python raises
PolicyIncompatibleError with the message prefix policy validation error: for
the full-FCF messages, and ValidationError with the message prefix
boundary cut error: for the boundary message.
Resolution: Train a new policy for the changed study or restore the matching inputs; see Check order. For a cost-scale refusal, re-run the program that produced the checkpoint with the running novomodelo version, as its message states.
Boundary inflow-lag mismatch
Section titled “Boundary inflow-lag mismatch”When it occurs: A boundary load (policy.boundary) finds an inflow-lag slot of the current study that the boundary checkpoint cannot supply. Either the checkpoint prices no inflow-lag coefficient for a hydro at a lag depth the study needs, or the coefficient it holds references a different past than the study: both sides carry a reference date and the two dates differ. Compatibility requirements gives the order of the boundary checks.
Messages:
No coefficient for a hydro at a lag depth; {names} lists every missing pair as hydro {id} at lag depth {depth}, separated by commas:
boundary policy has no inflow-lag coefficient for {names}: the boundary is lag-depth-incompatible with the current studyA coefficient that references a different past; the two dates print as YYYY-MM-DD:
boundary policy's inflow-lag coefficient for hydro {id} at lag depth {depth} references a different past than the current study: boundary {date}, current {date}Python class: Python raises ValidationError with the message prefix boundary cut error: . novomodelo validate labels the message policy.boundary: .
Resolution: If the checkpoint has no coefficient for a hydro at a lag depth, train the source checkpoint with inflow_lags: true on the stage that follows the source pool’s stage. If the two dates differ, give the source pool’s stage and the study’s last stage the same start date, and do the same for each earlier pair of stages; Compatibility requirements describes both conditions.
Solver profile validation
Section titled “Solver profile validation”Per-phase solver-profile overrides (training.solver.backward,
training.solver.forward, simulation.solver) are validated against the
compiled backend’s support matrix at study setup, before any stage LP is
built, on every MPI process; every rejection below is a configuration error with its
own message.
novomodelo validate runs these checks on every case: it reports a rejection with phase
StudySetupError (config.json: configuration validation error: …), or
BoundaryReconciliationError with policy.boundary configured.
novomodelo run refuses the case before training. Both exit 1.
CLP backend: every override is rejected
Section titled “CLP backend: every override is rejected”When it occurs: A binary built with the CLP backend (see
Installation — CLP build)
rejects every solver-profile override field outright — CLP’s own option
surface has not been measured against these fields, so novomodelo refuses to
silently apply a HiGHS-flavored value to it. An empty "solver": {} block and
an absent solver block are both legal; only a field that is actually set
triggers the error. When a profile sets more than one field, only the first
field in declaration order is named, so fixing one field at a time surfaces
one error per fix cycle rather than a combined report.
Example:
solver profile field "dual_edge_weight" for phase "backward" is unsupported on backend "clp": rejected until CLP is re-measuredFor primal_feasibility_tolerance, the field token additionally carries the
value that was set:
solver profile field "primal_feasibility_tolerance" (1e-7) for phase "forward" is unsupported on backend "clp": rejected until CLP is re-measured"<phase>" is one of forward, backward, or simulation.
Resolution: Remove the override field from config.json on a CLP-built
binary, or switch to the default HiGHS backend if the override is required.
HiGHS backend: seven per-field range checks
Section titled “HiGHS backend: seven per-field range checks”The default (highs) backend accepts every solver-profile field. The closed
enums (dual_edge_weight, scale, price, presolve) and use_warm_start
are unconditionally valid — every variant is checked when config.json is
read, so there is no separate runtime range check for them. The remaining
seven numeric fields are range-checked at study setup; each failure names the
field, the rejected value, and the phase (forward / backward /
simulation) whose profile set it.
| Field | Rule | Message |
|---|---|---|
primal_feasibility_tolerance | must be finite | unsupported primal_feasibility_tolerance {value} for backend "highs" in phase "{phase}": value must be finite |
dual_feasibility_tolerance | must be finite and >= 0.0000000001 | unsupported dual_feasibility_tolerance {value} for backend "highs" in phase "{phase}": value must be finite and >= 0.0000000001 |
simplex_update_limit | must be <= 2147483647 | unsupported simplex_update_limit {value} for backend "highs" in phase "{phase}": value must be <= 2147483647 |
cost_perturbation | must be finite and >= 0 | unsupported cost_perturbation {value} for backend "highs" in phase "{phase}": value must be finite and >= 0 |
refactor_error_tolerance | must be finite and >= 0 | unsupported refactor_error_tolerance {value} for backend "highs" in phase "{phase}": value must be finite and >= 0 |
factor_pivot_threshold | must be in [0.0008, 0.5] | unsupported factor_pivot_threshold {value} for backend "highs" in phase "{phase}": value must be in [0.0008, 0.5] |
steepest_edge_devex_fallback_threshold | must be finite and >= 1.0 | unsupported steepest_edge_devex_fallback_threshold {value} for backend "highs" in phase "{phase}": value must be finite and >= 1.0 |
0.0000000001 is 1e-10 printed in full; 2147483647 is the largest 32-bit
signed integer; [0.0008, 0.5] is HiGHS’s own accepted pivot-threshold range.
Resolution: Adjust the offending field to satisfy the rule in the table.
{phase} in the message identifies which of training.solver.backward,
training.solver.forward, or simulation.solver set the invalid value.
Runtime errors
Section titled “Runtime errors”A failure during novomodelo run prints error: {message} on stderr, followed by hint lines that depend on the exit code:
- Exit
1, a refusal met during the run (for example a--threadsmismatch), adds-> run `novomodelo validate <CASE_DIR>` for a full diagnostic report. - Exit
2, whose message isI/O error in {context}: {source}, adds-> check that the path exists and you have read/write permissions. - Exit
3adds-> check constraint bounds (hydros may have conflicting min/max storage)and-> run `novomodelo validate <CASE_DIR>` for a full diagnostic report. - Exit
4adds-> this may indicate a software or environment problemand-> report this at https://github.com/ons-ccee-epe/novomodelo/issues.
On two or more MPI processes, each failing process (an MPI rank) first prints rank {rank}: on its own line and then the same lines, and the job stops with that process’s exit code. When rank 0 fails to write the run outputs, every rank exits with rank 0’s code, and the others print rank 0 failed to write the run outputs; failing on every rank in lockstep. A checkpoint that does not fit the study is refused before training or simulation starts; see Policy-load errors.
Solver failures
Section titled “Solver failures”A failure inside a training iteration ends training and exits with the code of its error: 3 for an LP or solver failure, 4 for a communication failure, 1 for a refusal. A run without --quiet first prints Training failed after {n} iterations. Partial outputs written to {dir}. The run also logs ERROR training failed after {n} iterations: {error} on stderr, with --quiet as well. Under MPI, a process that fails first can abort the job before the root process prints these lines. The same LP failures in simulation exit 3; see Simulation failures.
| Message | Exit code | When it occurs | Resolution |
|---|---|---|---|
LP infeasible at stage {stage}, iteration {iteration}, scenario {scenario} | 3 | A stage LP of a training iteration has no feasible solution; {stage} and {scenario} are 0-based positions. | Check the constraint bounds at that stage, such as a generic constraint whose slack is disabled or conflicting hydro bounds; novomodelo validate does not solve the stage LPs, so it can accept such a case. |
{solver_message} | 3 | A stage LP of a training iteration ends in a solver failure other than infeasibility; {solver_message} is one of the solver messages below, for example LP is unbounded. | Look up {solver_message} in the rows below. |
LP is unbounded | 3 | The objective of the stage LP is unbounded below. | Look for a missing bound or a cost with the wrong sign in the case. |
numerical difficulty: {message} | 3 | The solver reports numerical difficulties that persist through all its retries; {message} is the solver’s description. | Report the case with the message. |
time limit exceeded after {seconds}s or iteration limit reached after {iterations} iterations | 3 | A single LP solve ends at its time budget or at the solver’s simplex iteration limit. | Report the case with the message. |
internal solver error (code {code}): {message} or internal solver error: {message} | 3 | The solver fails in a way it cannot classify. | Report the case with the message. |
basis inconsistent: num_row={num_row}, total_basic={total_basic} (col_basic={col_basic}, row_basic={row_basic}) or basis row count mismatch: lp_rows={lp_rows}, basis_rows={basis_rows} | 3 | The solver rejects a warm-start basis that does not fit the LP. | Report the case with the message. |
non-uniform n_workers_local across MPI ranks: local={local}, min={min}, max={max}; all ranks must run with the same --threads value | 1 | The MPI processes of one job started with different --threads values, and the backward pass of the first iteration detects it; local is the printing process’s thread count, and min and max are the smallest and largest counts. | Start every process of the job with the same --threads value. |
{solver} initialisation failed: {e} | 3 | The solver backend (HiGHS or CLP) cannot create its solver instance when training starts; {e} is the backend’s message. | Report it with the full message. |
{solver} initialisation failed for simulation pool: {e} | 3 | The solver backend cannot create the solver instances of the simulation workers. | Report it with the full message. |
An infeasible stage LP in the first training iteration of a single-process run, on a case that novomodelo validate accepts, prints:
ERROR training failed after 0 iterations: infeasible subproblem at stage 0, iteration 1, scenario 0Training failed after 0 iterations. Partial outputs written to ./out.error: LP infeasible at stage 0, iteration 1, scenario 0 -> check constraint bounds (hydros may have conflicting min/max storage) -> run `novomodelo validate <CASE_DIR>` for a full diagnostic reportSimulation failures
Section titled “Simulation failures”A stage-LP failure in simulation exits 3, and every other simulation failure exits 4.
| Message | Exit code | When it occurs | Resolution |
|---|---|---|---|
LP infeasible at scenario {scenario_id}, stage {stage_id}: {solver_message} | 3 | A stage LP of a simulation scenario has no feasible solution; {solver_message} reads LP infeasible. | Check the constraint bounds at that stage, such as a generic constraint whose slack is disabled; novomodelo validate does not solve the stage LPs, so it can accept such a case. |
solver error at scenario {scenario_id}, stage {stage_id}: {solver_message} | 3 | A stage LP of a simulation scenario ends in a solver failure other than infeasibility; {solver_message} is one of the solver messages of Solver failures. | Find the solver message in those rows. |
stochastic error: {detail} | 4 | The scenario sampler of the simulation cannot be built or fails to sample; {detail} is the stochastic model’s message. | See Stochastic failures during a run. |
invalid simulation configuration: {detail} | 4 | The simulation setup fails an internal consistency check. | Report the case with the message. |
simulation output channel closed unexpectedly or simulation drain thread panicked | 4 | The thread that writes the simulation results stops before the simulation ends. | Report the case with the full output of the run. |
simulation cost aggregation error: I/O error during simulation output: {message} | 4 | The gather of the per-scenario costs across the MPI processes fails at the end of the simulation; {message} names the gather, allgatherv(counts) or allgatherv(costs), and the communication error. This is a communication failure, not a filesystem error; it exits 4. | See Communication failures. |
a peer rank failed simulation; failing on every rank in lockstep | 4 | Another MPI process fails during the simulation, and this process stops with it. | Read the error printed under the rank {rank}: line of the process that failed first. |
Communication failures
Section titled “Communication failures”| Message | Exit code | When it occurs | Resolution |
|---|---|---|---|
communication backend error: communication backend 'mpi' is not available in this build (available: local) | 4 | --comm-backend mpi is set on a binary built without MPI support, which offers only the local backend. | Run the novomodelo-mpi build under an MPI launcher (see HPC & Cluster Deployment), or drop --comm-backend mpi to run on one process. |
communication backend error: 'mpi' backend initialization failed: {source} | 4 | The MPI runtime fails to initialise in an MPI build; {source} carries the MPI library’s message. | Check that the MPI runtime is installed and that the job starts under mpiexec, mpirun or srun. |
collective operation '{operation}' failed with MPI error code {code}: {message} | 4 | An MPI collective operation (broadcast, barrier, allreduce or allgatherv) fails during the run. The text follows a label that names the step, such as broadcast error (data): , post-training barrier error: or simulation path gather error: . | Read the MPI library’s message and the output of the other processes to find the process that failed first. |
cut_wire: unsupported version {version} | 1 | In the backward pass of a training iteration, a process reads a cut record whose version byte is not the one its own binary writes, which happens when the processes of one job run builds with different cut wire-format versions. {version} is the byte the sending process wrote. The message adds the validate hint. | Restart every process with the same novomodelo binary. |
try_from_broadcast_payload: unsupported wire version {version} at stage {stage} (expected {expected}) | 1 | At the end of training, a process other than rank 0 reads the warm-start bases that rank 0 broadcasts and finds a wire-format version other than its own; {version} is the version rank 0 wrote and {stage} the 0-based node position. The novomodelo validate hint after it does not apply. | Restart every process with the same novomodelo binary. |
rank 0 signaled broadcast failure (length 0) | 4 | Rank 0 fails while loading the case, before it broadcasts the study, so the other processes receive an empty broadcast; rank 0 exits with the code of its own error. | Fix the error that rank 0 prints under its rank 0: line. |
hydro model preprocessing error on non-root rank: {e} | 4 | A process other than rank 0 fails to prepare the hydro models, which it rebuilds from the case directory. | Give every process read access to the same case directory, and read {e} for the cause. |
enumerated training at world >= 2 requires a graph with no interior branching (a deterministic trunk + terminal fan): node {node_id} is a non-leaf node not shared by every root→leaf path — the enumerated forward's per-rank state exchange is elided for it, so the replicated backward would cut against a zeroed incoming state on any rank that never visits it, pending the interior-node state-exchange fix | 1 | Enumerated training (training.selection.method "enumerated") runs on two or more MPI processes over a policy graph with an interior branching node; the message has no prefix and is followed by -> run `novomodelo validate <CASE_DIR>` for a full diagnostic report, although novomodelo validate does not check this and exits 0 on the case. | Keep all branching at the last stage, run on one process, or use sampled selection. |
Requesting the MPI backend from a binary built without MPI support prints:
error: communication backend error: communication backend 'mpi' is not available in this build (available: local) -> this may indicate a software or environment problem -> report this at https://github.com/ons-ccee-epe/novomodelo/issuesOutput-writing failures
Section titled “Output-writing failures”A filesystem failure while writing the policy checkpoint or the training and simulation results exits 2, except the write of one simulation scenario’s results, which the last row describes. The training/ files written before training starts exit 4 on any failure, so an unwritable --output directory ends with exit 4 unless removing a stale output fails first, which exits 2.
| Message | Exit code | When it occurs | Resolution |
|---|---|---|---|
I/O error in {context}: {source} | 2 | A write of the policy checkpoint or of training or simulation results after training starts fails on the filesystem, for example when the disk is full or a results file cannot be created; {context} is the path. | Check that {context} exists and is writable and that the disk has free space, then rerun. |
I/O error in checkpoint write at iteration {n}: {path}: {source} | 2 | A periodic checkpoint cannot be written to policy.path. | Check that the policy directory is writable and the disk has space. |
checkpoint write at iteration {n}: refusing to write a checkpoint: {entry}, found in {dir}, is not part of a checkpoint (manifest.bin, metadata.json, cuts/, basis/, states/); move it elsewhere or write the checkpoint to another directory | 1 | The policy directory holds an entry no checkpoint writer leaves there; nothing on disk changes. | Move the entry, or set policy.path to another directory. |
checkpoint write at iteration {n}: {e} | 4 | Encoding a periodic checkpoint fails; {e} is the encoder’s message. | Report the case with the message. |
I/O error in {path}: {source} | 2 | A stale output in --output, such as a _SUCCESS marker, cannot be removed before training starts; {path} is the entry. | Make the entry removable, or point --output at a directory without it. |
failed to write hydro model summary: {e}, failed to write provenance report: {e} or failed to write scaling report: {e} | 4 | One of the training/ files that novomodelo run writes before training starts cannot be written; {e} is the writer’s message, for example I/O error accessing {path}: {source}. | Point --output at a directory the process can create and write. |
rank 0 pre-training export failed; failing on every rank in lockstep | 4 | On MPI, rank 0 fails one of those writes or removals, and every other process prints this message. | Fix the error that rank 0 prints under its rank 0: line. |
serialization error for entity {entity}: {message} | 4 | Encoding an output table fails for the named entity collection. | Report the case with the message. |
manifest error for {manifest_type}: {message} | 4 | Building an output manifest fails. | Report the case with the message. |
ERROR simulation write error: {e} | 0 | The results of one simulation scenario cannot be written. The run continues and exits 0, and scenarios.failed in simulation/metadata.json counts the failed scenarios. | Fix the cause named in {e}, rerun the simulation, and check scenarios.failed after every run. |
Stochastic failures during a run
Section titled “Stochastic failures during a run”A stochastic-model refusal raised while the study is set up exits 1 in both commands; StochasticPreparationError quotes it. The simulation sampler’s stochastic error: … exits 4 (Simulation failures).