RadCAD
A Python framework for designing, testing, and validating complex systems through modelling and simulation.
Install / Use
/learn @CADLabs/RadCADREADME
radCAD

A Python framework for modelling and simulating dynamical systems. A dynamical system is a system whose state evolves over time according to a fixed set of rules, often expressed as mathematical equations, that depend on its current state and, optionally, external inputs.
Models are structured using state transitions for encoding differential equations, or any other logic, as an example. Simulations are configured using methods such as parameter sweeps, Monte Carlo runs, and A/B testing. See cadCAD.education for the most comprehensive cadCAD beginner course.
Goals:
- simple API for ease of use
- performance driven (more speed = more experiments, larger parameter sweeps, in less time)
- cadCAD compatible (standard functions, data structures, and simulation results)
- maintainable, testable codebase
Have a question not answered in this README or the cadCAD Edu courses? Post a comment in the issues or check out the cadCAD Discord community.
Table of Contents
- Open-source Models Using radCAD
- Example Models
- Features
- Installation
- Documentation
- Development
- Testing
- Jupyter Notebooks
- Benchmarking
- Acknowledgements
Open-source Models Using radCAD
- Ethereum Economic Model by CADLabs: A modular dynamical-systems model of Ethereum's validator economics
- Beacon Runner by Ethereum RIG: An agent-based model of Ethereum's Proof-of-Stake consensus layer
- GEB Controller Simulations by BlockScience: A Proportional-Integral-Derivative (PID) controller based upon a reference document approach for the Maker DAI market that was never implemented
- Fei Protocol Model by CADLabs: A modular dynamical-systems model of Fei Protocol
Example Models
Iterable Models
Using Models as live in-the-loop digital twins, creating your own model pipelines, and streaming simulation results to update a visualization. That's what an iterable Model class enables.

Game of Life
Live radCAD demo model on Streamlit
A simple game where at each timestep, the following transitions occur:
- Any live cell with fewer than two live neighbours dies, as if by underpopulation.
- Any live cell with two or three live neighbours lives on to the next generation.
- Any live cell with more than three live neighbours dies, as if by overpopulation.
- Any dead cell with exactly three live neighbours becomes a live cell, as if by reproduction.
See examples/game_of_life/game-of-life.ipynb

Predator-Prey
A simple model that applies the two Lotka-Volterra differential equations, frequently used to describe the dynamics of biological systems in which two species interact:
Original models thanks to Danilo @danlessa!
- System dynamics model: examples/predator_prey_sd/predator-prey-sd.ipynb
- Agent based model: examples/predator_prey_abm/predator-prey-abm.ipynb
Features
- [x] Parameter sweeps
params = {
'a': [1, 2, 3],
'b': [1, 2],
'c': [1]
}
# Creates a parameter sweep of:
# [{'a': 1, 'b': 1, 'c': 1}, {'a': 2, 'b': 2, 'c': 1}, {'a': 3, 'b': 2, 'c': 1}]
- [x] Monte Carlo runs
RUNS = 100 # Set to the number of Monte Carlo Runs
Simulation(model=model, timesteps=TIMESTEPS, runs=RUNS)
- [x] A/B tests
model_a = Model(initial_state=states_a, state_update_blocks=state_update_blocks_a, params=params_a)
model_b = Model(initial_state=states_b, state_update_blocks=state_update_blocks_b, params=params_b)
simulation_1 = Simulation(model=model_a, timesteps=TIMESTEPS, runs=RUNS)
simulation_2 = Simulation(model=model_b, timesteps=TIMESTEPS, runs=RUNS)
# Simulate any number of models in parallel
experiment = Experiment([simulation_1, simulation_2])
result = experiment.run()
- [x] cadCAD compatibility and familiar data structure
a b simulation subset run substep timestep
0 1.000000 2.0 0 0 1 0 0
1 0.540302 2.0 0 0 1 1 1
2 0.540302 7.0 0 0 1 2 1
3 0.463338 7.0 0 0 1 1 2
4 0.463338 12.0 0 0 1 2 2
... ... ... ... ... ... ... ...
799999 0.003162 999982.0 1 1 1 2 99998
800000 0.003162 999982.0 1 1 1 1 99999
800001 0.003162 999992.0 1 1 1 2 99999
800002 0.003162 999992.0 1 1 1 1 100000
800003 0.003162 1000002.0 1 1 1 2 100000
- [x] Tested against Python 3.8 to 3.12 using Ubuntu, MacOS, and Windows
Advanced Features
- [x] Disable
deepcopyoption for improved performance (at cost of mutability) - [x] Robust exception handling with partial results, and tracebacks
- [x] Parallel processing with multiple backend options:
multiprocessing,pathos,ray - [x] Distributed computing and remote execution in a cluster (AWS, GCP, Kubernetes, ...) using Ray - Fast and Simple Distributed Computing
- [x] Hooks to easily extend the functionality - e.g. save results to HDF5 file format after completion
- [x] Model classes are iterable, so you can iterate over them step-by-step from one state to the next (useful for gradient descent, live digital twins)
- [x] Parameters can be configured using nested dataclasses! This enables typing and dot notation for accessing parameters, and the creation of parameter namespaces.
Installation
pip install radcad
Documentation
radCAD provides the following classes:
- A system is represented in some form as a
Model - A
Modelcan be simulated using aSimulation - An
Experimentconsists of one or moreSimulations - An
Experimentor aSimulationis run by theEngine
So, the hierarchy is as follows Model > Simulation > Experiment > Engine.
from radcad import Model, Simulation, Experiment
model = Model(initial_state=initial_state, state_update_blocks=state_update_blocks, params=params)
simulation = Simulation(model=model, timesteps=100_000, runs=1)
result = simulation.run()
# Or, multiple simulations:
# experiment = Experiment([simulation_1, simulation_2, simulation_3])
# result = experiment.run()
df = pd.DataFrame(result)
Model Parameters
A unique feature of radCAD is being able to use nested dataclasses to configure the model's parameters. This enables typing and dot notation for accessing parameters, and the creation of parameter namespaces, demonstrated below.
from dataclasses import dataclass
from radcad.utils import default
from radcad.types import StateVariables, PolicySignal
...
@dataclass
class LiquidityPoolParameters:
initial_liquidity: int = 100_000
@dataclass
class ProtocolParameters:
liquidity_pool: LiquidityPoolParameters = default(LiquidityPoolParameters())
...
@dataclass
class Parameters:
protocol: ProtocolParameters = default(ProtocolParameters())
agents: AgentParameters = default(AgentParameters())
...
models.params = Parameters()
...
def update_liquidity(
params: Parameters,
substep: int,
state_history: List[List[StateVariables]],
previous_state: StateVariables,
policy_input: PolicySignal
) -> Tuple[str, int]:
if not previous_state["liquidity"]:
updated_liquidity = params.protocol.liquidity_pool.initial_liquidity
else:
updated_liquidity = ...
return "liquidity", updated_liquidity
cadCAD Compatibility
Migrating from cadCAD to radCAD
cadCAD
# cadCAD configuration modules
from cadCAD.configuration.utils import config_sim
from cadCAD.configuration import Experiment
# cadCAD simulation engine modules
from cadCAD.engine import ExecutionMode, ExecutionContext
from cadCAD.engine import Executor
# cadCAD global simulation configuration list
from cadCAD import configs
# Clears any prior configs
del configs[:]
sim_config = config_sim({
'N': 1, # Number of Monte Carlo Runs
'T': range(100), # Number of timesteps
'M': system_params # System Parameters
})
experiment.append_configs(
# Model initial
Related Skills
node-connect
352.0kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
claude-opus-4-5-migration
111.1kMigrate prompts and code from Claude Sonnet 4.0, Sonnet 4.5, or Opus 4.1 to Opus 4.5
frontend-design
111.1kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
model-usage
352.0kUse CodexBar CLI local cost usage to summarize per-model usage for Codex or Claude, including the current (most recent) model or a full model breakdown. Trigger when asked for model-level usage/cost data from codexbar, or when you need a scriptable per-model summary from codexbar cost JSON.
