Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions CITATION.cff
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,9 @@ abstract: >-
model ports are not James Matthew Miraflor's original work. He directed and
reviewed an AI-assisted process covering the fork's corrections, engine API,
testing, documentation, IFPRI implementation, CAMCGE replication benchmark,
and release workflow. He is cited as author and maintainer of this revised
software project, not of the underlying models or inherited code. The
and release workflow. He is cited as project lead and maintainer of this
revised software project, not as author of the underlying models or
inherited code. The
official IFPRI source package and test data remain external. Hosoe is checked
against GAMS Model Library references, IFPRI against full-precision external
reference runs, and CAMCGE against its published base equilibrium and three
Expand Down
17 changes: 9 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -337,17 +337,17 @@ handling, input validation, reporting utilities, SAM tooling, clean-room IFPRI
subsystem, CAMCGE replication benchmark, documentation, and test suite — are
released under the MIT License.

### Authorship
### Project leadership and maintenance

**James Matthew Miraflor**
Scientific Computing Laboratory
Department of Computer Science
University of the Philippines Diliman
**James Matthew Miraflor — Project Lead and Maintainer**<br>
Scientific Computing Laboratory<br>
Department of Computer Science<br>
University of the Philippines Diliman<br>
<jbmiraflor@up.edu.ph>

This fork is maintained by James Matthew Miraflor, who directed and reviewed an
AI-assisted revision, testing, and documentation workflow. Authorship of
CGE-Core as a revised software project does not imply authorship of the
CGE-Core is led and maintained by James Matthew Miraflor, who directs and
reviews its AI-assisted development, validation, integration, documentation,
and release workflow. This project role does not imply authorship of the
underlying PyCGE code, Hosoe models, IFPRI specification, or CAMCGE model.

---
Expand All @@ -363,6 +363,7 @@ sources.
title = {{CGE-Core}: a Pyomo-based computable general equilibrium framework},
year = {2026},
version = {0.5.0},
note = {Project lead and maintainer},
url = {https://github.com/miraflor/CGE-core}
}
```
Expand Down
22 changes: 11 additions & 11 deletions cge_core/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,22 @@
"""Public domain API for CGE-Core v0.6.

This module is new CGE-Core work. It provides a small scientific-Python
facade over the validated legacy :class:`cge_core.engine.PyCGE` workflow
without changing the economic equations or the legacy engine contract.
facade over the validated lower-level :class:`cge_core.engine.PyCGE` workflow
without changing the economic equations or the lower-level engine contract.

The public lifecycle is::

CGE -> solve_benchmark -> Equilibrium -> Scenario -> Result

``CGE`` is a configuration blueprint. Every benchmark solve owns a fresh
legacy engine. Every Scenario owns a deep-copied engine, so simultaneously
live counterfactuals cannot share the legacy engine's single ``sim`` slot.
lower-level engine. Every Scenario owns a deep-copied engine, so simultaneously
live counterfactuals cannot share the lower-level engine's single ``sim`` slot.
``Result`` stores plain numerical snapshots so earlier results never change
when a Scenario is subsequently modified and solved again.

Provenance: new CGE-Core v0.6 facade written for the CGE-Core reengineering
work (2026), via an AI-assisted workflow directed and reviewed by the project
maintainer. The underlying PyCGE engine and Hosoe model ports retain their
Provenance: new CGE-Core v0.6 facade developed for the CGE-Core
reengineering work (2026) through an AI-assisted workflow directed and
reviewed by James Matthew Miraflor, project lead and maintainer. The underlying PyCGE engine and Hosoe model ports retain their
own provenance; this module does not claim authorship of them.
"""
from __future__ import annotations
Expand Down Expand Up @@ -64,7 +64,7 @@ def _number(item: Any) -> Optional[float]:


def _component_item(instance, name: str, index: Any):
"""Resolve one Var/Param item with the legacy engine's scalar convention."""
"""Resolve one Var/Param item with the lower-level engine's scalar convention."""
component = instance.component(name)
if component is None:
raise ComponentError(f"'{name}' does not exist in this scenario.")
Expand Down Expand Up @@ -172,7 +172,7 @@ class CGE:
"""Configured static-CGE blueprint.

``CGE`` owns no solved model state. Each :meth:`solve_benchmark` call
creates a fresh legacy backend that is thereafter owned by the returned
creates a fresh lower-level backend that is thereafter owned by the returned
:class:`Equilibrium`.

Args:
Expand Down Expand Up @@ -224,7 +224,7 @@ class Equilibrium:
"""Solved, protected benchmark equilibrium.

``frozen=True`` protects the public wrapper from rebinding. The private
legacy engine remains mutable by design so it can be deep-copied when a
lower-level engine remains mutable by design so it can be deep-copied when a
Scenario is created; public reads always come from the immutable snapshot.
"""

Expand Down Expand Up @@ -315,7 +315,7 @@ def unfix(self, component: str, index: Any = None) -> None:
f"{component}[{index}] is already endogenous in this scenario."
)

# The legacy engine's fix=False path also accepts a value. Passing
# The lower-level engine's fix=False path also accepts a value. Passing
# the current value preserves it exactly as the solver starting point.
current = _number(item)
self._engine.model_modify_sim(
Expand Down
15 changes: 7 additions & 8 deletions cge_core/samtools.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,19 @@

Example::

from cge_core import PyCGE, samtools
from cge_core.examples.stdcge_model_def import StdModelDef
from cge_core import CGE, samtools
from cge_core.models import StdCGE

accounts = dict(hoh='HH', gov='GOVT', inv='SAV-INV',
ext='ROW', idt='ITAX', trf='TARIFF')
samtools.build_dataset('ph_sam.csv', 'ph_data_dir',
factors=['CAP', 'LAB'],
institutions=accounts.values())
cge = PyCGE(StdModelDef(accounts=accounts))
cge.model_data('ph_data_dir')
model = CGE(model=StdCGE(accounts=accounts), data='ph_data_dir')

Provenance: new in CGE-Core v0.3.0; written by James Matthew Miraflor
(2026) via an AI-assisted ("vibecoded") workflow directed and reviewed by
him. Not part of the original NIST PyCGE.
Provenance: new in CGE-Core v0.3.0; developed through an AI-assisted
workflow directed and reviewed by James Matthew Miraflor (2026), project
lead and maintainer. Not part of the original NIST PyCGE.
"""
from __future__ import annotations

Expand Down Expand Up @@ -166,7 +165,7 @@ def build_dataset(sam_path: PathLike,
factors (sequence of str): factor account labels.
institutions (iterable of str): institutional account labels;
for the standard model, pass the six values of the
``accounts`` mapping given to ``StdModelDef`` (household,
``accounts`` mapping given to ``StdCGE`` (household,
government, investment, external, indirect tax, tariff).

Returns:
Expand Down
107 changes: 64 additions & 43 deletions docs/OG_CORE_CROSSWALK.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# Reading CGE-Core if you know OG-Core

CGE-Core follows the documentation conventions of
[OG-Core](https://github.com/PSLmodels/OG-Core) (DeBacker & Evans) — NumPy
docstrings with `.. math::` blocks, calibrated-parameter transparency, and a
strict separation of *model algebra* from *solution workflow* — but the two
frameworks solve structurally different problems. This note maps one onto the
other so an OG-Core reader can orient in minutes.
CGE-Core follows several documentation conventions familiar from
[OG-Core](https://github.com/PSLmodels/OG-Core) (DeBacker & Evans), including
calibrated-parameter transparency and a strict separation of *model algebra*
from *solution workflow*. The two frameworks nevertheless solve structurally
different problems. This note maps one onto the other so an OG-Core reader can
orient quickly.

## What kind of model this is

Expand All @@ -19,59 +19,80 @@ other so an OG-Core reader can orient in minutes.
| Solution | Steady state (`SS.py`) + transition path (`TPI.py`) via fixed-point iteration | One square nonlinear system solved simultaneously by IPOPT |
| Data anchor | Calibrated `Specifications` object | A balanced social accounting matrix (SAM) |

The deeper difference: OG-Core computes equilibrium by *iterating* on
aggregates until household/firm behavior is consistent with prices; CGE-Core
hands the entire first-order-condition system to an NLP solver at once. That
is why Walras' law appears here as a concrete degrees-of-freedom problem (one
market-clearing equation must be deactivated before IPOPT will solve — see
`docs/MODEL.md`), whereas in OG-Core it is absorbed by the outer-loop
construction.
The deeper difference is computational. OG-Core computes equilibrium by
iterating on aggregates until household and firm behavior is consistent with
prices. CGE-Core hands a square simultaneous equilibrium system to a nonlinear
solver. Walras' law therefore appears concretely in the Hosoe models as a
closure requirement: one redundant market-clearing equation is removed and
one price is chosen as numeraire.

## File-by-file mapping
## Public workflow mapping

| OG-Core role | OG-Core file(s) | CGE-Core file |
| OG-Core role | OG-Core interface | CGE-Core public interface |
| --- | --- | --- |
| Model algebra: firms, households, taxes, aggregates | `firms.py`, `household.py`, `tax.py`, `aggregates.py` | `cge_core/examples/stdcge_model_def.py` (all agents in one simultaneous Pyomo system), `splcge_model_def.py` (pedagogical closed economy) |
| Parameters / calibration | `parameters.py` (`Specifications`), `default_parameters.json` | The `Param` declarations inside the model definitions: benchmark `*0` magnitudes read off the SAM, then share/scale parameters recovered so the base year is reproduced exactly (see "Calibration" in `docs/MODEL.md`) |
| Solving | `SS.py`, `TPI.py`, `execute.py` | `cge_core/engine.py` (`PyCGE.model_calibrate` = solve baseline; `PyCGE.model_solve` = solve counterfactual) |
| Reform specification | Reform dictionaries passed to `Specifications.update_specifications` | `PyCGE.model_modify_sim(name, index, value)` — e.g. set a tariff rate `taum` to 0 |
| Output / comparison | `output_tables.py`, `output_plots.py` | `PyCGE.model_compare`, `PyCGE.model_postprocess` (CSV exports, structured records) |
| Utilities | `utils.py` | `cge_core/datasets.py`, `cge_core/examples/_solver.py` |
| Country calibration packages | OG-USA, OG-PHL, ... | Swap the bundled two-good SAM for a country SAM with the same account structure |
| Model specification | `Specifications` plus model modules | `StdCGE` / `SplCGE` model definition passed to `CGE` |
| Benchmark solution | `SS.run_SS(p)` | `CGE.solve_benchmark(...)` |
| Reform specification | `Specifications.update_specifications(...)` | `benchmark.scenario(...)` then `Scenario.set(...)` |
| Counterfactual solution | `SS.run_SS(...)` or transition machinery | `Scenario.solve()` |
| Read outputs | dictionaries / output utilities | `Equilibrium.value(...)`, `Result.value(...)`, `Result.objective` |
| Compare reform with reference | output tables / plots | `Result.compare(benchmark)` |
| Data helpers | `utils.py` and calibration inputs | `cge_core.datasets`, `cge_core.samtools` |

## Workflow correspondence

OG-Core:

```python
p = Specifications() # parameters
p.update_specifications(reform) # reform
ss_output = SS.run_SS(p) # solve
p = Specifications()
p.update_specifications(reform)
ss_output = SS.run_SS(p)
```

CGE-Core:

```python
cge = PyCGE(StdModelDef()) # algebra
cge.model_data(data_dir) # SAM in, validated
cge.model_instance('pf', 'LAB') # numeraire: pf_LAB = 1
cge.model_drop_redundant('eqpf', 'LAB') # Walras' law -> square system
cge.model_calibrate(solver) # baseline (reproduces the SAM)
cge.model_sim() # clone baseline
cge.model_modify_sim('taum', 'BRD', 0) # reform: abolish a tariff
cge.model_solve(solver) # counterfactual
cge.model_compare('print') # baseline vs. reform
from cge_core import CGE
from cge_core.models import StdCGE

model = CGE(model=StdCGE(), data=data_dir)

benchmark = model.solve_benchmark(
numeraire=("pf", "LAB"),
redundant=("eqpf", "LAB"),
solver=solver,
)

scenario = benchmark.scenario("tariff abolition")
scenario.set("taum", "BRD", 0.0)

result = scenario.solve(solver=solver)
comparison = result.compare(benchmark)
```

Two conventions worth flagging because they have no OG-Core analogue:
Two conventions are worth flagging because they have no direct OG-Core
analogue:

1. **Numeraire.** All prices are relative. In the standard Hosoe example,
`numeraire=("pf", "LAB")` fixes the labor-factor price as the price anchor.
2. **Redundant market equation.** Walras' law makes one market-clearing
equation redundant. `redundant=("eqpf", "LAB")` tells the Hosoe workflow
which equation to deactivate so the solved system is square.

The test suite also checks the dropped market after solution as an
internal-consistency test, loosely analogous to resource-constraint checks on
OG-Core output.

## Lower-level implementation

The public lifecycle above is implemented by the supported lower-level
`PyCGE`/Pyomo engine. Advanced users can still work directly with
`cge_core.engine.PyCGE`, including its explicit benchmark/simulation state
machine. That engine API is documented separately and is not the recommended
interface for ordinary Hosoe-model policy experiments.

1. **Numeraire.** All prices are relative; `model_instance('pf', 'LAB')`
fixes the wage as numeraire, matching Hosoe's `pf.fx("LAB") = 1`.
2. **The dropped equation.** `model_drop_redundant` deactivates exactly one
market-clearing condition. The test suite asserts the dropped market
still clears at the solution (Walras' law), which is the model's
internal-consistency check — loosely analogous to OG-Core's
resource-constraint checks on `SS` output.
This distinction is important: the public facade is the stable scientific
workflow, while the lower-level engine remains available for implementation
inspection, model development, and compatibility.

## Docstring conventions

Expand All @@ -91,5 +112,5 @@ def eqF_rule(model, h, i):
```

The equation label (`eqF`) is the name used in the GAMS Model Library source
(`stdcge.gms`, SEQ=276), so every line can be diffed against the published
(`stdcge.gms`, SEQ=276), so equations can be checked against the published
reference implementation; `docs/MODEL.md` collects the full equation table.
2 changes: 1 addition & 1 deletion docs/_config.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Jupyter Book configuration for the CGE-Core documentation.
title: CGE-Core
author: James Matthew Miraflor (fork maintainer; original PyCGE by Fung & Burtwistle, NIST)
author: "James Matthew Miraflor — Project Lead and Maintainer"
copyright: "2026"

execute:
Expand Down
15 changes: 10 additions & 5 deletions docs/getting-started/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,18 @@ This route requires the IPOPT system library and a working PyNumero ASL build.
## Check the installation

```python
from cge_core import PyCGE, example_data
from cge_core.examples.stdcge_model_def import StdModelDef

print(example_data("stdcge"))
from cge_core import CGE, example_data
from cge_core.models import StdCGE

model = CGE(
model=StdCGE(),
data=example_data("stdcge"),
)
print(type(model).__name__)
```

If that imports successfully, continue to {doc}`quickstart`.
If that imports and constructs the model blueprint successfully, continue to
{doc}`quickstart`.

```{important}
A solver is required at runtime. Installing the Python package alone is not enough to solve a CGE model.
Expand Down
10 changes: 5 additions & 5 deletions docs/microsites/control-room/assets/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@
['Intermediate use','X[i,j]: how the shock propagates through the input-output network as sectors buy inputs from one another.'],
['Factor demand','F[h,i] by factor × sector'],
['Household demand','Xp[i]: consumption response after prices, factor income, taxes and saving adjust.'],
['Government demand','Xg[i]: model-consistent public purchases by good; not currently a free-form runtime spending knob in StdModelDef.'],
['Government demand','Xg[i]: model-consistent public purchases by good; not currently a free-form runtime spending knob in the Standard CGE model.'],
['Investment demand','Xv[i]: how total saving is translated into demand for investment goods.'],
['Trade','E[i] exports and M[i] imports: external adjustment after relative prices, tariffs and the exchange rate move.'],
['Composite supply','Q[i] Armington composite; D[i] domestic sales'],
Expand Down Expand Up @@ -260,7 +260,7 @@
['X[i,j]','Intermediate input','Amount of good i used as an input by sector j.','This is the model’s input-output propagation channel.'],
['Z[i]','Gross sector output','Total output produced by sector i.','It combines value added and required intermediate inputs.'],
['Xp[i]','Household consumption','Household purchases of composite good i.','Final private consumption.'],
['Xg[i]','Government consumption','Government purchases of composite good i.','A benchmark/endogenous model quantity, not currently a free-form spending shock in StdModelDef.'],
['Xg[i]','Government consumption','Government purchases of composite good i.','A benchmark/endogenous model quantity, not currently a free-form spending shock in the Standard CGE model.'],
['Xv[i]','Investment demand','Purchases of good i for investment.','Total saving is translated into demand for investment goods using calibrated shares.']
]],
['Trade quantities',[
Expand All @@ -287,16 +287,16 @@
['Taxes and tax revenue',[
['tauz[i]','Production-tax rate','Ad valorem tax rate on sector i’s gross output.','This is the policy rate you can shock.'],
['taum[i]','Import-tariff rate','Ad valorem tariff rate on imports of good i.','This is the policy rate you can shock.'],
['taud','Direct-tax rate','Flat direct tax rate on household factor income.','Calibrated from the SAM and not exposed as a mutable scenario policy control in current StdModelDef.'],
['taud','Direct-tax rate','Flat direct tax rate on household factor income.','Calibrated from the SAM and not exposed as a mutable scenario policy control in the current Standard CGE model.'],
['Tz[i]','Production-tax revenue','Revenue raised from tauz[i].','Endogenous fiscal result after output changes.'],
['Tm[i]','Tariff revenue','Revenue raised from taum[i].','Endogenous fiscal result after imports change.'],
['Td','Direct-tax revenue','Revenue from the calibrated direct tax.','Changes endogenously with household factor income.']
]],
['World conditions and technology',[
['pWe[i]','World export price','Exogenous foreign-currency export price, normalized to 1 in the benchmark.','Small-country assumption: the country takes this price as given.'],
['pWm[i]','World import price','Exogenous foreign-currency import price, normalized to 1 in the benchmark.','A natural place to impose an external commodity-price shock.'],
['sigma[i]','Armington elasticity','Ease of substitution between imports and domestic goods.','Currently fixed at 2 in StdModelDef.'],
['psi[i]','CET elasticity','Ease of transforming output between exports and domestic sales.','Currently fixed at 2 in StdModelDef.'],
['sigma[i]','Armington elasticity','Ease of substitution between imports and domestic goods.','Currently fixed at 2 in the Standard CGE model.'],
['psi[i]','CET elasticity','Ease of transforming output between exports and domestic sales.','Currently fixed at 2 in the Standard CGE model.'],
['ax[i,j]','Intermediate input coefficient','Units of input good i required per unit of output in sector j.','Calibrated Leontief input-output coefficient.'],
['ay[i]','Value-added coefficient','Units of value added required per unit of gross output in sector i.','Calibrated production requirement.']
]]
Expand Down
Loading
Loading