Skip to content

Case Conversion (novomodelo-bridge)

novomodelo-bridge is a standalone Python package that converts NEWAVE (long-term) and DECOMP (short-term) hydrothermal dispatch cases into Novomodelo case directories and compares the source model’s published results with Novomodelo’s simulation output.

The package is maintained in a separate repository: github.com/ons-ccee-epe/novomodelo-bridge.


Terminal window
uv tool install novomodelo-bridge # isolated, on-PATH CLI (recommended)
pipx install novomodelo-bridge # alternative
pip install novomodelo-bridge # into the current environment

novomodelo-bridge requires Python 3.12 or newer. The novomodelo CLI (the solver) is installed separately (see installing novomodelo) and is needed only to run novomodelo validate and novomodelo run on the converted case.

novomodelo-python is a required runtime dependency, not an optional extra — it provides the Python bindings that convert --validate, dashboard, and convert decomp’s boundary-FCF import need, so installing novomodelo-bridge pulls it in automatically. (compare does not use the bindings: it reads Novomodelo’s simulation-output parquet files directly.)

A novomodelo-bridge release X.Y.Z targets novomodelo X.Y.Z: the converted case follows that release’s input contract. The bridge records its minimum novomodelo version (0.18.0) in the conversion manifest and uses it to decide whether the installed novomodelo-python can run convert --validate. The Dependencies section lists the pin that tracks that minimum. If the installed novomodelo-python is older than the minimum novomodelo version, convert --validate skips its validation step (printing a note) instead of failing.


A NEWAVE case goes from preflight check to results comparison in six steps.

Terminal window
novomodelo-bridge check newave <newave-dir> # preflight check; exit 2 = will not convert
novomodelo-bridge convert newave <newave-dir> <case-dir> --dry-run # convert in memory, write nothing
novomodelo-bridge convert newave <newave-dir> <case-dir> --validate # convert, then validate with novomodelo-python
novomodelo validate <case-dir> # validate the converted case
novomodelo run <case-dir> # solve; results go to <case-dir>/output/
novomodelo-bridge compare newave <newave-dir> <case-dir>/output # published NEWAVE results vs Novomodelo simulation

For a DECOMP deck, replace newave with decomp in the novomodelo-bridge commands; Converting a DECOMP Deck covers the boundary-FCF import that convert decomp runs by default.


The convert subcommand reads a source case directory and writes a complete Novomodelo case directory:

Terminal window
novomodelo-bridge convert newave /path/to/source/case /path/to/output/case

convert newave flags:

FlagDescription
--validateAfter conversion, validate the output with the novomodelo package.
--forceOverwrite the destination directory if it already contains files.
--diagnostics-json PATHAlso write the conversion diagnostics (counts + findings) as JSON.
--jsonEmit a single machine-readable JSON verdict to stdout and suppress the human-readable (Rich) output.
--dry-runRun the full conversion in memory and report what would be written, without creating or modifying the destination.
-v, --verbose INTEGERIncrease console log verbosity (-v INFO, -vv DEBUG). Default: 0.
--log-file PATHWrite the full DEBUG log to PATH (the console verbosity is unaffected). Missing parent directories are created; a path that cannot be opened is reported as a --log-file option error and the command exits 2.
--no-colorDisable coloured output (also honoured via the NO_COLOR env var).
--quietSuppress the summary and info notes; warnings/errors still show.

convert decomp (below) accepts this same flag set plus --no-fcf.

CodeMeaning
0Conversion completed.
1Conversion error.
2--validate reported a load failure.

convert decomp uses the same 0/1/2 exit-code set. On every novomodelo-bridge command, a usage error (an unknown option, a missing argument, or an invalid option value such as an unwritable --log-file path) also exits 2, with a usage message.

With --json, convert (and every other novomodelo-bridge command) emits one JSON document to stdout instead of the Rich console output, with exactly five top-level keys in this fixed order: schema_version, command, status, summary, diagnostics. summary holds the command-specific payload (entity counts, for convert); diagnostics is the same findings list the Rich panels render.

For the complete flag reference for every command, see the novomodelo-bridge CLI reference.

Terminal window
novomodelo-bridge convert decomp /path/to/deck /path/to/output/case

By default, when the deck declares its cortes/cortesh cut files (its FC records), convert decomp imports the deck’s boundary FCF as a terminal-stage novomodelo policy checkpoint via an in-process, one-iteration novomodelo pass. This import is slow and requires novomodelo-python. It is always skipped under --dry-run, and is skipped — with an informational note, not an error — when the deck declares no cut files. Pass --no-fcf to skip the boundary-FCF import for a quicker conversion with no terminal FCF.

The conversion pipeline transforms the source case’s input files into a complete Novomodelo case directory, organised by output sub-directory:

  • system/ — plant and network configuration: hydros, thermals, buses, lines, non-controllable sources, hydro production models, and hydro geometry.
  • scenarios/ — stochastic inputs: inflow, load, and non-controllable seasonal statistics and factors.
  • constraints/ — per-stage bounds, penalty overrides, and generic constraints.
  • Top level — study configuration, stage definitions, penalties, and initial conditions.

For the authoritative, field-level source-to-Novomodelo lineage — which deck file, record, and column feeds which output field, and what transformation is applied — see the bridge’s own generated data maps. They are produced by scripts/gen-lineage-docs.py, verified by tests against a real conversion, marked “Do not edit by hand,” and written in Portuguese: newave-data-map.md and decomp-data-map.md.

convert newave writes this file set:

output/
config.json
stages.json
penalties.json
initial_conditions.json
conversion_manifest.json
system/
hydros.json
thermals.json
buses.json
lines.json
non_controllable_sources.json
hydro_production_models.json
hydro_geometry.parquet
tailrace_curves.parquet (conditional)
hydro_energy_productivity.parquet (conditional)
scenarios/
load_factors.json
non_controllable_factors.json
inflow_history.parquet
inflow_seasonal_stats.parquet
load_seasonal_stats.parquet
non_controllable_stats.parquet
constraints/
generic_parameters.json
line_bounds.parquet
hydro_bounds.parquet (conditional)
thermal_bounds.parquet (conditional)
penalty_overrides_bus.parquet (conditional)
penalty_overrides_hydro.parquet (conditional)
generic_constraints.json (conditional: deck has generic constraints)
generic_constraint_bounds.parquet (conditional: deck has generic constraints)

Files marked (conditional) are written only when the source deck supplies the corresponding data; --dry-run writes nothing at all (the conversion report’s would_write_paths previews the set a real run would write).

A case written by convert decomp does not write scenarios/inflow_history.parquet, constraints/penalty_overrides_bus.parquet or constraints/penalty_overrides_hydro.parquet, and it also writes files that convert newave never writes:

  • system/energy_contracts.json and system/pumping_stations.json
  • scenarios/external_inflow_scenarios.parquet, scenarios/external_load_scenarios.parquet and scenarios/external_ncs_scenarios.parquet
  • constraints/contract_bounds.parquet, constraints/hydro_unit_group_bounds.parquet and constraints/pumping_bounds.parquet
  • post_study_stages.json at the top level
  • the imported boundary under boundary/ (see Converting a DECOMP Deck)

After running both the source tool and Novomodelo on the same case, the compare subcommand aligns the source tool’s published results against Novomodelo’s simulation-output parquets:

Terminal window
novomodelo-bridge compare newave /path/to/source/case /path/to/novomodelo/output

compare newave <NEWAVE_DIR> <NOVOMODELO_OUTPUT_DIR> compares NEWAVE’s published results (MEDIAS-*.CSV, pmo.dat) against Novomodelo simulation output. compare decomp <DECOMP_DIR> <NOVOMODELO_OUTPUT_DIR> compares a DECOMP run’s published operation (dec_oper_*.csv) the same way. Both source directories must hold the case/deck and its result files directly (not nested in a subdirectory).

FlagDescription
--tolerance FLOATRelative tolerance for the results comparison. Default 1e-2. Env NOVOMODELO_BRIDGE_RESULTS_TOLERANCE.
--format FORMATOutput format(s): console,html,csv,parquet,json,all (comma-separated and/or repeatable). Default console,parquet,json. Env NOVOMODELO_BRIDGE_FORMAT.
--out-dir PATHDirectory for file artifacts. Default <novomodelo_output_dir>/comparison_artifacts. Env NOVOMODELO_BRIDGE_OUT_DIR.
--jsonEmit a single machine-readable JSON verdict to stdout instead of the Rich tables.

compare also shares convert’s -v/--verbose, --log-file, --no-color, and --quiet flags.

--tolerance, --format and --out-dir each take their value from the first source that sets it: the flag, then the environment variable, then the config file, then the built-in default. compare reads one config file, the first of these that exists: novomodelo-bridge.toml in the current directory or any parent; $XDG_CONFIG_HOME/novomodelo-bridge/config.toml, only when XDG_CONFIG_HOME is set and non-empty; ~/.config/novomodelo-bridge/config.toml. It is not merged with the others. It can set [compare.results] tolerance, [compare] format and [compare] out_dir; a malformed file, or a key of the wrong type, is ignored with a warning on stderr.

compare is informational: it reports every divergence it finds, beyond tolerance or not, but a divergence never fails the run. On both the newave and decomp tracks the exit code reports only whether the comparison could run:

Exit codeMeaning
0The comparison ran (whether or not any divergence was found).
1compare newave only: the NEWAVE case directory is missing. (On compare decomp a missing deck directory exits 2.)
2An invalid --format token, or a source deck or Novomodelo output file that cannot be read (missing or malformed data).

A NEWAVE case whose dger.dat requests training without a final simulation (tipo_execucao 1 with tipo_simulacao_final 0) converts with simulation.enabled set to false. novomodelo run then writes no simulation output, so compare newave finds no Novomodelo simulation output to read and exits 2, naming the missing simulation/hydro_bus_generation/ directory.


For programmatic use, install novomodelo-bridge into your project environment (pip install novomodelo-bridge; an isolated uv tool or pipx install exposes only the CLI), then import the conversion pipeline directly:

from pathlib import Path
from novomodelo_bridge.newave.pipeline import convert_newave_case
report = convert_newave_case(
Path("/path/to/source/case"),
Path("/path/to/output/case"),
on_phase=lambda phase: print(f"phase: {phase}"),
dry_run=False,
)
print(report)

convert_newave_case(src, dst, *, on_phase=None, dry_run=False) -> ConversionReport takes src/dst as positional Paths, plus two keyword-only arguments: an optional on_phase progress callback invoked with the current conversion phase’s name, and dry_run (default False) — set it to True to run the full conversion in memory and populate the report without writing anything to dst.

The returned ConversionReport carries hydro_count, thermal_count, bus_count, line_count, stage_count, diagnostics (structured findings), warnings (flat warning strings), and would_write_paths (the output paths written, or that would have been written under dry_run=True).

DECOMP conversions use the equivalent novomodelo_bridge.decomp.pipeline.convert_decomp_case(src, dst, *, force=False, on_phase=None, dry_run=False, fcf_inputs_out=None) -> ConversionReport.


check validates a source case or deck without converting or writing any output — a preflight check before committing to a full convert run:

Terminal window
novomodelo-bridge check newave /path/to/source/case
novomodelo-bridge check decomp /path/to/deck

check decomp also reports what the conversion will leave behind (deck features convert decomp does not carry over), so a limitation is never a silent omission.

check shares convert’s --json, -v/--verbose, --log-file, --no-color, and --quiet flags.

CodeMeaning
0Ready to convert.
1Ready, with warnings.
2Will not convert.

dashboard builds an interactive HTML dashboard from Novomodelo simulation results:

Terminal window
novomodelo-bridge dashboard /path/to/novomodelo/case
FlagDescription
--output, -o PATHOutput HTML file path. Default <case_dir>/dashboard.html.
--openOpen the generated dashboard in the default web browser after writing.
--jsonEmit a single machine-readable JSON verdict to stdout.

dashboard also shares convert’s -v/--verbose, --log-file, --no-color, and --quiet flags. Exit codes: 0 written, 1 error.


NEWAVE identifies subsystems and plants by arbitrary 1-based codes; Novomodelo uses 0-based ids with no gaps. novomodelo-bridge sorts the source codes in ascending order and assigns the ids 0, 1, 2 and so on in that order, so an id does not depend on how the deck orders its entities, and an entity keeps the same id in every output file. compare newave rebuilds the same numbering from the source case, so results trace back to the source codes. convert decomp sorts the deck’s codes the same way and adds one converter-created transshipment bus after the declared subsystems.

In a NEWAVE case, a hydro becomes a Novomodelo entity when confhd.dat marks it in service: operating (EX) or operating with an expansion still to come (EE). An EE plant operates from the first stage at the machine configuration modif.dat declares for the study start, and gains the machines listed in exph.dat as they enter service, through per-stage bounds. Plants that do not exist yet are left out, except a future plant whose exph.dat schedule fills its dead volume. Fictitious accounting plants are removed; they are identified structurally, as a plant with zero productivity that shares its inflow gauge with a generating plant, and a cascade link through a removed plant is rewired to the next real plant downstream.

convert decomp converts the deck’s operated hydros, with the deck’s registry overrides applied, and its small plants as must-run non-controllable sources.

For the full list of conversion rules, see the novomodelo-bridge NEWAVE track guide.

When the source case configures risk-averse optimization (CVaR), novomodelo-bridge converts the alpha and lambda parameters to per-stage risk_measure entries in stages.json. Three modes are supported:

  • Disabled — all stages use "expectation".
  • Constant — all stages use the same CVaR parameters.
  • Temporal — per-stage alpha/lambda values, with fallback to constants when a stage override is zero.

Three families of source constraints are converted into Novomodelo’s generic-constraint authoring format: one definition per constraint in generic_constraints.json (a name, an expression, and an optional slack), merged with sequential integer IDs, and per-stage right-hand-side bounds in generic_constraint_bounds.parquet.

  • VminOP — minimum stored-energy constraints. Each is a weighted sum of the participating reservoirs’ end-of-stage storage, where the weight is that reservoir’s accumulated productivity, referenced as a named parameter (@rho_acum_h<id>) declared in constraints/generic_parameters.json and resolved by Novomodelo to its own ρ at solve time. This keeps the constraint’s coefficients aligned with the study’s productivities rather than freezing a converted value.
  • Electric — operational constraints on hydro generation and line flows.
  • AGRINT — group dispatch constraints for thermal and hydro plants.

novomodelo-bridge 0.18.0 installs its runtime dependencies with the package. Two pins matter:

  • novomodelo-python==0.18.0: the pinned version is the bridge’s MIN_NOVOMODELO_VERSION (0.18.0), and a packaging test keeps the pin and that constant in lockstep.
  • inewave>=1.16.1: the NEWAVE file reader.

The minimum novomodelo version does not make versions interchangeable: an imported boundary needs an exact version match (see the caution under Converting a DECOMP Deck).