lcm.shocks separates a probability law from its numerical integration rule. A group
owns named base innovations and maps them into physical values at the current state. The
declarations and numerical helpers are independent of model consumers.
from lcm import shocks as sh
wage = sh.ShockGroup(
shocks={"eps": sh.Normal(mu=0.0, sigma=None)},
nodes=sh.Product(eps=sh.GaussHermite(5)),
)
nodes = wage.resolve_nodes(group_name="wage", age=40)
realised = wage.realise(nodes=nodes, params={"eps__sigma": 0.2})
realised.raise_if_invalid()params here is the group-local namespace. A model registry supplies the enclosing
group namespace, giving params["wage"]["eps__sigma"]. A fixed parameter is numeric;
None requests a runtime parameter. A callable receives named current-state, time and
parameter inputs. validate_dependencies walks the caller’s function DAG and refuses an
action, realised shock, next-state, choice or downstream value dependency, including
indirect dependencies. The caller supplies every group’s shock names and downstream node
vocabulary so an unknown dependency cannot conceal one of these roles.
StateConditioned(on=..., by=...) is a category lookup for any law parameter. Bind it
with bind_conditioning(state_grids=...) before evaluation; lookup order follows the
conditioning grid’s category codes. The mapping must name exactly those categories.
Laws and base coordinates¶
Normal(mu, sigma)uses one standard-normal base coordinate. Scale is nonnegative; zero gives a deterministic law.LogNormal(mu, sigma)exponentiates a normal variable. Its parameters describe the logarithm, and its scale may be zero. A nonfinite physical value is invalid.Uniform(start, stop)transforms a unit-uniform coordinate affinely. Equal endpoints are deterministic; reversed endpoints are refused.NormalMixture(p1, mu1, sigma1, mu2, sigma2)maps one normal-base coordinate through the mixture quantile. The first component has probabilityp1. Active components require positive scales; an inactive component may have zero scale. This contract excludes active point masses.Discrete(categories, probs)uses a pylcm categorical class. Fixed probabilities are a vector in category-code order; runtime or callable probabilities may have leading cell axes. Solve enumeration retains every category and multiplies its probability into the joint weights. A realised uniform uses half-open CDF cells and skips zero probabilities, including exact cutpoints and the terminal zero plateau.
Every law parameter is finite in the evaluated cell. Resolved parameter shapes must broadcast to that cell’s physical values. Probability vectors are nonnegative, have the category count on the final axis, and sum to unity within the active dtype tolerance. Validation never silently normalizes a malformed vector.
The mixture solver brackets a quantile between the component quantiles at its common base probability. Log-CDF and log-survival equations retain both tails. A capacity or floating-point failure produces an invalid status rather than an unchecked quantile. Smooth first derivatives follow the implicit CDF equation. Differentiating through an inactive zero-scale component is outside that smooth derivative contract: derivative consumers must additionally reject nonfinite tangents or marginals. Primal validity alone is insufficient to admit a derivative consumer.
Omitted correlation means independent base coordinates. Explicit correlation=None
requests runtime corr in the group namespace. A matrix fixes Gaussian correlation in
shock order. It is restricted to normal-base continuous shocks; there is no copula or
cross-group realised-draw conditioning declaration. Exact represented PSD validation
retains singular support. For a compiled runtime-correlation call, first use
bind_correlation to prepare its exact pivot support. This binds the support, retaining
matrix values as traced runtime parameters and preserving their interior derivatives.
The host boundary must call validate_runtime_correlation for each invocation; support
changes require rebinding. An unprepared traced matrix is refused. Rounded Schur
residuals cannot certify an exact zero pivot, so the exact host check owns singular
support. The rounded matrix is also checked at the active precision before its factor is
formed.
Integration rules¶
Product(**rules) supplies one compatible rule per continuous shock. Shock declaration
order determines columns; the last coordinate varies fastest in the tensor product.
Discrete variables are independently enumerated exactly beside the continuous rule.
GaussHermite(n)integrates the standard-normal measure. Hermite coordinates are multiplied bysqrt(2)and weights divided bysqrt(pi); its analytic polynomial exactness is through degree2*n-1, subject to floating-point representation.EqualSpaced(n, n_std)has normal-base support[-n_std, n_std], midpoint CDF bins and edge bins containing both tails. It requires adjacent endpoints.EqualSpacedUniform(n)uses inclusive unit endpoints with equal weights. A singleton uses the midpoint.MonteCarlo(n, seed)uses equal-weight PCG64 uniforms.QuasiMonteCarlo(n, seed, sequence="sobol")uses scrambled Sobol LMS+shift points with a fixed bit budget and a power-of-two sample count. It retains every point in sequence order. Unit endpoints are opened before inverse-normal transformation. It is the rule for thinning a group of several shocks, whoseProducttensor grows exponentially with the number of shocks.UserNodes(nodes, weights)owns a finite(K,d)base-coordinate matrix and(K,)weights. It preserves point order and copies mutable input arrays. Uniform coordinates lie in the unit interval. It cannot replace exact category enumeration.
UserNodes weights must be nonnegative. Negative weights raise ShockDeclarationError
when the rule is declared, because pylcm takes the max over shocks and samples nodes by
weight, both of which need probability weights.
MC and QMC regenerate per exact age or explicitly labelled period from a canonical
versioned seed, time-coordinate and group-name encoding. Parameters do not enter that
encoding. Their solve seed is independent of simulation randomness. Fingerprints retain
rule declarations, group and column order, exact time, numerical algorithm and library
versions, endpoint handling and the resolved point/weight bytes. Physical-law identity
is separate from node-rule identity: has_same_laws_as ignores the node declaration.
Joint-law comparison conservatively retains named column order and fingerprints callable
captured semantics; a reorder cannot reinterpret correlation rows silently.
Typed draw inputs¶
BaseInnovations(values=array) identifies independent named coordinates before
correlation and the physical law. Normal-base coordinates must be finite; Uniform’s
forward support is [0, 1], and categorical bases use [0, 1).
PhysicalValues(values=array) identifies values to invert under the producing law and
its current context. The tags retain their arrays as dynamic JAX pytree leaves; they do
not copy them into declaration metadata. Base coordinates require floating-point arrays.
Physical values use the producing law’s active float dtype or int32 categorical codes.
The constructors reject integer or floating-point narrowing before JAX tracing can erase
the input dtype.
Independent physical inputs support normal, lognormal, uniform, normal-mixture and
categorical laws. Categorical physical inputs are integer category codes with positive
source probability. Their latent uniform uses the addressed residual within the source
CDF interval; it is neither a midpoint nor the category code. An interval or continuous
inverse that cannot be resolved at the active precision is invalid. Compatible
deterministic independent inputs retain the original addressed base. Independent and
full-rank continuous observations retain their supplied physical bits. The draw frame
records signed recovery_error in physical units: the producing forward value at the
recovered base minus the supplied observation. This describes inverse precision, not
mathematical support. Finite/support/resolution checks remain mandatory; ordinary
external values need not be exact images of a native quantile evaluation.
Correlated physical inputs require the complete physical frame alone. Partial frames and mixed physical/base forms are refused. Complete informative frames use the same admitted factor as the forward law, retaining addressed null coordinates. Singular joint-frame consistency requires an exact represented forward witness for the complete supplied frame; a rounded inverse without that witness remains unresolved. An EPS proximity check does not establish external support. Wholly deterministic compatible frames retain all addressed coordinates. Complete frames mixing informative and constant marginals use a canonical producing-coordinate witness. Free coordinates retain their addressed bits; the recovered coordinates are not a conditional latent draw. The original forward law must reproduce every supplied physical bit, or recovery remains unresolved. A seedless support check returns physical values and status without exporting latent coordinates. Named base overrides use the forward transform. Supplying a value never makes it observed by a policy.
An explicit fixed or admitted runtime correlation matrix that is exactly identity uses the independent rules. The proof uses the current represented matrix, without an epsilon, and is checked again when runtime correlation changes. A genuinely nonidentity group, including a block-diagonal group, retains the complete-group physical-input restriction; this interface does not perform component completion.
For an ordinary GridSearch simulation, shock_paths maps a named current shock to one
of these records. Its array has shape (periods,) for a common path, or
(subjects, periods) in original initial-condition row order. Mapping-form
initial_conditions can include tagged (subjects,) entry values on current-shock
names. An entry value applies only at that subject’s actual entry period; subsequent
periods use fresh addressed draws. A path supersedes the entry value and its tag for the
same name. Current shocks in DataFrame inputs require the tagged mapping form. Untagged
current-shock arrays receive an explicit migration error. Ordinary persistent states and
transition-local state paths retain their own input interpretation.
Shape and dtype are checked at the boundary. Law support and correlated completeness are enforced on the cells actually consumed; values before entry and unread tail cells do not condition a draw. This does not introduce a missing-value convention. Input records retain their original buffers through memory admission; only the current original-row chunk is uploaded as draw operands. Native profiles carry array-free tag/dtype prototypes, and changing a tag or value cannot reuse a stale interpretation.
Entry checks validate an explicitly observed target-only frame at the original subject’s mapped producing context, with current parameters and the resolved clock. Fixed edge leaves survive runtime overrides of other slots; current-call values take precedence when both bind the same slot. An unavailable producer does not evaluate its numerical physical law. Hidden target-only inputs remain hidden during entry feasibility and are validated by the actual final actor when consumed. Seedless feasibility helpers do not select a policy to determine a hidden input’s first producer; they explicitly refuse that unresolved producer context.
The scalar draw producer returns exact supplied physical bits, addressed and held independent bases, producing parameters/inputs, recovery errors and mandatory validity. Cross-regime routing and EGM replay must preserve that producing context and bind their own information/reconstruction contracts; a supplied input does not authorize policy conditioning on a hidden value.
Simulation rows name the final acting regime after current-period exits. They retain the
period-entry regime’s realized current shocks, including observed exit inputs, on that
same row. A shock read by the final actor keeps its unqualified column name; when the
period-entry regime also realized that name, source__<name> reports its original value
on movers. Source-only names keep their unqualified names. In dataframes, missing
observations and later periods without that realization contain NaN. Discrete reports
use the declared labels, or category codes with use_categorical_codes=True. Raw
categorical report arrays retain their integer dtype and use -1 for missing codes.
Scheduled participation checks retain their actual observed entry-frame draws on
fallback rows too; source__<name> refers to the actor evaluated by that current check.
shock_paths supplies draws rather than recovering a missing observation. Tagged
BaseInnovations hold the underlying innovation across a move; a state-dependent law
can turn it into different source and target physical values. Tagged PhysicalValues
bind the producing frame and transport its recovered base to the target law. These
reporting columns do not alter choices, transition inputs or draws.
Combinations not yet composed with current shocks¶
A group read only by GridSearch regimes stays on the integrated current-IID route.
Model(...) raises UnsupportedOperationError (“Current IID in regime ... is not yet
composed with ...”) when a regime reading such a group also uses:
action partitions above one (
ExecutionConfig(action_partitions=...));a
Decoupleddecision;invariant state blocks (
ExecutionConfig(invariant_block_widths=...));a gated value channel: gated edges out of or into the regime, or participation reads of it;
a same-period value channel:
same_period_refsfrom or to the regime;a sharded discrete state whose law is the identity (a co-mapped state).
Model.lookup_policy raises UnsupportedOperationError for such a regime: a current
shock needs an explicit observation or conditional query, which the lookup does not
take. A group that an endogenous-grid regime reads is solved on the node-state axis
instead; see
Named current-IID shocks in endogenous-grid regimes.
Validity¶
Realisation is a JAX-compatible tuple of physical values, joint weights and a scalar
valid channel. Every consumer must enforce that status at its runtime boundary.
raise_if_invalid() performs this check eagerly. Compiled callers carry the channel
into their runtime checks; they must never use an invalid value or silently replace it
with a finite placeholder.
For exact categorical enumeration, NodeSet.weights repeats continuous weights;
realise multiplies the cell’s categorical probabilities before integration. The
completed Realisation.weights is the joint integration operator.
Model registry and compatibility¶
Model(shocks={"wage": wage}) snapshots named groups as the immutable model.shocks
mapping. Regime(shocks=("wage",)) references that shared declaration. Group namespaces
cannot alias regime or edge parameter roots. State, action and function names live in
separate namespaces and can also name a group. Physical shock names cannot collide with
model inputs or be redeclared by competing groups. Base address identity is the
immutable physical shock name and component, independent of group membership and node
rules. Physical values, category codes and local quadrature indices do not identify it.
The registry records each consumer phase’s current-state law dependencies and domains
before broadcast pruning. Runtime slots such as eps__sigma, callable parameters and
corr remain group-local metadata; they are not added to Model.get_params_template()
as ordinary regime parameters. Integrated execution must explicitly project those slots
from params[group], bind correlation support and enforce per-invocation exact host
correlation admission and numerical validity.
Timing is read off the regime’s functions. A regime whose functions read a group’s shock
x sees this period’s realization, known at the choice. A regime that reads the group
only as next_<shock>, from its state transitions, sees the draw after the choice: the
group carries no axis there, and the solve integrates the transitions over its nodes and
weights. Simulation draws each subject’s value from the shock’s continuous law,
addressed like every other draw: the nodes serve only the solve’s integration, and the
draw enters only next period’s state law. Utility and constraints may not read
next_<shock>, and a regime reading both x and next_x of one group is refused; an
IID process state carries a realization into the next period. Each post-decision shock
lowers to one fixed node set, so the group needs continuous laws with fixed parameters,
independent shocks and a deterministic unsigned rule. A group read as a value in one
regime and only as a draw in another is refused. A shock_paths entry for a
post-decision shock is a plain array of physical values, on or off the nodes, dated at
the destination period. repr(model) lists, per regime, which groups carry an axis and
which are integrated out.
Registry declarations can be inspected and persisted. Ordinary GridSearch consumers bind
the current law inside their node decision and fresh simulation draw. A group that an
endogenous-grid regime reads is solved on the node-state axis instead, for every reader
of the group; model.shocks still lists it, published values integrate its nodes, and
simulation draws and overrides keep the rules above. See
Named current-IID shocks in endogenous-grid regimes for
the supported laws and rules. Other solver/query intersections require their
corresponding integrated producer and otherwise refuse explicitly. An unlowered physical
shock cannot silently become a free parameter.
Legacy IID state processes retain their public classes, state layouts, phase-specific laws, transition timing and simulation draw modes. Compatibility records retain the original joint declaration and physical-grid rule. In particular, legacy normal-mixture CDF bins on a physical equally spaced grid are not a standard-normal base rule transformed through mixture quantiles. Persistent processes and transition-local state laws keep their existing consumers. An empty registry and empty regime references leave feature-free durable fingerprints unchanged.