Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

IID shock declarations

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

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.

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:

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.