Expectations, Values, Recipes, and “Where Does \(E\) Live?” in Dolo¶
This note consolidates the key “forward-looking recipe” insights that came up in our Dec 22 discussion and the follow-up debugging/exploration:
- how
recipes.yamldefines what timed objects exist (signatures), - why
values: [V]andexpectations: [m]are different kinds of objects, - why the YAML syntax does not tell you whether you are computing \(f(E[V])\) vs \(E[f(V)]\),
- where the actual integration / expectation operator lives in Dolo algorithms,
- and how this resolves the “variable vs function” ambiguity (e.g.,
V[t+1]).
Links to prior notes:
- 06-time-indices-compilation.md (how [t±1] becomes function arguments)
- 03-function-compilation.md (how equation blocks become compiled functions)
1. The Three Layers: SYNTAX → COMPILATION → ALGORITHM¶
It helps to separate Dolo into three conceptual layers:
- YAML syntax (what you write): timed symbols like
c[t+1],V[t+1],m[t]. - Compilation (what functions get generated):
recipes.yamldetermines signatures and which timed objects are passed as positional arguments. - Algorithms (what is integrated / iterated): solvers decide where to loop over shocks/integration nodes and thus where the expectation operator \(E\) lives.
The main confusion is usually mixing (1) and (3): the YAML notation looks like it contains probability/expectation semantics, but in Dolo that semantics mostly lives in the algorithms.
2. Recipes Define Signatures (Not Expectation Semantics)¶
In packages/dolo/dolo/compiler/recipes.yaml, the dtcc recipe lists which symbol groups exist and what each equation block “expects” as inputs.
For example (abridged):
dtcc:
symbols: [exogenous, states, controls, poststates, rewards, values, expectations, shocks, parameters]
specs:
value:
target: [values, 0, v]
eqs:
- [exogenous, 0, m]
- [states, 0, s]
- [controls, 0, x]
- [values, 0, v]
- [exogenous, 1, M]
- [states, 1, S]
- [controls, 1, X]
- [values, 1, V]
- [parameters,0, p]
expectation:
target: [expectations, 0, z]
eqs:
- [exogenous, 1, M]
- [states, 1, S]
- [controls, 1, X]
- [parameters,0, p]
What this means:
- A compiled value function is allowed to reference both “current” objects (m,s,x,v) and “future” objects (M,S,X,V).
- A compiled expectation function is an “integrand factory”: it can only reference future objects (M,S,X) and parameters.
What it does not mean:
- It does not say “take expectations of V[t+1]”. There is no explicit E[...] operator in the recipe.
- Recipes define data plumbing (what arguments get passed), not the probabilistic operator.
3. Time Indices Are Compile-Time Directives¶
In Dolo syntax, [t], [t+1], [t-1] are best understood as compile-time directives that determine which argument group a symbol belongs to.
Example:
This is compiled into a function that receives both “current” arrays and “future” arrays (see 06-time-indices-compilation.md).
Key point:
A time lead
[t+1]by itself does not mean “take expectation”.
It only means “this equation refers to future-period objects, so the compiled function must accept the future argument blocks”.
4. values vs expectations: Two Different Roles¶
Both values and expectations are “forward-looking” in economics, but Dolo treats them differently because they play different roles in the computational pipeline.
Symbol group intuition¶
| Symbol group | What it is (computationally) | Typical example | How it is produced |
|---|---|---|---|
values |
A function object represented on a grid (via DecisionRule) and updated by a fixed-point / Bellman step |
V |
value iteration / evaluation of Bellman recursion |
expectations |
A moment / intermediate object (also tabulated on grid) used to simplify policy updates | m (“expected marginal value”) |
computed as an expectation of an integrand, then fed into direct_response / EGM-style steps |
Why V[t+1] is not in expectations¶
In a Bellman recursion, V[t+1] is not “a new expectation variable” — it is a recursive reference to the same function \(V(\cdot)\) evaluated at a different argument (next state), and typically integrated over shocks:
So values: [V] signals “there is a fixed-point object to solve for”.
By contrast, expectations: [m] is usually a derived object like:
and then the policy update uses m as an input.
5. Where Does the Expectation Operator \(E\) Actually Live?¶
Dolo does not place an explicit E[...] operator in YAML. Instead, the expectation is implemented by algorithms that:
- discretize the exogenous process into nodes (
inode) and weights (iweight), - evaluate an integrand at each node,
- sum (or average) across nodes.
This is why, in practice, “expectation is taken with respect to the whole block output”: the algorithm wraps the compiled block evaluation inside a quadrature/transition loop.
(A) Time iteration (Euler residuals)¶
In time_iteration, the residuals are accumulated as a weighted sum across integration nodes: the algorithm computes something like \(E[\text{EulerResidual}(t+1)]\) as part of the residual evaluation.
Concrete example (abridged from packages/dolo/dolo/algos/time_iteration.py):
for I_ms in range(dprocess.n_inodes(i_ms)):
M = dprocess.inode(i_ms, I_ms)
prob = dprocess.iweight(i_ms, I_ms)
S = g(m, s, xm, M, parms)
XM = dr.eval_ijs(i_ms, I_ms, S)
rr = f(m, s, xm, M, S, XM, parms) # integrand evaluation
res[i_ms, :, :] += prob * rr # expectation / quadrature
(B) EGM-style models¶
In egm.py, the compiled expectation block function h(...) is treated as the integrand, and the algorithm does:
Then z (an expectations object) is fed into a policy update (direct_response_egm).
Concrete example (abridged from packages/dolo/dolo/algos/egm.py):
for i_M in range(dp.n_inodes(i_m)):
w = dp.iweight(i_m, i_M)
M = dp.inode(i_m, i_M)
S = gt(m, a, M, p)
X = drfut(i_M, S)
z[i_m, :, :] += w * h(M, S, X, p) # h(...) is the integrand
(C) Value iteration (Bellman problems)¶
Value iteration effectively has two places where expectations appear:
- Policy evaluation: compute the value function implied by a fixed policy (integration occurs during value evaluation).
- Policy improvement: when maximizing over
x, the objective includes \(\beta\,E[V(s')]\), so the integrator is inside the objective evaluation.
So it is normal to see expectation/integration both “before” and “inside” the maximization loop: policy iteration interleaves evaluation and improvement.
Two concrete snippets from packages/dolo/dolo/algos/value_iteration.py:
1) Inside the maximizer objective (expectation depends on candidate x):
cont_v = 0.0
for I_ms in range(dprocess.n_inodes(i_ms)):
M = dprocess.inode(i_ms, I_ms)
prob = dprocess.iweight(i_ms, I_ms)
S = transition(m, s, x, M, parms)
V = drv(I_ms, S)[0]
cont_v += prob * V
return felicity(m, s, x, parms) + beta * cont_v
2) During policy evaluation (expectation inside the value-function update):
for I_ms in range(n_mv):
M = dprocess.inode(i_ms, I_ms)
prob = dprocess.iweight(i_ms, I_ms)
S = g(m, s, xm, M, parms)
XM = dr.eval_ijs(i_ms, I_ms, S)
VM = drv.eval_ijs(i_ms, I_ms, S)
rr = val(m, s, xm, vm, M, S, XM, VM, parms)
res[i_ms, :, :] += prob * rr
6. The \(f(E[V])\) vs \(E[f(V)]\) Question (and Why YAML Doesn’t Decide)¶
Suppose you write an expression like:
In stochastic Dolo algorithms, the integration loop typically wraps evaluation of the RHS as an integrand. That means the solver computes:
not \( \beta\, f(E[V(s')]) \).
Why? Because the algorithm is:
- loop over shock nodes,
- compute next state \(s'\),
- evaluate \(V(s')\),
- compute \(f(V(s'))\),
- weight and sum across nodes.
There is no syntax-level E[...] operator in YAML that would let you place expectation inside or outside f.
Implication: if you truly need \(f(E[V])\), you would need to introduce an intermediate object representing \(E[V]\) and then apply \(f\). In stock Dolo/dtcc, that is not expressible without extending the recipe signatures (because the expectation: block does not take values[1] as an input by default).
7. Resolving the Function-vs-Variable Ambiguity¶
The core ambiguity is:
In YAML,
V[t]looks like a scalar time-indexed variable.
In computation,Vis a function over the state space (and possibly over exogenous nodes).
Dolo resolves this implicitly by its data structures:
- Controls are represented as a DecisionRule \(x = x(m,s)\).
- Values are represented as a DecisionRule \(V = V(m,s)\).
- A reference like V[t+1] means “evaluate the value-function approximation at the next state”.
So mentally, you should read:
V[t]as “\(V(m_t, s_t)\)”V[t+1]as “\(V(m_{t+1}, s_{t+1})\)” (with \(s_{t+1}\) coming fromtransition)
and then the algorithm decides whether to integrate over \(m_{t+1}\) (and/or other shocks) and where that integration sits in the computational flow.
8. Implications for DDSL-SYM / T core (from Dec 22)¶
This Dolo behavior motivates the “expectation location” design principle we recorded on Dec 22:
- Dolo’s YAML syntax stays implicit and economist-friendly (good DDSL-SYM target).
- The true semantics of expectation placement live in the algorithmic layer (what we want to make explicit in T core).
In other words:
In Dolo, expectation is primarily an algorithmic operator, not a syntactic operator.
T core should make that operator explicit; DDSL-SYM can keep the implicit[t+1]style.