Core Concepts
This page explains the three building blocks that every ClimateCritters workflow uses: CCModel, Forcing, and CCOutput.
CCModel
CCModel is the abstract base class for all signal models. You never instantiate it directly — instead you use one of the concrete subclasses (e.g. Lorenz63, EBM, Stommel).
Every subclass shares the same integration interface:
output = model.integrate(
t_span=(t0, tf), # integration window
y0=[...], # initial conditions (one value per state variable)
method='RK45', # solver: 'RK45', 'euler', 'rk4', 'euler_maruyama'
dt=None, # required for 'euler', 'rk4', 'euler_maruyama'
)Internally, each model implements dydt(t, state) — the right-hand side of the ODE — and optionally populate_diagnostics_from_history() for quantities computed after the full trajectory is available.
Parameter handling
Model parameters (e.g. sigma, C, D) can be:
| Type | Example | Resolved as |
|---|---|---|
| Constant | sigma=10.0 |
Fixed value at every timestep |
Callable (t) |
sigma=lambda t: 10.0 + 0.1*t |
Called with current time |
Callable (t, state) |
sigma=lambda t, x: ... |
Called with time and state vector |
Callable (t, state, model) |
sigma=lambda t, x, m: ... |
Also receives the model instance |
cc.Forcing |
sigma=cc.Forcing(...) |
Evaluated via get_forcing(t) |
The first argument of any callable must be named t or time. See contracts/signal_model_contract.md for the full specification.
Forcing
Forcing wraps any time-dependent input signal and provides a uniform get_forcing(t) interface to the model. It accepts a callable, a data array, a CSV file, or a compiled ForcingSequence:
# Callable
f = cc.Forcing(lambda t: 1360.0 + 5.0 * np.sin(2 * np.pi * t / 11.0))
# Data array
f = cc.Forcing(data=my_array, time=my_time, interpolation='cubic')
# Bundled CSV dataset
f = cc.Forcing.from_csv(dataset='vieira_tsi')Composable scenarios
For signals that change character over time, build a ForcingSequence from Hold, Ramp, and Harmonic segments using +, then call .compile():
scenario = (
cc.forcing.Hold(duration=100, value=1360.0)
+ cc.forcing.Ramp(duration=50, y0=1360.0, yf=1380.0, shape='linear')
+ cc.forcing.Hold(duration=100, value=1380.0)
)
f = scenario.compile() # → Forcing, ready to registerTwo signals can be superposed with +:
combined = cc.Forcing(orbital_func) + cc.Forcing(noise_func)See the Forcing page for the full API.
CCOutput
integrate() always returns a CCOutput object. It holds:
| Attribute | Contents |
|---|---|
output.time |
Solver time axis (1-D array) |
output.state_variables |
Structured NumPy array, one field per state variable |
output.diagnostic_variables |
Dict of derived quantities (e.g. 'energy', 'ice_line_lat') |
output.solution |
Raw solver solution (scipy or custom) |
Accessing variables
x = output.state_variables['x'] # state variable by name
energy = output.diagnostic_variables['energy'] # diagnostic by nameReframing the time axis
To slice off a spin-up period or resample to a regular grid:
t_out = np.linspace(100, 500, 401)
output.reframe_time_axis(t_out)
# output.time and output.state_variables are now on t_outExporting to Pyleoclim
ts = output.to_pyleo(var_names='x') # returns a pyleo.Series
mts = output.to_pyleo(var_names=['x', 'y']) # returns a pyleo.MultipleSeriesOnce in Pyleoclim, the full analysis toolkit is available: ts.spectral(), ts.wavelet(), ts.dashboard(), ts.interp(), etc.
Putting it together
import numpy as np
import climatecritters as cc
from climatecritters.model_critters import EBM
# composable forcing: spin-up then a solar ramp
forcing = cc.Forcing.from_sequence([
cc.forcing.Hold(duration=500, value=1360.0),
cc.forcing.Ramp(duration=200, y0=1360.0, yf=1390.0, shape='linear'),
])
# time-varying albedo
model = EBM(forcing=forcing, albedo=lambda t, x: 0.3 + 0.01 * np.sin(t))
output = model.integrate(t_span=(0, 700), y0=[288.0], method='RK45')
# discard spin-up, export to pyleo
t_analysis = np.linspace(500, 700, 201)
output.reframe_time_axis(t_analysis)
output.to_pyleo(var_names='T').plot()