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.

Constraints and structured Conditions

Most constraints do not need Condition. The choice depends on what pylcm must know in order to express the restriction along the selected solver’s candidate route.

Use an ordinary callable when the Boolean result is enough

import jax.numpy as jnp


def feasible_consumption(*, consumption, resources):
    return (consumption > 0) & (consumption <= resources) & jnp.isfinite(consumption)

GridSearch evaluates this predicate on complete state-action candidates. A specialized solver can also evaluate it at any route stage where every argument exists.

Use Condition when the comparison structure must survive

A callable exposes inputs and an output, but not the fact that it means savings >= 0 or wealth < asset_limit. Retain that structure when a specialized solver must:

You may also choose Condition simply because its declarative form is clearer. It does not expand the mathematical set of Booleans available to grid search, and it does not make an unsupported constraint supported by an EGM solver.

Syntax

ref(name) refers to a state, action, DAG output, parameter, or declared margin role.

import lcm

# Named value and literal.
nonnegative_savings = lcm.ref("savings") >= 0.0

# Named value and named value.
below_asset_limit = lcm.ref("wealth") < lcm.ref("asset_limit")

# Intersection and union.
working_age = (lcm.ref("age") >= 18) & (lcm.ref("age") < 67)
insured_or_eligible = (lcm.ref("insured") == 1) | (
    lcm.ref("income") <= lcm.ref("eligibility_limit")
)

# Complement.
not_retired = ~(lcm.ref("retired") == 1)

# Conditional requirement.
hours_if_working = lcm.implies(
    premise=lcm.ref("working") == 1,
    consequent=lcm.ref("hours") <= 40,
)

Use &, |, and ~, not Python’s and, or, and not. Comparisons may use <, <=, ==, !=, >=, and >. Strictness determines which side owns an exact boundary point.

When a particular solver needs retained structure

RestrictionBroad callable evaluationWhy retained structure may be needed
Consumption is finite and below resourcesComplete grid-search candidateUsually not needed
Savings lower boundSavings may be constructed rather than exposed at every EGM stageSolver proves it from its grid
Liquid-state intervalNBEGM partitions work by declared liquid boundariesCompiler needs names, thresholds, and strictness
Arbitrary Boolean using unavailable intermediate valuesGrid search may expose themStructure cannot help unless the solver implements a proof/compiler

The last row is important: Condition is information, not magic.

Prefer a margin-specific lower bound

For the borrowing constraint of a declared liquid margin, prefer:

borrowing_limit = lcm.post_decision_lower_bound(
    margin=liquid,
    lower=0.0,
)

It prevents the name in the condition from drifting away from the LiquidMargin.post_decision_state name and lets the solver compare the lower bound with its savings grid.

State and discrete-action predicates in EGM continuation queries

DC-EGM and admitted NB-EGM routes evaluate eligible current-state and discrete-action predicates at the actual continuation query. They retain each conditional alternative, apply its budget and predicates, then aggregate the surviving choices. This also preserves a feasible region lying between stored nodes. Predicates depending on a continuous action still require an existing solver proof or compilation route.

Generic value-function interpolation, including a GridSearch parent and canonical simulation continuation valuation, uses values masked at stored nodes. Its off-node value can therefore differ from an EGM-family conditional carry query. Simulation checks current-action constraints on the actual subjects and candidate actions.

Budget kinks and cliffs are not ordinary feasibility constraints. Declare them with case pieces or a piecewise-affine schedule.