Canonical model¶
The Value union¶
A parameter is one of three JSON shapes; emitters call Reduce/Select to
project an indexed value to a scalar (and error if it can't for a flat target).
0.9 // scalar
"pv_cf" // time-series id (see timeseries)
{ "data": [10,20], "index": [["monetary"],["co2"]], "dims": ["costs"] } // indexed
Canonical payload — Job¶
{
"api_key"?: "…", // required iff the server has API_KEY configured; redacted from the bundle
"model": {
"metadata": { "name": "…", "currency"?: "EUR", "currency_year"?: 2025 },
"time": { "start": "2025-01-01", "end": "2025-01-02", "resolution"?: "1H", "subset"?: ["…","…"], // subset: Calliope only
"timesteps"?: ["…"], "weights"?: Value }, // explicit labels (P/C); snapshot weights (P; C scalar)
"periods"?: ["period1"] | [{"name":"2030","year":2030,"length_years":10}], // with years: investment periods (PyPSA)
"discount_rate"?: 0.07, // global; overrides per-tech interest_rate
"carriers": { "<id>": { "unit"?: "MWh", "co2_intensity"?: 0.2, "color"?: "#…", "native"?: {…} } },
"nodes": { "<id>": { "coords"?: {"lat":48.8,"lon":12.9,"alt"?:0},
"allowed_techs"?: ["…"], "available_area"?: 1e4,
"climate"?: {"ghi":"series_id","temp_air":15,…}, // AdOpT weather for physics techs
"electrical"?: {"v_nom":20, "shunt"?:{"g":0,"b":0.001}}, // kV + shunt (S); the node's AC bus (PyPSA power flow)
"native"?: {…} } },
"timeseries"?:{ "<id>": { "source": "inline"|"file", "values"?: [ … ], "path"?: "…", "column"?: "…" } }, // file: accepted by the schema, rejected by validation until a target reads files
"technologies": {
"<id>": {
"role": "supply"|"demand"|"conversion"|"storage",
"node": "<id>" | ["<id>", …],
"carrier_in"?: "<id>" | ["<id>"],
"carrier_out"?: "<id>" | ["<id>"],
"efficiency"?: Value,
"flows"?: [ { "carrier":"<id>", "direction":"in"|"out", "ratio":0.4, "reference"?:true } ],
"performance"?: {
"type": "constant"|"piecewise"|"physics",
"efficiency"?: Value, // constant
"breakpoints"?: [ {"load":0.3,"efficiency":0.40} ], "min_load"?: 0.3, // piecewise
"model"?: "pv"|"wind"|"heat_pump"|"open_hydro", // physics
"params"?: { … }, "precomputed"?: "series_id"
},
"capacity"?: { "existing":0, "expandable":true, "min"?:0, "max"?:500, "unit"?:"MW",
"per_unit"?:10, "units_min"?:0, "units_max"?:12, "systemwide_min"?:0, "systemwide_max"?:1000,
"decommission"?: { "mode":"impossible"|"continuous"|"only_complete", "cost"?:0 } },
"operation"?: { "max_pu"?:Value, "min_pu"?:Value, "ramp_up"?:0.6, "ramp_down"?:0.6,
"committable"?:true, "start_up_cost"?:5000, "shut_down_cost"?:0,
"min_uptime"?:3, "min_downtime"?:2,
"ramp_start_up"?:0.6, "ramp_shut_down"?:0.6, "standby_cost"?:5, "initial_uptime"?:2 }, // UC extras (PyPSA)
"storage"?: { "energy_capacity"?:{…Capacity}, "max_hours"?:4, "charge_eff"?:0.95,
"discharge_eff"?:0.95, "self_discharge"?:0.001, "inflow"?:Value,
"spill_cost"?:0, "initial_soc"?:0.5, "cyclic"?:true,
"max_charge_rate"?:0.5, "max_discharge_rate"?:0.5, "depth_of_discharge"?:0.1,
"no_simultaneous_charge_discharge"?:false,
"soc_profile"?:Value }, // pinned state of charge (PyPSA StorageUnit)
"demand_profile"?: Value, // role: demand
"dispatch"?: Value, // fixed MW per timestep; power_flow input (ignored elsewhere)
"electrical"?: { "reactive_dispatch"?:Value, "power_factor"?:0.95, "leading"?:false,
"control"?:"pq"|"pv", "voltage_setpoint"?:1.0 }, // reactive power + voltage control (power_flow)
"area"?: { "max"?:1500, "per_capacity"?:0.01 }, // Calliope-only
"source"?: { "cap"?:…, "unit"?:"per_area", "max"?:Value },// Calliope-only
"costs"?: { "<class>": { "investment_per_capacity"?:6e5, "investment_per_energy_capacity"?:…,
"fixed_om"?:…, "variable_om"?:Value, "variable_om_quadratic"?:0.01, "purchase"?:… } },
"lifetime"?: 25, "interest_rate"?: 0.07, "cost_basis"?: "overnight"|"annualized",
"node_overrides"?: { "<node>": { …subset of the fields above… } },
"native"?: { "pypsa"?:{…}, "calliope"?:{…}, "adopt-net0"?:{…} }
}
},
"transmission"?: { "<id>": { "carrier":"<id>", "from":"<node>", "to":"<node>",
"bidirectional"?:true, "capacity"?:{…}, "efficiency"?:0.97,
"distance"?:…, "costs"?:{…},
"electrical"?: { "kind":"line"|"transformer", "r":1.2, "x":3.5, // AC branch (PyPSA power flow)
"r_per_length"?:0.12, "x_per_length"?:0.35, // … or per km × distance
"s_nom"?:20, "num_parallel"?:1, "b"?:0.0001, "g"?:0,
"tap_ratio"?:1, "phase_shift"?:0, "max_loading"?:0.9 },
"dispatch"?: Value, // fixed transfer of a controlled link (HVDC)
"lifetime"?:40, "interest_rate"?:0.05, "cost_basis"?:"overnight", // annualize the arc's investment
"native"?:{…} } },
"trade"?: { "<id>": { "node":"<node>", "carrier":"<id>",
"import"?:{ "limit"?:500, "price"?:Value, "emission_factor"?:0.35 },
"export"?:{ "limit"?:500, "price"?:Value } } },
"emission_limits"?: [ { "name"?:"co2_cap", "carrier"?:"co2", "sense":"<="|">="|"==",
"limit":1e6, "period"?:"…" } ],
"constraints"?: [ { "name":"…", "sense":"<="|">="|"==", "bound":800, "period"?:"…",
"terms":[ { "coefficient":1,
"variable":"capacity"|"flow_out"|"flow_in"|"storage_cap"|"emissions",
"techs"?:["…"], "carriers"?:["…"], "nodes"?:["…"] } ] } ],
"native"?: { "pypsa"?:{…}, "calliope"?:{…}, "adopt-net0"?:{…} }
},
"experiment": {
"mode"?: "plan"|"operate"|"alternatives"|"pareto"|"monte_carlo"|"stochastic"|"power_flow",
"power_flow"?: { "method"?:"newton_raphson"|"linear", "slack":"<tech>"|"<tech>@<node>",
"distribute_slack"?:false, "tolerance"?:1e-6 }, // mode power_flow (PyPSA)
"network"?: { "model"?:"transport"|"linear_power_flow", "security"?:{ "outages":["<arc>"] } }, // the voltage law inside optimize (PyPSA)
"objective"?: "min_cost"|"min_emissions",
"foresight"?: "perfect"|"myopic",
"time_aggregation"?: { "method":"typical_days"|"resample", "periods"?:12, "resolution"?:"3H" },
"operate"?: { "window":"24H", "horizon":"48H" }, // rolling horizon
"alternatives"?: { "number":5, "slack"?:0.1, "scoring_algorithm"?:"integer"|"relative_deployment" }, // MGA / SPORES
"pareto"?: { "points":6 }, "monte_carlo"?: { "samples":25, "standard_deviation"?:0.1, "on"?:["technology_capex"] },
"allow_unmet_demand"?: { "enabled":true, "penalty_price"?:1000 }, "copperplate"?: false,
"emission_accounting"?: "net"|"positive_only",
"reporting"?: { "output_dir"?:"…", "case_name"?:"…", "save_logs"?:"…", "shadow_prices"?:true },
"scenarios"?: [ { "name":"…", "weight"?:0.5, "overrides"?:{ "<path>": 1.2 } } ],
"sweep"?: [ { "parameter":"technologies.pv.capacity.max", "values":[200,400] } ],
"solver": { "name":"highs", "options"?:{ … } },
"custom_math"?: true, // Calliope: render constraints as add_math (default true)
"native"?: { … }
}
}
Model invariants: node and carrier are orthogonal (PyPSA's bus is derived);
flows supersedes efficiency for multi-carrier conversion; anything a target
can't represent portably goes in a native block (which makes that payload
target-locked).
Runtime / API schemas¶
// GET /jobs/{id}/status (JobView). ?target= on validate/convert/simulate
// accepts one target, a comma-separated list, or "all" — a multi-target job
// runs every framework in turn ("target":"pypsa,calliope,adopt-net0"). "all"
// means the default targets; a version variant ("calliope-0.6") runs only
// when named, and a version alias ("calliope-0.7") resolves to its default.
// With API_KEY configured, validate/convert/simulate payloads must carry a
// matching top-level "api_key" or the request is a 401 {"error":…}.
{ "id":"…", "target":"pypsa", "state":"queued|running|succeeded|failed",
"warnings":[…], "error":"", "log":"…full console log…",
"runs":[ RunResult ], "created":"…","started":"…","finished":"…" }
// one run (RunResult; one per target x sweep point)
{ "target":"pypsa", "index":0, "input_dir":"…/run_0/input",
"plan":{ "target":"pypsa","command":["python","run.py"],"work_dir":"…" },
"assigned":{ "technologies.pv.capacity.max":400 },
"stdout":"…", "exit_code":0, "error":"" }
// POST /simulate (202)
{ "scheduled":true, "id":"…", "state":"queued", "target":"pypsa",
"warnings":[…], "status_url":"/jobs/…/status", "result_url":"/jobs/…" }
// GET /capabilities
{ "targets":["adopt-net0","calliope","calliope-0.6","pypsa"], // every selectable target
"default_targets":["adopt-net0","calliope","pypsa"], // what ?target=all runs
"aliases":{ "calliope-0.7":"calliope" }, // version-explicit spellings
"versions":{ "calliope":"0.7.0", "calliope-0.6":"0.6.8", "pypsa":"1.2.4", "adopt-net0":"0.1.10" },
"features":{ "pypsa":{ "mode:stochastic":true, … }, "calliope":{…}, "calliope-0.6":{…}, "adopt-net0":{…} } }
Result bundle (GET /jobs/{id} → zip)¶
<id>.zip
├── metadata.json # JobView without the log (state, warnings, error, runs, timings)
├── log.txt # the whole console log
├── results.json # the RunResult array
├── config.json # the initial request payload (byte-identical, minus any api_key)
└── files/ # the complete emitted tree, per run
├── config.json # (same file, on-disk copy in the job dir)
└── run_0/
├── run.py # the executed Python driver (PyPSA/AdOpT; Calliope uses its CLI)
├── input/… # CSVs / model.yaml / input_data + sidecars
└── output/… # solver results: network.nc (PyPSA; + network_baseline.nc for MGA),
# results.nc + csv/ (Calliope);
# AdOpT writes results/ (H5 + Summary.xlsx) under input/
A multi-target job (?target=pypsa,calliope or target=all) nests each
framework's tree one level deeper — files/<target>/run_0/… — so one zip
carries every requested model, its results, the shared initial config, and the
combined log.
Emitter outputs¶
| Target | Entrypoint | Contents |
|---|---|---|
| PyPSA | CSV folder | network.csv (name + pinned pypsa_version), buses.csv, carriers.csv, generators.csv, loads.csv, links.csv, storage_units.csv, global_constraints.csv (unscoped constraints only), snapshots.csv (plain, or (period, timestep) under investment periods), materialized time-varying CSVs (loads-p_set, generators-p_max_pu/p_min_pu, generators-marginal_cost, links-marginal_cost, storage_units-inflow, storage_units-state_of_charge_set; *-p_set/*-q_set dispatch series in mode power_flow), _constraints.json (scoped constraints, applied by run.py), _scenarios.json (stochastic mode), _performance.json, _native.pypsa.json; mode power_flow or network model linear_power_flow: lines.csv, transformers.csv, v_nom + carrier AC on the electrical buses (power flow: control/p_set/q_set, shunt_impedances.csv, dispatch series); energy-sized storage: stores.csv + charger/discharger links + _storage.json; investment periods: investment_periods.csv and (period, timestep) snapshots |
Calliope 0.7 (calliope) |
model.yaml |
config (init.mode/init.resample/solve.solver/solve.spores/init.math_paths+extra_math incl. milp), techs (incl. co2 cost_flow_in / cost_source_use on supply, trade market techs, MILP unit-commitment params, piecewise breakpoint params), nodes, data_tables (table + add_dims.inputs) + materialized series CSVs (demand, availability, source), data_definitions.objective_cost_weights, additional_math.yaml (constraint IR + flow-ratio pinning + piecewise machinery), _constraints.json sidecar when custom_math=false |
Calliope 0.6 (calliope-0.6) |
model.yaml |
model (calliope_version, timeseries_data_path, subset_time, time resample / k-means apply_clustering), run (mode plan/operate/spores, solver, objective_options.cost_class weights, cyclic_storage, operation, spores_options), techs (essentials/constraints/costs; conversion_plus carrier tiers + carrier_ratios; trade market techs; units_max/energy_cap_per_unit MILP; emissions cost class), locations (coordinates, available_area, per-location file= resources + overrides), links (from,to → transmission techs + distance), group_constraints (constraint IR, emission limits, annual output bounds, SPORES cost slack), timeseries_data/*.csv (datetime index, one column per node; demand negative) |
| AdOpT-NET0 | input_data/ |
Topology.json, ConfigModel.json (glpk, write_results: 1), NodeLocations.csv, _meme_overrides.json (keyed node → tech), per-period Networks.json, per-node Technologies.json (database tech names) + ClimateData.csv + CarbonCost.csv + carrier_data/*.csv (;-delimited; demand/import/export incl. per-timestep prices) + EnergybalanceOptions.json. The run step copies tech data + applies overrides. |
The orchestrator writes each run's Python driver as run_i/run.py (PyPSA:
import → sidecar constraints → mode branch (plan / rolling-horizon operate /
MGA / stochastic / power flow: lpf() then pf() with the result tables under
output/power_flow/) → export network.nc, non-zero exit unless optimal or
converged; AdOpT:
copy tech data → apply node-keyed overrides → quick_solve). Calliope runs via
its CLI with --save_netcdf/--save_csv — calliope for 0.7, calliope-0.6
for 0.6 (two venvs in the image). The executed script ships in the result
bundle.