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.
Installation
Section titled “Installation”uv tool install novomodelo-bridge # isolated, on-PATH CLI (recommended)pipx install novomodelo-bridge # alternativepip install novomodelo-bridge # into the current environmentnovomodelo-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.
Conversion workflow
Section titled “Conversion workflow”A NEWAVE case goes from preflight check to results comparison in six steps.
novomodelo-bridge check newave <newave-dir> # preflight check; exit 2 = will not convertnovomodelo-bridge convert newave <newave-dir> <case-dir> --dry-run # convert in memory, write nothingnovomodelo-bridge convert newave <newave-dir> <case-dir> --validate # convert, then validate with novomodelo-pythonnovomodelo validate <case-dir> # validate the converted casenovomodelo run <case-dir> # solve; results go to <case-dir>/output/novomodelo-bridge compare newave <newave-dir> <case-dir>/output # published NEWAVE results vs Novomodelo simulationFor 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.
Converting a Case
Section titled “Converting a Case”The convert subcommand reads a source case directory and writes a complete
Novomodelo case directory:
novomodelo-bridge convert newave /path/to/source/case /path/to/output/caseOptions
Section titled “Options”convert newave flags:
| Flag | Description |
|---|---|
--validate | After conversion, validate the output with the novomodelo package. |
--force | Overwrite the destination directory if it already contains files. |
--diagnostics-json PATH | Also write the conversion diagnostics (counts + findings) as JSON. |
--json | Emit a single machine-readable JSON verdict to stdout and suppress the human-readable (Rich) output. |
--dry-run | Run the full conversion in memory and report what would be written, without creating or modifying the destination. |
-v, --verbose INTEGER | Increase console log verbosity (-v INFO, -vv DEBUG). Default: 0. |
--log-file PATH | Write 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-color | Disable coloured output (also honoured via the NO_COLOR env var). |
--quiet | Suppress the summary and info notes; warnings/errors still show. |
convert decomp (below) accepts this same flag set plus --no-fcf.
Exit codes
Section titled “Exit codes”| Code | Meaning |
|---|---|
0 | Conversion completed. |
1 | Conversion 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.
--json output
Section titled “--json output”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.
Converting a DECOMP Deck
Section titled “Converting a DECOMP Deck”novomodelo-bridge convert decomp /path/to/deck /path/to/output/caseBy 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.
What Gets Converted
Section titled “What Gets Converted”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.
Output Directory Structure
Section titled “Output Directory Structure”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.jsonandsystem/pumping_stations.jsonscenarios/external_inflow_scenarios.parquet,scenarios/external_load_scenarios.parquetandscenarios/external_ncs_scenarios.parquetconstraints/contract_bounds.parquet,constraints/hydro_unit_group_bounds.parquetandconstraints/pumping_bounds.parquetpost_study_stages.jsonat the top level- the imported boundary under
boundary/(see Converting a DECOMP Deck)
Comparing Results
Section titled “Comparing Results”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:
novomodelo-bridge compare newave /path/to/source/case /path/to/novomodelo/outputcompare 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).
| Flag | Description |
|---|---|
--tolerance FLOAT | Relative tolerance for the results comparison. Default 1e-2. Env NOVOMODELO_BRIDGE_RESULTS_TOLERANCE. |
--format FORMAT | Output format(s): console,html,csv,parquet,json,all (comma-separated and/or repeatable). Default console,parquet,json. Env NOVOMODELO_BRIDGE_FORMAT. |
--out-dir PATH | Directory for file artifacts. Default <novomodelo_output_dir>/comparison_artifacts. Env NOVOMODELO_BRIDGE_OUT_DIR. |
--json | Emit 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 code | Meaning |
|---|---|
0 | The comparison ran (whether or not any divergence was found). |
1 | compare newave only: the NEWAVE case directory is missing. (On compare decomp a missing deck directory exits 2.) |
2 | An 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.
Python API
Section titled “Python API”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 Pathfrom 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.
Other Commands
Section titled “Other Commands”Checking a Case
Section titled “Checking a Case”check validates a source case or deck without converting or writing any
output — a preflight check before committing to a full convert run:
novomodelo-bridge check newave /path/to/source/casenovomodelo-bridge check decomp /path/to/deckcheck 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.
Exit codes
Section titled “Exit codes”| Code | Meaning |
|---|---|
0 | Ready to convert. |
1 | Ready, with warnings. |
2 | Will not convert. |
Generating a Dashboard
Section titled “Generating a Dashboard”dashboard builds an interactive HTML dashboard from Novomodelo simulation
results:
novomodelo-bridge dashboard /path/to/novomodelo/case| Flag | Description |
|---|---|
--output, -o PATH | Output HTML file path. Default <case_dir>/dashboard.html. |
--open | Open the generated dashboard in the default web browser after writing. |
--json | Emit 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.
Conversion Details
Section titled “Conversion Details”Entity ID Renumbering
Section titled “Entity ID Renumbering”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.
Which Plants Are Converted
Section titled “Which Plants Are Converted”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.
Risk Measure Support
Section titled “Risk Measure Support”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.
Generic Constraints
Section titled “Generic Constraints”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 inconstraints/generic_parameters.jsonand 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.
Dependencies
Section titled “Dependencies”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’sMIN_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).
See Also
Section titled “See Also”- Configuration — all
config.jsonfields - Case Format — complete input schema reference