Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
4123fe9
Anchor consumption grid lower bound to consumption_floor parameter
hmgaudecker Apr 29, 2026
1342861
Refactor utility_scale_factor to take pref_type, return scalar
hmgaudecker Apr 29, 2026
8cd8e37
Halve aca-model production assets batch size to fit V100 16GB
hmgaudecker Apr 30, 2026
84d484a
Revert assets batch_size halving — V100 OOM was elsewhere
hmgaudecker Apr 30, 2026
f0892ef
Re-halve production assets batch size: V100 still OOMs per-period
hmgaudecker May 1, 2026
08e42cb
config: add n_aime_batch_size to splay AIME outer-loop on V100
hmgaudecker May 1, 2026
e08fc19
consumption_grid: read upper bound from `max_consumption` fixed param
hmgaudecker May 1, 2026
c1ffb2a
create_model: default `max_consumption` into fixed_params
hmgaudecker May 2, 2026
a217687
create_model: forward n_subjects through baseline + aca + benchmark
hmgaudecker May 2, 2026
d1eb320
create_model: require n_subjects (no default)
hmgaudecker May 2, 2026
9e25205
ci: install pylcm from feat/simulate-aot-n-subjects (carries Model.n_…
hmgaudecker May 2, 2026
cdd1016
consumption_grid: max_consumption is a required factory arg, attached…
hmgaudecker May 2, 2026
31a0ad2
Move max_consumption to canonical constant; drop kwarg threading
hmgaudecker May 3, 2026
714fee0
Assets grid: subtract MAX_CONSUMPTION margin from the floor
hmgaudecker May 4, 2026
63d2a38
Revert "Assets grid: subtract MAX_CONSUMPTION margin from the floor"
hmgaudecker May 4, 2026
4ae4446
Wire pension imputation correction (FJ 2011 Appendix A.5)
hmgaudecker May 4, 2026
83f2250
Bump pyproject-fmt v2.19.0 → v2.21.1 and ruff-pre-commit v0.15.6 → v0…
hmgaudecker May 4, 2026
3453080
get_benchmark_params: filter obsolete imputed_pension_wealth_next_per…
hmgaudecker May 4, 2026
b2e90bb
get_benchmark_params: synthesise <key>_next_period shifted views
hmgaudecker May 4, 2026
35eddcc
benchmark: declare target_his as derived categorical
hmgaudecker May 4, 2026
64d6567
_shift_one_period_forward: rename his level to target_his
hmgaudecker May 4, 2026
f09b5e3
Per-target next_assets: dead target uses next_assets_terminal (no pen…
hmgaudecker May 4, 2026
e1a3eb2
create_model: register target_his as derived categorical at base layer
hmgaudecker May 5, 2026
00ee7d2
aca/model.create_model: register target_his at base layer
hmgaudecker May 6, 2026
edfa540
tests: positive regression guard — assets=-$1M passes benchmark valid…
hmgaudecker May 6, 2026
d05df9e
borrowing_constraint: use max(cash_on_hand, floor) to dodge fp32 canc…
hmgaudecker May 6, 2026
4af8359
ci: bump pylcm pin to e4cae2aa (post-#342, post-#340 diagnostic)
hmgaudecker May 6, 2026
0c7f2d5
wip: debug script — cash_on_hand per failing subject
hmgaudecker May 6, 2026
8ffbf5c
wip: fix imports in debug script (broadcast_to_template + ACA_DATA_BLD)
hmgaudecker May 6, 2026
81cca3c
wip: import GRID_CONFIG_FOR_RUN from aca_estimation
hmgaudecker May 6, 2026
e320f41
wip: pass derived_categoricals to create_aca_model in debug
hmgaudecker May 6, 2026
2208fa6
wip: augment fixed_params for ACA policy in debug
hmgaudecker May 6, 2026
8adabda
borrowing_constraint: cast consumption_floor to consumption's dtype
hmgaudecker May 6, 2026
c895bd9
borrowing_constraint: drop dtype cast workaround
hmgaudecker May 7, 2026
e0cc622
tests: switch helpers import to relative form
hmgaudecker May 7, 2026
3d2faf4
tests: drop tests/__init__.py; expose helpers via conftest sys.path
hmgaudecker May 7, 2026
97c84cd
Drop precision-related workarounds and function defaults
hmgaudecker May 7, 2026
4901c9c
Merge cleanup/no-defaults-no-precision-workarounds
hmgaudecker May 7, 2026
9d59174
borrowing_constraint: restore max() form for kink-stability at extrem…
hmgaudecker May 8, 2026
67edfe0
consumption_grid: pin first gridpoint to consumption_floor exactly
hmgaudecker May 8, 2026
d9339ab
ci: bump pylcm pin to 2f486dc
hmgaudecker May 8, 2026
9e39a06
ci: bump pylcm pin to ca66ba9
hmgaudecker May 8, 2026
de4d16f
Rename consumption → consumption_unequiv across model + drop stale do…
hmgaudecker May 10, 2026
c31b5dd
benchmark: regenerate snapshot, drop rename/shift workarounds
hmgaudecker May 10, 2026
99badcd
Decompose consumption floor into equiv-param + unequiv-DAG; fix prose
hmgaudecker May 10, 2026
9ac2043
tests: wrap Python-int kwargs in jnp.int32 to satisfy strict pylcm types
hmgaudecker May 10, 2026
987e86e
Phase 1: docstring + naming cleanups
hmgaudecker May 11, 2026
b36375b
Phase 2: collapse scale_reference_age into reference_age; rename scal…
hmgaudecker May 11, 2026
e879e05
Phase 3: merge agent/utility.py into preferences.py; adopt u_X naming
hmgaudecker May 11, 2026
f6ed413
Phase 4: DAG functions for pref-type indexing
hmgaudecker May 11, 2026
6da86ec
Phase 5: require all params explicitly; consolidate base derived cate…
hmgaudecker May 11, 2026
e2861d0
Phase 6: precompute_targets → precompute_target_regimes; introduce Re…
hmgaudecker May 11, 2026
71a8535
ci: bump pylcm pin to 99a5e31 (post-#345 main)
hmgaudecker May 11, 2026
2779011
ci: step name reflects that pylcm pin is on main, not a feature branch
hmgaudecker May 11, 2026
fedd756
Rename consumption_unequiv → consumption_dollars; tighten types; drop…
hmgaudecker May 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ repos:
- id: check-hooks-apply
- id: check-useless-excludes
- repo: https://github.com/tox-dev/pyproject-fmt
rev: v2.19.0
rev: v2.21.1
hooks:
- id: pyproject-fmt
- repo: https://github.com/lyz-code/yamlfix
Expand Down Expand Up @@ -47,7 +47,7 @@ repos:
hooks:
- id: yamllint
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.15.6
rev: v0.15.12
hooks:
- id: ruff-check
args:
Expand Down
Binary file modified src/aca_model/_benchmark_data/benchmark_params.pkl
Binary file not shown.
44 changes: 25 additions & 19 deletions src/aca_model/aca/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,42 +8,46 @@
from typing import Any

from lcm import AgeGrid, DiscreteGrid, Model
from lcm.typing import UserParams

from aca_model.aca import PolicyVariant
from aca_model.aca.regimes import build_all_regimes
from aca_model.baseline.regimes import RegimeId
from aca_model.config import GRID_CONFIG, MODEL_CONFIG, GridConfig
from aca_model.config import MODEL_CONFIG, GridConfig


def create_model(
*,
policy: PolicyVariant = PolicyVariant.ACA,
fixed_params: Mapping[str, Any] | None = None,
wage_params: Mapping[str, Any] | None = None,
derived_categoricals: Mapping[str, DiscreteGrid | Mapping[str, DiscreteGrid]]
| None = None,
grid_config: GridConfig = GRID_CONFIG,
n_subjects: int,
policy: PolicyVariant,
fixed_params: UserParams,
wage_params: Mapping[str, Any],
derived_categoricals: Mapping[str, DiscreteGrid],
grid_config: GridConfig,
pref_type_grid: DiscreteGrid,
) -> Model:
"""Create an ACA policy variant model.

Args:
policy: Which ACA policy combination to apply.
fixed_params: Parameters to fix at model creation time. These are
partialled into compiled functions and removed from the params
template. Pass data-derived constants here; only estimation
parameters should go through `model.simulate(params=...)`.
n_subjects: Forwarded to `lcm.Model(n_subjects=...)`.
policy: Which ACA policy combination to apply (e.g.
`PolicyVariant.ACA`).
fixed_params: Parameters to fix at model creation time. Pass
data-derived constants here; only estimation parameters
should go through `model.simulate(params=...)`.
wage_params: Data-derived wage profile dict (`log_ft_wage_mean`,
`log_ft_wage_std`, `adj_wage_hours_*`) used only at grid-build
time to size the assets-floor to `-max_annual_labor_income`.
Not routed to the pylcm Model.
derived_categoricals: Extra categorical mappings for derived variables
not in the model's state/action grids. Needed when `fixed_params`
contains `pd.Series` indexed by DAG function outputs.
grid_config: Continuous-grid point counts. Defaults to production
values.
derived_categoricals: Categorical mappings for `pd.Series`
fixed_params index levels that aren't model state/action
grids — `target_his`, `his`, `good_health`, `is_married`,
`pref_type`.
grid_config: Continuous-grid point counts.
pref_type_grid: Pref-type `DiscreteGrid`.

Returns:
pylcm Model with ACA-specific function overrides.
pylcm Model.

"""
ages = AgeGrid(
Expand All @@ -56,13 +60,15 @@ def create_model(
grid_config=grid_config,
fixed_params=fixed_params,
wage_params=wage_params,
pref_type_grid=pref_type_grid,
)

return Model(
regimes=regimes,
ages=ages,
regime_id_class=RegimeId,
description=f"Structural retirement model ({policy.name})",
fixed_params=fixed_params or {},
fixed_params=fixed_params,
derived_categoricals=derived_categoricals,
n_subjects=n_subjects,
)
19 changes: 12 additions & 7 deletions src/aca_model/aca/regimes/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,30 @@
from collections.abc import Mapping
from typing import Any

from lcm import Regime
from lcm import DiscreteGrid, Regime
from lcm.typing import UserParams

from aca_model.aca.health_insurance import PolicyVariant
from aca_model.aca.regimes._overrides import apply_aca_overrides
from aca_model.baseline.regimes import build_all_regimes as baseline_build_all_regimes
from aca_model.baseline.regimes._common import REGIME_SPECS
from aca_model.config import GRID_CONFIG, GridConfig
from aca_model.config import GridConfig


def build_all_regimes(
policy: PolicyVariant,
grid_config: GridConfig = GRID_CONFIG,
*,
fixed_params: Mapping[str, Any] | None = None,
wage_params: Mapping[str, Any] | None = None,
policy: PolicyVariant,
grid_config: GridConfig,
fixed_params: UserParams,
wage_params: Mapping[str, Any],
pref_type_grid: DiscreteGrid,
) -> dict[str, Regime]:
"""Build all 19 regimes with ACA policy overrides."""
regimes = baseline_build_all_regimes(
grid_config, fixed_params=fixed_params, wage_params=wage_params
grid_config=grid_config,
fixed_params=fixed_params,
wage_params=wage_params,
pref_type_grid=pref_type_grid,
)
result = {}
for name, regime in regimes.items():
Expand Down
3 changes: 2 additions & 1 deletion src/aca_model/aca/regimes/_overrides.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@

from aca_model.aca import health_insurance as aca_hi
from aca_model.aca.health_insurance import PolicyVariant
from aca_model.baseline.regimes._common import RegimeSpec


def apply_aca_overrides(
functions: dict,
spec: dict[str, str],
spec: RegimeSpec,
policy: PolicyVariant,
) -> None:
"""Override baseline functions with ACA versions in-place.
Expand Down
65 changes: 49 additions & 16 deletions src/aca_model/agent/assets_and_income.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,41 +35,74 @@ def cash_on_hand(
return assets + after_tax_income + ssi_benefit - hic_premium


def transfers(
cash_on_hand: FloatND,
consumption_floor: float,
def consumption_dollars_floor(
consumption_equiv_floor: float,
equivalence_scale: FloatND,
) -> FloatND:
"""Government transfers to enforce consumption floor.
"""Per-household $-floor on consumption."""
return consumption_equiv_floor * equivalence_scale

tr = max{0, C_min * equivalence_scale - cash_on_hand}
"""
floor = consumption_floor * equivalence_scale
return jnp.maximum(0.0, floor - cash_on_hand)

def transfers(
cash_on_hand: FloatND,
consumption_dollars_floor: FloatND,
) -> FloatND:
"""Government transfers to enforce the consumption floor."""
return jnp.maximum(0.0, consumption_dollars_floor - cash_on_hand)


def next_assets(
cash_on_hand: FloatND,
transfers: FloatND,
pension_assets_adjustment: FloatND,
consumption: ContinuousAction,
consumption_dollars: ContinuousAction,
oop_costs: FloatND,
) -> ContinuousState:
"""Compute beginning-of-next-period assets.
"""Compute beginning-of-next-period assets for non-terminal targets.

OOP health costs are deducted here (not from cash_on_hand) so that the
consumption choice does not condition on the HCC shock realization.
"""
return (
cash_on_hand + transfers + pension_assets_adjustment - consumption - oop_costs
cash_on_hand
+ transfers
+ pension_assets_adjustment
- consumption_dollars
- oop_costs
)


def borrowing_constraint(
consumption: ContinuousAction,
def next_assets_when_dead(
cash_on_hand: FloatND,
transfers: FloatND,
pension_assets_adjustment: FloatND,
consumption_dollars: ContinuousAction,
oop_costs: FloatND,
) -> ContinuousState:
"""Compute beginning-of-next-period assets for the dead/terminal target.

No `pension_assets_adjustment` term: with no future, there is no
next-period pension wealth to impute against. Avoiding the dependency
also keeps the `dead` per-target transition's DAG free of `next_aime`
(which would otherwise need to come from a transition `dead` does not
have, since `aime` is not a state in the terminal regime).
"""
return cash_on_hand + transfers - consumption_dollars - oop_costs


def borrowing_constraint(
consumption_dollars: ContinuousAction,
cash_on_hand: FloatND,
consumption_dollars_floor: FloatND,
) -> BoolND:
"""Consumption cannot exceed available resources (no borrowing)."""
return consumption <= cash_on_hand + transfers + pension_assets_adjustment
"""Consumption cannot exceed post-transfer resources.

Post-transfer resources are `max(cash_on_hand, consumption_dollars_floor)`:
the transfer system tops `cash_on_hand` to the floor when below,
otherwise resources are unchanged. The algebraic identity is
`cash_on_hand + transfers == max(cash_on_hand, floor)`; the `max`
form is preferred because the additive form rounds to `floor + ε`
(with `|ε| ~ ULP(|cash_on_hand|)`) at extreme cash, which flips
the kink-boundary comparison at large negative values of `assets`.
The `max` form returns `floor` exactly.
"""
return consumption_dollars <= jnp.maximum(cash_on_hand, consumption_dollars_floor)
Loading