DDSL Foundations and Towards dolo+¶
DDSL Phase 1.1 — Deck 2/2¶
Convening Meeting — January 2026
What problem is DDSL solving?¶
We want a language for recursive dynamic programs where:
- the economic object (Bellman structure / stage semantics) is specified cleanly
- the numerics (grids, interpolation, quadrature, solver choices) are separate
- we can stay compatible with Dolo while moving toward a rigorous CORE representation (Scope pointer: see Numerics are Separated and Model Representation Boundaries.)
Design principle (top-down)¶
Specify what the actor knows (perches + shock timing).
The Bellman equations follow.
The numerics are a separate concern.
This principle is the reason the DDSL is “stage-first.” (Scope pointer: see Problem Chunks.)
Clarification (from the convening discussion): perches are information sets, not “endpoints of operators”.
Perch indices = filtration / adaptedness¶
In DDSL-SYM, a perch index is a measurability claim:
$z$(unmarked) means$z$is \(\mathcal{F}_{\text{dcsn}}\)-measurable (the policy can condition on it)
So transitions and equations must make this true:
- the RHS can only use information available at that perch (so the mapping exists)
- “shift rules” are exactly measurability/adaptedness checks (what you may write at each perch) (Scope pointer: see Information Structure Restriction.)
Two syntactic layers: SYM vs T core¶
DDSL-SYM — economist-facing, backwards-compatible surface - looks like (extended) Dolo
T core (formerly “DDSL-CORE”) — typed operator-theoretic internal form - explicit operators, domains, perches - not necessarily authored by economists; it is the mapping target that makes SYM precise (Scope pointer: see Model Representation Boundaries.)
Key goal: SYM and CORE should be round-trippable (no semantic loss).
Signs vs Symbols (à la Tillich)¶
Paul Tillich distinguished signs (arbitrary pointers) from symbols (participate in the reality they represent).
Signs: - Meaning assigned by convention - Arbitrary (red light could be blue) - Does not participate in what it signifies
Symbols: - Intrinsic connection to meaning - Irreplaceable (wedding ring ≠ mere metal) - Opens a window to deeper reality
The analogy:
- DDSL-SYM ≈ signs — convenient, economist-friendly notation; implicit math; could be replaced by equivalent syntax
- T core ≈ symbols — participates directly in the functional-analytic structure; typed operators reveal the Bellman reality
Both layers are syntax. But T core is the structure that is closer to the meaning map \(\Upsilon\); SYM merely points to it.
Our contribution is critical: make sure signs point to the right core.
"All things are like signs upon the earth and that is why they have come to be" - Judas Iscariot¶
Three layers of meaning (and two maps)¶
Interpretation: Υ and ρ are morphisms (in a topological / structural sense): they preserve compositional structure. In practice, we want the diagram to commute: compiling via “syntax → math → computation” agrees with compiling “methodized+calibrated syntax → computation.”
Core value-add: making these morphisms explicit, inspectable, and composable—and keeping SYM ↔ T core round-trippable—is the central contribution of DDSL. It turns “folk translations” (math ↔ code) into first-class, checkable objects.
In particular, the “computation” layer begins as a model representation: the parsed, typed in-memory stage object (e.g. dolang).
Syntax has a concrete in-memory representation¶
Key point: this object is still syntax; Υ interprets it, methodization/calibration enrich it, and ρ compiles it.
Υ vs ρ in one sentence each¶
Υ (Upsilon): turns well-typed syntax into the mathematical dynamic program.
ρ (rho): turns a methodized + calibrated stage into an executable representation.
Formal definition (sketch): Backus levels¶
Following Backus, we distinguish three levels:
- Object level: primitive atoms and primitive symbols (numbers,
@in,@def,E,argmax, method constructors, …) - Function level: well-formed function objects built from primitives (what appears in model files)
- Functional level: higher-order operators that map functions to functions (movers, Bellman factorization, expectation operators, …)
This is the “Bellman–Backus–Euler calculus” viewpoint.
Formal definition (sketch): universes¶
Let:
- \(\mathbb{O}\): all denotable objects in syntax
- \(\mathbb{O}_P \subset \mathbb{O}\): primitive DDSL-native objects (atoms + primitive operator symbols)
- \(\mathbb{F} \subset \mathbb{O}\): well-formed function objects (typed DDSL functions)
- \(\mathbb{T} \subset \mathbb{O}\): functional operators (higher-order function objects)
On the semantics side:
- \(\mathbb{DM}\): the “Platonic” DP universe (Bellman operators, push-forwards, integration/optimization operators, …)
- \(\mathbb{F}^{\text{COMP}}\): computational representations (AST-like host-language objects)
Formal definition (sketch): the two maps and the definitional system¶
The definitional system \(\mathbb{D}\) fixes:
1) a typing discipline (what counts as a well-typed element of \(\mathbb{F}\)), and
2) the meaning map:
Separately (implementation level), the computational representation map is:
Key point: \(\rho\) is not part of \(\mathbb{D}\); it is the constructive compilation layer.
Formal definition (sketch): “declared names are function objects”¶
In the Backus spirit, every declared name (outside bound variables) is interpreted as a function object.
Examples:
Xa @def R+:Xais a nullary function object denoting a space.β @in (0,1):βis a nullary function object constrained by a domain; calibration supplies its numeric value.g_av: [xa, w] @mapsto xv @via | ...:g_avis a non-nullary function object with a typed domain/codomain.
Bound variables (xa, w, …) are purely syntactic binders; they disappear under ρ (become host-language parameters).
Symbols start as strings (object level)¶
At the raw YAML level, a symbol name is literally a string:
"β","Xa","w","E_y"
The symbols: block builds a symbol environment (a typed “name table”) by parsing declarations:
Key separation: - name = string token (element of \(\mathbb{O}\)) - declaration = a syntax object (element of \(\mathbb{F}\)) that gives the name a type/role in the model
Υ is a meaning map on well-typed syntax objects (not raw strings)¶
The meaning map is defined on well-typed function objects:
Informally:
- \(\Upsilon(Xa @def R+)\) is “the space \(\mathbb{R}_+\)” (a Platonic object)
- \(\Upsilon(β @in (0,1))\) is a mathematical parameter symbol constrained to \((0,1)\)
- \(\Upsilon(g_{\text{av}})\) is a mathematical function \(X_a \times W \to X_v\)
So: syntax → math by denotation, not by numerical evaluation.
Calibration: assigning concrete values to nullary function objects¶
Calibration is a mapping from symbol names (strings) to numeric atoms:
Conceptually, calibration produces a calibrated syntactic stage where some nullary function objects now have values:
This changes the instance of the model (numbers), not the symbolic structure of the stage.
Methodization: attaching schemes to operator instances (still syntax)¶
Methodization attaches numerical schemes to operator instances in the stage (expectations, argmax/rootfinding, APPROX stacks, draws/push-forwards, …).
Example (expectation operator keyed by RV name):
methods:
- on: E_y
schemes:
- scheme: expectation
method: !gauss-hermite
settings:
n_nodes: n_y_nodes
Methodization does not change \(\Upsilon\); it enriches syntax so that \(\rho\) can compile the stage.
The “stage” as the modular unit¶
A DDSL stage is a modular object containing: - perch structure (arrival/decision/continuation) - symbol declarations (types/domains) - kernels / function bodies (transitions, reward pieces, auxiliary defs)
A stage contains no numeric settings and no solver choices.
Key point: methodized + calibrated stages are still in the syntax layer¶
During the pipeline we produce variants of the same stage object:
- \(S\): the symbolic stage (authored
stage.yaml) - \(\mathcal{M}(S)\): a methodized stage (schemes/methods attached; still syntax)
- \(\mathbb{C}(\mathcal{M}(S))\): a calibrated stage (numbers substituted/bound; still syntax)
Only after applying the representation map \(\rho\) do we leave syntax and get executable objects:
This is important: methodization/calibration enrich the stage, they do not replace it with “math” or “code.”
Kernels vs methods (the clean separation)¶
Kernel = a named function body (Υ-level object).
Examples: transition kernels, reverse-state kernels, auxiliary identities.
Methods attach to operator instances used to execute a stage under ρ: - expectations \(E_y\) - optimization / rootfinding (argmax, solves) - approximation (APPROX: grids + interpolation) - sampling / push-forward operators
Operator registry vs operation registry¶
Two registries play different roles:
-
Operator registry: identity + signatures + how instances are formed
(e.g.,E_{y}(...)→ operator instanceE_y) -
Operation registry: what numerical schemes/methods exist
(quadrature methods, interpolation families, solvers, …)
Practical rule for v0.1:
- you need operator identity (what instance is this?) to bind methods consistently
- a full operation registry is optional (a catalog / validation aid)
Methodization as a functor¶
Conceptually: - stage says what operations occur - methodization says how to compute operator instances and represent outputs
Settings vs calibration (two kinds of numbers)¶
We separate numeric data into:
- calibration (economic parameters): \(\beta, \gamma, \sigma, \ldots\)
- settings (numerical configuration): grid sizes, bounds, tolerances, quadrature nodes, …
This keeps “the math” distinct from “how we approximate it.” (Scope pointer: see Numerics are Separated.)
Methodization config: flat methods: list (simple authoring)¶
Methodization is a flat list:
- one entry per operator instance (e.g.
E_y) - one entry per mover / stage function (e.g.
cntn_to_dcsn_mover) - each entry can carry a scheme stack (e.g. backward algorithm + interpolation/grid)
Expectation operators are naturally keyed by the shock RV:
E_{y}(...) → E_y.
One scheme vs scheme stack¶
Each method entry has a schemes: [...] list.
- Length 1 = “atomic” choice
- Length > 1 = a scheme stack (bundle)
Each scheme entry has scheme + optional method (+ optional settings wiring).
The “scheme stack” is useful when one conceptual operation is really a bundle (e.g., grid + interpolation).
Provisional simplification: grid lives inside interpolation¶
In this (provisional) view, a grid constructor like cartesian is not meaningful on its own:
the grid is meaningful only as part of “how we represent / approximate a function.”
So we attach grid construction as part of an interpolation/approximation scheme on the relevant methodization target (typically the backward mover).
Example: scheme stack for a policy approximation¶
From consumption_savings_iid_egm_doloplus_split/methods.yml:
- on: cntn_to_dcsn_mover
schemes:
- scheme: bellman_backward
method: !egm
- scheme: interpolation
method: !Cartesian
settings:
orders: [n_w]
bounds: [[w_grid_min, w_grid_max]]
domain: [w_domain_min, w_domain_max]
Numeric values live in settings.yaml.
Example: expectation operator is unique per RV¶
If the stage has a single exogenous shock RV y:
- you typically have one expectation operator instance:
E_y - it can be reused wherever
E_{y}(...)appears
If you truly need different numerical methods, you must create distinct instances (distinct RV names or explicit aliases).
File split (what lives where)¶
In the split-file examples we aim for:
stage.yaml: purely symbolic stage (perches, symbols, kernels)methods.yml: methodization (schemes/methods + settings symbol wiring)calibration.yaml: parameter-only valuessettings.yaml: numeric settings valuesinitial_values.yaml: solver warm-starts (optional)
Part III — Worked example: consumption-savings (EGM-style) in split files¶
Folder:
packages/dolo/examples/models/consumption_savings_iid_egm_doloplus_split/
The point of the example is not “new economics,” but “clean separation”:
- stage = symbolic
- methods = numerics (but no numbers)
- calibration/settings = numbers
Symbolic stage: symbols: (no numbers)¶
# stage.yaml (symbolic stage: no numeric values)
symbols:
spaces:
Xb: "@def R+"
Xw: "@def R+"
Xa: "@def R+"
Y: "@def R"
prestate:
b: "@in Xb"
exogenous:
y:
- "@in Y"
- "@dist Normal(μ_y, σ_y)"
states:
w: "@in Xw"
poststates:
a: "@in Xa"
controls:
c: "@in R+"
values:
V: "@in R"
shadow_value:
dV: "@in R+"
parameters:
β: "@in (0,1)"
γ: "@in R+"
ρ: "@in R"
r: "@in R++"
μ_y: "@in R"
σ_y: "@in R+"
settings:
n_w: "@in Z+"
n_y_nodes: "@in Z+"
n_sim: "@in Z+"
sim_seed: "@in Z+"
w_domain_min: "@in R+"
w_domain_max: "@in R+"
w_grid_min: "@in R+"
w_grid_max: "@in R+"
Symbolic stage: equations: + timing + settings references¶
equations:
arvl_to_dcsn_transition: |
w = exp(y) + b[<]*r
dcsn_to_cntn_transition: |
a[>] = w - c
cntn_to_dcsn_transition: |
w = a[>] + c
cntn_to_dcsn_mover:
Bellman: |
V = max_{c}((c^(1-γ))/(1-γ) + β*V[>])
InvEuler: |
c[>] = (β*dV[>])^(-1/γ)
ShadowBellman: |
dV = (c)^(-γ)
dcsn_to_arvl_mover:
Bellman: |
V[<] = E_{y}(V)
ShadowBellman: |
dV[<] = r * E_{y}(dV)
Methods (how to compute / approximate)¶
From methods.yml (illustrative excerpt):
- on: E_y
schemes:
- scheme: expectation
method: !gauss-hermite
settings: {n_nodes: n_y_nodes}
- on: cntn_to_dcsn_mover
schemes:
- scheme: bellman_backward
method: !egm
- scheme: interpolation
method: !Cartesian
settings: {orders: [n_w], bounds: [[w_grid_min, w_grid_max]], domain: [w_domain_min, w_domain_max]}
- on: arvl_to_dcsn_mover
schemes:
- scheme: simulation
method: !monte-carlo
settings: {n_paths: n_sim, seed: sim_seed}
Calibration + settings (numbers)¶
From calibration.yaml (parameters only):
From settings.yaml (numerical configuration):
settings:
n_w: 100
n_y_nodes: 9
n_sim: 10000
sim_seed: 42
w_domain_min: 0.01
w_domain_max: 4.0
w_grid_min: 0.0
w_grid_max: 4.0
Part IV — SYM → CORE mapping: kernels (transition equations)¶
Part IV — SYM → CORE mapping: movers (operator blocks)¶
Reference: DDSL ↔ Dolo ↔ T core mapping¶
Transitions (kernels):
| DDSL-SYM (stage.yaml) | Dolo name | T core (Υ) |
|---|---|---|
arvl_to_dcsn_transition |
half_transition |
\(g_{av}: X_a \times Y \to X_v\) |
dcsn_to_cntn_transition |
auxiliary_direct_egm |
\(g_{ve}: X_v \times A \to X_e\) |
cntn_to_dcsn_transition |
reverse_state |
\(g_{ev}: X_e \times A \to X_v\) |
Reference: Movers ↔ Dolo ↔ T core¶
Movers (operators on functions):
| DDSL-SYM | Dolo name | T core (Υ) |
|---|---|---|
cntn_to_dcsn_mover.Bellman |
(implicit) | \(\mathcal{B}_{dcsn}: V_e \mapsto V_v\) |
cntn_to_dcsn_mover.InvEuler |
direct_response_egm |
\(c^*(x_v) = (\beta \cdot dV_e)^{-1/\gamma}\) |
cntn_to_dcsn_mover.ShadowBellman |
expectation integrand | \(dV_v(x_v) = u'(c^*(x_v))\) |
dcsn_to_arvl_mover.Bellman |
expectation |
\(\mathcal{B}_{arvl}: V_v \mapsto V_a = \mathbb{E}_y[V_v \circ g_{av}]\) |
dcsn_to_arvl_mover.ShadowBellman |
expectation (mr) | \(dV_a = r \cdot \mathbb{E}_y[dV_v]\) |
EGM: from c[>] points to an interpolated policy \(c(w)\)¶
This is the step that is implicit in Dolo (hard-coded in the solver), but becomes explicit in SYM → T core.
Given a continuation (poststate) grid \(\{a_i\}\) and the continuation shadow value \(dV_{\succ}(a)\):
-
Inverse Euler on the continuation grid (SYM:
InvEuler): \(c_i \equiv c_{\succ}(a_i) = \left(\beta\, dV_{\succ}(a_i)\right)^{-1/\gamma}\) -
Endogenous wealth points (SYM:
cntn_to_dcsn_transition/ reverse-state):
\(w_i = a_i + c_i\) -
Approximate policy function on the decision state: build an interpolant \(\hat c(\cdot)\) from the point cloud \(\{(w_i, c_i)\}\), and evaluate it on the decision grid (e.g. the
!Cartesiangrid declared via methodization/settings).
Key point: writing c[>] makes the "policy on the continuation grid" representation explicit, and methodization supplies the interpolation scheme that turns \((w_i,c_i)\) into a decision-rule object.
Reference: Symbol groups ↔ ADC perches¶
| Dolo group | DDSL-SYM group | ADC perch | CORE type |
|---|---|---|---|
| — | prestate |
Arrival | \(x_a \in X_a\) |
states |
states |
Decision | \(x_v \in X_v\) |
poststates |
poststates |
Continuation | \(x_e \in X_e\) |
controls |
controls |
Decision | \(a \in A\) |
exogenous |
exogenous |
Shock | \(y \sim D_y\) |
values |
values |
all perches | \(V: X \to \mathbb{R}\) |
Crowd Q: does DDSL describe forward and backward operators?¶
Yes — both are part of the same stage-level Bellman structure:
- Backward operator: the Bellman/mover updates (e.g.
cntn_to_dcsn_mover) are explicit in SYM. Methodization chooses algorithms like!egmand interpolation schemes. - Forward operator: distribution evolution / simulation is the push-forward induced by kernels + shock distributions.
Methodization chooses how to compute it (e.g.
!monte-carlovs quadrature).
In the split-file example this appears as:
methods.yml:on: cntn_to_dcsn_mover(backward) andon: arvl_to_dcsn_mover(forward simulation).
Crowd Q: why not start with a time sequence of equations?¶
Two distinct composition problems:
- Within a stage: there is a sequence (arrival → decision → continuation), but it is best understood as information progression (“what is known when”), not as “time unfolding” in the syntax.
- Within a period: a period is a namespace + an ordering of stages.
-
if names are unique within the period, many connections are “trivial by naming”
-
Across periods: you often need explicit “twisters” (connectors) to map end-of-period names to next-period names.
For branching models, stage composition becomes a graph/category of stages + connectors.
Compatibility-first (practical constraint)¶
North star:
- build around dolang and legacy Dolo
- avoid breaking existing Dolo models
- keep dolo-plus stages down-compilable to a runnable “horse” representation (when possible)
(Scope pointer: see Phase 1.1 Exemplar Models and Factorizations in Scope.)
This keeps the project “clinical” while expanding expressivity.
Summary¶
- Perches describe information (deck 1).
- Stages are modular semantic units: perches + symbols + kernels.
- Methodization attaches schemes/methods to operator instances and approximations.
- Calibration/settings provide numbers.
- The end goal is a clean SYM ↔ T core pipeline that stays compatible with Dolo.