Metadata-Version: 2.4
Name: freecesm
Version: 0.1.0
Summary: FreeCESM: an OOP prototype of the CESM/CAM call graph
Author-email: Feng Zhu <fengzhu@ucar.edu>
License-Expression: BSD-3-Clause
Classifier: Natural Language :: English
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.13
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: numpy
Provides-Extra: plot
Requires-Dist: matplotlib; extra == "plot"
Provides-Extra: test
Requires-Dist: pytest; extra == "test"
Dynamic: license-file

# FreeCESM

A small, readable Python **OOP prototype** of the CESM/CAM call graph — not a
numerical model, but a structural sketch: every class is named after its
Fortran component, every method after what the process *does*, with the
Fortran call-site name kept in the docstring/trace for cross-reference.

It mirrors the **PI-atm** end-to-end control-flow graph published at
[coverage.vegavoid.com](https://coverage.vegavoid.com/) — a coverage-instrumented
trace of the real CESM/CAM Fortran source for a pre-industrial, atmosphere-active
case.

## Install

```bash
pip install -e .
```

## Quick start

```python
from freecesm import Driver

driver = Driver(case="PI-atm", nsteps=10)
trace = driver.execute()

print(f"{len(trace)} call-graph nodes executed")
print(driver.cam.state.summary())
```

## Architecture

- **`Driver`** — mirrors `cesm_driver`: `pre_init` → `init` → `run` (the
  `cesm_run` coupling loop, with restart/history gates) → `final`.
- **`Coupler`** — dispatches `component_run` on each active component in
  call-graph order (ice → lnd → rof → ocn → atm) and stands in for the
  `component_exch` flux exchange.
- **`FreeCAM`** — the only component with real internal structure. Its entire
  per-step running order — physics, the coupling exchange, dynamics, output —
  lives as one explicit, editable list: `FreeCAM.workflow`.
- **`Parameterization`** — one physics process per subclass (`DryAdjustment`,
  `DeepConvection`, `Radiation`, `Chemistry`, ...), each just implementing a
  `tendency(state)`.
- **`Dynamics`** — the spectral-element dycore, collapsed to one `run()`.
- **`CaseConfig` / `CASES`** — CESM's real "compset" idea, in miniature: a
  dataclass naming a factory for *every* component slot (atmosphere included —
  nothing is special-cased), so a run can be an atmosphere-only PI case, a
  fully coupled case, or something built on the fly, e.g. an ocean-only case
  with a data atmosphere (`FreeDATM`) instead of `FreeCAM`.

Other components — `FreeCLM`, `FreeCICE` (prescribed or active), `FreeDOCN`,
`FreePOP`, `FreeRTM`, `FreeDATM`, `StubComponent` (GLC/WAV) — are lightweight
stand-ins, each with a one-line `run()`.

### Extending the workflow

Adding a new physical process is two steps: subclass `Parameterization` with a
`tendency`, then edit `FreeCAM.workflow` — it's a plain Python list.

```python
from freecesm import Parameterization

class VolcanicAerosol(Parameterization):
    name = "volcanic_aerosol (custom)"

    def tendency(self, state):
        state.T -= 0.15

driver.cam.workflow.insert(3, VolcanicAerosol())
```

### Building a custom case

```python
from freecesm import CaseConfig, Driver, FreeCICE, FreeDOCN

my_case = CaseConfig(
    name="PI-dataocean",
    description="Coupled atmosphere/land/ice, but a data ocean instead of POP.",
    forcing="1850, fixed preindustrial",
    make_ice=lambda: FreeCICE(mode="active"),
    make_ocn=FreeDOCN,
)
driver = Driver(case=my_case, nsteps=5)
```

## Tests

```bash
pip install -e ".[test]"
pytest
```
