DDSL YAML schema (v0.1)¶
This page is a shape specification for the YAML objects used to author DDSL v0.1 models in this repo. It is intentionally “schema-like” (types, required keys, invariants), but is not yet a machine-validated JSON Schema.
The guiding architecture is layered (see the dolo-plus syntax guide and Stages/Calibration foundations):
- Stage: atomic ADC module (perches: arrival/decision/continuation) representing a Bellman equation operator for a generalized Markov decision process.
- Period: ordered composition of stages + optional connectors (intra-period renames).
- Nest (lifecycle): ordered composition of periods + twisters (inter-period adapters).
Where applicable, this matches the v0.1 syntax/semantic rules in:
- Spec 0.1h — Period and nest instantiation
- Spec 1.2 — Stage structure and timing
- Spec 0.4 — Periods and models
0. Isomorphic mappings¶
The DDSL architecture maintains two fully isomorphic mappings between three layers. For the core ideas motivating these mappings — information sets within stages, forward–backward duality, and the graph/category view of stage composition — see the project overview.
YAML Syntax ──Υ──▶ Mathematical Model ──ρ──▶ Executatable Representation
(dolo-plus) (Bellman operators) (Python/Julia)
Υ: YAML syntax → Mathematical model¶
The stage YAML (symbols + equations) defines the mathematical model. Parameters and equations are part of the economic problem; settings and methodization are not.
| YAML (dolo-plus) | Mathematical object |
|---|---|
symbols.spaces with @def R+ |
Measurable space \((X, \Sigma)\) |
symbols.spaces with @def linspace(...) |
Finite discrete space \(\{x_1, \ldots, x_n\}\) |
symbols.prestate |
Arrival state space \(X_{\prec}\) |
symbols.states |
Decision state space \(X\) |
symbols.poststates |
Continuation state space \(X_{\succ}\) |
symbols.controls |
Control set \(C\) |
symbols.exogenous with @dist |
Probability kernel \(P(\cdot \mid \cdot)\) |
symbols.values |
Value function \(V : X \to \mathbb{R}\) |
symbols.values_marginal |
Marginal value \(\partial_x V : X \to \mathbb{R}_+\) (one per state variable via d_{x}V) |
symbols.parameters |
Problem constants \(\theta \in \Theta\) |
arvl_to_dcsn_transition |
Transition kernel \(\mathrm{g}_{\prec\sim} : X_{\prec} \times W \to X\) |
dcsn_to_cntn_transition |
Transition kernel \(\mathrm{g}_{\sim\succ} : X \times C \to X_{\succ}\) |
cntn_to_dcsn_mover |
Backward operator \(\mathbb{B} : \mathcal{V}(X_{\succ}) \to \mathcal{V}(X)\) |
dcsn_to_arvl_mover |
Backward operator \(\mathbb{I} : \mathcal{V}(X) \to \mathcal{V}(X_{\prec})\) |
| Stage (full YAML) | ADC Bellman operator \(\mathbb{T} = \mathbb{I} \circ \mathbb{B}\) |
| Period (stages + connectors) | Composed operator \(\mathcal{T}_{\mathrm{per}} = \mathcal{T}_{s_1} \circ \varphi \circ \mathcal{T}_{s_2}\) |
Connector rename: |
Field isomorphism \(\varphi : X_{\succ}^{(s_1)} \xrightarrow{\sim} X_{\prec}^{(s_2)}\) |
| Nest (periods + twisters) | Lifecycle operator \(\mathcal{T}_{\mathrm{nest}} = \mathcal{T}_{p_1} \circ \psi_1 \circ \mathcal{T}_{p_2} \circ \cdots\) |
Twister rename: |
Field isomorphism \(\psi : X_{\succ}^{(p_i)} \xrightarrow{\sim} X_{\prec}^{(p_{i+1})}\) |
Note:
symbols.settingsand methodization YAML are not part of Υ — they carry no mathematical content. Settings are numerical configuration; methodization selects solver schemes.
ρ: Mathematical model → Executable code¶
The ρ mapping translates mathematical objects into computational representations. This is where methodization and settings enter: they determine how each mathematical object is discretized and solved.
| Mathematical object | Code object | Determined by |
|---|---|---|
| Measurable space \((X, \Sigma)\) | Grid object (Cartesian, adaptive) | Settings (n_a, a_min, a_max) |
| Finite discrete space \(\{x_1, \ldots, x_n\}\) | Array / enumeration | Parameters (n_H, H_min, H_max) |
| Value function \(V\) | Interpolated array V[grid] |
Grid specification |
| Marginal value \(\partial V\) | Interpolated array dV[grid] |
Grid specification |
| Probability kernel \(P\) | Quadrature nodes + weights | Methodization (!gauss-hermite, etc.) |
| Transition kernel \(g\) | Callable function | Direct translation |
| Backward operator \(\mathcal{B}\) | Operator class with __call__ |
Methodization (!egm, !vfi, etc.) |
| Forward operator \(\mathcal{F}\) | Simulation function | Methodization (!monte-carlo, etc.) |
| Composed operator \(\mathcal{T}_{\mathrm{per}}\) | Sequential pipeline | Period structure |
| Field isomorphism \(\varphi\) | Array reindex / rename map | Connector/twister rename: |
Design principle: The separation between Υ and ρ enforces a clean boundary. The mathematical model (stage symbols + equations + parameters) is solver-agnostic: the same model can be solved by VFI, EGM, or any other scheme without changing the stage YAML. Methodization and settings live entirely in ρ.
Discrete vs. continuous grids: When a space is defined by a constructor (e.g.
@def linspace(H_min, H_max, n_H)), the constructor arguments are parameters — they define the mathematical model and belong to Υ. When a grid approximates a continuous space, the grid size is a setting — it is numerical configuration and belongs to ρ.Equality only in the limit: The numerical object under ρ is an approximation of the mathematical object under Υ. Under appropriate assumptions: \(\lim_{n \to \infty} \rho(\mathcal{M}(\mathbb{S}), \mathbb{C}_n) = \Upsilon(\mathbb{S}, \mathbb{C}_{\text{param}})\). (See Representation Maps §6.5.)
1. Common atoms (used throughout)¶
1.1 Symbols¶
Identifiers may be ASCII or Unicode (e.g. β, μ_θ, n_m). In DDSL, the same syntax of “a name” is used for multiple kinds of objects, and it’s important not to conflate them.
- Field variables (economic fields): names declared under
symbols.prestate,symbols.states,symbols.poststates,symbols.controls,symbols.exogenous(and the reserved value slots insymbols.values/symbols.values_marginal). These denote random-variable / measurable-function objects that represent economic quantities defined on an information set (filtration). Fields are fixed mathematical objects in \(L^2(\Omega)\)—not placeholders—and composition is defined through field isomorphism, not string equality. - Parameters (problem constants): names declared under
symbols.parameters. These are deterministic constants of the economic problem (not interface fields). They do not participate in stage/period/nest wiring. - Settings (numerical configuration): names declared under
symbols.settings. These are configuration symbols (grid sizes, bounds, tolerances, simulation counts), not interface fields. They are consumed by methodization and builder/orchestration code. - Spaces (domain names): names declared under
symbols.spacesvia@def. These are type-level objects used only for typing. When a space is defined by a constructor (e.g.linspace(...)), its arguments are parameters of the mathematical model.
1.2 The _{.} operator convention¶
When _{...} (underscore followed by braces) appears after an operator name, it specifies with respect to what the operator acts. This is the surface-syntax analogue of Mathematica's positional Derivative[1,0] — but uses named variables instead of slot indices.
| Pattern | Meaning | Mathematical |
|---|---|---|
d_{a}V |
Partial derivative of \(V\) wrt \(a\) | \(\partial_a \mathrm{v}\) |
E_{θ}(V) |
Expectation wrt \(\theta\) | \(\mathbb{E}_\theta[\mathrm{v}]\) |
max_{c}{...} |
Maximise over \(c\) | \(\sup_c\{\cdots\}\) |
Disambiguation from plain underscore: _{...} (with braces) is always "with respect to." Plain underscore without braces is always a name separator (e.g. m_d = "the variable named m-d", V_own = "the value function named V-own"). The two are structurally distinct and cannot be confused.
The variable inside _{...} must be a declared symbol (state, control, or exogenous). For multi-state stages requiring separate marginals, declare one values_marginal entry per (variable, perch) pair: d_{a}V[<], d_{h}V[<], etc.
Open design question: d_{a}V — declared symbol or canonical operator?
Is d_{a}V[<] a declared symbol (user writes it in values_marginal:, naming convention only) or a canonical operator application (the system computes \(\partial_a V_{<}\) automatically from V[<])? Under the symbol view, d_a_V could also name the same object (plain underscore). Under the operator view, d_{a} is a first-class operator and the marginal need not be declared. Current v0.1: declared symbol (interpretation 1). See symbols-conventions.md for details. #ambiguity #operator-vs-symbol #todo
1.3 Domain/typing strings and space constructors¶
Declarations are typing judgments, encoded as strings (see Symbols & declarations):
"@def <SpaceExpr>"declares a space (domain object), e.g.Xm: "@def R++".- A space may also be defined by a constructor call, e.g.
XH: "@def linspace(H_min, H_max, n_H)"— here the constructor arguments (H_min,H_max,n_H) must be declared as parameters (not settings), since they define the mathematical model. "@in <SpaceExpr-or-SpaceName>"declares the type of a symbol; the symbol's kind is determined by where it is declared:m: "@in Xm"undersymbols.prestatedeclares a field variableβ: "@in (0,1)"undersymbols.parametersdeclares a parametern_m: "@in Z+"undersymbols.settingsdeclares a setting
Important: stage files declare types of parameters/settings; numeric values live in calibration/config inputs, not in the stage file.
1.4 Perches (information timing)¶
Perch tags are the surface syntax for agent problems at information timing / information sets: a symbol tagged [_p] asserts measurability w.r.t. the perch filtration (what is known at that perch). See Perch Annotations and Spec 1.2: Stage structure and timing. A canonical Bellman operator has three perches:
[<]arrival (canonical slot \(-1\)); perch associated with information set before any information is revealed and before any actions. Aliases:[_arvl],[<-].- unmarked decision (canonical slot \(0\)); perch associated with information set observed by the agent and before any actions. Aliases:
[_dcsn],[-]. [>]continuation (canonical slot \(+1\)); perch associated with information set after all information revealed and agent decisions made. Aliases:[_cntn],[->].
2. Stage object schema (adc-stage, v0.1)¶
A stage is a closed declaration environment: every symbol referenced in equations: must be declared under symbols: (or be a reserved perch slot like V[>]).
Open question: What happens to undeclared symbols — are they treated as string placeholders? How are new functions declared? #ambiguity #variable-declarations
2.1 Transition kernels¶
State variables are functions (fields) measurable w.r.t. the information set at a given perch. The transition from one perch to the next is given by measurable functions that enforce this information structure — the transition kernels arvl_to_dcsn_transition and dcsn_to_cntn_transition are measurable maps between perches. They must respect perch measurability: objects at the decision perch can depend only on information available at that perch, etc. (See Perch Annotations and Spec 1.2.)
2.2 Backward operators¶
The mover blocks (cntn_to_dcsn_mover, dcsn_to_arvl_mover) are backward Bellman operators: they transform continuation objects \(V[>]\) (and optionally \(dV[>]\)) into earlier-perch value objects via expectation and/or optimization (e.g. E_{θ}(·), max_{c}(·), argmax_{c}(·)). These operator instances are the primary targets of methodization.
Identity transitions (often elidable)
Many stages contain structural identity maps:
- Within-stage identities: e.g. an arrival→decision identity state (
k_d = k) or an identity arrival mover (V[<] = V). These are usually written explicitly for clarity, but they are not semantically informative beyond asserting identity. - Within-period identities: when stage interfaces already match, the connector is identity and should be omitted (no
connectors:entry). - Between-period identities: an identity twister can be written as
{}/ emptyrename.
See §1.2.4 Identity connectors/transitions for the omission convention.
This is the “atomic block” used by the period/nest layer.
# stage.yaml (DDSL-SYM, adc-stage dialect)
name: <string> # required (library-unique identifier)
symbols: # required (closed environment)
spaces: # required
<SpaceName>: "@def <SpaceExpr>" # e.g. R+, R++, Z+, linspace(...)
# ──────────────────────────────────────────────────────────────────────────
# Interface groups (these determine composition)
# ──────────────────────────────────────────────────────────────────────────
prestate: # required (arrival interface)
<var>: "@in <SpaceExpr-or-SpaceName>"
states: # required (decision interface; may be empty)
<var>: "@in <SpaceExpr-or-SpaceName>"
poststates: # required (continuation interface)
<var>: "@in <SpaceExpr-or-SpaceName>"
controls: # required (may be empty)
<control>: "@in <SpaceExpr-or-SpaceName>"
exogenous: # optional (shocks)
<shock>:
- "@in <SpaceExpr-or-SpaceName>"
- "@dist <DistExpr>" # e.g. "@dist LogNormal(μ_θ, σ_θ)"
# ──────────────────────────────────────────────────────────────────────────
# Underlying value slots (present in every stage)
# ──────────────────────────────────────────────────────────────────────────
values: # required
V[<]: "@in R"
V: "@in R"
V[>]: "@in R"
values_marginal: # optional (needed for envelope/EGM)
# Single-state stages (wrt variable omitted — unambiguous):
dV[<]: "@in R+"
dV: "@in R+"
dV[>]: "@in R+"
# Multi-state stages (one entry per state variable, using _{.} convention):
# d_{a}V[<]: "@in R+" # ∂ₐv at arrival
# d_{h}V[<]: "@in R+" # ∂ₕv at arrival
# d_{a}V: "@in R+" # ∂ₐv at decision
# d_{h}V: "@in R+" # ∂ₕv at decision
# d_{a}V[>]: "@in R+" # ∂ₐv at continuation
# Branching stages: continuation marginals use branch-specific
# poststate variable names. No branch sub-keys needed — the
# poststate names are already branch-namespaced.
#
# Example (housing keep/adjust):
# d_{w_keep}V[>]: "@in R" # ∂v/∂w_keep (keep branch)
# d_{h_keep}V[>]: "@in R" # ∂v/∂h_keep (keep branch)
# d_{w_adj}V[>]: "@in R" # ∂v/∂w_adj (adjust branch)
#
# This is syntactic sugar. Formally, each branch has its own
# continuation value function (v_{succ,keep}, v_{succ,adjust}).
# The desugaring rule:
#
# d_{x}V[>] → ∂_x v_{succ,j} where j is the branch
# in whose poststates x is declared.
#
# The branch is inferred from the wrt-variable's declaration
# scope — possible because poststate names are disjoint across
# branches (no overloading rule).
functions: # optional (user-defined functions)
# Arrow syntax: 'args -> body'
# Arguments before -> are formal parameters; body after -> is
# the expression. The compiler substitutes at the call site.
#
# Single argument:
# u_c: 'c -> alpha * c^(1 - gamma_c) / (1 - gamma_c)'
# y: 'z -> w_0 + w_1 * z'
#
# Multiple arguments:
# f: 'x, y -> x^2 + y^2'
#
# Usage in equations:
# V = max_{c}(u_c(c) + u_h(h) + beta * V[>])
#
# All non-argument symbols in the body (alpha, gamma_c, etc.)
# must be declared parameters.
parameters: # optional (type declarations only)
<param>: "@in <SpaceExpr>"
settings: # optional (type declarations only)
<setting>: "@in <SpaceExpr>"
equations: # required
# Stage kernels (pure functions)
arvl_to_dcsn_transition: | # required in v0.1 ADC
<equations...>
dcsn_to_cntn_transition: |
<equations...>
# Backward movers (operators on V, dV; may have sub-equations)
cntn_to_dcsn_mover: # required
<SubEqName>: |
<equations...>
# common sub-equation names in v0.1:
# - Bellman
# - InvEuler
# - MarginalBellman / ShadowBellman
# - cntn_to_dcsn_transition (reverse-state for EGM, etc.)
dcsn_to_arvl_mover: # required
<SubEqName>: |
<equations...>
dolo_plus: # optional (metadata for tooling)
dialect: adc-stage
version: "0.1"
equation_symbols: # canonical math names for translator
arvl_to_dcsn_transition: g_ad
dcsn_to_cntn_transition: g_de
cntn_to_dcsn_mover: T_ed
dcsn_to_arvl_mover: T_da
slot_map: # prestate → poststate linkage
prestate: <var>
poststate: <var>
validation: # perch index aliases
index_aliases:
_arvl: -1
_dcsn: 0
_cntn: 1
Invariants (stage)¶
- No free names: every symbol referenced in
equations:is declared insymbols:(or is a reserved perch slot likeV[>]). - Composition interface:
symbols.prestateandsymbols.poststatesdetermine the stage's arrival/continuation interfaces \(X_{\prec}, X_{\succ}\). - Separation of concerns: method choices and numeric values are not specified here (see methodization + calibration below).
3. Calibration file schema (values)¶
Calibration binds numbers to declared parameters/settings (see Spec 0.1c).
# calibration.yaml
parameters:
<param>: <number | list | array-like>
settings:
<setting>: <number | list | array-like>
Constraints:
- Keys must be declared in
stage.yamlundersymbols.parameters/symbols.settings. - This file contains values only (no
@in/@deftyping strings).
4. Methodization file schema (methods)¶
Methodization attaches schemes/methods to operator instances and mover blocks (see Spec 0.1d).
This is the shape used by the *_stage_methods.yml files in the repo.
# *_methods.yml
stage: <stage-name> # OR: library: <library-name>
methods:
- on: <target-id> # e.g. cntn_to_dcsn_mover, cntn_to_dcsn_mover.InvEuler, E_θ
schemes:
- scheme: <scheme-id> # e.g. bellman_backward, expectation, interpolation
method: !<method-tag> # e.g. !egm, !gauss-hermite, !Cartesian
description: <string?> # optional
settings: # optional: names of settings (resolved by calibration)
<setting-key>: <setting-symbol>
Notes:
- Targets may refer to operator instances (
E_θ) or mover blocks (cntn_to_dcsn_mover) or sub-equations (cntn_to_dcsn_mover.InvEuler). - Settings values are not written here—only symbolic references to settings declared in the stage file and valued in calibration.
5. Period template schema (composition of stages)¶
A period is an ordered list of stage occurrences plus optional rename connectors for mismatched interfaces (see Spec 0.4 — Periods and models).
Key principle: A period defines composition through interface-compatibility. When stage interfaces match (same field names), composition is implicit. When names differ, an explicit connector provides the rename.
5.1 Simple form (library references)¶
When stages are defined in a shared library, periods can reference them by name:
# period.yaml (simple form)
name: <string>
stages: # required (ordered list of stage names)
- <stage-name-1>
- <stage-name-2>
connectors: # optional; omit if identity
- from: <stage-name-1>
to: <stage-name-2>
rename: {<out-name>: <in-name>}
5.2 Embedded form (inline stage objects)¶
Alternatively, stages can be embedded directly as objects:
# period.yaml (embedded form)
name: <string>
description: <string?> # optional
stages: # required (ordered)
- <occurrence-name-1>: <stage-object-or-include>
- <occurrence-name-2>: <stage-object-or-include>
# Each entry is a stage object (inline, anchor, or !include).
connectors: # optional; omit if identity
- from: <occurrence-name-1>
to: <occurrence-name-2>
rename: {<out-name>: <in-name>} # wiring-only adapter (typically a bijective rename)
Constraints¶
- If adjacent stage interfaces already match, the connector is identity and should be omitted.
- Connectors are intra-period wiring only; inter-period wiring is handled by twisters at the nest layer.
- The period namespace is a set of economic fields (random variables), not merely string labels. Composition is valid when fields match (not just when names coincide).
6. Nest (lifecycle) template schema (composition of periods)¶
A nest is an ordered list of period objects and a position-aligned list of twisters (see Spec 0.1h).
# nest.yaml
name: <string>
description: <string?> # optional
periods: # required (ordered; contain full period objects)
- <period-object>
- <period-object>
- <period-object>
twisters: # optional but typical; length = len(periods)-1
- rename: {<field>: <field>} # connects periods[0] → periods[1] (forward time)
- {} # identity twister may be `{}` (or omit rename)
terminal: # optional (builder-level boundary condition)
# How to provide V[>] / dV[>] at the terminal boundary.
# Commonly provided by the orchestrator, not by syntax.
kind: <string>
# ...
Constraints:
- Time indices do not appear inside stages or periods: “time” lives only in nest position (and the orchestration loop).
- In v0.1h convention,
twisters[i]connectsperiods[i] → periods[i+1]in forward time (alignment by list position).
7. Single-file recursive “blocks” form (matches the sketch)¶
If you prefer a single YAML file that is recursively nested “blocks with links”, the same semantics can be written as syntax sugar.
---
kind: nest # enum: stage | period | nest
name: <string>
description: <string?>
blocks: # periods
- kind: period
name: <string?>
description: <string?>
blocks: # stages
- kind: stage
# (insert the stage schema from §2 here)
links:
connectors: [] # same shape as §5 connectors
links:
twisters: [] # same shape as §6 twisters
calibration: {} # optional global overrides (builder-defined)
options: {} # optional solver/builder hints
---
This form is not a new semantic layer: it is just a convenient way to serialize the canonical stage → period → nest structure in one place.
8. Normalized "lifecycle block" schema (CORE-ish)¶
The canonical v0.1 authoring objects in this repo are stage / period / nest (above). If you want a single recursive block schema shaped like:
states: [...],controls: [...],distributions: [...],parameters: [...]equations: {objective, transitions, definitions}- recursive
blocks:pluslinks: {block, lifecycle, terminal}
you can treat it as a normalized (CORE-ish) representation that projects to the v0.1 shapes as follows:
states: union of dolo-plus symbol groupsprestate,states,poststates(useroleto distinguish).controls: dolo-plussymbols.controls(decision-perch objects).distributions: dolo-plussymbols.exogenous(domain +@dist …).equations.transitions: dolo-plusequations.arvl_to_dcsn_transitionandequations.dcsn_to_cntn_transition.equations.objective: typically a mover sub-equation, e.g.equations.cntn_to_dcsn_mover.Bellman.links.block: period-level connectors (intra-period rename adapters).links.lifecycle: nest-level twisters (inter-period adapters).
Note: this normalized schema is useful for documentation and for builder-layer IRs, but v0.1 parsing/compilation in this repo expects the concrete stage/period/nest shapes in §§2–6.
---
# ─────────────────────────────────────────────────────────────────────────────
# DDSL “Block” (recursive) — normalized authoring schema (documentation/IR)
# ─────────────────────────────────────────────────────────────────────────────
kind: <stage | period | nest> # required
name: <string> # required
description: <string?> # optional
# Declarations (types only; values live in calibration)
states: # stage-level; optional at period/nest
- name: <string>
role: <prestate | state | poststate>
perch: <"<" | (unmarked) | ">"?> # optional if role implies perch
description: <string?>
domain: "@in <SpaceExpr-or-SpaceName>"
controls: # stage-level
- name: <string>
perch: (unmarked)
description: <string?>
domain: "@in <SpaceExpr-or-SpaceName>"
distributions: # stage-level (exogenous)
- name: <string>
description: <string?>
domain: "@in <SpaceExpr-or-SpaceName>"
distribution:
type: <string> # e.g. LogNormal
args: {<param>: <param>, ...} # symbolic parameter names
parameters:
- name: <string>
description: <string?>
domain: "@in <SpaceExpr>"
default: <number?> # optional; treated as a calibration default if used
settings:
- name: <string>
description: <string?>
domain: "@in <SpaceExpr>"
default: <number?>
equations: # stage-level; period/nest usually omit
objective: | # optional; often lives inside mover sub-equations
<equations...>
transitions: # optional (often corresponds to g_ad / g_de)
- name: arvl_to_dcsn_transition
equation: |
<equations...>
- name: dcsn_to_cntn_transition
equation: |
<equations...>
definitions: # optional
- name: <definition-id>
equation: |
<equations...>
movers: # optional (Bellman operators)
- name: cntn_to_dcsn_mover
subequations:
Bellman: |
<equations...>
# InvEuler / MarginalBellman / etc.
# Recursive composition (nest contains periods; period contains stages)
blocks: # optional; recursion permitted
- <Block>
links: # optional; wiring-only (no economics)
block: # intra-period connectors (stage → stage)
- from: <string>
to: <string>
rename: {<out-name>: <in-name>}
lifecycle: # inter-period twisters (period → period)
- rename: {<field>: <field>}
terminal: # terminal boundary condition (builder-defined)
kind: <string>
# ...
# Numeric bindings (values), typically loaded from separate files
calibration:
parameters: {<param>: <value>, ...}
settings: {<setting>: <value>, ...}
options: {} # solver/builder hints (representation-layer)
---