Targets and versions¶
A target is one framework backend: its capability profile, validation
gates, emitter and run plan live together in one package under
internal/target/ and self-register with the target registry. The service
ships four:
| Target | Framework | Version | Selected by |
|---|---|---|---|
pypsa |
PyPSA | 1.2.4 | default (all) |
calliope |
Calliope 0.7 | 0.7.0 | default (all); alias calliope-0.7 |
calliope-0.6 |
Calliope 0.6 | 0.6.8 | by name only (version variant) |
adopt-net0 |
AdOpT-NET0 | 0.1.10 | default (all) |
Framework versions are targets¶
A framework may be registered once per supported release. The bare name is
the default version; another release is a version variant
(calliope-0.6) that runs only when named, so ?target=all expands to the
default targets and never runs two releases of one framework unless asked.
A version-explicit alias of a default (calliope-0.7 → calliope) keeps
a pinned spelling valid when the default moves on and resolves everywhere a
target name is accepted. GET /capabilities reports targets (every
selectable name), default_targets (what all runs), aliases, versions
(the framework release each target drives) and the feature matrix.
Each version has its own emitter, capability profile and native key:
native.calliope is Calliope 0.7 syntax and is never applied by the 0.6
target, which reads native["calliope-0.6"]. The decision and its
alternatives are recorded in
ADR-0006.
curl -s -X POST 'localhost:8080/validate?target=calliope-0.6' -d @examples/shared_full.json
curl -s -X POST 'localhost:8080/convert?target=calliope,calliope-0.6' -d @examples/shared_full.json
The pinned releases are installed by the environment image; the capability reference lists, property by property, what each target honors.
Extension points and known caveats¶
- All emitters produce solver-runnable output for a faithful core; the
long tail routes through
nativeand sidecars. Verified end-to-end against pinned framework versions (see the environment): PyPSA (HiGHS), Calliope 0.7.0 and 0.6.8 (cbc, incl. plan/operate/SPORES/emission-limits), AdOpT-NET0. - Power flow is a PyPSA run mode, not a hidden hand-over.
experiment.mode: power_flowsolves the AC network the canonical fields describe (node.electrical.v_nom,transmission.electrical,technology.dispatch) withlpf()/pf()over a fixed dispatch; nothing is optimized, and the results land underoutput/power_flow/. Reactive power (reactive_dispatchor apower_factor), voltage control (control: pv,voltage_setpoint), shunts and controlled HVDC transfers (transmission.dispatch) complete the network. The API consumer chains the calls: plan on any target, then send the payload with the dispatch filled in to?target=pypsa. Calliope and AdOpT reject the mode; every power-flow input is ignored with one warning wherever no power flow runs, so the two payloads may be one document (ADR-0008, ADR-0009). - PyPSA plans on the electrical network too.
experiment.network.model: linear_power_flowsolves an optimization under Kirchhoff's voltage law (Lines and Transformers with expansion,max_loading, annualized transmission investment fromlifetimeandinterest_rate) andnetwork.security.outagesmakes it security-constrained; other targets reject that model like a mode. Energy-sized storage is a Store with charger and discharger,capacity.per_unitbuilds integer modules, continuous retirement splits existing from new capacity,allow_unmet_demandadds priced slack,copperplatea free hub, andmodel.periodswith years are investment periods; the unit-commitment extras, a quadratic cost and a pinned state of charge round it off. Every optimization run writesoutput/results/with dispatch, flows, prices, capacities and statistics. Fields the other targets have no slot for are ignored there with a warning (ADR-0010, ADR-0011). - Calliope versions are targets.
calliopeis Calliope 0.7.0 (aliascalliope-0.7);calliope-0.6is Calliope 0.6.8 — a separate emitter for the legacy dialect, driven by thecalliope-0.6CLI from a second venv in the image.?target=allruns the defaultcalliopeonly; namecalliope-0.6to run it (alone or alongside). The two targets have their own capability profiles and native keys (native.calliopeis 0.7 syntax,native["calliope-0.6"]is 0.6 syntax), andGET /capabilitiesreportsdefault_targets,aliasesandversions. - Calliope 0.7 specifics (all verified against
calliope==0.7.0): - Run mode and solver names are translated to Calliope's own (
plan→base,alternatives→spores;highs→cbc).experiment.custom_mathrenders the linear-constraint IR toadditional_math.yaml, registered viaconfig.init.math_paths+extra_math. - 0.7.0 (vs. the earlier dev7 pin): data tables are spelled
table/add_dims.inputs; a supply tech's fuel cost iscost_source_use(cost_flow_inno longer applies to supply); thedefined()math helper is gone, so ratio-pinning math iterates techs and subsets with[tech] in techs; node coordinates must be given on all nodes or none (a partial set is dropped with a warning); an already-annualized cost basis pinscost_depreciation_rate: 1. - Emissions:
co2_intensityon a consumed carrier becomes a per-techcost_flow_in[costs=co2], withobjective_cost_weights[co2]=0so it is tracked (foremission_limits) but not minimized. Limitation: this uses costs-only indexing, exact for a single fuel per tech (the common case); a tech burning two different co2 carriers would need per-carrier indexing (which clashes with Calliope's built-in cost expression). - Inline time series are materialized to data-table CSVs at emit time.
- Operate mode sizes nothing: Calliope reads
flow_cap,storage_cap,source_capandarea_useas inputs (default infinite) and ignores*_maxbounds. The emitter installs every element at its fixed size — techs and lines atcapacity.existing, elsecapacity.max(warned), imports at theirlimit,source.cap/area.maxassource_cap/area_use— and validation rejects a model with no fixed capacity at all, which Calliope would otherwise solve and then abort on (#8). - Calliope 0.6 specifics (all verified against
calliope==0.6.8): - The whole model is one document:
model(incl.calliope_version,timeseries_data_path),run,techs,locations,links,group_constraints. Time series arefile=<file>.csv:<node>references intotimeseries_data/(demand negative, one column per node). - 0.6 requires
lifetime+interest_ratewherever a fixed cost appears; missing values (or an annualized basis) become lifetime 1 / rate 0, i.e. the figure is charged in full each year — the same outcome as 0.7's default depreciation rate. - Storage has one
energy_efffor both directions (asymmetric charge/discharge efficiencies emit their geometric mean, with a warning) and a model-widerun.cyclic_storage(mixed per-tech settings warn and disable it); operate mode is never cyclic. - Gains over 0.7: built-in k-means typical days (
time_aggregation: typical_days,periods= k, fixedrandom_seed) and a native emissions-minimizing objective. Absent: indexed parameters, piecewise performance, custom math (constraints are single-termgroup_constraints), multi-inputflows,time.weights,shadow_prices. - HiGHS + Calliope is unsupported — Calliope's pyomo backend cannot drive
HiGHS (ASL needs a nonexistent binary; the persistent/appsi paths hit an
add_block/solver_iobug), and 0.6.8's pyomo 6.2 predates the HiGHS interface entirely. Usecbcfor both Calliope targets (free/OSS); HiGHS remains the solver for PyPSA. (0.7.0 ships a directconfig.build.backend: highswithout piecewise support; not wired yet.) - Runner commands assume the Python tools are on PATH;
DryRunneris the default so the service runs with no Python installed (-execswitches toCommandRunner). Theenvironment/image provides all three frameworks (both Calliope releases) + cbc. - JobStore is in-memory — jobs don't survive a restart. Finished jobs are
GC'd after 24 h (15-min sweep, see
cmd/meme/main.go); a persistent store is the production follow-up. - AdOpT-NET0 runs and solves (verified end-to-end via
/simulate -exec), but it works differently from PyPSA/Calliope in two ways you must know: - Solver:
cbc/highsare not options.adopt_net0supports only gurobi and glpk; the emitter selects glpk (free/OSS) regardless ofexperiment.solver.name. - Run modes are ConfigModel toggles, not separate entrypoints. AdOpT's
single
quick_solve()branches onConfigModel.optimization, so the emitter translatesexperiment.modethere rather than into the run command:paretosetsobjective: pareto,monte_carlosetsmonte_carlo.N > 0(default 10), andtime_aggregation.method: typical_dayssetstypicaldays.Nto the requestedperiods. Without this the capability matrix would accept the payload but adopt would silently solve a plain cost-optimal plan. Iteration counts come from the typed blocksexperiment.monte_carlo.samplesandexperiment.pareto.points, like Calliope'sexperiment.alternatives; the oldersolver.optionsnamespaces still work but validate with a deprecation warning. - Technologies are database-driven. A tech is a named library entry
(
Photovoltaic,Storage_Battery,HeatPump_AirSourced, …). The emitter maps supported canonical techs to a database name, lists it inTechnologies.json, and writes an economics/size override sidecar (_meme_overrides.json); the run step copies the tech data viacopy_technology_dataand patches it with the overrides before solving. Only mapped roles are emitted (storage →Storage_Battery; physics supply →Photovoltaic/WindTurbine_Onshore_1500/HeatPump_AirSourced). Generic supply/conversion techs (ccgt,chp) have no library counterpart and are rejected (422has no adopt_net0 database mapping) — supply them vianative.adopt-net0(name a real database tech inTechnologies.json+ override). - Physics techs need climate data — supply it via
node.climate.Photovoltaic/wind readClimateData.csv(ghi/dni/dhi/temp_air/rh/ws10/ hydro_inflow). The emitter materializes those columns from a node'sclimatemap (each entry a scalar or timeseries id), so a/simulatepayload can carry the weather and PV/wind produce real output. Columns left unspecified are blank. Demand, trade (import/export), emission limits, node locations, and storage all work from the canonical model directly. - Adding/altering a target is largely a data edit to the capability matrix
plus one
Emitterimplementation; the schema, validation, and orchestrator are target-agnostic. Adding a framework version is the same work under a-<major.minor>name: register it (as atarget.Variantof the default if it must not joinall), give it its own native key, pin it in its own venv inenvironment/, and run the corpus against it.