Extension Contract#
This page defines the small public contract that downstream scientific packages may rely on when they use CGE-Core’s engine-backed Hosoe models.
The purpose is stability, not extensibility for its own sake. CGE-Core does not promise a plugin framework or a universal interface for every CGE model. It promises a narrow equilibrium/scenario lifecycle that can be used without reaching into the lower-level engine or raw Pyomo state.
Scope#
This extension contract applies to the public Hosoe Simple and Standard workflow:
from cge_core import CGE, Equilibrium, Scenario, Result, example_data
from cge_core.models import SplCGE, StdCGE
The IFPRI subsystem has its own dedicated API and closure machinery. CAMCGE remains a repository-level replication benchmark. Neither is folded into this contract.
Contract at a glance#
CGE(model, data)
|
+-- solve_benchmark(...)
|
v
Equilibrium
| \
| +-- value(...)
| +-- objective
|
+-- scenario(name)
|
v
Scenario
|
+-- set(...)
+-- solve(...)
|
v
Result
|
+-- value(...)
+-- objective
+-- compare(...)
A downstream package should be able to do this repeatedly:
scenario.set("taum", "BRD", 0.0)
first = scenario.solve()
scenario.set("taum", "MLK", 0.0)
second = scenario.solve()
first remains an immutable snapshot of the first solved state even after the
same Scenario is modified and solved again.
Stable capabilities#
1. Configure a static CGE blueprint#
model = CGE(model=StdCGE(), data=example_data("stdcge"))
CGE is a stateless configuration object. A solved backend belongs to the
Equilibrium returned by solve_benchmark(), not to the blueprint itself.
2. Solve a benchmark equilibrium#
benchmark = model.solve_benchmark(
numeraire=("pf", "LAB"),
redundant=("eqpf", "LAB"),
)
For the Hosoe engine-backed models, numeraire= and redundant= are the
explicit structural closure spelling. This is a Hosoe-family contract, not a
universal closure abstraction.
3. Read the benchmark without raw Pyomo access#
wage = benchmark.value("pf", "LAB")
utility = benchmark.objective
value() is the stable read path for variables and parameters exposed by the
solved model snapshot.
4. Create an isolated counterfactual#
scenario = benchmark.scenario("tariff reform")
A Scenario owns independent mutable state. Creating or modifying a scenario
does not mutate the benchmark, and multiple scenarios may coexist.
5. Set admissible scenario values#
scenario.set("taum", "BRD", 0.0)
For a mutable Param, set() changes the scenario value. For a Var, it
sets and fixes that variable in the scenario. Whether a component is
economically appropriate to shock remains the responsibility of the model
definition and the researcher.
6. Solve, read, modify, and solve again#
result1 = scenario.solve()
x1 = result1.value("Z", "BRD")
scenario.set("taum", "MLK", 0.0)
result2 = scenario.solve()
x2 = result2.value("Z", "BRD")
Repeated set() -> solve() cycles are part of the contract. Previously
returned Result objects remain unchanged.
7. Compare solved states#
changes = result2.compare(benchmark)
Result.compare(reference) accepts a compatible Equilibrium or Result.
The direction is always:
current result - reference
The comparison table includes component/index columns plus reference value,
current value, difference, and percentage change. Percentage change is NaN
when the reference value is zero. Objective comparison metadata is stored in
DataFrame.attrs["objective"].
Public boundary#
Downstream packages should depend on the public surface, not the implementation used to realize it.
Supported dependency |
Status |
|---|---|
|
extension contract |
|
extension contract |
|
extension contract |
|
extension contract |
|
supported public helper |
|
supported public model import |
|
supported public model import |
|
advanced/lower-level API; not this contract |
|
private implementation detail |
|
private implementation detail |
raw Pyomo objects reached through private state |
private implementation detail |
The lower-level PyCGE API remains supported for advanced users, but
a downstream package that chooses to depend on its mutable base/sim state
machine is intentionally outside this extension-stability promise.
Deliberate exclusions#
The minimal contract does not include:
a universal
Closureobject;arbitrary backend/plugin registration;
direct access to the private engine;
raw Pyomo traversal as a stable interface;
persistence or serialization guarantees;
Scenario.unfix()as a required downstream capability;IFPRI or CAMCGE being adapted to the Hosoe facade;
dynamic transition equations or time-stepping logic.
Scenario.unfix() is public and documented for ordinary use, but the minimal
downstream contract does not require it. Making an exogenous parameter
endogenous is a model-definition or closure change, not a generic scenario
operation.
Why the contract is narrow#
A narrow contract lets another package orchestrate CGE-Core without knowing
how the current engine stores base, sim, Pyomo components, solver results,
or calibration state. That is enough for downstream static-equilibrium
composition, including future packages that may coordinate repeated
equilibrium solves.
Regression tests treat this lifecycle as a public behavioral requirement. Internal cleanup may change how the capability is implemented, but should not silently remove the documented behavior.