From 9f3937b11043059af1fca54b0c654755a5e46198 Mon Sep 17 00:00:00 2001 From: mj023 Date: Mon, 4 May 2026 14:06:49 +0200 Subject: [PATCH 01/77] First changes --- src/lcm/grids/base.py | 11 +++++ src/lcm/grids/continuous.py | 2 + src/lcm/grids/discrete.py | 8 +++- src/lcm/interfaces.py | 76 ++++++++++++++++++++++++++++++--- src/lcm/solution/solve_brute.py | 28 ++++++++---- tests/test_distributed.py | 73 +++++++++++++++++++++++++++++++ 6 files changed, 182 insertions(+), 16 deletions(-) create mode 100644 tests/test_distributed.py diff --git a/src/lcm/grids/base.py b/src/lcm/grids/base.py index fb5698cb6..9101ceff3 100644 --- a/src/lcm/grids/base.py +++ b/src/lcm/grids/base.py @@ -16,6 +16,17 @@ def batch_size(self) -> int: """ + + @property + @abstractmethod + def distributed(self) -> bool: + """Whether to distribute the grid over the available devices. + + `ContinuousGrid` overrides this via its dataclass field. + `DiscreteGrid` overrides this via its own property. + + """ + @abstractmethod def to_jax(self) -> Int1D | Float1D: """Convert the grid to a Jax array.""" diff --git a/src/lcm/grids/continuous.py b/src/lcm/grids/continuous.py index c81f9c6a6..dbb7cea11 100644 --- a/src/lcm/grids/continuous.py +++ b/src/lcm/grids/continuous.py @@ -27,6 +27,8 @@ class ContinuousGrid(Grid): batch_size: int = 0 """Size of the batches that are looped over during the solution.""" + distributed: bool = False + """Size of the batches that are looped over during the solution.""" @overload def get_coordinate(self, value: ScalarFloat) -> ScalarFloat: ... diff --git a/src/lcm/grids/discrete.py b/src/lcm/grids/discrete.py index f844aad6b..e2f5fa32f 100644 --- a/src/lcm/grids/discrete.py +++ b/src/lcm/grids/discrete.py @@ -19,13 +19,14 @@ class DiscreteGrid(Grid): """ - def __init__(self, category_class: type, batch_size: int = 0) -> None: + def __init__(self, category_class: type, batch_size: int = 0, distributed = False) -> None: _validate_discrete_grid(category_class) names_and_values = get_field_names_and_values(category_class) self.__categories = tuple(names_and_values.keys()) self.__codes = tuple(names_and_values.values()) self.__ordered: bool = getattr(category_class, "_ordered", False) self.__batch_size: int = batch_size + self.__distributed: bool = distributed @property def categories(self) -> tuple[str, ...]: @@ -46,6 +47,11 @@ def ordered(self) -> bool: def batch_size(self) -> int: """Return batch size during solution.""" return self.__batch_size + + @property + def distributed(self) -> bool: + """Return batch size during solution.""" + return self.__distributed def to_jax(self) -> Int1D: """Convert the grid to a Jax array.""" diff --git a/src/lcm/interfaces.py b/src/lcm/interfaces.py index 9ff454b2f..d455d8201 100644 --- a/src/lcm/interfaces.py +++ b/src/lcm/interfaces.py @@ -3,9 +3,12 @@ from types import MappingProxyType from typing import cast +from functools import reduce +from operator import mul import pandas as pd from jax import Array - +import jax +from lcm.exceptions import PyLCMError from lcm.grids import Grid, IrregSpacedGrid from lcm.shocks import _ShockGrid from lcm.typing import ( @@ -281,12 +284,73 @@ def state_action_space(self, regime_params: FlatRegimeParams) -> StateActionSpac replacements[state_name] = spec.compute_gridpoints(**shock_kw) if not replacements: - return self._base_state_action_space + new_states = dict(self._base_state_action_space.states) + new_state_action_space = self._base_state_action_space + else: + new_states = dict(self._base_state_action_space.states) | replacements + new_state_action_space = self._base_state_action_space.replace( + states=MappingProxyType(new_states) + ) + + avail_devices = jax.devices() + distributed_grids = {name:grid for name,grid in self.grids.items() if grid.distributed == True} + print(distributed_grids) + if len(distributed_grids) == 1: + n_points = distributed_grids[list(distributed_grids)[0]].to_jax().shape[0] + state_name = list(distributed_grids)[0] + if n_points % len(avail_devices) == 0: + mesh = jax.make_mesh((len(avail_devices),), ('X'), axis_types=(jax.sharding.AxisType.Auto),devices=avail_devices) + new_states[state_name] = jax.device_put(new_states[state_name], jax.NamedSharding(mesh=mesh, spec=jax.P('X',))) + new_state_action_space = self._base_state_action_space.replace( + states=MappingProxyType(new_states) + ) + else: + raise PyLCMError( + "When distributing over one grid, the number of points in the grid " + "needs to be a multiple of the available devices. Gridpoints: " + f" {n_points} Available Devices: {len(avail_devices)}" + ) + if len(distributed_grids) > 1: + permutations = reduce(mul, [grid.to_jax().shape[0] for grid in distributed_grids.values()]) + print(permutations) + if permutations == len(avail_devices): + device_orders = _partitioning_algo(list(distributed_grids.values()), avail_devices) + print(device_orders) + for i, (state_name, grid) in enumerate(distributed_grids.items()): + mesh = jax.make_mesh((grid.to_jax().shape[0],), ('X'), devices=device_orders[i]) + new_states[state_name] = jax.device_put(new_states[state_name],jax.NamedSharding(mesh=mesh, spec=jax.P('X',))) + new_state_action_space = self._base_state_action_space.replace( + states=MappingProxyType(new_states) + ) + else: + raise PyLCMError( + "When distributing over multiple grids, the product of the number of" + " points of the grids needs to match the number of available devices." + f" Gridpoints: {permutations} Available Devices: {len(avail_devices)}" + ) + return new_state_action_space + +def _partitioning_algo(grids: list[Grid], devices: list): + number_devices = len(devices) + print(len(grids[0].to_jax())) + first_groups = [[] for i in range(len(grids[0].to_jax()))] + for i in range(grids[0].to_jax().shape[0]): + for j in range(number_devices//len(grids[0].to_jax())): + first_groups[i].append(devices[j+number_devices//grids[0].to_jax().shape[0]]) + device_orders = [sum(first_groups, [])] + last_groups = [] + for grid in grids[1:]: + n_points = grid.to_jax().shape[0] + next_groups = [[] for i in range(n_points)] + for group in last_groups: + for i in range(n_points): + for j in range(len(group)/n_points): + next_groups[i].append(devices[j+number_devices/n_points]) + device_orders.append(sum(next_groups, [])) + last_groups = next_groups + return device_orders + - new_states = dict(self._base_state_action_space.states) | replacements - return self._base_state_action_space.replace( - states=MappingProxyType(new_states) - ) @dataclasses.dataclass(frozen=True) diff --git a/src/lcm/solution/solve_brute.py b/src/lcm/solution/solve_brute.py index eb70efe9b..4d877c9bc 100644 --- a/src/lcm/solution/solve_brute.py +++ b/src/lcm/solution/solve_brute.py @@ -49,14 +49,15 @@ def solve( # Compute V array shapes and build a consistent next_regime_to_V_arr # template. Using the same pytree structure (keys and shapes) across # all periods avoids JIT re-compilation from pytree mismatches. - regime_V_shapes = _get_regime_V_shapes( + regime_V_shapes = _get_regime_V_shapes_and_shardings( internal_regimes=internal_regimes, internal_params=internal_params, ) + next_regime_to_V_arr = MappingProxyType( { - regime_name: jnp.zeros(shape) - for regime_name, shape in regime_V_shapes.items() + regime_name: jax.device_put(jnp.zeros(shape), device=sharding) + for regime_name, (shape, sharding) in regime_V_shapes.items() } ) @@ -70,7 +71,7 @@ def solve( max_compilation_workers=max_compilation_workers, logger=logger, ) - + solution: dict[int, MappingProxyType[RegimeName, FloatND]] = {} # Async diagnostics accumulators: every `jnp.any(isnan)`, @@ -359,7 +360,7 @@ def _func_dedup_key(*, func: Callable) -> Hashable: return id(func) -def _get_regime_V_shapes( +def _get_regime_V_shapes_and_shardings( *, internal_regimes: MappingProxyType[RegimeName, InternalRegime], internal_params: InternalParams, @@ -377,14 +378,23 @@ def _get_regime_V_shapes( Dict of regime names to V array shapes. """ - shapes: dict[RegimeName, tuple[int, ...]] = {} + shapes_and_shardings: dict[RegimeName, tuple[tuple[int, ...], jax.NamedSharding]] = {} + avail_devices = jax.devices() for regime_name, regime in internal_regimes.items(): state_action_space = regime.state_action_space( regime_params=internal_params[regime_name], ) - shapes[regime_name] = tuple(len(v) for v in state_action_space.states.values()) - return shapes - + spec = [] + for name in state_action_space.states: + if regime.grids[name].distributed: + spec.append('X') + else: + spec.append(None) + shape = tuple(len(v) for v in state_action_space.states.values()) + mesh = jax.make_mesh((len(avail_devices),), ('X'), axis_types=(jax.sharding.AxisType.Auto),devices=avail_devices) + sharding = jax.NamedSharding(mesh, spec= jax.P(*spec)) + shapes_and_shardings[regime_name] = (shape, sharding) + return shapes_and_shardings @dataclass(frozen=True) class _DiagnosticRow: diff --git a/tests/test_distributed.py b/tests/test_distributed.py new file mode 100644 index 000000000..0227e7d86 --- /dev/null +++ b/tests/test_distributed.py @@ -0,0 +1,73 @@ +from lcm.ages import AgeGrid +from lcm.grids import categorical +from lcm.grids.continuous import LinSpacedGrid +from lcm.grids.discrete import DiscreteGrid +from lcm.model import Model +from lcm.regime import MarkovTransition, Regime +from jax import numpy as jnp + +def test_unused_state_raises_error(): + """Model raises error when a state is defined but never used.""" + + @categorical(ordered=False) + class RegimeId: + working_life: int + retirement: int + + @categorical(ordered=True) + class Type: + low: int + high: int + + # Define a regime where 'unused_state' is not used in any function + working_life = Regime( + functions={ + "utility": lambda wealth, consumption, type1, type2: ( + (jnp.log(consumption) + wealth * 0.001)*type1*type2 + ), + }, + states={ + "wealth": LinSpacedGrid( + start=1, + stop=100, + n_points=10, + ), + "type1": DiscreteGrid(Type,distributed= True), + "type2": DiscreteGrid(Type, distributed=True), + }, + state_transitions={ + "wealth": lambda wealth, consumption: wealth - consumption, + "type1": None, + "type2": None, + }, + actions={"consumption": LinSpacedGrid(start=1, stop=50, n_points=10)}, + transition=lambda age: jnp.where(age >=4, RegimeId.retirement, RegimeId.working_life), + active=lambda age: age < 5, + ) + + retirement = Regime( + transition=None, + functions={"utility": lambda wealth, type1, type2: (wealth * 0.5)*type1*type2}, + states={ + "wealth": LinSpacedGrid(start=1, stop=100, n_points=10), + "type1": DiscreteGrid(Type, distributed=True), + "type2": DiscreteGrid(Type, distributed=True), + }, + active=lambda age: age >= 5, + ) + + model = Model( + regimes={"working_life": working_life, "retirement": retirement}, + ages=AgeGrid(start=0, stop=5, step="Y"), + regime_id_class=RegimeId, + ) + res = model.simulate(params={"discount_factor": 0.95}, + initial_conditions={ + "age": jnp.full(5, 0), + "wealth": jnp.full(5, 100.0), + "type1": jnp.full(5, 1), + "type2": jnp.full(5, 1), + "regime": jnp.zeros(5, dtype=jnp.int32), + }, + period_to_regime_to_V_arr=None, + seed=12345,) From e3bd7e48e34e1991d797bbf68468069f348c21b3 Mon Sep 17 00:00:00 2001 From: mj023 Date: Fri, 8 May 2026 21:53:38 +0200 Subject: [PATCH 02/77] Add second distribution pattern --- src/lcm/grids/base.py | 1 - src/lcm/grids/discrete.py | 6 ++- src/lcm/interfaces.py | 93 ++++++++++++++++----------------- src/lcm/solution/solve_brute.py | 31 ++++++----- 4 files changed, 67 insertions(+), 64 deletions(-) diff --git a/src/lcm/grids/base.py b/src/lcm/grids/base.py index 9101ceff3..4a0a071e9 100644 --- a/src/lcm/grids/base.py +++ b/src/lcm/grids/base.py @@ -16,7 +16,6 @@ def batch_size(self) -> int: """ - @property @abstractmethod def distributed(self) -> bool: diff --git a/src/lcm/grids/discrete.py b/src/lcm/grids/discrete.py index e2f5fa32f..0ad9ade27 100644 --- a/src/lcm/grids/discrete.py +++ b/src/lcm/grids/discrete.py @@ -19,7 +19,9 @@ class DiscreteGrid(Grid): """ - def __init__(self, category_class: type, batch_size: int = 0, distributed = False) -> None: + def __init__( + self, category_class: type, batch_size: int = 0, distributed=False + ) -> None: _validate_discrete_grid(category_class) names_and_values = get_field_names_and_values(category_class) self.__categories = tuple(names_and_values.keys()) @@ -47,7 +49,7 @@ def ordered(self) -> bool: def batch_size(self) -> int: """Return batch size during solution.""" return self.__batch_size - + @property def distributed(self) -> bool: """Return batch size during solution.""" diff --git a/src/lcm/interfaces.py b/src/lcm/interfaces.py index 899a5b89e..1d5b904a7 100644 --- a/src/lcm/interfaces.py +++ b/src/lcm/interfaces.py @@ -1,13 +1,14 @@ import dataclasses from collections.abc import Callable +from functools import reduce +from operator import mul from types import MappingProxyType from typing import cast -from functools import reduce -from operator import mul +import jax import pandas as pd from jax import Array -import jax + from lcm.exceptions import PyLCMError from lcm.grids import Grid, IrregSpacedGrid from lcm.shocks import _ShockGrid @@ -314,65 +315,61 @@ def state_action_space(self, regime_params: FlatRegimeParams) -> StateActionSpac | action_replacements ) if action_replacements - else None + else dict(self._base_state_action_space.continuous_actions) ) - + avail_devices = jax.devices() - distributed_grids = {name:grid for name,grid in self.grids.items() if grid.distributed == True} - print(distributed_grids) + distributed_grids = { + name: grid for name, grid in self.grids.items() if grid.distributed == True + } if len(distributed_grids) == 1: n_points = distributed_grids[list(distributed_grids)[0]].to_jax().shape[0] state_name = list(distributed_grids)[0] if n_points % len(avail_devices) == 0: - mesh = jax.make_mesh((len(avail_devices),), ('X'), axis_types=(jax.sharding.AxisType.Auto),devices=avail_devices) - new_states[state_name] = jax.device_put(new_states[state_name], jax.NamedSharding(mesh=mesh, spec=jax.P('X',))) + mesh = jax.make_mesh( + (len(avail_devices),), + ("X"), + axis_types=(jax.sharding.AxisType.Auto), + devices=avail_devices, + ) + new_states[state_name] = jax.device_put( + new_states[state_name], + jax.NamedSharding(mesh=mesh, spec=jax.P("X")), + ) else: raise PyLCMError( - "When distributing over one grid, the number of points in the grid " - "needs to be a multiple of the available devices. Gridpoints: " - f" {n_points} Available Devices: {len(avail_devices)}" - ) + "When distributing over one grid, the number of points in the grid " + "needs to be a multiple of the available devices. Gridpoints: " + f" {n_points} Available Devices: {len(avail_devices)}" + ) if len(distributed_grids) > 1: - permutations = reduce(mul, [grid.to_jax().shape[0] for grid in distributed_grids.values()]) - print(permutations) + permutations = reduce( + mul, [grid.to_jax().shape[0] for grid in distributed_grids.values()] + ) if permutations == len(avail_devices): - device_orders = _partitioning_algo(list(distributed_grids.values()), avail_devices) - print(device_orders) - for i, (state_name, grid) in enumerate(distributed_grids.items()): - mesh = jax.make_mesh((grid.to_jax().shape[0],), ('X'), devices=device_orders[i]) - new_states[state_name] = jax.device_put(new_states[state_name],jax.NamedSharding(mesh=mesh, spec=jax.P('X',))) + mesh = jax.make_mesh( + tuple(len(grid.to_jax()) for grid in distributed_grids.values()), + tuple(distributed_grids.keys()), + axis_types=tuple( + jax.sharding.AxisType.Auto for grid in distributed_grids + ), + devices=avail_devices, + ) + for state_name in distributed_grids: + new_states[state_name] = jax.device_put( + new_states[state_name], + jax.NamedSharding(mesh=mesh, spec=jax.P(state_name)), + ) else: raise PyLCMError( - "When distributing over multiple grids, the product of the number of" - " points of the grids needs to match the number of available devices." - f" Gridpoints: {permutations} Available Devices: {len(avail_devices)}" + "When distributing over multiple grids, the product of the number of" + " points of the grids needs to match the number of available devices." + f" Gridpoints: {permutations} Available Devices: {len(avail_devices)}" ) return self._base_state_action_space.replace( - states=MappingProxyType(new_states), - continuous_actions=MappingProxyType(new_continuous_actions) - ) - -def _partitioning_algo(grids: list[Grid], devices: list): - number_devices = len(devices) - print(len(grids[0].to_jax())) - first_groups = [[] for i in range(len(grids[0].to_jax()))] - for i in range(grids[0].to_jax().shape[0]): - for j in range(number_devices//len(grids[0].to_jax())): - first_groups[i].append(devices[j+number_devices//grids[0].to_jax().shape[0]]) - device_orders = [sum(first_groups, [])] - last_groups = [] - for grid in grids[1:]: - n_points = grid.to_jax().shape[0] - next_groups = [[] for i in range(n_points)] - for group in last_groups: - for i in range(n_points): - for j in range(len(group)/n_points): - next_groups[i].append(devices[j+number_devices/n_points]) - device_orders.append(sum(next_groups, [])) - last_groups = next_groups - return device_orders - - + states=MappingProxyType(new_states), + continuous_actions=MappingProxyType(new_continuous_actions), + ) @dataclasses.dataclass(frozen=True) diff --git a/src/lcm/solution/solve_brute.py b/src/lcm/solution/solve_brute.py index 4d877c9bc..c33fecbcb 100644 --- a/src/lcm/solution/solve_brute.py +++ b/src/lcm/solution/solve_brute.py @@ -56,8 +56,8 @@ def solve( next_regime_to_V_arr = MappingProxyType( { - regime_name: jax.device_put(jnp.zeros(shape), device=sharding) - for regime_name, (shape, sharding) in regime_V_shapes.items() + regime_name: jax.device_put(jnp.zeros(shape)) + for regime_name, shape in regime_V_shapes.items() } ) @@ -71,7 +71,7 @@ def solve( max_compilation_workers=max_compilation_workers, logger=logger, ) - + solution: dict[int, MappingProxyType[RegimeName, FloatND]] = {} # Async diagnostics accumulators: every `jnp.any(isnan)`, @@ -134,7 +134,6 @@ def solve( period=jnp.int32(period), age=ages.values[period], ) - # Async reductions: gated on log level. `"off"` skips # everything — no kernel launches, no host syncs, no # NaN fail-fast. `"warning"` / `"progress"` launches the @@ -325,9 +324,7 @@ def _compile_and_log( compiled[func_id] = comp # Map back to (regime, period) keys. - return { - key: compiled[_func_dedup_key(func=func)] for key, func in all_functions.items() - } + return {key: func for key, func in all_functions.items()} def _resolve_compilation_workers(*, max_compilation_workers: int | None) -> int: @@ -378,8 +375,10 @@ def _get_regime_V_shapes_and_shardings( Dict of regime names to V array shapes. """ - shapes_and_shardings: dict[RegimeName, tuple[tuple[int, ...], jax.NamedSharding]] = {} - avail_devices = jax.devices() + shapes_and_shardings: dict[ + RegimeName, tuple[tuple[int, ...], jax.NamedSharding] + ] = {} + avail_devices = jax.devices() for regime_name, regime in internal_regimes.items(): state_action_space = regime.state_action_space( regime_params=internal_params[regime_name], @@ -387,15 +386,21 @@ def _get_regime_V_shapes_and_shardings( spec = [] for name in state_action_space.states: if regime.grids[name].distributed: - spec.append('X') + spec.append("X") else: spec.append(None) shape = tuple(len(v) for v in state_action_space.states.values()) - mesh = jax.make_mesh((len(avail_devices),), ('X'), axis_types=(jax.sharding.AxisType.Auto),devices=avail_devices) - sharding = jax.NamedSharding(mesh, spec= jax.P(*spec)) - shapes_and_shardings[regime_name] = (shape, sharding) + mesh = jax.make_mesh( + (len(avail_devices),), + ("X"), + axis_types=(jax.sharding.AxisType.Auto), + devices=avail_devices, + ) + + shapes_and_shardings[regime_name] = shape return shapes_and_shardings + @dataclass(frozen=True) class _DiagnosticRow: """Metadata captured during the backward-induction loop. From 1b2baa31676aff8f4414f19cc383e7ef038f0977 Mon Sep 17 00:00:00 2001 From: Hans-Martin von Gaudecker Date: Mon, 11 May 2026 09:22:30 +0200 Subject: [PATCH 03/77] Add parallelization across multiple devices during solve (#346) Add a `distributed=True` flag on `DiscreteGrid` to shard the grid across JAX devices, thread the distribution pattern through `solve_brute._get_regime_V_shapes_and_shardings`, and validate the device-count match at runtime via a new check in `InternalRegime.state_action_space`. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/lcm/grids/base.py | 10 ++++ src/lcm/grids/continuous.py | 2 + src/lcm/grids/discrete.py | 10 +++- src/lcm/interfaces.py | 64 +++++++++++++++++++++++--- src/lcm/solution/solve_brute.py | 39 +++++++++++----- tests/test_distributed.py | 81 +++++++++++++++++++++++++++++++++ 6 files changed, 186 insertions(+), 20 deletions(-) create mode 100644 tests/test_distributed.py diff --git a/src/lcm/grids/base.py b/src/lcm/grids/base.py index fb5698cb6..4a0a071e9 100644 --- a/src/lcm/grids/base.py +++ b/src/lcm/grids/base.py @@ -16,6 +16,16 @@ def batch_size(self) -> int: """ + @property + @abstractmethod + def distributed(self) -> bool: + """Whether to distribute the grid over the available devices. + + `ContinuousGrid` overrides this via its dataclass field. + `DiscreteGrid` overrides this via its own property. + + """ + @abstractmethod def to_jax(self) -> Int1D | Float1D: """Convert the grid to a Jax array.""" diff --git a/src/lcm/grids/continuous.py b/src/lcm/grids/continuous.py index 839d4cefb..e92b00194 100644 --- a/src/lcm/grids/continuous.py +++ b/src/lcm/grids/continuous.py @@ -29,6 +29,8 @@ class ContinuousGrid(Grid): batch_size: int = 0 """Size of the batches that are looped over during the solution.""" + distributed: bool = False + """Size of the batches that are looped over during the solution.""" @overload def get_coordinate(self, value: ScalarFloat) -> ScalarFloat: ... diff --git a/src/lcm/grids/discrete.py b/src/lcm/grids/discrete.py index 72ded5eae..51d6585d4 100644 --- a/src/lcm/grids/discrete.py +++ b/src/lcm/grids/discrete.py @@ -19,13 +19,16 @@ class DiscreteGrid(Grid): """ - def __init__(self, category_class: type, batch_size: int = 0) -> None: + def __init__( + self, category_class: type, batch_size: int = 0, distributed=False + ) -> None: _validate_discrete_grid(category_class) names_and_values = get_field_names_and_values(category_class) self.__categories = tuple(names_and_values.keys()) self.__codes = tuple(names_and_values.values()) self.__ordered: bool = getattr(category_class, "_ordered", False) self.__batch_size: int = batch_size + self.__distributed: bool = distributed @property def categories(self) -> tuple[str, ...]: @@ -47,6 +50,11 @@ def batch_size(self) -> int: """Return batch size during solution.""" return self.__batch_size + @property + def distributed(self) -> bool: + """Return batch size during solution.""" + return self.__distributed + def to_jax(self) -> Int1D: """Convert the grid to a Jax array. diff --git a/src/lcm/interfaces.py b/src/lcm/interfaces.py index c74842e6a..5c7b8be74 100644 --- a/src/lcm/interfaces.py +++ b/src/lcm/interfaces.py @@ -1,11 +1,15 @@ import dataclasses from collections.abc import Callable +from functools import reduce +from operator import mul from types import MappingProxyType from typing import cast +import jax import pandas as pd from jax import Array +from lcm.exceptions import PyLCMError from lcm.grids import Grid, IrregSpacedGrid from lcm.shocks import _ShockGrid from lcm.typing import ( @@ -294,15 +298,12 @@ def state_action_space(self, regime_params: FlatRegimeParams) -> StateActionSpac shock_kw[p] = cast("float", all_params[f"{name}__{p}"]) state_replacements[name] = spec.compute_gridpoints(**shock_kw) - if not state_replacements and not action_replacements: - return self._base_state_action_space - new_states = ( MappingProxyType( dict(self._base_state_action_space.states) | state_replacements ) if state_replacements - else None + else dict(self._base_state_action_space.states) ) new_continuous_actions = ( MappingProxyType( @@ -310,11 +311,60 @@ def state_action_space(self, regime_params: FlatRegimeParams) -> StateActionSpac | action_replacements ) if action_replacements - else None + else dict(self._base_state_action_space.continuous_actions) ) + + avail_devices = jax.devices() + distributed_grids = { + name: grid for name, grid in self.grids.items() if grid.distributed == True + } + if len(distributed_grids) == 1: + n_points = distributed_grids[list(distributed_grids)[0]].to_jax().shape[0] + state_name = list(distributed_grids)[0] + if n_points % len(avail_devices) == 0: + mesh = jax.make_mesh( + (len(avail_devices),), + ("X"), + axis_types=(jax.sharding.AxisType.Auto), + devices=avail_devices, + ) + new_states[state_name] = jax.device_put( + new_states[state_name], + jax.NamedSharding(mesh=mesh, spec=jax.P("X")), + ) + else: + raise PyLCMError( + "When distributing over one grid, the number of points in the grid " + "needs to be a multiple of the available devices. Gridpoints: " + f" {n_points} Available Devices: {len(avail_devices)}" + ) + if len(distributed_grids) > 1: + permutations = reduce( + mul, [grid.to_jax().shape[0] for grid in distributed_grids.values()] + ) + if permutations == len(avail_devices): + mesh = jax.make_mesh( + tuple(len(grid.to_jax()) for grid in distributed_grids.values()), + tuple(distributed_grids.keys()), + axis_types=tuple( + jax.sharding.AxisType.Auto for grid in distributed_grids + ), + devices=avail_devices, + ) + for state_name in distributed_grids: + new_states[state_name] = jax.device_put( + new_states[state_name], + jax.NamedSharding(mesh=mesh, spec=jax.P(state_name)), + ) + else: + raise PyLCMError( + "When distributing over multiple grids, the product of the number of" + " points of the grids needs to match the number of available devices." + f" Gridpoints: {permutations} Available Devices: {len(avail_devices)}" + ) return self._base_state_action_space.replace( - states=new_states, - continuous_actions=new_continuous_actions, + states=MappingProxyType(new_states), + continuous_actions=MappingProxyType(new_continuous_actions), ) diff --git a/src/lcm/solution/solve_brute.py b/src/lcm/solution/solve_brute.py index 77e63e24f..03a8f1b52 100644 --- a/src/lcm/solution/solve_brute.py +++ b/src/lcm/solution/solve_brute.py @@ -49,13 +49,14 @@ def solve( # Compute V array shapes and build a consistent next_regime_to_V_arr # template. Using the same pytree structure (keys and shapes) across # all periods avoids JIT re-compilation from pytree mismatches. - regime_V_shapes = _get_regime_V_shapes( + regime_V_shapes = _get_regime_V_shapes_and_shardings( internal_regimes=internal_regimes, internal_params=internal_params, ) + next_regime_to_V_arr = MappingProxyType( { - regime_name: jnp.zeros(shape) + regime_name: jax.device_put(jnp.zeros(shape)) for regime_name, shape in regime_V_shapes.items() } ) @@ -146,7 +147,6 @@ def solve( period=jnp.int32(period), age=ages.values[period], ) - # Async reductions: gated on log level. `"off"` skips # everything — no kernel launches, no host syncs, no # NaN fail-fast. `"warning"` / `"progress"` folds two @@ -351,9 +351,7 @@ def _compile_and_log( compiled[func_id] = comp # Map back to (regime, period) keys. - return { - key: compiled[_func_dedup_key(func=func)] for key, func in all_functions.items() - } + return {key: func for key, func in all_functions.items()} def _resolve_compilation_workers(*, max_compilation_workers: int | None) -> int: @@ -386,7 +384,7 @@ def _func_dedup_key(*, func: Callable) -> Hashable: return id(func) -def _get_regime_V_shapes( +def _get_regime_V_shapes_and_shardings( *, internal_regimes: MappingProxyType[RegimeName, InternalRegime], internal_params: InternalParams, @@ -404,13 +402,30 @@ def _get_regime_V_shapes( Dict of regime names to V array shapes. """ - shapes: dict[RegimeName, tuple[int, ...]] = {} + shapes_and_shardings: dict[ + RegimeName, tuple[tuple[int, ...], jax.NamedSharding] + ] = {} + avail_devices = jax.devices() for regime_name, regime in internal_regimes.items(): state_action_space = regime.state_action_space( regime_params=internal_params[regime_name], ) - shapes[regime_name] = tuple(len(v) for v in state_action_space.states.values()) - return shapes + spec = [] + for name in state_action_space.states: + if regime.grids[name].distributed: + spec.append("X") + else: + spec.append(None) + shape = tuple(len(v) for v in state_action_space.states.values()) + mesh = jax.make_mesh( + (len(avail_devices),), + ("X"), + axis_types=(jax.sharding.AxisType.Auto), + devices=avail_devices, + ) + + shapes_and_shardings[regime_name] = shape + return shapes_and_shardings @dataclass(frozen=True) @@ -559,9 +574,9 @@ def _reconstruct_next_regime_to_V_arr( We rebuild the same mapping post-hoc from `solution`. The shapes come from the regime's state-action space at the supplied params — identical to what - `_get_regime_V_shapes` saw during solve setup. + `_get_regime_V_shapes_and_shardings` saw during solve setup. """ - regime_V_shapes = _get_regime_V_shapes( + regime_V_shapes = _get_regime_V_shapes_and_shardings( internal_regimes=internal_regimes, internal_params=internal_params, ) diff --git a/tests/test_distributed.py b/tests/test_distributed.py new file mode 100644 index 000000000..f5729478d --- /dev/null +++ b/tests/test_distributed.py @@ -0,0 +1,81 @@ +from jax import numpy as jnp + +from lcm.ages import AgeGrid +from lcm.grids import categorical +from lcm.grids.continuous import LinSpacedGrid +from lcm.grids.discrete import DiscreteGrid +from lcm.model import Model +from lcm.regime import Regime + + +def test_unused_state_raises_error(): + """Model raises error when a state is defined but never used.""" + + @categorical(ordered=False) + class RegimeId: + working_life: int + retirement: int + + @categorical(ordered=True) + class Type: + low: int + high: int + + # Define a regime where 'unused_state' is not used in any function + working_life = Regime( + functions={ + "utility": lambda wealth, consumption, type1, type2: ( + (jnp.log(consumption) + wealth * 0.001) * type1 * type2 + ), + }, + states={ + "wealth": LinSpacedGrid( + start=1, + stop=100, + n_points=10, + ), + "type1": DiscreteGrid(Type, distributed=True), + "type2": DiscreteGrid(Type, distributed=True), + }, + state_transitions={ + "wealth": lambda wealth, consumption: wealth - consumption, + "type1": None, + "type2": None, + }, + actions={"consumption": LinSpacedGrid(start=1, stop=50, n_points=10)}, + transition=lambda age: jnp.where( + age >= 4, RegimeId.retirement, RegimeId.working_life + ), + active=lambda age: age < 5, + ) + + retirement = Regime( + transition=None, + functions={ + "utility": lambda wealth, type1, type2: (wealth * 0.5) * type1 * type2 + }, + states={ + "wealth": LinSpacedGrid(start=1, stop=100, n_points=10), + "type1": DiscreteGrid(Type, distributed=True), + "type2": DiscreteGrid(Type, distributed=True), + }, + active=lambda age: age >= 5, + ) + + model = Model( + regimes={"working_life": working_life, "retirement": retirement}, + ages=AgeGrid(start=0, stop=5, step="Y"), + regime_id_class=RegimeId, + ) + res = model.simulate( + params={"discount_factor": 0.95}, + initial_conditions={ + "age": jnp.full(5, 0), + "wealth": jnp.full(5, 100.0), + "type1": jnp.full(5, 1), + "type2": jnp.full(5, 1), + "regime": jnp.zeros(5, dtype=jnp.int32), + }, + period_to_regime_to_V_arr=None, + seed=12345, + ) From 7eeaf37021376d1c3de248f951c0be163811a73b Mon Sep 17 00:00:00 2001 From: mj023 Date: Mon, 11 May 2026 16:45:54 +0200 Subject: [PATCH 04/77] Fix AOT + Add Simulation --- src/lcm/simulation/initial_conditions.py | 9 +++++++++ src/lcm/solution/solve_brute.py | 23 ++++++++++++++--------- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/src/lcm/simulation/initial_conditions.py b/src/lcm/simulation/initial_conditions.py index 3db3063ee..c5cde86e3 100644 --- a/src/lcm/simulation/initial_conditions.py +++ b/src/lcm/simulation/initial_conditions.py @@ -63,6 +63,13 @@ def build_initial_states( n_subjects = len(next(iter(initial_states.values()))) for regime_name, internal_regime in internal_regimes.items(): + # Logic for distribution of subjects over devices + distributed = any([grid.distributed for grid in internal_regime.grids.values()]) + devices = jax.devices() + mesh = jax.make_mesh( + (len(devices),), ("X"), (jax.sharding.AxisType.Auto), devices=devices + ) + sharding = jax.NamedSharding(mesh=mesh, spec=jax.P("X")) for state_name in _get_regime_state_names(internal_regime): key = f"{regime_name}__{state_name}" grid = internal_regime.grids[state_name] @@ -86,6 +93,8 @@ def build_initial_states( ) else: flat[key] = jnp.full(n_subjects, jnp.nan, dtype=canonical_float_dtype()) + if distributed: + flat[key] = jax.device_put(flat[key], device=sharding) return MappingProxyType(flat) diff --git a/src/lcm/solution/solve_brute.py b/src/lcm/solution/solve_brute.py index 03a8f1b52..9918de650 100644 --- a/src/lcm/solution/solve_brute.py +++ b/src/lcm/solution/solve_brute.py @@ -56,8 +56,8 @@ def solve( next_regime_to_V_arr = MappingProxyType( { - regime_name: jax.device_put(jnp.zeros(shape)) - for regime_name, shape in regime_V_shapes.items() + regime_name: jax.device_put(jnp.zeros(shape), sharding) + for regime_name, (shape, sharding) in regime_V_shapes.items() } ) @@ -351,7 +351,9 @@ def _compile_and_log( compiled[func_id] = comp # Map back to (regime, period) keys. - return {key: func for key, func in all_functions.items()} + return { + key: compiled[_func_dedup_key(func=func)] for key, func in all_functions.items() + } def _resolve_compilation_workers(*, max_compilation_workers: int | None) -> int: @@ -413,18 +415,21 @@ def _get_regime_V_shapes_and_shardings( spec = [] for name in state_action_space.states: if regime.grids[name].distributed: - spec.append("X") + spec.append(name) else: spec.append(None) shape = tuple(len(v) for v in state_action_space.states.values()) + dist_shape = tuple(shape[i] for i in range(len(spec)) if spec[i] is not None) mesh = jax.make_mesh( - (len(avail_devices),), - ("X"), - axis_types=(jax.sharding.AxisType.Auto), + dist_shape, + (name for name in spec if name is not None), + axis_types=tuple( + jax.sharding.AxisType.Auto for i in range(len(dist_shape)) + ), devices=avail_devices, ) - - shapes_and_shardings[regime_name] = shape + sharding = jax.sharding.NamedSharding(mesh=mesh, spec=jax.P(*spec)) + shapes_and_shardings[regime_name] = (shape, sharding) return shapes_and_shardings From da3b3f01ea9b7eae086009a188f4df54c1bf1bfc Mon Sep 17 00:00:00 2001 From: mj023 Date: Tue, 12 May 2026 18:00:54 +0200 Subject: [PATCH 05/77] Fix tests --- src/lcm/grids/continuous.py | 8 +++ src/lcm/interfaces.py | 106 ++++++++++++++++------------- src/lcm/solution/solve_brute.py | 10 +-- src/lcm/state_action_space.py | 10 ++- tests/solution/test_solve_brute.py | 2 + tests/test_distributed.py | 50 +++++++++++--- tests/test_nan_diagnostics.py | 1 + 7 files changed, 125 insertions(+), 62 deletions(-) diff --git a/src/lcm/grids/continuous.py b/src/lcm/grids/continuous.py index e92b00194..1a0744bda 100644 --- a/src/lcm/grids/continuous.py +++ b/src/lcm/grids/continuous.py @@ -66,6 +66,7 @@ def __init__( stop: float | ScalarFloat, n_points: int | ScalarInt, batch_size: int = 0, + distributed: bool = False, ) -> None: _init_uniform_grid( self, @@ -73,6 +74,7 @@ def __init__( stop=stop, n_points=n_points, batch_size=batch_size, + distributed=distributed, requires_positive_start=False, ) @@ -153,6 +155,7 @@ def __init__( stop: float | ScalarFloat, n_points: int | ScalarInt, batch_size: int = 0, + distributed: bool = False, ) -> None: _init_uniform_grid( self, @@ -160,6 +163,7 @@ def __init__( stop=stop, n_points=n_points, batch_size=batch_size, + distributed=distributed, requires_positive_start=True, ) @@ -190,6 +194,7 @@ def _init_uniform_grid( stop: float | ScalarFloat, n_points: int | ScalarInt, batch_size: int, + distributed: bool, requires_positive_start: bool, ) -> None: """Cast `start` / `stop` / `n_points` to canonical JAX scalars, validate, store. @@ -215,6 +220,7 @@ def _init_uniform_grid( object.__setattr__(grid, "stop", stop_jax) object.__setattr__(grid, "n_points", n_points_jax) object.__setattr__(grid, "batch_size", batch_size) + object.__setattr__(grid, "distributed", distributed) @dataclass(frozen=True, kw_only=True, init=False) @@ -248,6 +254,7 @@ def __init__( points: Sequence[float] | Float1D | None = None, n_points: int | None = None, batch_size: int = 0, + distributed: bool = False, ) -> None: if points is not None: _validate_irreg_spaced_grid(points) @@ -274,6 +281,7 @@ def __init__( object.__setattr__(self, "points", stored_points) object.__setattr__(self, "n_points", n_points) object.__setattr__(self, "batch_size", batch_size) + object.__setattr__(self, "distributed", distributed) @property def pass_points_at_runtime(self) -> bool: diff --git a/src/lcm/interfaces.py b/src/lcm/interfaces.py index 5c7b8be74..ac50daeb6 100644 --- a/src/lcm/interfaces.py +++ b/src/lcm/interfaces.py @@ -63,6 +63,9 @@ class StateActionSpace: states: MappingProxyType[StateName, ContinuousState | DiscreteState] """Immutable mapping of state variable names to their values.""" + distributed_states: MappingProxyType[StateName, ContinuousState | DiscreteState] + """Immutable mapping of distributed state variable names to their values.""" + discrete_actions: MappingProxyType[ActionName, DiscreteAction] """Immutable mapping of discrete action variable names to their values.""" @@ -314,59 +317,66 @@ def state_action_space(self, regime_params: FlatRegimeParams) -> StateActionSpac else dict(self._base_state_action_space.continuous_actions) ) - avail_devices = jax.devices() - distributed_grids = { - name: grid for name, grid in self.grids.items() if grid.distributed == True - } - if len(distributed_grids) == 1: - n_points = distributed_grids[list(distributed_grids)[0]].to_jax().shape[0] - state_name = list(distributed_grids)[0] - if n_points % len(avail_devices) == 0: - mesh = jax.make_mesh( - (len(avail_devices),), - ("X"), - axis_types=(jax.sharding.AxisType.Auto), - devices=avail_devices, - ) - new_states[state_name] = jax.device_put( - new_states[state_name], - jax.NamedSharding(mesh=mesh, spec=jax.P("X")), - ) - else: - raise PyLCMError( - "When distributing over one grid, the number of points in the grid " - "needs to be a multiple of the available devices. Gridpoints: " - f" {n_points} Available Devices: {len(avail_devices)}" - ) - if len(distributed_grids) > 1: - permutations = reduce( - mul, [grid.to_jax().shape[0] for grid in distributed_grids.values()] - ) - if permutations == len(avail_devices): - mesh = jax.make_mesh( - tuple(len(grid.to_jax()) for grid in distributed_grids.values()), - tuple(distributed_grids.keys()), - axis_types=tuple( - jax.sharding.AxisType.Auto for grid in distributed_grids - ), - devices=avail_devices, - ) - for state_name in distributed_grids: - new_states[state_name] = jax.device_put( - new_states[state_name], - jax.NamedSharding(mesh=mesh, spec=jax.P(state_name)), - ) - else: - raise PyLCMError( - "When distributing over multiple grids, the product of the number of" - " points of the grids needs to match the number of available devices." - f" Gridpoints: {permutations} Available Devices: {len(avail_devices)}" - ) + return self._base_state_action_space.replace( states=MappingProxyType(new_states), continuous_actions=MappingProxyType(new_continuous_actions), ) +def distribute_states_to_devices(new_states, grids): + + distributed_states = new_states + + avail_devices = jax.devices() + distributed_grids = { + name: grid for name, grid in grids.items() if grid.distributed + } + if len(distributed_grids) == 1: + state_name = next(iter(distributed_grids)) + n_points = distributed_grids[state_name].to_jax().shape[0] + if n_points % len(avail_devices) == 0: + mesh = jax.make_mesh( + (len(avail_devices),), + ("X"), + axis_types=(jax.sharding.AxisType.Auto), + devices=avail_devices, + ) + distributed_states[state_name] = jax.device_put( + new_states[state_name], + jax.NamedSharding(mesh=mesh, spec=jax.P("X")), + ) + else: + raise PyLCMError( + "When distributing over one grid, the number of points in the grid " + "needs to be a multiple of the available devices. Gridpoints: " + f" {n_points} Available Devices: {len(avail_devices)}" + ) + if len(distributed_grids) > 1: + permutations = reduce( + mul, [grid.to_jax().shape[0] for grid in distributed_grids.values()] + ) + if permutations == len(avail_devices): + mesh = jax.make_mesh( + tuple(len(grid.to_jax()) for grid in distributed_grids.values()), + tuple(distributed_grids.keys()), + axis_types=tuple( + jax.sharding.AxisType.Auto for grid in distributed_grids + ), + devices=avail_devices, + ) + for state_name in distributed_grids: + distributed_states[state_name] = jax.device_put( + new_states[state_name], + jax.NamedSharding(mesh=mesh, spec=jax.P(state_name)), + ) + else: + raise PyLCMError( + "When distributing over multiple grids, the product of the" + " number of points of the grids needs to match the number" + f" of available devices. Gridpoints: {permutations} Available" + f"Devices: {len(avail_devices)}" + ) + return distributed_states @dataclasses.dataclass(frozen=True) class PeriodRegimeSimulationData: diff --git a/src/lcm/solution/solve_brute.py b/src/lcm/solution/solve_brute.py index 9918de650..7905d62cd 100644 --- a/src/lcm/solution/solve_brute.py +++ b/src/lcm/solution/solve_brute.py @@ -414,12 +414,14 @@ def _get_regime_V_shapes_and_shardings( ) spec = [] for name in state_action_space.states: - if regime.grids[name].distributed: + if name in state_action_space.distributed_states: spec.append(name) else: spec.append(None) shape = tuple(len(v) for v in state_action_space.states.values()) - dist_shape = tuple(shape[i] for i in range(len(spec)) if spec[i] is not None) + dist_shape = tuple( + len(v) for v in state_action_space.distributed_states.values() + ) mesh = jax.make_mesh( dist_shape, (name for name in spec if name is not None), @@ -581,13 +583,13 @@ def _reconstruct_next_regime_to_V_arr( the regime's state-action space at the supplied params — identical to what `_get_regime_V_shapes_and_shardings` saw during solve setup. """ - regime_V_shapes = _get_regime_V_shapes_and_shardings( + regime_V_shapes_and_shardings = _get_regime_V_shapes_and_shardings( internal_regimes=internal_regimes, internal_params=internal_params, ) later_periods = sorted(p for p in solution if p > period) result: dict[RegimeName, FloatND] = {} - for regime_name, shape in regime_V_shapes.items(): + for regime_name, (shape, _sharding) in regime_V_shapes_and_shardings.items(): rolled: FloatND | None = None for q in later_periods: if regime_name in solution[q]: diff --git a/src/lcm/state_action_space.py b/src/lcm/state_action_space.py index 7c88e1658..9faa24165 100644 --- a/src/lcm/state_action_space.py +++ b/src/lcm/state_action_space.py @@ -37,13 +37,20 @@ def create_state_action_space( sn: _grid_to_jax_or_placeholder(grids[sn]) for sn in variable_info.query("is_state").index } + distributed_states = { + sn: _grid_to_jax_or_placeholder(grids[sn]) + for sn in variable_info.query("is_state").index + if grids[sn].distributed + } else: _validate_all_states_present( provided_states=states, required_state_names=set(variable_info.query("is_state").index), ) _states = states - + distributed_states = { + sn: state for sn, state in states.items() if grids[sn].distributed + } discrete_actions = { name: grids[name].to_jax() for name in variable_info.query("is_action & is_discrete").index @@ -58,6 +65,7 @@ def create_state_action_space( return StateActionSpace( states=MappingProxyType(_states), + distributed_states=MappingProxyType(distributed_states), discrete_actions=MappingProxyType(discrete_actions), continuous_actions=MappingProxyType(continuous_actions), state_and_discrete_action_names=state_and_discrete_action_names, diff --git a/tests/solution/test_solve_brute.py b/tests/solution/test_solve_brute.py index 9b6095d14..15caab23f 100644 --- a/tests/solution/test_solve_brute.py +++ b/tests/solution/test_solve_brute.py @@ -77,6 +77,7 @@ def test_solve_brute(): "wealth": jnp.array([0.0, 1.0, 2.0]), } ), + distributed_states=MappingProxyType({}), state_and_discrete_action_names=("lazy", "labor_supply", "wealth"), ) # ================================================================================== @@ -159,6 +160,7 @@ def test_solve_brute_single_period_Qc_arr(): } ), states=MappingProxyType({}), + distributed_states=MappingProxyType({}), state_and_discrete_action_names=("a", "b", "c"), ) diff --git a/tests/test_distributed.py b/tests/test_distributed.py index f5729478d..bed8a5125 100644 --- a/tests/test_distributed.py +++ b/tests/test_distributed.py @@ -1,3 +1,5 @@ +import jax +import pytest from jax import numpy as jnp from lcm.ages import AgeGrid @@ -7,10 +9,17 @@ from lcm.model import Model from lcm.regime import Regime +try: + jax.config.update("jax_num_cpu_devices", 4) + _HAS_4_CPU = len(jax.devices()) >= 4 +except jax.errors.JaxRuntimeError: + _HAS_4_CPU = False -def test_unused_state_raises_error(): - """Model raises error when a state is defined but never used.""" +_skip_no_4_cpu = pytest.mark.skipif(not _HAS_4_CPU, reason="requires 4 CPU's") + +@pytest.fixture +def distributed_model(): @categorical(ordered=False) class RegimeId: working_life: int @@ -62,20 +71,43 @@ class Type: active=lambda age: age >= 5, ) - model = Model( + return Model( regimes={"working_life": working_life, "retirement": retirement}, ages=AgeGrid(start=0, stop=5, step="Y"), regime_id_class=RegimeId, ) - res = model.simulate( + + +@_skip_no_4_cpu +def test_solution_running_on_multiple_cpus(distributed_model): + """Test that distribution over multiple CPU's works.""" + + period_to_regime_to_V_arr = distributed_model.solve( + params={"discount_factor": 0.95}, + ) + + assert period_to_regime_to_V_arr[0]["working_life"].sharding.num_devices == 4 + + +@_skip_no_4_cpu +def test_simulation_running_on_multiple_cpus(distributed_model): + """Test that distribution over multiple CPU's works.""" + + res = distributed_model.simulate( params={"discount_factor": 0.95}, initial_conditions={ - "age": jnp.full(5, 0), - "wealth": jnp.full(5, 100.0), - "type1": jnp.full(5, 1), - "type2": jnp.full(5, 1), - "regime": jnp.zeros(5, dtype=jnp.int32), + "age": jnp.full(4, 0), + "wealth": jnp.full(4, 100.0), + "type1": jnp.full(4, 1), + "type2": jnp.full(4, 1), + "regime": jnp.zeros(4, dtype=jnp.int32), }, period_to_regime_to_V_arr=None, seed=12345, ) + + assert res._raw_results["working_life"][2].states["type1"].sharding.num_devices == 4 + assert res._raw_results["working_life"][2].states["type2"].sharding.num_devices == 4 + assert ( + res._raw_results["working_life"][2].states["wealth"].sharding.num_devices == 4 + ) diff --git a/tests/test_nan_diagnostics.py b/tests/test_nan_diagnostics.py index 47c42f32a..5160b1050 100644 --- a/tests/test_nan_diagnostics.py +++ b/tests/test_nan_diagnostics.py @@ -30,6 +30,7 @@ def _make_state_action_space( states=MappingProxyType( {"wealth": jnp.linspace(1.0, 5.0, n_wealth)}, ), + distributed_states=MappingProxyType({}), discrete_actions=MappingProxyType({}), continuous_actions=MappingProxyType( {"consumption": jnp.linspace(0.1, 2.0, n_consumption)}, From 33311ea55200d93cfd39be621e93f0f7f9c54768 Mon Sep 17 00:00:00 2001 From: mj023 Date: Tue, 12 May 2026 18:49:19 +0200 Subject: [PATCH 06/77] Fix Typing and Tests --- src/lcm/grids/discrete.py | 2 +- src/lcm/interfaces.py | 34 +++++++++++----------- src/lcm/simulation/initial_conditions.py | 4 +-- src/lcm/solution/solve_brute.py | 4 +-- tests/test_distributed.py | 37 ++++++++++++++---------- tests/test_runtime_shock_params.py | 6 +++- tests/test_shock_draw.py | 26 ++++++++++++----- tests/test_shock_grids.py | 16 ++++++---- 8 files changed, 77 insertions(+), 52 deletions(-) diff --git a/src/lcm/grids/discrete.py b/src/lcm/grids/discrete.py index 182f6b77c..32f007fae 100644 --- a/src/lcm/grids/discrete.py +++ b/src/lcm/grids/discrete.py @@ -20,7 +20,7 @@ class DiscreteGrid(Grid): """ def __init__( - self, category_class: type, batch_size: int = 0, distributed=False + self, category_class: type, batch_size: int = 0, *, distributed: bool = False ) -> None: _validate_discrete_grid(category_class) names_and_values = get_field_names_and_values(category_class) diff --git a/src/lcm/interfaces.py b/src/lcm/interfaces.py index ac50daeb6..753886c3b 100644 --- a/src/lcm/interfaces.py +++ b/src/lcm/interfaces.py @@ -302,35 +302,33 @@ def state_action_space(self, regime_params: FlatRegimeParams) -> StateActionSpac state_replacements[name] = spec.compute_gridpoints(**shock_kw) new_states = ( - MappingProxyType( - dict(self._base_state_action_space.states) | state_replacements - ) + dict(self._base_state_action_space.states) | state_replacements if state_replacements else dict(self._base_state_action_space.states) ) new_continuous_actions = ( - MappingProxyType( - dict(self._base_state_action_space.continuous_actions) - | action_replacements - ) + dict(self._base_state_action_space.continuous_actions) | action_replacements if action_replacements else dict(self._base_state_action_space.continuous_actions) ) - - + new_states = distribute_states_to_devices( + new_states=new_states, grids=self.grids + ) return self._base_state_action_space.replace( states=MappingProxyType(new_states), continuous_actions=MappingProxyType(new_continuous_actions), ) -def distribute_states_to_devices(new_states, grids): + +def distribute_states_to_devices( + new_states: dict[StateName, ContinuousState], + grids: MappingProxyType[StateOrActionName, Grid], +) -> dict[StateName, ContinuousState]: distributed_states = new_states avail_devices = jax.devices() - distributed_grids = { - name: grid for name, grid in grids.items() if grid.distributed - } + distributed_grids = {name: grid for name, grid in grids.items() if grid.distributed} if len(distributed_grids) == 1: state_name = next(iter(distributed_grids)) n_points = distributed_grids[state_name].to_jax().shape[0] @@ -338,7 +336,7 @@ def distribute_states_to_devices(new_states, grids): mesh = jax.make_mesh( (len(avail_devices),), ("X"), - axis_types=(jax.sharding.AxisType.Auto), + axis_types=(jax.sharding.AxisType.Auto,), devices=avail_devices, ) distributed_states[state_name] = jax.device_put( @@ -355,7 +353,8 @@ def distribute_states_to_devices(new_states, grids): permutations = reduce( mul, [grid.to_jax().shape[0] for grid in distributed_grids.values()] ) - if permutations == len(avail_devices): + if permutations <= len(avail_devices): + avail_devices = avail_devices[:permutations] mesh = jax.make_mesh( tuple(len(grid.to_jax()) for grid in distributed_grids.values()), tuple(distributed_grids.keys()), @@ -372,11 +371,12 @@ def distribute_states_to_devices(new_states, grids): else: raise PyLCMError( "When distributing over multiple grids, the product of the" - " number of points of the grids needs to match the number" + " number of points of the grids needs to be smaller than the number" f" of available devices. Gridpoints: {permutations} Available" f"Devices: {len(avail_devices)}" ) - return distributed_states + return distributed_states + @dataclasses.dataclass(frozen=True) class PeriodRegimeSimulationData: diff --git a/src/lcm/simulation/initial_conditions.py b/src/lcm/simulation/initial_conditions.py index aa001921d..9c9a62937 100644 --- a/src/lcm/simulation/initial_conditions.py +++ b/src/lcm/simulation/initial_conditions.py @@ -66,10 +66,10 @@ def build_initial_states( for regime_name, internal_regime in internal_regimes.items(): # Logic for distribution of subjects over devices - distributed = any([grid.distributed for grid in internal_regime.grids.values()]) + distributed = any(grid.distributed for grid in internal_regime.grids.values()) devices = jax.devices() mesh = jax.make_mesh( - (len(devices),), ("X"), (jax.sharding.AxisType.Auto), devices=devices + (len(devices),), ("X"), (jax.sharding.AxisType.Auto,), devices=devices ) sharding = jax.NamedSharding(mesh=mesh, spec=jax.P("X")) for state_name in _get_regime_state_names(internal_regime): diff --git a/src/lcm/solution/solve_brute.py b/src/lcm/solution/solve_brute.py index 7905d62cd..47d8c6eb4 100644 --- a/src/lcm/solution/solve_brute.py +++ b/src/lcm/solution/solve_brute.py @@ -390,7 +390,7 @@ def _get_regime_V_shapes_and_shardings( *, internal_regimes: MappingProxyType[RegimeName, InternalRegime], internal_params: InternalParams, -) -> dict[RegimeName, tuple[int, ...]]: +) -> dict[RegimeName, tuple[tuple[int, ...], jax.NamedSharding]]: """Compute value function array shapes for all regimes. The V array has one dimension per state variable, with size equal to @@ -424,7 +424,7 @@ def _get_regime_V_shapes_and_shardings( ) mesh = jax.make_mesh( dist_shape, - (name for name in spec if name is not None), + tuple(name for name in spec if name is not None), axis_types=tuple( jax.sharding.AxisType.Auto for i in range(len(dist_shape)) ), diff --git a/tests/test_distributed.py b/tests/test_distributed.py index bed8a5125..0520f1cdb 100644 --- a/tests/test_distributed.py +++ b/tests/test_distributed.py @@ -8,29 +8,34 @@ from lcm.grids.discrete import DiscreteGrid from lcm.model import Model from lcm.regime import Regime +from lcm.typing import ScalarInt +# Run these tests on the CPU for parallelization, does not work if pytest runs +# multiple workers, because jax will be initialized already try: + jax.config.update("jax_platform_name", "cpu") jax.config.update("jax_num_cpu_devices", 4) - _HAS_4_CPU = len(jax.devices()) >= 4 -except jax.errors.JaxRuntimeError: - _HAS_4_CPU = False + _PYTEST_PARALLEL = False +except RuntimeError: + _PYTEST_PARALLEL = True -_skip_no_4_cpu = pytest.mark.skipif(not _HAS_4_CPU, reason="requires 4 CPU's") +_skip_pytest_parallel = pytest.mark.skipif( + _PYTEST_PARALLEL, reason="Can't set num cpus in pytest paralellel" +) @pytest.fixture def distributed_model(): @categorical(ordered=False) class RegimeId: - working_life: int - retirement: int + working_life: ScalarInt + retirement: ScalarInt @categorical(ordered=True) class Type: - low: int - high: int + low: ScalarInt + high: ScalarInt - # Define a regime where 'unused_state' is not used in any function working_life = Regime( functions={ "utility": lambda wealth, consumption, type1, type2: ( @@ -78,7 +83,7 @@ class Type: ) -@_skip_no_4_cpu +@_skip_pytest_parallel def test_solution_running_on_multiple_cpus(distributed_model): """Test that distribution over multiple CPU's works.""" @@ -89,18 +94,18 @@ def test_solution_running_on_multiple_cpus(distributed_model): assert period_to_regime_to_V_arr[0]["working_life"].sharding.num_devices == 4 -@_skip_no_4_cpu +@_skip_pytest_parallel def test_simulation_running_on_multiple_cpus(distributed_model): """Test that distribution over multiple CPU's works.""" res = distributed_model.simulate( params={"discount_factor": 0.95}, initial_conditions={ - "age": jnp.full(4, 0), - "wealth": jnp.full(4, 100.0), - "type1": jnp.full(4, 1), - "type2": jnp.full(4, 1), - "regime": jnp.zeros(4, dtype=jnp.int32), + "age": jnp.full(36, 0), + "wealth": jnp.full(36, 100.0), + "type1": jnp.full(36, 1), + "type2": jnp.full(36, 1), + "regime": jnp.zeros(36, dtype=jnp.int32), }, period_to_regime_to_V_arr=None, seed=12345, diff --git a/tests/test_runtime_shock_params.py b/tests/test_runtime_shock_params.py index 1e130e866..b6055fc67 100644 --- a/tests/test_runtime_shock_params.py +++ b/tests/test_runtime_shock_params.py @@ -85,7 +85,11 @@ def test_runtime_shock_params_property(): def test_fully_specified_shock(): """Tauchen with all params should have no runtime-supplied params.""" grid = lcm.shocks.ar1.Tauchen( - n_points=5, gauss_hermite=False, batch_size=0, **_TAUCHEN_PARAMS + n_points=5, + gauss_hermite=False, + batch_size=0, + distributed=False, + **_TAUCHEN_PARAMS, ) assert grid.params_to_pass_at_runtime == () assert grid.is_fully_specified diff --git a/tests/test_shock_draw.py b/tests/test_shock_draw.py index 6356f86b5..2a08f140e 100644 --- a/tests/test_shock_draw.py +++ b/tests/test_shock_draw.py @@ -48,7 +48,9 @@ def test_draw_shock_uniform(params_at_init): """Uniform.draw_shock uses start/stop params.""" kwargs = {"start": 2.0, "stop": 4.0} if params_at_init: - grid = lcm.shocks.iid.Uniform(n_points=5, batch_size=0, **kwargs) + grid = lcm.shocks.iid.Uniform( + n_points=5, batch_size=0, distributed=False, **kwargs + ) params = grid.params else: grid = lcm.shocks.iid.Uniform(n_points=5) @@ -65,11 +67,13 @@ def test_draw_shock_normal(params_at_init): kwargs = {"mu": 5.0, "sigma": 0.1} if params_at_init: grid = lcm.shocks.iid.Normal( - n_points=5, batch_size=0, gauss_hermite=True, **kwargs + n_points=5, batch_size=0, distributed=False, gauss_hermite=True, **kwargs ) params = grid.params else: - grid = lcm.shocks.iid.Normal(n_points=5, batch_size=0, gauss_hermite=True) + grid = lcm.shocks.iid.Normal( + n_points=5, batch_size=0, distributed=False, gauss_hermite=True + ) params = MappingProxyType(kwargs) draws = _draw_many(grid, params) aaae(draws.mean(), 5.0, decimal=1) @@ -82,7 +86,7 @@ def test_draw_shock_lognormal(params_at_init): kwargs = {"mu": 1.0, "sigma": 0.1} if params_at_init: grid = lcm.shocks.iid.LogNormal( - n_points=5, batch_size=0, gauss_hermite=True, **kwargs + n_points=5, batch_size=0, distributed=False, gauss_hermite=True, **kwargs ) params = grid.params else: @@ -100,7 +104,7 @@ def test_draw_shock_tauchen(params_at_init): kwargs = {"rho": 0.5, "sigma": 0.1, "mu": 2.0} if params_at_init: grid = lcm.shocks.ar1.Tauchen( - n_points=5, batch_size=0, gauss_hermite=True, **kwargs + n_points=5, batch_size=0, distributed=False, gauss_hermite=True, **kwargs ) params = grid.params else: @@ -116,7 +120,9 @@ def test_draw_shock_rouwenhorst(params_at_init): """Rouwenhorst.draw_shock uses mu/sigma/rho params.""" kwargs = {"rho": 0.5, "sigma": 0.1, "mu": 2.0} if params_at_init: - grid = lcm.shocks.ar1.Rouwenhorst(n_points=5, batch_size=0, **kwargs) + grid = lcm.shocks.ar1.Rouwenhorst( + n_points=5, batch_size=0, distributed=False, **kwargs + ) params = grid.params else: grid = lcm.shocks.ar1.Rouwenhorst(n_points=5) @@ -131,7 +137,9 @@ def test_draw_shock_normal_mixture(params_at_init): """NormalMixture.draw_shock produces draws with correct mixture moments.""" kwargs = _NORMAL_MIXTURE_KWARGS if params_at_init: - grid = lcm.shocks.iid.NormalMixture(n_points=5, batch_size=0, **kwargs) + grid = lcm.shocks.iid.NormalMixture( + n_points=5, batch_size=0, distributed=False, **kwargs + ) params = grid.params else: grid = lcm.shocks.iid.NormalMixture(n_points=5) @@ -153,7 +161,9 @@ def test_draw_shock_tauchen_normal_mixture(params_at_init): """TauchenNormalMixture.draw_shock produces yields correct conditional moments.""" kwargs = _TAUCHEN_NORMAL_MIXTURE_KWARGS if params_at_init: - grid = lcm.shocks.ar1.TauchenNormalMixture(n_points=5, batch_size=0, **kwargs) + grid = lcm.shocks.ar1.TauchenNormalMixture( + n_points=5, batch_size=0, distributed=False, **kwargs + ) params = grid.params else: grid = lcm.shocks.ar1.TauchenNormalMixture(n_points=5) diff --git a/tests/test_shock_grids.py b/tests/test_shock_grids.py index 14d465408..e22d2571a 100644 --- a/tests/test_shock_grids.py +++ b/tests/test_shock_grids.py @@ -372,7 +372,7 @@ def test_lognormal_gauss_hermite_weights_sum_to_one(): def test_normal_mixture_transition_probs_rows_sum_to_one(): """NormalMixture transition probability rows sum to 1.""" grid = lcm.shocks.iid.NormalMixture( - n_points=7, batch_size=0, **_NORMAL_MIXTURE_KWARGS + n_points=7, batch_size=0, distributed=False, **_NORMAL_MIXTURE_KWARGS ) P = grid.get_transition_probs() row_sums = P.sum(axis=1) @@ -382,7 +382,9 @@ def test_normal_mixture_transition_probs_rows_sum_to_one(): def test_iid_normal_mixture_stationary_moments(): """IID NormalMixture stationary mean and std match mixture moments.""" kwargs = _NORMAL_MIXTURE_KWARGS - grid = lcm.shocks.iid.NormalMixture(n_points=21, batch_size=0, **kwargs) + grid = lcm.shocks.iid.NormalMixture( + n_points=21, batch_size=0, distributed=False, **kwargs + ) got_mean, got_std = _stationary_moments( grid.get_gridpoints(), grid.get_transition_probs() ) @@ -412,7 +414,7 @@ def test_iid_normal_mixture_stationary_moments(): def test_tauchen_normal_mixture_transition_probs_rows_sum_to_one(): """TauchenNormalMixture transition probability rows sum to 1.""" grid = lcm.shocks.ar1.TauchenNormalMixture( - n_points=7, batch_size=0, **_TAUCHEN_NORMAL_MIXTURE_KWARGS + n_points=7, batch_size=0, distributed=False, **_TAUCHEN_NORMAL_MIXTURE_KWARGS ) P = grid.get_transition_probs() row_sums = P.sum(axis=1) @@ -422,7 +424,9 @@ def test_tauchen_normal_mixture_transition_probs_rows_sum_to_one(): def test_tauchen_normal_mixture_centers_on_unconditional_mean(): """TauchenNormalMixture gridpoints center on (mu + mean_eps) / (1 - rho).""" kwargs = _TAUCHEN_NORMAL_MIXTURE_KWARGS - grid = lcm.shocks.ar1.TauchenNormalMixture(n_points=11, batch_size=0, **kwargs) + grid = lcm.shocks.ar1.TauchenNormalMixture( + n_points=11, batch_size=0, distributed=False, **kwargs + ) points = grid.get_gridpoints() midpoint = (points[0] + points[-1]) / 2 mean_eps = kwargs["p1"] * kwargs["mu1"] + (1 - kwargs["p1"]) * kwargs["mu2"] @@ -433,7 +437,9 @@ def test_tauchen_normal_mixture_centers_on_unconditional_mean(): def test_tauchen_normal_mixture_stationary_moments_and_autocorrelation(): """TauchenNormalMixture stationary mean, std, and autocorrelation match theory.""" kwargs = _TAUCHEN_NORMAL_MIXTURE_KWARGS - grid = lcm.shocks.ar1.TauchenNormalMixture(batch_size=0, n_points=21, **kwargs) + grid = lcm.shocks.ar1.TauchenNormalMixture( + batch_size=0, distributed=False, n_points=21, **kwargs + ) points = grid.get_gridpoints() P = grid.get_transition_probs() From 5aa62dc3887020eca1d4408ea9ec6534102ade11 Mon Sep 17 00:00:00 2001 From: mj023 Date: Tue, 12 May 2026 19:05:22 +0200 Subject: [PATCH 07/77] Add error to simulate --- src/lcm/simulation/initial_conditions.py | 7 ++ tests/test_distributed.py | 100 +++++++++++++++++++++-- 2 files changed, 102 insertions(+), 5 deletions(-) diff --git a/src/lcm/simulation/initial_conditions.py b/src/lcm/simulation/initial_conditions.py index 9c9a62937..321b92397 100644 --- a/src/lcm/simulation/initial_conditions.py +++ b/src/lcm/simulation/initial_conditions.py @@ -19,6 +19,7 @@ from lcm.dtypes import canonical_float_dtype, safe_to_float_dtype from lcm.exceptions import ( InvalidInitialConditionsError, + PyLCMError, format_messages, ) from lcm.grids import DiscreteGrid @@ -68,6 +69,12 @@ def build_initial_states( # Logic for distribution of subjects over devices distributed = any(grid.distributed for grid in internal_regime.grids.values()) devices = jax.devices() + if distributed and n_subjects % len(devices) != 0: + raise PyLCMError( + "When using distributed grids, the number of subjects during the" + " simulation needs to be a multiple of the available devices. Subjects:" + f" {n_subjects} Available Devices: {len(devices)}" + ) mesh = jax.make_mesh( (len(devices),), ("X"), (jax.sharding.AxisType.Auto,), devices=devices ) diff --git a/tests/test_distributed.py b/tests/test_distributed.py index 0520f1cdb..976d0aaef 100644 --- a/tests/test_distributed.py +++ b/tests/test_distributed.py @@ -3,6 +3,7 @@ from jax import numpy as jnp from lcm.ages import AgeGrid +from lcm.exceptions import PyLCMError from lcm.grids import categorical from lcm.grids.continuous import LinSpacedGrid from lcm.grids.discrete import DiscreteGrid @@ -25,7 +26,7 @@ @pytest.fixture -def distributed_model(): +def correct_distributed_model(): @categorical(ordered=False) class RegimeId: working_life: ScalarInt @@ -83,11 +84,71 @@ class Type: ) +@pytest.fixture +def wrong_distributed_model(): + @categorical(ordered=False) + class RegimeId: + working_life: ScalarInt + retirement: ScalarInt + + @categorical(ordered=True) + class Type: + low: ScalarInt + medium: ScalarInt + high: ScalarInt + + working_life = Regime( + functions={ + "utility": lambda wealth, consumption, type1, type2: ( + (jnp.log(consumption) + wealth * 0.001) * type1 * type2 + ), + }, + states={ + "wealth": LinSpacedGrid( + start=1, + stop=100, + n_points=10, + ), + "type1": DiscreteGrid(Type, distributed=True), + "type2": DiscreteGrid(Type, distributed=True), + }, + state_transitions={ + "wealth": lambda wealth, consumption: wealth - consumption, + "type1": None, + "type2": None, + }, + actions={"consumption": LinSpacedGrid(start=1, stop=50, n_points=10)}, + transition=lambda age: jnp.where( + age >= 4, RegimeId.retirement, RegimeId.working_life + ), + active=lambda age: age < 5, + ) + + retirement = Regime( + transition=None, + functions={ + "utility": lambda wealth, type1, type2: (wealth * 0.5) * type1 * type2 + }, + states={ + "wealth": LinSpacedGrid(start=1, stop=100, n_points=10), + "type1": DiscreteGrid(Type, distributed=True), + "type2": DiscreteGrid(Type, distributed=True), + }, + active=lambda age: age >= 5, + ) + + return Model( + regimes={"working_life": working_life, "retirement": retirement}, + ages=AgeGrid(start=0, stop=5, step="Y"), + regime_id_class=RegimeId, + ) + + @_skip_pytest_parallel -def test_solution_running_on_multiple_cpus(distributed_model): +def test_solution_running_on_multiple_cpus(correct_distributed_model): """Test that distribution over multiple CPU's works.""" - period_to_regime_to_V_arr = distributed_model.solve( + period_to_regime_to_V_arr = correct_distributed_model.solve( params={"discount_factor": 0.95}, ) @@ -95,10 +156,10 @@ def test_solution_running_on_multiple_cpus(distributed_model): @_skip_pytest_parallel -def test_simulation_running_on_multiple_cpus(distributed_model): +def test_simulation_running_on_multiple_cpus(correct_distributed_model): """Test that distribution over multiple CPU's works.""" - res = distributed_model.simulate( + res = correct_distributed_model.simulate( params={"discount_factor": 0.95}, initial_conditions={ "age": jnp.full(36, 0), @@ -116,3 +177,32 @@ def test_simulation_running_on_multiple_cpus(distributed_model): assert ( res._raw_results["working_life"][2].states["wealth"].sharding.num_devices == 4 ) + + +@_skip_pytest_parallel +def test_solution_error_if_not_multiple(wrong_distributed_model): + """Test that distribution over multiple CPU's works.""" + + with pytest.raises(PyLCMError, match="smaller than the number"): + wrong_distributed_model.solve( + params={"discount_factor": 0.95}, + ) + + +@_skip_pytest_parallel +def test_simulation_error_if_not_multiple(correct_distributed_model): + """Test that distribution over multiple CPU's works.""" + + with pytest.raises(PyLCMError, match="multiple"): + correct_distributed_model.simulate( + params={"discount_factor": 0.95}, + initial_conditions={ + "age": jnp.full(5, 0), + "wealth": jnp.full(5, 100.0), + "type1": jnp.full(5, 1), + "type2": jnp.full(5, 1), + "regime": jnp.zeros(5, dtype=jnp.int32), + }, + period_to_regime_to_V_arr=None, + seed=12345, + ) From dc6cf794ecc69bb91fbb4b042f5ce9e752c12236 Mon Sep 17 00:00:00 2001 From: Hans-Martin von Gaudecker Date: Wed, 13 May 2026 09:57:51 +0200 Subject: [PATCH 08/77] Phase 1: flip simulation state carrier to nested StatesPerRegime Replace the flat `{regime}__{state}` qname-joined state container with nested `MappingProxyType[RegimeName, MappingProxyType[StateName, Array]]`. Pylcm code no longer constructs or parses the `__` separator on the simulation read/write path. Type aliases (lcm/typing.py): - `RegimeStates = MappingProxyType[StateName, Array]` - `StatesPerRegime = MappingProxyType[RegimeName, RegimeStates]` `_update_states_for_subjects` becomes `_advance_states_for_subjects`, takes paired `current_states_per_regime` / `next_states_per_regime` arguments, and is a pure StatesPerRegime-in/out merge. The `next_` prefix strip moves upstream into `calculate_next_states`, immediately after `next_state_vmapped(...)`, so both arguments share inner-key naming. Touchpoints: - `simulation/initial_conditions.py:build_initial_states` returns nested StatesPerRegime; per-regime inner mappings replace the flat `regime__state` dict. - `simulation/transitions.py:60` (`create_regime_state_action_space`) accesses `current_states_per_regime[regime_name][sn]`. - `simulation/transitions.py:125-142` (`calculate_next_states`) strips the `next_` prefix and calls the renamed function. - `simulation/transitions.py:262-310` (`_advance_states_for_subjects`): nested merge with no string concat and no removeprefix. - `simulation/simulate.py:294-296` filter collapses to `states[regime_name]`. Tracks pylcm#343 Phase 1; Phase 2 (nest the DAG function dict and rewrite three remaining `tree_path_from_qname` introspection sites) lands in a follow-up. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/lcm/simulation/initial_conditions.py | 27 +++-- src/lcm/simulation/simulate.py | 22 ++-- src/lcm/simulation/transitions.py | 109 ++++++++++++-------- src/lcm/typing.py | 6 ++ tests/simulation/test_initial_conditions.py | 14 +-- tests/simulation/test_update_states.py | 74 +++++++------ tests/test_float_dtype_invariants.py | 8 +- tests/test_int_dtype_invariants.py | 40 ++++--- 8 files changed, 171 insertions(+), 129 deletions(-) diff --git a/src/lcm/simulation/initial_conditions.py b/src/lcm/simulation/initial_conditions.py index 7835e9b71..28bad78ad 100644 --- a/src/lcm/simulation/initial_conditions.py +++ b/src/lcm/simulation/initial_conditions.py @@ -31,6 +31,7 @@ RegimeIdsToNames, RegimeName, RegimeNamesToIds, + StatesPerRegime, ) from lcm.utils.containers import invert_regime_ids from lcm.utils.functools import get_union_of_args @@ -45,8 +46,8 @@ def build_initial_states( *, initial_states: Mapping[str, Array], internal_regimes: MappingProxyType[RegimeName, InternalRegime], -) -> MappingProxyType[str, Array]: - """Build flat regime-namespaced state dict from user-provided initial states. +) -> StatesPerRegime: + """Build the regime-keyed state carrier from user-provided initial states. For each regime, copies provided states and fills missing ones with `jnp.nan` (continuous) or `MISSING_CAT_CODE` (discrete). @@ -57,16 +58,15 @@ def build_initial_states( instances. Returns: - Immutable mapping of regime-namespaced state names to arrays. - Example: `{"work__wealth": arr, "work__health": arr, ...}` + Nested immutable mapping `{regime_name: {state_name: array}}`. """ - flat: dict[str, Array] = {} n_subjects = len(next(iter(initial_states.values()))) + nested: dict[RegimeName, MappingProxyType[str, Array]] = {} for regime_name, internal_regime in internal_regimes.items(): + regime_states: dict[str, Array] = {} for state_name in _get_regime_state_names(internal_regime): - key = f"{regime_name}__{state_name}" grid = internal_regime.grids[state_name] if isinstance(grid, DiscreteGrid): # Cast user-supplied discrete states to the grid's index @@ -74,22 +74,27 @@ def build_initial_states( # for that state. target_dtype = grid.to_jax().dtype if state_name in initial_states: - flat[key] = initial_states[state_name].astype(target_dtype) + regime_states[state_name] = initial_states[state_name].astype( + target_dtype + ) else: - flat[key] = jnp.full( + regime_states[state_name] = jnp.full( n_subjects, MISSING_CAT_CODE, dtype=target_dtype ) elif state_name in initial_states: # Cast user-supplied continuous states to the canonical float # dtype so the simulate state pool has one signature across # periods regardless of the user-supplied dtype. - flat[key] = safe_to_float_dtype( + regime_states[state_name] = safe_to_float_dtype( initial_states[state_name], name=f"initial_states.{state_name}" ) else: - flat[key] = jnp.full(n_subjects, jnp.nan, dtype=canonical_float_dtype()) + regime_states[state_name] = jnp.full( + n_subjects, jnp.nan, dtype=canonical_float_dtype() + ) + nested[regime_name] = MappingProxyType(regime_states) - return MappingProxyType(flat) + return MappingProxyType(nested) def validate_initial_conditions( diff --git a/src/lcm/simulation/simulate.py b/src/lcm/simulation/simulate.py index 22799c202..277aa5aff 100644 --- a/src/lcm/simulation/simulate.py +++ b/src/lcm/simulation/simulate.py @@ -33,6 +33,7 @@ RegimeNamesToIds, ScalarFloat, ScalarInt, + StatesPerRegime, ) from lcm.utils.containers import invert_regime_ids from lcm.utils.error_handling import validate_V @@ -205,7 +206,7 @@ def _simulate_regime_in_period( internal_regime: InternalRegime, period: int, age: ScalarInt | ScalarFloat, - states: MappingProxyType[str, Array], + states: StatesPerRegime, subject_regime_ids: Int1D, new_subject_regime_ids: Int1D, period_to_regime_to_V_arr: MappingProxyType[ @@ -215,7 +216,7 @@ def _simulate_regime_in_period( regime_names_to_ids: RegimeNamesToIds, active_regimes_next_period: tuple[RegimeName, ...], key: Array, -) -> tuple[PeriodRegimeSimulationData, MappingProxyType[str, Array], Int1D, Array]: +) -> tuple[PeriodRegimeSimulationData, StatesPerRegime, Int1D, Array]: """Simulate one regime for one period. This function processes all subjects in a given regime for a single period, @@ -226,7 +227,8 @@ def _simulate_regime_in_period( internal_regime: Internal representation of the regime. period: Current period (0-indexed). age: Age corresponding to current period. - states: Current states for all subjects (namespaced by regime). + states: Carrier of current-period state arrays for every regime and + state. subject_regime_ids: Current regime membership for all subjects. new_subject_regime_ids: Array to populate with next period's regime memberships. period_to_regime_to_V_arr: Value function arrays for all periods and regimes. @@ -238,7 +240,7 @@ def _simulate_regime_in_period( Returns: Tuple containing: - PeriodRegimeSimulationData for this regime-period - - Updated states dictionary + - Updated state carrier - Updated new_subject_regime_ids array - Updated JAX random key @@ -250,7 +252,7 @@ def _simulate_regime_in_period( state_action_space = create_regime_state_action_space( internal_regime=internal_regime, - states=states, + current_states_per_regime=states, regime_params=internal_params[regime_name], ) # Compute optimal actions @@ -290,16 +292,10 @@ def _simulate_regime_in_period( if V_arr.ndim == 0: V_arr = jnp.broadcast_to(V_arr, (n_subjects,)) - res = { - state_name.removeprefix(f"{regime_name}__"): state - for state_name, state in states.items() - if state_name.startswith(f"{regime_name}__") - } - simulation_result = PeriodRegimeSimulationData( V_arr=V_arr, actions=optimal_actions, - states=MappingProxyType(res), + states=states[regime_name], in_regime=subject_ids_in_regime, ) @@ -313,7 +309,7 @@ def _simulate_regime_in_period( period=period, age=age, regime_params=internal_params[regime_name], - states=states, + current_states_per_regime=states, state_action_space=state_action_space, key=next_states_key, subjects_in_regime=subject_ids_in_regime, diff --git a/src/lcm/simulation/transitions.py b/src/lcm/simulation/transitions.py index 3742cb8b0..f9e7d8387 100644 --- a/src/lcm/simulation/transitions.py +++ b/src/lcm/simulation/transitions.py @@ -19,14 +19,13 @@ from lcm.typing import ( ActionName, Bool1D, - ContinuousState, - DiscreteState, FlatRegimeParams, Int1D, RegimeName, RegimeNamesToIds, ScalarFloat, ScalarInt, + StatesPerRegime, ) from lcm.utils.namespace import flatten_regime_namespace @@ -34,7 +33,7 @@ def create_regime_state_action_space( *, internal_regime: InternalRegime, - states: MappingProxyType[str, Array], + current_states_per_regime: StatesPerRegime, regime_params: FlatRegimeParams, ) -> StateActionSpace: """Create the state-action space containing only the relevant subjects in a regime. @@ -45,7 +44,8 @@ def create_regime_state_action_space( Args: internal_regime: The internal regime instance. - states: The current states of all subjects. + current_states_per_regime: Carrier of state arrays for every regime and + state, indexed by regime name then state name. regime_params: Flat regime parameters supplied at runtime, used to substitute runtime-supplied action gridpoints. @@ -56,8 +56,9 @@ def create_regime_state_action_space( base = internal_regime.state_action_space(regime_params=regime_params) relevant_state_names = internal_regime.variable_info.query("is_state").index + regime_states = current_states_per_regime[internal_regime.name] states_for_state_action_space = { - sn: states[f"{internal_regime.name}__{sn}"] for sn in relevant_state_names + sn: regime_states[sn] for sn in relevant_state_names } _validate_all_states_present( provided_states=states_for_state_action_space, @@ -74,11 +75,11 @@ def calculate_next_states( period: int, age: ScalarInt | ScalarFloat, regime_params: FlatRegimeParams, - states: MappingProxyType[str, Array], + current_states_per_regime: StatesPerRegime, state_action_space: StateActionSpace, key: Array, subjects_in_regime: Bool1D, -) -> MappingProxyType[str, Array]: +) -> StatesPerRegime: """Calculate next period states for subjects in a regime. Args: @@ -88,14 +89,14 @@ def calculate_next_states( period: Current period. age: Age corresponding to current period. regime_params: Flat regime parameters. - states: Current states for all subjects (all regimes). + current_states_per_regime: Carrier of current-period state arrays for + every regime and state, indexed by regime name then state name. state_action_space: State-action space for subjects in this regime. key: JAX random key. Returns: - Updated states dictionary with next period states for subjects in this regime. - Immutable mapping of updated states for all subjects, with updates only for - those in the current regime. + Updated carrier with next-period state values for subjects in this regime; + entries for other subjects are left untouched. """ # Identify stochastic transitions and generate random keys @@ -133,13 +134,25 @@ def calculate_next_states( **regime_params, ) - # Update global states array with computed next states for subjects in regime - # --------------------------------------------------------------------------------- - # The transition function adds a 'next_' prefix to all state names. We remove - # this prefix and update only the entries corresponding to subjects in this regime. - return _update_states_for_subjects( - all_states=states, - computed_next_states=states_with_next_prefix, + # Transition functions are DAG-named `next_` to distinguish them from + # the current-period state values they consume. Strip the prefix here so + # `_advance_states_for_subjects` sees keys that line up with the carrier's + # `StateName` keys directly. + next_states_per_regime = MappingProxyType( + { + target: MappingProxyType( + { + name.removeprefix("next_"): value + for name, value in target_next_states.items() + } + ) + for target, target_next_states in states_with_next_prefix.items() + } + ) + + return _advance_states_for_subjects( + current_states_per_regime=current_states_per_regime, + next_states_per_regime=next_states_per_regime, subject_indices=subjects_in_regime, ) @@ -259,37 +272,42 @@ def random_id( return random_ids(keys, regime_transition_probs) -def _update_states_for_subjects( +def _advance_states_for_subjects( *, - all_states: MappingProxyType[str, Array], - computed_next_states: MappingProxyType[ - RegimeName, MappingProxyType[str, DiscreteState | ContinuousState] - ], + current_states_per_regime: StatesPerRegime, + next_states_per_regime: StatesPerRegime, subject_indices: Bool1D, -) -> MappingProxyType[str, Array]: - """Update the global states dictionary with next states for specific subjects. +) -> StatesPerRegime: + """Merge next-period state values into the carrier for selected subjects. - Outputs from `get_next_state_function_for_simulation` are nested by target - regime, with inner keys carrying the `next_` prefix - (`{target: {next_: array}}`). Strip the prefix and combine with the - target name into the flat `__` key used in `all_states`, - updating only the entries corresponding to the specified subjects. + The carrier and the per-regime update share the `StatesPerRegime` shape: an + outer mapping by regime name, an inner mapping by state name. Where + `subject_indices` is true, the corresponding slot is replaced with the + matching entry from `next_states_per_regime`; otherwise the carrier value + is left untouched. Regimes that don't appear in `next_states_per_regime` + are passed through unchanged. Args: - all_states: Current states for all subjects across all regimes. - computed_next_states: Newly computed states, nested by target regime - and keyed by `next_`, for specific subjects. - subject_indices: Indices of subjects whose states should be updated. + current_states_per_regime: Carrier of state arrays prior to this advance, + indexed by regime name then state name. + next_states_per_regime: Per-regime next-period state values to merge in, + indexed by regime name then state name (no `next_` prefix — the + caller in `calculate_next_states` strips it before invoking). + subject_indices: Boolean mask selecting which subjects' values are + overwritten by the next-period entries. Returns: - Updated states dictionary with next states for the specified subjects. + Updated carrier with next-period values written in for selected subjects. """ - updated_states = dict(all_states) - for target, target_next_states in computed_next_states.items(): - for next_state_name, next_state_values in target_next_states.items(): - state_name = f"{target}__{next_state_name.removeprefix('next_')}" - target_dtype = all_states[state_name].dtype + updated: dict[RegimeName, dict[str, Array]] = { + regime_name: dict(regime_states) + for regime_name, regime_states in current_states_per_regime.items() + } + for target, target_next_states in next_states_per_regime.items(): + for state_name, next_state_values in target_next_states.items(): + current_arr = current_states_per_regime[target][state_name] + target_dtype = current_arr.dtype # Preserve storage dtype only when the transition output is the # same numeric kind. Across kinds (e.g. int storage + float # transition output) leave JAX's promotion in place; the @@ -299,10 +317,15 @@ def _update_states_for_subjects( if next_state_values.dtype.kind == target_dtype.kind else next_state_values ) - updated_states[state_name] = jnp.where( + updated[target][state_name] = jnp.where( subject_indices, new_values, - all_states[state_name], + current_arr, ) - return MappingProxyType(updated_states) + return MappingProxyType( + { + regime_name: MappingProxyType(regime_states) + for regime_name, regime_states in updated.items() + } + ) diff --git a/src/lcm/typing.py b/src/lcm/typing.py index b6f39b078..23dac9388 100644 --- a/src/lcm/typing.py +++ b/src/lcm/typing.py @@ -45,6 +45,12 @@ RegimeName, MappingProxyType[TransitionFunctionName, InternalUserFunction] ] +# Nested state carrier threading through the simulation loop. +# `RegimeStates`: one regime's state arrays, keyed by state name. +# `StatesPerRegime`: every regime's `RegimeStates` bundle, keyed by regime name. +type RegimeStates = MappingProxyType[StateName, Array] +type StatesPerRegime = MappingProxyType[RegimeName, RegimeStates] + type _ParamsLeaf = bool | float | Array | pd.Series | MappingLeaf | SequenceLeaf type UserParams = Mapping[ diff --git a/tests/simulation/test_initial_conditions.py b/tests/simulation/test_initial_conditions.py index bd8691bd6..d54c56db1 100644 --- a/tests/simulation/test_initial_conditions.py +++ b/tests/simulation/test_initial_conditions.py @@ -88,19 +88,19 @@ def internal_params(model: Model) -> InternalParams: def test_build_initial_states_single_regime(model: Model) -> None: - """Single regime gets its states from flat dict.""" - flat = { + """Each regime's state arrays land under its name in the nested carrier.""" + initial = { "wealth": jnp.array([10.0, 50.0]), "health": jnp.array([0, 1]), } result = build_initial_states( - initial_states=flat, internal_regimes=model.internal_regimes + initial_states=initial, internal_regimes=model.internal_regimes ) - assert "active__wealth" in result - assert "active__health" in result - # Terminal regime has no states, so no terminal__ keys should exist - assert not any(k.startswith("terminal__") for k in result) + assert "wealth" in result["active"] + assert "health" in result["active"] + # Terminal regime has no states, so its inner mapping is empty. + assert dict(result["terminal"]) == {} def test_validate_initial_conditions_valid_input( diff --git a/tests/simulation/test_update_states.py b/tests/simulation/test_update_states.py index f626fc84b..a3405e728 100644 --- a/tests/simulation/test_update_states.py +++ b/tests/simulation/test_update_states.py @@ -3,80 +3,86 @@ import jax.numpy as jnp from numpy.testing import assert_array_equal -from lcm.simulation.transitions import _update_states_for_subjects +from lcm.simulation.transitions import _advance_states_for_subjects -def test_update_states_strips_next_prefix(): - all_states = MappingProxyType( +def test_advance_states_writes_new_values_for_selected_subjects(): + """Selected subjects receive the next-period array; others stay put.""" + current_states_per_regime = MappingProxyType( { - "working__wealth": jnp.array([10.0, 20.0, 30.0]), + "working": MappingProxyType({"wealth": jnp.array([10.0, 20.0, 30.0])}), } ) - computed_next_states = MappingProxyType( + next_states_per_regime = MappingProxyType( { - "working": MappingProxyType({"next_wealth": jnp.array([15.0, 25.0, 35.0])}), + "working": MappingProxyType({"wealth": jnp.array([15.0, 25.0, 35.0])}), } ) subject_indices = jnp.array([True, False, True]) - result = _update_states_for_subjects( - all_states=all_states, - computed_next_states=computed_next_states, + result = _advance_states_for_subjects( + current_states_per_regime=current_states_per_regime, + next_states_per_regime=next_states_per_regime, subject_indices=subject_indices, ) - assert_array_equal(result["working__wealth"], jnp.array([15.0, 20.0, 35.0])) + assert_array_equal(result["working"]["wealth"], jnp.array([15.0, 20.0, 35.0])) -def test_update_states_multiple_regimes_and_states(): - all_states = MappingProxyType( +def test_advance_states_handles_multiple_regimes_and_states(): + """Regimes named in `next_states_per_regime` get updated; others stay intact.""" + current_states_per_regime = MappingProxyType( { - "working__wealth": jnp.array([10.0, 20.0]), - "working__health": jnp.array([1.0, 2.0]), - "retired__wealth": jnp.array([100.0, 200.0]), + "working": MappingProxyType( + { + "wealth": jnp.array([10.0, 20.0]), + "health": jnp.array([1.0, 2.0]), + } + ), + "retired": MappingProxyType({"wealth": jnp.array([100.0, 200.0])}), } ) - computed_next_states = MappingProxyType( + next_states_per_regime = MappingProxyType( { "working": MappingProxyType( { - "next_wealth": jnp.array([15.0, 25.0]), - "next_health": jnp.array([1.5, 2.5]), + "wealth": jnp.array([15.0, 25.0]), + "health": jnp.array([1.5, 2.5]), } ), } ) subject_indices = jnp.array([True, True]) - result = _update_states_for_subjects( - all_states=all_states, - computed_next_states=computed_next_states, + result = _advance_states_for_subjects( + current_states_per_regime=current_states_per_regime, + next_states_per_regime=next_states_per_regime, subject_indices=subject_indices, ) - assert_array_equal(result["working__wealth"], jnp.array([15.0, 25.0])) - assert_array_equal(result["working__health"], jnp.array([1.5, 2.5])) - # Untouched state remains unchanged - assert_array_equal(result["retired__wealth"], jnp.array([100.0, 200.0])) + assert_array_equal(result["working"]["wealth"], jnp.array([15.0, 25.0])) + assert_array_equal(result["working"]["health"], jnp.array([1.5, 2.5])) + assert_array_equal(result["retired"]["wealth"], jnp.array([100.0, 200.0])) -def test_update_states_no_subjects_selected(): - all_states = MappingProxyType( +def test_advance_states_no_subjects_selected_leaves_carrier_unchanged(): + """When no subject is selected, the next-period values are ignored entirely.""" + current_states_per_regime = MappingProxyType( { - "r__wealth": jnp.array([10.0, 20.0]), + "r": MappingProxyType({"wealth": jnp.array([10.0, 20.0])}), } ) - computed_next_states = MappingProxyType( + next_states_per_regime = MappingProxyType( { - "r": MappingProxyType({"next_wealth": jnp.array([99.0, 99.0])}), + "r": MappingProxyType({"wealth": jnp.array([99.0, 99.0])}), } ) subject_indices = jnp.array([False, False]) - result = _update_states_for_subjects( - all_states=all_states, - computed_next_states=computed_next_states, + result = _advance_states_for_subjects( + current_states_per_regime=current_states_per_regime, + next_states_per_regime=next_states_per_regime, subject_indices=subject_indices, ) - assert_array_equal(result["r__wealth"], jnp.array([10.0, 20.0])) + assert_array_equal(result["r"]["wealth"], jnp.array([10.0, 20.0])) diff --git a/tests/test_float_dtype_invariants.py b/tests/test_float_dtype_invariants.py index 0ba99f7f3..28b9588b0 100644 --- a/tests/test_float_dtype_invariants.py +++ b/tests/test_float_dtype_invariants.py @@ -39,7 +39,7 @@ def test_build_initial_states_casts_user_float64_to_canonical(x64_disabled: None initial_states=initial_states, # ty: ignore[invalid-argument-type] internal_regimes=model.internal_regimes, ) - assert flat["working_life__wealth"].dtype == canonical_float_dtype() + assert flat["working_life"]["wealth"].dtype == canonical_float_dtype() def test_build_initial_states_casts_user_int_to_canonical(x64_disabled: None): @@ -53,7 +53,7 @@ def test_build_initial_states_casts_user_int_to_canonical(x64_disabled: None): initial_states=initial_states, internal_regimes=model.internal_regimes, ) - assert flat["working_life__wealth"].dtype == canonical_float_dtype() + assert flat["working_life"]["wealth"].dtype == canonical_float_dtype() def test_build_initial_states_missing_continuous_fallback_dtype_is_canonical( @@ -66,7 +66,7 @@ def test_build_initial_states_missing_continuous_fallback_dtype_is_canonical( initial_states={"placeholder": jnp.asarray([0.0, 0.0])}, internal_regimes=model.internal_regimes, ) - assert flat["working_life__wealth"].dtype == canonical_float_dtype() + assert flat["working_life"]["wealth"].dtype == canonical_float_dtype() def test_build_initial_states_missing_continuous_fallback_values_are_nan( @@ -82,7 +82,7 @@ def test_build_initial_states_missing_continuous_fallback_values_are_nan( initial_states={"placeholder": jnp.asarray([0.0, 0.0])}, internal_regimes=model.internal_regimes, ) - assert bool(jnp.all(jnp.isnan(flat["working_life__wealth"]))) + assert bool(jnp.all(jnp.isnan(flat["working_life"]["wealth"]))) def test_process_params_casts_float64_array_to_canonical_under_no_x64( diff --git a/tests/test_int_dtype_invariants.py b/tests/test_int_dtype_invariants.py index 89e73e2f0..d2347c230 100644 --- a/tests/test_int_dtype_invariants.py +++ b/tests/test_int_dtype_invariants.py @@ -16,7 +16,7 @@ MISSING_CAT_CODE, build_initial_states, ) -from lcm.simulation.transitions import _update_states_for_subjects +from lcm.simulation.transitions import _advance_states_for_subjects from tests.test_models.deterministic.regression import ( RegimeId, dead, @@ -53,15 +53,17 @@ def test_build_initial_states_discrete_dtype_is_int32() -> None: "wealth": jnp.array([20.0, 50.0]), "age": jnp.array([18.0, 18.0]), } - flat = build_initial_states( + nested = build_initial_states( initial_states=initial_states, internal_regimes=model.internal_regimes, ) - for key, arr in flat.items(): - if arr.dtype.kind == "i": - assert arr.dtype == jnp.int32, ( - f"Initial state {key} has dtype {arr.dtype}, expected int32." - ) + for regime_name, regime_states in nested.items(): + for state_name, arr in regime_states.items(): + if arr.dtype.kind == "i": + assert arr.dtype == jnp.int32, ( + f"Initial state {regime_name}.{state_name} has dtype " + f"{arr.dtype}, expected int32." + ) def test_missing_cat_code_is_int32_minimum() -> None: @@ -69,32 +71,36 @@ def test_missing_cat_code_is_int32_minimum() -> None: assert jnp.iinfo(jnp.int32).min == MISSING_CAT_CODE -def test_update_states_for_subjects_keeps_same_dtype_round_trip() -> None: +def test_advance_states_for_subjects_keeps_same_dtype_round_trip() -> None: """Canonical-dtype transition outputs round-trip through the state pool. With every input boundary pinned to the canonical dtype, a well-typed user - transition returns canonical-dtype outputs and `_update_states_for_subjects` + transition returns canonical-dtype outputs and `_advance_states_for_subjects` writes them through `jnp.where` without dtype change. This test pins the contract for the int side; mixed-dtype inputs are out of scope — the function does not defend against transitions that violate the canonical- dtype invariant. """ - all_states = MappingProxyType( - {"work__health": jnp.asarray([0, 1, 0, 1], dtype=jnp.int32)} + current_states_per_regime = MappingProxyType( + { + "work": MappingProxyType( + {"health": jnp.asarray([0, 1, 0, 1], dtype=jnp.int32)} + ) + } ) next_values = jnp.asarray([1, 1, 1, 1], dtype=jnp.int32) - computed = MappingProxyType( - {"work": MappingProxyType({"next_health": next_values})} + next_states_per_regime = MappingProxyType( + {"work": MappingProxyType({"health": next_values})} ) subjects = jnp.asarray([True, False, True, False]) - updated = _update_states_for_subjects( - all_states=all_states, - computed_next_states=computed, + updated = _advance_states_for_subjects( + current_states_per_regime=current_states_per_regime, + next_states_per_regime=next_states_per_regime, subject_indices=subjects, ) - assert updated["work__health"].dtype == jnp.int32 + assert updated["work"]["health"].dtype == jnp.int32 def test_process_params_casts_python_int_to_int32() -> None: From 9c0a7f6e8ec0cb4aa14b8095303ae64172316be9 Mon Sep 17 00:00:00 2001 From: Hans-Martin von Gaudecker Date: Wed, 13 May 2026 10:29:26 +0200 Subject: [PATCH 09/77] Phase 2: rewrite qname introspection + structured stochastic factories MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Removes the flat-iterate + `tree_path_from_qname` decode pattern from the three simulation introspection sites (`transitions.py:111`, `compile.py:381`, `result.py:436`) — iterate the nested `transitions: Mapping[RegimeName, Mapping[TransitionFunctionName, ...]]` directly and assemble qnames at the boundary via `qname_from_tree_path`. Per-leaf stochastic factories in `next_state.py` now take `target` and `next_state_name` as separate args: - `_create_discrete_stochastic_next_func` builds the wrapper-kwarg qname (`weight___` / `key___`) locally instead of receiving the pre-joined `name`. - `_create_continuous_stochastic_next_func` takes the nested `all_grids` and the structured `(target, next_state_name)`, dropping the `name.split("next_")[1]` / `name.replace("next_", "")` parse used to recover components from the qname. `processing.py:599-611`, `next_state.py:143`, and `Q_and_F.py:516` replace `f"{regime}__next_{shock}"` / `f"weight_{regime}__{key}"` f-string concat with `qname_from_tree_path` calls. The wrapper-kwarg strings still exist inside `dags`'s qname encoding (that's the public DAG-arg convention `dags` ships), but pylcm code no longer produces or parses them outside the introduction boundary. DAG topology unchanged: per-(target, shock) wrappers stay independent nodes, `dags.concatenate_functions` keeps its pruning behaviour. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/lcm/regime_building/Q_and_F.py | 3 +- src/lcm/regime_building/next_state.py | 49 ++++++++++++++++----------- src/lcm/regime_building/processing.py | 11 +++--- src/lcm/simulation/compile.py | 12 +++---- src/lcm/simulation/result.py | 13 +++---- src/lcm/simulation/transitions.py | 21 ++++++------ tests/test_next_state.py | 6 ++-- 7 files changed, 64 insertions(+), 51 deletions(-) diff --git a/src/lcm/regime_building/Q_and_F.py b/src/lcm/regime_building/Q_and_F.py index 814619ddf..9ab96a492 100644 --- a/src/lcm/regime_building/Q_and_F.py +++ b/src/lcm/regime_building/Q_and_F.py @@ -4,6 +4,7 @@ import jax.numpy as jnp from dags import concatenate_functions, with_signature +from dags.tree import qname_from_tree_path from jax import Array from lcm.regime_building.h_dag import _get_build_H_kwargs @@ -513,7 +514,7 @@ def _get_joint_weights_function( """ arg_names = [ - f"weight_{regime_name}__{key}" + f"weight_{qname_from_tree_path((regime_name, key))}" for key in transitions if key in stochastic_transition_names ] diff --git a/src/lcm/regime_building/next_state.py b/src/lcm/regime_building/next_state.py index 537a5e5d3..9a6f8529e 100644 --- a/src/lcm/regime_building/next_state.py +++ b/src/lcm/regime_building/next_state.py @@ -27,7 +27,6 @@ TransitionFunctionName, TransitionFunctionsMapping, ) -from lcm.utils.namespace import flatten_regime_namespace def get_next_state_function_for_solution( @@ -140,7 +139,7 @@ def get_next_stochastic_weights_function( """ targets = [ - f"weight_{regime_name}__{func_name}" + f"weight_{qname_from_tree_path((regime_name, func_name))}" for func_name in transitions if func_name in stochastic_transition_names ] @@ -185,47 +184,53 @@ def _extend_target_transitions_for_simulation( """ shock_names: set[ShockName] = set(variable_info.query("is_shock").index.to_list()) - flat_grids = flatten_regime_namespace(all_grids) extended: dict[TransitionFunctionName, Callable[..., Array]] = dict( target_transitions ) for next_state_name in target_transitions: if next_state_name not in stochastic_transition_names: continue - qname = qname_from_tree_path((target, next_state_name)) raw_state_name = next_state_name.removeprefix("next_") if raw_state_name in shock_names: extended[next_state_name] = _create_continuous_stochastic_next_func( - name=qname, flat_grids=flat_grids + target=target, + next_state_name=next_state_name, + all_grids=all_grids, ) else: extended[next_state_name] = _create_discrete_stochastic_next_func( - name=qname, - labels=flat_grids[ - qname_from_tree_path((target, raw_state_name)) - ].to_jax(), + target=target, + next_state_name=next_state_name, + labels=all_grids[target][raw_state_name].to_jax(), ) return extended def _create_discrete_stochastic_next_func( - *, name: str, labels: DiscreteState + *, + target: RegimeName, + next_state_name: TransitionFunctionName, + labels: DiscreteState, ) -> StochasticNextFunction: """Get function that simulates the next state of a stochastic variable. Args: - name: Name of the stochastic variable. + target: Target regime name. + next_state_name: Transition function name with the `next_` prefix + (e.g. `next_health`). labels: 1d array of labels. Returns: A function that simulates the next state of the stochastic variable. The function must be called with keyword arguments: - - weight_{name}: 2d array of weights. The first dimension corresponds to the + - weight_{qname}: 2d array of weights. The first dimension corresponds to the number of simulation units. The second dimension corresponds to the number of grid points (labels). - - key_{name}: PRNG key for the stochastic next function, e.g. 'next_health'. + - key_{qname}: PRNG key for the stochastic next function. `qname` is the + dags-qualified `__`. """ + name = qname_from_tree_path((target, next_state_name)) @with_signature( args={f"weight_{name}": "FloatND", f"key_{name}": "dict[str, Array]"}, @@ -242,7 +247,10 @@ def next_stochastic_state(**kwargs: FloatND) -> DiscreteState: def _create_continuous_stochastic_next_func( - *, name: str, flat_grids: MappingProxyType[str, Grid] + *, + target: RegimeName, + next_state_name: TransitionFunctionName, + all_grids: MappingProxyType[RegimeName, MappingProxyType[StateOrActionName, Grid]], ) -> StochasticNextFunction: """Get function that simulates the next state of a stochastic variable. @@ -251,17 +259,18 @@ def _create_continuous_stochastic_next_func( before calling the shock calculation function. Args: - name: Name of the stochastic variable (e.g. `"regime__next_shock"`). - flat_grids: Flattened immutable mapping of regime-qualified names to Grid spec - objects. + target: Target regime name. + next_state_name: Transition function name with the `next_` prefix + (e.g. `next_shock`). + all_grids: Immutable mapping of regime names to Grid spec objects. Returns: A function that simulates the next state of the stochastic variable. """ - prev_state_name = name.split("next_")[1] - flat_key = name.replace("next_", "") - grid: _ShockGrid = flat_grids[flat_key] # ty: ignore [invalid-assignment] + prev_state_name = next_state_name.removeprefix("next_") + grid: _ShockGrid = all_grids[target][prev_state_name] # ty: ignore [invalid-assignment] + name = qname_from_tree_path((target, next_state_name)) if isinstance(grid, _ShockGridAR1): return _create_ar1_next_func( diff --git a/src/lcm/regime_building/processing.py b/src/lcm/regime_building/processing.py index e3017032d..a928b404d 100644 --- a/src/lcm/regime_building/processing.py +++ b/src/lcm/regime_building/processing.py @@ -596,19 +596,20 @@ def _process_regime_core( if isinstance(grid := grids.get(shock), _ShockGrid) } functions |= { - f"weight_{regime}__next_{shock}": _get_weights_func_for_shock( - name=shock, grid=grid + f"weight_{qname_from_tree_path((regime, f'next_{shock}'))}": ( + _get_weights_func_for_shock(name=shock, grid=grid) ) for (regime, shock), grid in target_shock_grids.items() } | { - f"{regime}__next_{shock}": _get_stochastic_next_function_for_shock( - name=shock, grid=grid.to_jax() + qname_from_tree_path((regime, f"next_{shock}")): ( + _get_stochastic_next_function_for_shock(name=shock, grid=grid.to_jax()) ) for (regime, shock), grid in target_shock_grids.items() } shock_transition_keys = { - f"{regime}__next_{shock}" for regime, shock in target_shock_grids + qname_from_tree_path((regime, f"next_{shock}")) + for regime, shock in target_shock_grids } internal_transition = { func_name: functions[func_name] diff --git a/src/lcm/simulation/compile.py b/src/lcm/simulation/compile.py index ad8caeeb5..06a8ceff7 100644 --- a/src/lcm/simulation/compile.py +++ b/src/lcm/simulation/compile.py @@ -20,7 +20,7 @@ import jax import jax.numpy as jnp -from dags.tree import tree_path_from_qname +from dags.tree import qname_from_tree_path from jax import Array from lcm.ages import AgeGrid @@ -36,7 +36,6 @@ RegimeName, ) from lcm.utils.logging import format_duration -from lcm.utils.namespace import flatten_regime_namespace def compile_all_simulate_functions( @@ -374,11 +373,12 @@ def _build_next_state_args( internal_regime.simulate_functions.stochastic_transition_names ) stoch_next_func_names = sorted( - next_func_name - for next_func_name in flatten_regime_namespace( - internal_regime.simulate_functions.transitions + qname_from_tree_path((target_regime, transition_name)) + for target_regime, target_transitions in ( + internal_regime.simulate_functions.transitions.items() ) - if tree_path_from_qname(next_func_name)[-1] in stoch_transition_names + for transition_name in target_transitions + if transition_name in stoch_transition_names ) _, stoch_keys = generate_simulation_keys( key=jax.random.key(0), diff --git a/src/lcm/simulation/result.py b/src/lcm/simulation/result.py index dfbbde96e..d686f9ca3 100644 --- a/src/lcm/simulation/result.py +++ b/src/lcm/simulation/result.py @@ -12,7 +12,7 @@ import jax.numpy as jnp import pandas as pd from dags import concatenate_functions -from dags.tree import tree_path_from_qname +from dags.tree import qname_from_tree_path from jax import Array from lcm.ages import AgeGrid @@ -33,7 +33,6 @@ UserFunction, ) from lcm.utils.dispatchers import vmap_1d -from lcm.utils.namespace import flatten_regime_namespace CLOUDPICKLE_IMPORT_ERROR_MSG = ( "Pickling SimulationResult objects requires the optional dependency 'cloudpickle'. " @@ -429,11 +428,13 @@ def _get_stochastic_weight_function_names(regime: InternalRegime) -> set[str]: for stochastic state transitions. They should not be exposed as available targets. """ stochastic_transition_names = regime.simulate_functions.stochastic_transition_names - flat_transitions = flatten_regime_namespace(regime.simulate_functions.transitions) return { - f"weight_{name}" - for name in flat_transitions - if tree_path_from_qname(name)[-1] in stochastic_transition_names + f"weight_{qname_from_tree_path((target_regime, transition_name))}" + for target_regime, target_transitions in ( + regime.simulate_functions.transitions.items() + ) + for transition_name in target_transitions + if transition_name in stochastic_transition_names } diff --git a/src/lcm/simulation/transitions.py b/src/lcm/simulation/transitions.py index f9e7d8387..6b689ce44 100644 --- a/src/lcm/simulation/transitions.py +++ b/src/lcm/simulation/transitions.py @@ -9,7 +9,7 @@ from types import MappingProxyType import jax -from dags.tree import tree_path_from_qname +from dags.tree import qname_from_tree_path from jax import Array, vmap from jax import numpy as jnp @@ -27,7 +27,6 @@ ScalarInt, StatesPerRegime, ) -from lcm.utils.namespace import flatten_regime_namespace def create_regime_state_action_space( @@ -104,16 +103,16 @@ def calculate_next_states( stochastic_transition_names = ( internal_regime.simulate_functions.stochastic_transition_names ) - stochastic_next_function_names = [ - next_func_name - for next_func_name in flatten_regime_namespace( - internal_regime.simulate_functions.transitions + # Sorted to fix a downstream-ordering bug when the nested iteration + # yields names in a non-deterministic order. + stochastic_next_function_names = sorted( + qname_from_tree_path((target_regime, transition_name)) + for target_regime, target_transitions in ( + internal_regime.simulate_functions.transitions.items() ) - if tree_path_from_qname(next_func_name)[-1] in stochastic_transition_names - ] - # There is a bug that sometimes changes the order of the names, - # sorting fixes this - stochastic_next_function_names.sort() + for transition_name in target_transitions + if transition_name in stochastic_transition_names + ) key, stochastic_variables_keys = generate_simulation_keys( key=key, diff --git a/tests/test_next_state.py b/tests/test_next_state.py index 1f3067ae9..1fcc3eb81 100644 --- a/tests/test_next_state.py +++ b/tests/test_next_state.py @@ -99,11 +99,13 @@ class MockCategory: def test_create_stochastic_next_func(): labels = jnp.arange(2) - got_func = _create_discrete_stochastic_next_func(name="a", labels=labels) + got_func = _create_discrete_stochastic_next_func( + target="t", next_state_name="next_a", labels=labels + ) key = jnp.arange(2, dtype="uint32") # PRNG dtype weights = jnp.array([0.0, 1]) - got = got_func(key_a=key, weight_a=weights) + got = got_func(key_t__next_a=key, weight_t__next_a=weights) assert jnp.array_equal(got, jnp.array(1)) From 9b7f8a25a2fa0d15b8403d87d1d6af5984deeb56 Mon Sep 17 00:00:00 2001 From: Hans-Martin von Gaudecker Date: Wed, 13 May 2026 10:30:56 +0200 Subject: [PATCH 10/77] Refactor: simplify state-carrier names, tighten StateName annotations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Drop redundant `current_` prefix: `current_states_per_regime` → `states_per_regime` throughout simulation transitions. - Specialise `create_regime_state_action_space` to take `regime_states: RegimeStates` instead of the full per-regime carrier; caller indexes by regime name. - Annotate state-keyed inner mappings with `StateName` alias rather than bare `str` (build_initial_states locals, _advance_states_for_subjects, initial_states/subject_states helper params). - Rename misleading test locals: `flat`/`nested`/`updated` → `states_per_regime`/`next_states`. Co-Authored-By: Claude Opus 4.7 --- src/lcm/simulation/initial_conditions.py | 31 ++++++++++++------------ src/lcm/simulation/simulate.py | 4 +-- src/lcm/simulation/transitions.py | 24 +++++++++--------- src/lcm/typing.py | 3 --- tests/simulation/test_update_states.py | 28 ++++++++++----------- tests/test_float_dtype_invariants.py | 16 ++++++------ tests/test_int_dtype_invariants.py | 12 ++++----- 7 files changed, 58 insertions(+), 60 deletions(-) diff --git a/src/lcm/simulation/initial_conditions.py b/src/lcm/simulation/initial_conditions.py index 28bad78ad..80d29e9b2 100644 --- a/src/lcm/simulation/initial_conditions.py +++ b/src/lcm/simulation/initial_conditions.py @@ -31,6 +31,7 @@ RegimeIdsToNames, RegimeName, RegimeNamesToIds, + StateName, StatesPerRegime, ) from lcm.utils.containers import invert_regime_ids @@ -44,7 +45,7 @@ def build_initial_states( *, - initial_states: Mapping[str, Array], + initial_states: Mapping[StateName, Array], internal_regimes: MappingProxyType[RegimeName, InternalRegime], ) -> StatesPerRegime: """Build the regime-keyed state carrier from user-provided initial states. @@ -62,10 +63,10 @@ def build_initial_states( """ n_subjects = len(next(iter(initial_states.values()))) - nested: dict[RegimeName, MappingProxyType[str, Array]] = {} + states_per_regime: dict[RegimeName, MappingProxyType[StateName, Array]] = {} for regime_name, internal_regime in internal_regimes.items(): - regime_states: dict[str, Array] = {} + regime_states: dict[StateName, Array] = {} for state_name in _get_regime_state_names(internal_regime): grid = internal_regime.grids[state_name] if isinstance(grid, DiscreteGrid): @@ -92,9 +93,9 @@ def build_initial_states( regime_states[state_name] = jnp.full( n_subjects, jnp.nan, dtype=canonical_float_dtype() ) - nested[regime_name] = MappingProxyType(regime_states) + states_per_regime[regime_name] = MappingProxyType(regime_states) - return MappingProxyType(nested) + return MappingProxyType(states_per_regime) def validate_initial_conditions( @@ -232,7 +233,7 @@ def _format_missing_states_message(missing: set[str], required: set[str]) -> str def _collect_state_name_errors( *, - initial_states: Mapping[str, Array], + initial_states: Mapping[StateName, Array], regime_id_arr: Array, regime_ids_to_names: RegimeIdsToNames, internal_regimes: MappingProxyType[RegimeName, InternalRegime], @@ -290,7 +291,7 @@ def _collect_state_name_errors( def _collect_structural_errors( *, - initial_states: Mapping[str, Array], + initial_states: Mapping[StateName, Array], regime_id_arr: Array, regime_ids_to_names: RegimeIdsToNames, regime_names_to_ids: RegimeNamesToIds, @@ -390,7 +391,7 @@ def _collect_structural_errors( def _collect_feasibility_errors( *, - initial_states: Mapping[str, Array], + initial_states: Mapping[StateName, Array], regime_id_arr: Array, regime_names_to_ids: RegimeNamesToIds, internal_regimes: MappingProxyType[RegimeName, InternalRegime], @@ -441,7 +442,7 @@ def _collect_feasibility_errors( def _validate_discrete_state_values( *, - initial_states: Mapping[str, Array], + initial_states: Mapping[StateName, Array], internal_regimes: MappingProxyType[RegimeName, InternalRegime], regime_id_arr: Array, regime_names_to_ids: RegimeNamesToIds, @@ -508,7 +509,7 @@ def _validate_discrete_state_values( def _batched_feasibility_check( *, feasibility_func: Callable[..., Array], - subject_states: Mapping[str, Array], + subject_states: Mapping[StateName, Array], action_kwargs: Mapping[str, Array], filtered_params: Mapping[str, object], flat_actions: Mapping[ActionName, Array], @@ -576,7 +577,7 @@ def _check_regime_feasibility( # noqa: C901 *, internal_regime: InternalRegime, regime_name: RegimeName, - initial_states: Mapping[str, Array], + initial_states: Mapping[StateName, Array], subject_indices: list[int], regime_params: Mapping[str, object], ages: AgeGrid, @@ -628,7 +629,7 @@ def _check_regime_feasibility( # noqa: C901 # Build per-subject state arrays idx_arr = jnp.array(subject_indices) - subject_states: dict[str, Array] = {} + subject_states: dict[StateName, Array] = {} for sn in state_names: if sn in accepted: subject_states[sn] = initial_states[sn][idx_arr] @@ -717,7 +718,7 @@ def _check_combo(action_kw: dict[str, Array]) -> Array: def _per_constraint_feasibility( *, internal_regime: InternalRegime, - subject_states: Mapping[str, Array], + subject_states: Mapping[StateName, Array], regime_params: Mapping[str, object], flat_actions: Mapping[ActionName, Array], idx_arr: Array, @@ -784,7 +785,7 @@ def _raise_feasibility_type_error( exc: TypeError, regime_name: RegimeName, internal_regime: InternalRegime, - subject_states: dict[str, Array], + subject_states: dict[StateName, Array], ) -> Never: """Re-raise a TypeError from feasibility checking with diagnostic context. @@ -830,7 +831,7 @@ def _format_infeasibility_message( infeasible_indices: Sequence[int], internal_regime: InternalRegime, regime_name: RegimeName, - initial_states: Mapping[str, Array], + initial_states: Mapping[StateName, Array], state_names: Sequence[str], per_constraint_admits_any: Mapping[str, np.ndarray], ) -> str: diff --git a/src/lcm/simulation/simulate.py b/src/lcm/simulation/simulate.py index 277aa5aff..70c6f0d6c 100644 --- a/src/lcm/simulation/simulate.py +++ b/src/lcm/simulation/simulate.py @@ -252,7 +252,7 @@ def _simulate_regime_in_period( state_action_space = create_regime_state_action_space( internal_regime=internal_regime, - current_states_per_regime=states, + regime_states=states[regime_name], regime_params=internal_params[regime_name], ) # Compute optimal actions @@ -309,7 +309,7 @@ def _simulate_regime_in_period( period=period, age=age, regime_params=internal_params[regime_name], - current_states_per_regime=states, + states_per_regime=states, state_action_space=state_action_space, key=next_states_key, subjects_in_regime=subject_ids_in_regime, diff --git a/src/lcm/simulation/transitions.py b/src/lcm/simulation/transitions.py index f9e7d8387..5386d5ba9 100644 --- a/src/lcm/simulation/transitions.py +++ b/src/lcm/simulation/transitions.py @@ -23,8 +23,10 @@ Int1D, RegimeName, RegimeNamesToIds, + RegimeStates, ScalarFloat, ScalarInt, + StateName, StatesPerRegime, ) from lcm.utils.namespace import flatten_regime_namespace @@ -33,7 +35,7 @@ def create_regime_state_action_space( *, internal_regime: InternalRegime, - current_states_per_regime: StatesPerRegime, + regime_states: RegimeStates, regime_params: FlatRegimeParams, ) -> StateActionSpace: """Create the state-action space containing only the relevant subjects in a regime. @@ -44,8 +46,7 @@ def create_regime_state_action_space( Args: internal_regime: The internal regime instance. - current_states_per_regime: Carrier of state arrays for every regime and - state, indexed by regime name then state name. + regime_states: State arrays for this regime, keyed by state name. regime_params: Flat regime parameters supplied at runtime, used to substitute runtime-supplied action gridpoints. @@ -56,7 +57,6 @@ def create_regime_state_action_space( base = internal_regime.state_action_space(regime_params=regime_params) relevant_state_names = internal_regime.variable_info.query("is_state").index - regime_states = current_states_per_regime[internal_regime.name] states_for_state_action_space = { sn: regime_states[sn] for sn in relevant_state_names } @@ -75,7 +75,7 @@ def calculate_next_states( period: int, age: ScalarInt | ScalarFloat, regime_params: FlatRegimeParams, - current_states_per_regime: StatesPerRegime, + states_per_regime: StatesPerRegime, state_action_space: StateActionSpace, key: Array, subjects_in_regime: Bool1D, @@ -89,7 +89,7 @@ def calculate_next_states( period: Current period. age: Age corresponding to current period. regime_params: Flat regime parameters. - current_states_per_regime: Carrier of current-period state arrays for + states_per_regime: Carrier of current-period state arrays for every regime and state, indexed by regime name then state name. state_action_space: State-action space for subjects in this regime. key: JAX random key. @@ -151,7 +151,7 @@ def calculate_next_states( ) return _advance_states_for_subjects( - current_states_per_regime=current_states_per_regime, + states_per_regime=states_per_regime, next_states_per_regime=next_states_per_regime, subject_indices=subjects_in_regime, ) @@ -274,7 +274,7 @@ def random_id( def _advance_states_for_subjects( *, - current_states_per_regime: StatesPerRegime, + states_per_regime: StatesPerRegime, next_states_per_regime: StatesPerRegime, subject_indices: Bool1D, ) -> StatesPerRegime: @@ -288,7 +288,7 @@ def _advance_states_for_subjects( are passed through unchanged. Args: - current_states_per_regime: Carrier of state arrays prior to this advance, + states_per_regime: Carrier of state arrays prior to this advance, indexed by regime name then state name. next_states_per_regime: Per-regime next-period state values to merge in, indexed by regime name then state name (no `next_` prefix — the @@ -300,13 +300,13 @@ def _advance_states_for_subjects( Updated carrier with next-period values written in for selected subjects. """ - updated: dict[RegimeName, dict[str, Array]] = { + updated: dict[RegimeName, dict[StateName, Array]] = { regime_name: dict(regime_states) - for regime_name, regime_states in current_states_per_regime.items() + for regime_name, regime_states in states_per_regime.items() } for target, target_next_states in next_states_per_regime.items(): for state_name, next_state_values in target_next_states.items(): - current_arr = current_states_per_regime[target][state_name] + current_arr = states_per_regime[target][state_name] target_dtype = current_arr.dtype # Preserve storage dtype only when the transition output is the # same numeric kind. Across kinds (e.g. int storage + float diff --git a/src/lcm/typing.py b/src/lcm/typing.py index 23dac9388..f93b70e52 100644 --- a/src/lcm/typing.py +++ b/src/lcm/typing.py @@ -45,9 +45,6 @@ RegimeName, MappingProxyType[TransitionFunctionName, InternalUserFunction] ] -# Nested state carrier threading through the simulation loop. -# `RegimeStates`: one regime's state arrays, keyed by state name. -# `StatesPerRegime`: every regime's `RegimeStates` bundle, keyed by regime name. type RegimeStates = MappingProxyType[StateName, Array] type StatesPerRegime = MappingProxyType[RegimeName, RegimeStates] diff --git a/tests/simulation/test_update_states.py b/tests/simulation/test_update_states.py index a3405e728..86abf1485 100644 --- a/tests/simulation/test_update_states.py +++ b/tests/simulation/test_update_states.py @@ -8,7 +8,7 @@ def test_advance_states_writes_new_values_for_selected_subjects(): """Selected subjects receive the next-period array; others stay put.""" - current_states_per_regime = MappingProxyType( + states_per_regime = MappingProxyType( { "working": MappingProxyType({"wealth": jnp.array([10.0, 20.0, 30.0])}), } @@ -20,18 +20,18 @@ def test_advance_states_writes_new_values_for_selected_subjects(): ) subject_indices = jnp.array([True, False, True]) - result = _advance_states_for_subjects( - current_states_per_regime=current_states_per_regime, + next_states = _advance_states_for_subjects( + states_per_regime=states_per_regime, next_states_per_regime=next_states_per_regime, subject_indices=subject_indices, ) - assert_array_equal(result["working"]["wealth"], jnp.array([15.0, 20.0, 35.0])) + assert_array_equal(next_states["working"]["wealth"], jnp.array([15.0, 20.0, 35.0])) def test_advance_states_handles_multiple_regimes_and_states(): """Regimes named in `next_states_per_regime` get updated; others stay intact.""" - current_states_per_regime = MappingProxyType( + states_per_regime = MappingProxyType( { "working": MappingProxyType( { @@ -54,20 +54,20 @@ def test_advance_states_handles_multiple_regimes_and_states(): ) subject_indices = jnp.array([True, True]) - result = _advance_states_for_subjects( - current_states_per_regime=current_states_per_regime, + next_states = _advance_states_for_subjects( + states_per_regime=states_per_regime, next_states_per_regime=next_states_per_regime, subject_indices=subject_indices, ) - assert_array_equal(result["working"]["wealth"], jnp.array([15.0, 25.0])) - assert_array_equal(result["working"]["health"], jnp.array([1.5, 2.5])) - assert_array_equal(result["retired"]["wealth"], jnp.array([100.0, 200.0])) + assert_array_equal(next_states["working"]["wealth"], jnp.array([15.0, 25.0])) + assert_array_equal(next_states["working"]["health"], jnp.array([1.5, 2.5])) + assert_array_equal(next_states["retired"]["wealth"], jnp.array([100.0, 200.0])) def test_advance_states_no_subjects_selected_leaves_carrier_unchanged(): """When no subject is selected, the next-period values are ignored entirely.""" - current_states_per_regime = MappingProxyType( + states_per_regime = MappingProxyType( { "r": MappingProxyType({"wealth": jnp.array([10.0, 20.0])}), } @@ -79,10 +79,10 @@ def test_advance_states_no_subjects_selected_leaves_carrier_unchanged(): ) subject_indices = jnp.array([False, False]) - result = _advance_states_for_subjects( - current_states_per_regime=current_states_per_regime, + next_states = _advance_states_for_subjects( + states_per_regime=states_per_regime, next_states_per_regime=next_states_per_regime, subject_indices=subject_indices, ) - assert_array_equal(result["r"]["wealth"], jnp.array([10.0, 20.0])) + assert_array_equal(next_states["r"]["wealth"], jnp.array([10.0, 20.0])) diff --git a/tests/test_float_dtype_invariants.py b/tests/test_float_dtype_invariants.py index 28b9588b0..7e4f14fca 100644 --- a/tests/test_float_dtype_invariants.py +++ b/tests/test_float_dtype_invariants.py @@ -35,11 +35,11 @@ def test_build_initial_states_casts_user_float64_to_canonical(x64_disabled: None "wealth": np.asarray([20.0, 50.0], dtype=np.float64), "age": np.asarray([18.0, 18.0], dtype=np.float64), } - flat = build_initial_states( + states_per_regime = build_initial_states( initial_states=initial_states, # ty: ignore[invalid-argument-type] internal_regimes=model.internal_regimes, ) - assert flat["working_life"]["wealth"].dtype == canonical_float_dtype() + assert states_per_regime["working_life"]["wealth"].dtype == canonical_float_dtype() def test_build_initial_states_casts_user_int_to_canonical(x64_disabled: None): @@ -49,11 +49,11 @@ def test_build_initial_states_casts_user_int_to_canonical(x64_disabled: None): "wealth": jnp.asarray([20, 50], dtype=jnp.int32), "age": jnp.asarray([18, 18], dtype=jnp.int32), } - flat = build_initial_states( + states_per_regime = build_initial_states( initial_states=initial_states, internal_regimes=model.internal_regimes, ) - assert flat["working_life"]["wealth"].dtype == canonical_float_dtype() + assert states_per_regime["working_life"]["wealth"].dtype == canonical_float_dtype() def test_build_initial_states_missing_continuous_fallback_dtype_is_canonical( @@ -62,11 +62,11 @@ def test_build_initial_states_missing_continuous_fallback_dtype_is_canonical( """A missing continuous state falls back to a canonical-dtype array.""" model = get_model(n_periods=3) # Supply a placeholder state to set n_subjects without touching `wealth`. - flat = build_initial_states( + states_per_regime = build_initial_states( initial_states={"placeholder": jnp.asarray([0.0, 0.0])}, internal_regimes=model.internal_regimes, ) - assert flat["working_life"]["wealth"].dtype == canonical_float_dtype() + assert states_per_regime["working_life"]["wealth"].dtype == canonical_float_dtype() def test_build_initial_states_missing_continuous_fallback_values_are_nan( @@ -78,11 +78,11 @@ def test_build_initial_states_missing_continuous_fallback_values_are_nan( with zeros (or anything else representable) pass; assert the values. """ model = get_model(n_periods=3) - flat = build_initial_states( + states_per_regime = build_initial_states( initial_states={"placeholder": jnp.asarray([0.0, 0.0])}, internal_regimes=model.internal_regimes, ) - assert bool(jnp.all(jnp.isnan(flat["working_life"]["wealth"]))) + assert bool(jnp.all(jnp.isnan(states_per_regime["working_life"]["wealth"]))) def test_process_params_casts_float64_array_to_canonical_under_no_x64( diff --git a/tests/test_int_dtype_invariants.py b/tests/test_int_dtype_invariants.py index d2347c230..4c31f4864 100644 --- a/tests/test_int_dtype_invariants.py +++ b/tests/test_int_dtype_invariants.py @@ -53,11 +53,11 @@ def test_build_initial_states_discrete_dtype_is_int32() -> None: "wealth": jnp.array([20.0, 50.0]), "age": jnp.array([18.0, 18.0]), } - nested = build_initial_states( + states_per_regime = build_initial_states( initial_states=initial_states, internal_regimes=model.internal_regimes, ) - for regime_name, regime_states in nested.items(): + for regime_name, regime_states in states_per_regime.items(): for state_name, arr in regime_states.items(): if arr.dtype.kind == "i": assert arr.dtype == jnp.int32, ( @@ -81,7 +81,7 @@ def test_advance_states_for_subjects_keeps_same_dtype_round_trip() -> None: function does not defend against transitions that violate the canonical- dtype invariant. """ - current_states_per_regime = MappingProxyType( + states_per_regime = MappingProxyType( { "work": MappingProxyType( {"health": jnp.asarray([0, 1, 0, 1], dtype=jnp.int32)} @@ -94,13 +94,13 @@ def test_advance_states_for_subjects_keeps_same_dtype_round_trip() -> None: ) subjects = jnp.asarray([True, False, True, False]) - updated = _advance_states_for_subjects( - current_states_per_regime=current_states_per_regime, + next_states = _advance_states_for_subjects( + states_per_regime=states_per_regime, next_states_per_regime=next_states_per_regime, subject_indices=subjects, ) - assert updated["work"]["health"].dtype == jnp.int32 + assert next_states["work"]["health"].dtype == jnp.int32 def test_process_params_casts_python_int_to_int32() -> None: From 914d9d69d1c3460a59f1a9c708fe5577cda29083 Mon Sep 17 00:00:00 2001 From: Hans-Martin von Gaudecker Date: Wed, 13 May 2026 11:10:07 +0200 Subject: [PATCH 11/77] Review feedback: prefer f-strings for encode, clarify state-name naming MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Revert encode-only `qname_from_tree_path` calls back to plain f-strings where the result is consumed by an outer f-string or the arg list itself contains f-strings (`next_state.py:142`, `Q_and_F.py:517`, `processing.py:599-611`, `result.py:432`). - Keep `qname_from_tree_path` where it stands alone in nested iteration (`transitions.py:109`, `compile.py:376`) or as an explicit assignment in the per-leaf factories. Rename the assigned local from `name` to `qname` (and the matching param in `_create_ar1_next_func` / `_create_iid_next_func`). - Rename `prev_state_name` → `state_name` in the continuous-shock factory family. It's the state-name without the `next_` prefix; "prev" was relative to the transition output and confused the lookup against the carrier. - Sharpen the `labels` docstring in `_create_discrete_stochastic_next_func`: category codes the discrete state can take, drawn via `jax.random.choice` weighted by `weight_`. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/lcm/regime_building/Q_and_F.py | 3 +- src/lcm/regime_building/next_state.py | 58 +++++++++++++-------------- src/lcm/regime_building/processing.py | 11 +++-- src/lcm/simulation/result.py | 3 +- 4 files changed, 34 insertions(+), 41 deletions(-) diff --git a/src/lcm/regime_building/Q_and_F.py b/src/lcm/regime_building/Q_and_F.py index 9ab96a492..814619ddf 100644 --- a/src/lcm/regime_building/Q_and_F.py +++ b/src/lcm/regime_building/Q_and_F.py @@ -4,7 +4,6 @@ import jax.numpy as jnp from dags import concatenate_functions, with_signature -from dags.tree import qname_from_tree_path from jax import Array from lcm.regime_building.h_dag import _get_build_H_kwargs @@ -514,7 +513,7 @@ def _get_joint_weights_function( """ arg_names = [ - f"weight_{qname_from_tree_path((regime_name, key))}" + f"weight_{regime_name}__{key}" for key in transitions if key in stochastic_transition_names ] diff --git a/src/lcm/regime_building/next_state.py b/src/lcm/regime_building/next_state.py index 9a6f8529e..90674ba5c 100644 --- a/src/lcm/regime_building/next_state.py +++ b/src/lcm/regime_building/next_state.py @@ -139,7 +139,7 @@ def get_next_stochastic_weights_function( """ targets = [ - f"weight_{qname_from_tree_path((regime_name, func_name))}" + f"weight_{regime_name}__{func_name}" for func_name in transitions if func_name in stochastic_transition_names ] @@ -190,8 +190,8 @@ def _extend_target_transitions_for_simulation( for next_state_name in target_transitions: if next_state_name not in stochastic_transition_names: continue - raw_state_name = next_state_name.removeprefix("next_") - if raw_state_name in shock_names: + state_name = next_state_name.removeprefix("next_") + if state_name in shock_names: extended[next_state_name] = _create_continuous_stochastic_next_func( target=target, next_state_name=next_state_name, @@ -201,7 +201,7 @@ def _extend_target_transitions_for_simulation( extended[next_state_name] = _create_discrete_stochastic_next_func( target=target, next_state_name=next_state_name, - labels=all_grids[target][raw_state_name].to_jax(), + labels=all_grids[target][state_name].to_jax(), ) return extended @@ -218,29 +218,31 @@ def _create_discrete_stochastic_next_func( target: Target regime name. next_state_name: Transition function name with the `next_` prefix (e.g. `next_health`). - labels: 1d array of labels. + labels: Category codes the discrete state can take (the DiscreteGrid + rendered as a 1d JAX array). The simulated realisation is one of + these, drawn via `jax.random.choice` weighted by `weight_`. Returns: A function that simulates the next state of the stochastic variable. The function must be called with keyword arguments: - weight_{qname}: 2d array of weights. The first dimension corresponds to the number of simulation units. The second dimension corresponds to the number of - grid points (labels). + grid points (one slot per `labels` entry). - key_{qname}: PRNG key for the stochastic next function. `qname` is the dags-qualified `__`. """ - name = qname_from_tree_path((target, next_state_name)) + qname = qname_from_tree_path((target, next_state_name)) @with_signature( - args={f"weight_{name}": "FloatND", f"key_{name}": "dict[str, Array]"}, + args={f"weight_{qname}": "FloatND", f"key_{qname}": "dict[str, Array]"}, return_annotation="DiscreteState", ) def next_stochastic_state(**kwargs: FloatND) -> DiscreteState: return jax.random.choice( - key=kwargs[f"key_{name}"], + key=kwargs[f"key_{qname}"], a=labels, - p=kwargs[f"weight_{name}"], + p=kwargs[f"weight_{qname}"], ) return next_stochastic_state @@ -268,34 +270,29 @@ def _create_continuous_stochastic_next_func( A function that simulates the next state of the stochastic variable. """ - prev_state_name = next_state_name.removeprefix("next_") - grid: _ShockGrid = all_grids[target][prev_state_name] # ty: ignore [invalid-assignment] - name = qname_from_tree_path((target, next_state_name)) + state_name = next_state_name.removeprefix("next_") + grid: _ShockGrid = all_grids[target][state_name] # ty: ignore [invalid-assignment] + qname = qname_from_tree_path((target, next_state_name)) if isinstance(grid, _ShockGridAR1): - return _create_ar1_next_func( - name=name, prev_state_name=prev_state_name, grid=grid - ) + return _create_ar1_next_func(qname=qname, state_name=state_name, grid=grid) if isinstance(grid, _ShockGridIID): - return _create_iid_next_func( - name=name, prev_state_name=prev_state_name, grid=grid - ) + return _create_iid_next_func(qname=qname, state_name=state_name, grid=grid) msg = f"Expected _ShockGridIID or _ShockGridAR1, got {type(grid)}" raise TypeError(msg) def _create_ar1_next_func( - *, name: str, prev_state_name: StateName, grid: _ShockGridAR1 + *, qname: str, state_name: StateName, grid: _ShockGridAR1 ) -> StochasticNextFunction: fixed_params = dict(grid.params) runtime_param_names = { - qname_from_tree_path((prev_state_name, p)): p - for p in grid.params_to_pass_at_runtime + qname_from_tree_path((state_name, p)): p for p in grid.params_to_pass_at_runtime } args: dict[str, str] = { - f"key_{name}": "dict[str, Array]", - prev_state_name: "ContinuousState", + f"key_{qname}": "dict[str, Array]", + state_name: "ContinuousState", **dict.fromkeys(runtime_param_names, "float"), } _draw_shock = grid.draw_shock @@ -310,23 +307,22 @@ def next_stochastic_state(**kwargs: FloatND) -> ContinuousState: ) return _draw_shock( params=params, - key=kwargs[f"key_{name}"], - current_value=kwargs[prev_state_name], + key=kwargs[f"key_{qname}"], + current_value=kwargs[state_name], ) return next_stochastic_state def _create_iid_next_func( - *, name: str, prev_state_name: StateName, grid: _ShockGridIID + *, qname: str, state_name: StateName, grid: _ShockGridIID ) -> StochasticNextFunction: fixed_params = dict(grid.params) runtime_param_names = { - qname_from_tree_path((prev_state_name, p)): p - for p in grid.params_to_pass_at_runtime + qname_from_tree_path((state_name, p)): p for p in grid.params_to_pass_at_runtime } args: dict[str, str] = { - f"key_{name}": "dict[str, Array]", + f"key_{qname}": "dict[str, Array]", **dict.fromkeys(runtime_param_names, "float"), } _draw_shock = grid.draw_shock @@ -341,7 +337,7 @@ def next_stochastic_state(**kwargs: FloatND) -> ContinuousState: ) return _draw_shock( params=params, - key=kwargs[f"key_{name}"], + key=kwargs[f"key_{qname}"], ) return next_stochastic_state diff --git a/src/lcm/regime_building/processing.py b/src/lcm/regime_building/processing.py index a928b404d..e3017032d 100644 --- a/src/lcm/regime_building/processing.py +++ b/src/lcm/regime_building/processing.py @@ -596,20 +596,19 @@ def _process_regime_core( if isinstance(grid := grids.get(shock), _ShockGrid) } functions |= { - f"weight_{qname_from_tree_path((regime, f'next_{shock}'))}": ( - _get_weights_func_for_shock(name=shock, grid=grid) + f"weight_{regime}__next_{shock}": _get_weights_func_for_shock( + name=shock, grid=grid ) for (regime, shock), grid in target_shock_grids.items() } | { - qname_from_tree_path((regime, f"next_{shock}")): ( - _get_stochastic_next_function_for_shock(name=shock, grid=grid.to_jax()) + f"{regime}__next_{shock}": _get_stochastic_next_function_for_shock( + name=shock, grid=grid.to_jax() ) for (regime, shock), grid in target_shock_grids.items() } shock_transition_keys = { - qname_from_tree_path((regime, f"next_{shock}")) - for regime, shock in target_shock_grids + f"{regime}__next_{shock}" for regime, shock in target_shock_grids } internal_transition = { func_name: functions[func_name] diff --git a/src/lcm/simulation/result.py b/src/lcm/simulation/result.py index d686f9ca3..82ce2cf85 100644 --- a/src/lcm/simulation/result.py +++ b/src/lcm/simulation/result.py @@ -12,7 +12,6 @@ import jax.numpy as jnp import pandas as pd from dags import concatenate_functions -from dags.tree import qname_from_tree_path from jax import Array from lcm.ages import AgeGrid @@ -429,7 +428,7 @@ def _get_stochastic_weight_function_names(regime: InternalRegime) -> set[str]: """ stochastic_transition_names = regime.simulate_functions.stochastic_transition_names return { - f"weight_{qname_from_tree_path((target_regime, transition_name))}" + f"weight_{target_regime}__{transition_name}" for target_regime, target_transitions in ( regime.simulate_functions.transitions.items() ) From a5d932a4899c58aa8c272caef44f35d2c2836575 Mon Sep 17 00:00:00 2001 From: Hans-Martin von Gaudecker Date: Wed, 13 May 2026 12:31:22 +0200 Subject: [PATCH 12/77] Replace variable_info DataFrame with typed mapping MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Internal `InternalRegime.variable_info` flips from `pd.DataFrame` with five boolean columns (`is_state`, `is_action`, `is_continuous`, `is_discrete`, `is_shock`) to `MappingProxyType[StateOrActionName, VariableInfo]` where `VariableInfo` is a frozen dataclass with three fields: - `kind: Literal["state", "action"]` - `topology: Literal["continuous", "discrete"]` - `is_shock: bool` `VariableInfo` + `VariableInfoMapping` alias live in `interfaces.py` next to the other internal data types. The constructor in `regime_building/variable_info.py` is rewritten in pure Python, with shock variables tagged `topology="discrete"` (matching the existing `is_continuous = ContinuousGrid and not _ShockGrid` semantics — shock grids approximate a continuous random variable but are processed by discrete-sweep machinery). ty gains visibility into every `variable_info` access. The 15 call sites that previously used `.query("is_state")` etc. now use comprehensions like `[k for k, v in vi.items() if v.kind == "state"]`. Closes part 1 of #176. Co-Authored-By: Claude Opus 4.7 --- src/lcm/interfaces.py | 31 ++++- src/lcm/regime_building/V.py | 2 +- src/lcm/regime_building/next_state.py | 10 +- src/lcm/regime_building/processing.py | 20 ++-- src/lcm/regime_building/variable_info.py | 106 ++++++++++-------- src/lcm/simulation/initial_conditions.py | 24 +++- src/lcm/simulation/result.py | 4 +- src/lcm/simulation/transitions.py | 6 +- src/lcm/state_action_space.py | 28 ++--- src/lcm/utils/error_handling.py | 6 +- .../regime_building/test_regime_processing.py | 45 ++++---- tests/test_next_state.py | 6 +- tests/test_state_action_space.py | 24 ++-- 13 files changed, 182 insertions(+), 130 deletions(-) diff --git a/src/lcm/interfaces.py b/src/lcm/interfaces.py index c74842e6a..08b9e48a3 100644 --- a/src/lcm/interfaces.py +++ b/src/lcm/interfaces.py @@ -1,9 +1,8 @@ import dataclasses from collections.abc import Callable from types import MappingProxyType -from typing import cast +from typing import Literal, cast -import pandas as pd from jax import Array from lcm.grids import Grid, IrregSpacedGrid @@ -207,6 +206,30 @@ class SimulateFunctions: """Compiled function to compute next-period states.""" +@dataclasses.dataclass(frozen=True) +class VariableInfo: + """Kind/topology/shock tags for one state or action variable.""" + + kind: Literal["state", "action"] + """Whether the variable is a state or an action.""" + + topology: Literal["continuous", "discrete"] + """Topology as treated by pylcm's solve/simulate machinery. + + Shocks have topology `"discrete"` because their value space is + approximated by a finite grid of nodes, even though the underlying + random variable is mathematically continuous. Combine with `is_shock` + when the distinction matters. + + """ + + is_shock: bool + """Whether the variable is a shock (always a state).""" + + +type VariableInfoMapping = MappingProxyType[StateOrActionName, VariableInfo] + + @dataclasses.dataclass(frozen=True, kw_only=True) class InternalRegime: """Internal representation of a user regime.""" @@ -220,8 +243,8 @@ class InternalRegime: grids: MappingProxyType[StateOrActionName, Grid] """Immutable mapping of variable names to grid objects.""" - variable_info: pd.DataFrame - """DataFrame with variable metadata (is_state, is_action, etc.).""" + variable_info: VariableInfoMapping + """Immutable mapping of variable names to their kind/topology/shock tags.""" active_periods: tuple[int, ...] """Period indices during which this regime is active.""" diff --git a/src/lcm/regime_building/V.py b/src/lcm/regime_building/V.py index 8b4cb32b2..ff0ba46b2 100644 --- a/src/lcm/regime_building/V.py +++ b/src/lcm/regime_building/V.py @@ -53,7 +53,7 @@ def create_v_interpolation_info(regime: Regime) -> VInterpolationInfo: vi = get_variable_info(regime) grids = get_grids(regime) - state_names = vi.query("is_state").index.tolist() + state_names = [name for name, info in vi.items() if info.kind == "state"] discrete_states = { name: grid_spec diff --git a/src/lcm/regime_building/next_state.py b/src/lcm/regime_building/next_state.py index 90674ba5c..539e628df 100644 --- a/src/lcm/regime_building/next_state.py +++ b/src/lcm/regime_building/next_state.py @@ -4,12 +4,12 @@ from types import MappingProxyType import jax -import pandas as pd from dags import concatenate_functions, with_signature from dags.tree import qname_from_tree_path from jax import Array from lcm.grids import Grid +from lcm.interfaces import VariableInfoMapping from lcm.shocks import _ShockGrid from lcm.shocks.ar1 import _ShockGridAR1 from lcm.shocks.iid import _ShockGridIID @@ -63,7 +63,7 @@ def get_next_state_function_for_simulation( transitions: TransitionFunctionsMapping, functions: FunctionsMapping, all_grids: MappingProxyType[RegimeName, MappingProxyType[StateOrActionName, Grid]], - variable_info: pd.DataFrame, + variable_info: VariableInfoMapping, stochastic_transition_names: frozenset[TransitionFunctionName] = frozenset(), ) -> NextStateSimulationFunction: """Get function that computes the next states during the simulation. @@ -157,7 +157,7 @@ def _extend_target_transitions_for_simulation( target: RegimeName, target_transitions: MappingProxyType[TransitionFunctionName, Callable[..., Array]], all_grids: MappingProxyType[RegimeName, MappingProxyType[StateOrActionName, Grid]], - variable_info: pd.DataFrame, + variable_info: VariableInfoMapping, stochastic_transition_names: frozenset[TransitionFunctionName], ) -> dict[TransitionFunctionName, Callable[..., Array]]: """Replace stochastic transitions for one target with realisation wrappers. @@ -183,7 +183,9 @@ def _extend_target_transitions_for_simulation( Extended transitions dictionary keyed by unqualified `next_` names. """ - shock_names: set[ShockName] = set(variable_info.query("is_shock").index.to_list()) + shock_names: set[ShockName] = { + name for name, info in variable_info.items() if info.is_shock + } extended: dict[TransitionFunctionName, Callable[..., Array]] = dict( target_transitions ) diff --git a/src/lcm/regime_building/processing.py b/src/lcm/regime_building/processing.py index e3017032d..5b1733313 100644 --- a/src/lcm/regime_building/processing.py +++ b/src/lcm/regime_building/processing.py @@ -6,7 +6,6 @@ from typing import Any, Literal, cast import jax -import pandas as pd from dags import concatenate_functions, get_annotations, with_signature from dags.signature import rename_arguments from dags.tree import QNAME_DELIMITER, qname_from_tree_path, tree_path_from_qname @@ -23,6 +22,7 @@ SolveFunctions, SolveSimulateFunctionPair, StateActionSpace, + VariableInfoMapping, ) from lcm.params.processing import get_flat_param_names from lcm.params.regime_template import create_regime_params_template @@ -210,7 +210,7 @@ def _build_solve_functions( all_grids: MappingProxyType[RegimeName, MappingProxyType[StateOrActionName, Grid]], regime_params_template: RegimeParamsTemplate, regime_names_to_ids: RegimeNamesToIds, - variable_info: pd.DataFrame, + variable_info: VariableInfoMapping, regimes_to_active_periods: MappingProxyType[RegimeName, tuple[int, ...]], regime_to_v_interpolation_info: MappingProxyType[RegimeName, VInterpolationInfo], state_action_space: StateActionSpace, @@ -325,7 +325,7 @@ def _build_simulate_functions( all_grids: MappingProxyType[RegimeName, MappingProxyType[StateOrActionName, Grid]], regime_params_template: RegimeParamsTemplate, regime_names_to_ids: RegimeNamesToIds, - variable_info: pd.DataFrame, + variable_info: VariableInfoMapping, regimes_to_active_periods: MappingProxyType[RegimeName, tuple[int, ...]], regime_to_v_interpolation_info: MappingProxyType[RegimeName, VInterpolationInfo], state_action_space: StateActionSpace, @@ -477,7 +477,7 @@ def _process_regime_core( ], all_grids: MappingProxyType[RegimeName, MappingProxyType[StateOrActionName, Grid]], regime_params_template: RegimeParamsTemplate, - variable_info: pd.DataFrame, + variable_info: VariableInfoMapping, phase: Literal["solve", "simulate"], ) -> _CoreResult: """Process regime functions and transitions for a single phase. @@ -582,7 +582,7 @@ def _process_regime_core( # next functions for reachable target regimes from each target's grid. # Scope to targets already present in non-shock transitions to avoid # spurious entries for unreachable regimes. - shock_names = variable_info.query("is_shock").index.tolist() + shock_names = [name for name, info in variable_info.items() if info.is_shock] reachable_targets = { tree_path_from_qname(k)[0] for k in flat_nested_transitions @@ -776,7 +776,7 @@ def _wrap_transitions( def _get_stochastic_transition_names( *, regime: Regime, - variable_info: pd.DataFrame, + variable_info: VariableInfoMapping, ) -> frozenset[TransitionFunctionName]: """Compute stochastic transition names from regime state transitions and shocks. @@ -796,9 +796,9 @@ def _get_stochastic_transition_names( and any(isinstance(v, MarkovTransition) for v in raw.values()) ): markov_state_names.add(name) - shock_state_names: set[ShockName] = set( - variable_info.query("is_shock").index.tolist() - ) + shock_state_names: set[ShockName] = { + name for name, info in variable_info.items() if info.is_shock + } return frozenset(f"next_{name}" for name in markov_state_names | shock_state_names) @@ -1477,7 +1477,7 @@ def _build_next_state_vmapped( transitions: TransitionFunctionsMapping, stochastic_transition_names: frozenset[TransitionFunctionName], all_grids: MappingProxyType[RegimeName, MappingProxyType[StateOrActionName, Grid]], - variable_info: pd.DataFrame, + variable_info: VariableInfoMapping, regime_params_template: RegimeParamsTemplate, enable_jit: bool, ) -> NextStateSimulationFunction: diff --git a/src/lcm/regime_building/variable_info.py b/src/lcm/regime_building/variable_info.py index 065202a4b..aeff569a3 100644 --- a/src/lcm/regime_building/variable_info.py +++ b/src/lcm/regime_building/variable_info.py @@ -1,67 +1,39 @@ import math from types import MappingProxyType -import pandas as pd - from lcm.grids import ContinuousGrid, Grid +from lcm.interfaces import VariableInfo, VariableInfoMapping from lcm.regime import Regime from lcm.shocks import _ShockGrid from lcm.typing import StateOrActionName -def get_variable_info(regime: Regime) -> pd.DataFrame: - """Derive information about all variables in the regime. +def get_variable_info(regime: Regime) -> VariableInfoMapping: + """Derive kind/topology/shock tags for every variable in the regime. Args: regime: The regime as provided by the user. Returns: - A table with information about all variables in the regime. The index contains - the name of a regime variable. The columns are booleans that are True if the - variable has the corresponding property. The columns are: is_state, is_action, - is_continuous, is_discrete. + Immutable mapping from variable name to its `VariableInfo`. Iteration + order is: discrete states (by batch size), continuous states (by batch + size), then actions. """ variables = dict(regime.states) | dict(regime.actions) + raw: dict[StateOrActionName, VariableInfo] = {} + for name, spec in variables.items(): + is_state = name in regime.states + is_shock = isinstance(spec, _ShockGrid) + is_continuous = isinstance(spec, ContinuousGrid) and not is_shock + raw[name] = VariableInfo( + kind="state" if is_state else "action", + topology="continuous" if is_continuous else "discrete", + is_shock=is_shock, + ) - info = pd.DataFrame(index=pd.Index(list(variables))) - - info["is_state"] = info.index.isin(regime.states) - info["is_shock"] = [isinstance(spec, _ShockGrid) for spec in variables.values()] - info["is_action"] = ~info["is_state"] - - info["is_continuous"] = [ - isinstance(spec, ContinuousGrid) and not isinstance(spec, _ShockGrid) - for spec in variables.values() - ] - info["is_discrete"] = ~info["is_continuous"] - - ordered_discrete_states = sorted( - info.query("is_discrete & is_state").index.tolist(), - key=lambda x: ( - regime.states[x].batch_size - if regime.states[x].batch_size != 0 - else math.inf - ), - ) - ordered_continuous_states = sorted( - info.query("is_continuous & is_state").index.tolist(), - key=lambda x: ( - regime.states[x].batch_size - if regime.states[x].batch_size != 0 - else math.inf - ), - ) - ordered_states_and_actions = [ - *ordered_discrete_states, - *ordered_continuous_states, - *info.query("is_action").index.tolist(), - ] - - if set(ordered_states_and_actions) != set(info.index): - raise ValueError("Order and index do not match.") - - return info.loc[ordered_states_and_actions] + ordered = _ordered_state_action_names(regime, raw) + return MappingProxyType({name: raw[name] for name in ordered}) def get_grids( @@ -82,5 +54,43 @@ def get_grids( variable_info = get_variable_info(regime) raw_variables = dict(regime.states) | dict(regime.actions) - order = variable_info.index.tolist() - return MappingProxyType({k: raw_variables[k] for k in order}) + return MappingProxyType({name: raw_variables[name] for name in variable_info}) + + +def _ordered_state_action_names( + regime: Regime, + raw: dict[StateOrActionName, VariableInfo], +) -> list[StateOrActionName]: + """Order variables: discrete states, continuous states, actions. + + States are sorted by `batch_size` within each topology group; batch size 0 + sorts last (treated as +inf). + + """ + + def state_batch_size(name: StateOrActionName) -> float: + batch_size = regime.states[name].batch_size + return batch_size if batch_size != 0 else math.inf + + discrete_states = sorted( + ( + name + for name, info in raw.items() + if info.kind == "state" and info.topology == "discrete" + ), + key=state_batch_size, + ) + continuous_states = sorted( + ( + name + for name, info in raw.items() + if info.kind == "state" and info.topology == "continuous" + ), + key=state_batch_size, + ) + actions = [name for name, info in raw.items() if info.kind == "action"] + + ordered = [*discrete_states, *continuous_states, *actions] + if set(ordered) != set(raw): + raise ValueError("Order and index do not match.") + return ordered diff --git a/src/lcm/simulation/initial_conditions.py b/src/lcm/simulation/initial_conditions.py index 80d29e9b2..a10dd1ff4 100644 --- a/src/lcm/simulation/initial_conditions.py +++ b/src/lcm/simulation/initial_conditions.py @@ -201,7 +201,11 @@ def _get_regime_state_names( Set of state variable names. """ - return set(internal_regime.variable_info.query("is_state").index) + return { + name + for name, info in internal_regime.variable_info.items() + if info.kind == "state" + } def _format_missing_states_message(missing: set[str], required: set[str]) -> str: @@ -468,9 +472,9 @@ def _validate_discrete_state_values( discrete_info: dict[str, tuple[set[int], set[int]]] = {} for regime_name, internal_regime in internal_regimes.items(): regime_id = int(regime_names_to_ids[regime_name]) - for state_name in internal_regime.variable_info.query( - "is_state and is_discrete" - ).index: + for state_name, info in internal_regime.variable_info.items(): + if not (info.kind == "state" and info.topology == "discrete"): + continue grid = internal_regime.grids[state_name] if isinstance(grid, DiscreteGrid): codes, regime_ids = discrete_info.get(state_name, (set(), set())) @@ -602,7 +606,11 @@ def _check_regime_feasibility( # noqa: C901 ) accepted = get_union_of_args([feasibility_func]) - action_names = list(internal_regime.variable_info.query("is_action").index) + action_names = [ + name + for name, info in internal_regime.variable_info.items() + if info.kind == "action" + ] if not action_names: return None @@ -623,7 +631,11 @@ def _check_regime_feasibility( # noqa: C901 ) filtered_params = {k: v for k, v in regime_params.items() if k in accepted} - state_names = list(internal_regime.variable_info.query("is_state").index) + state_names = [ + name + for name, info in internal_regime.variable_info.items() + if info.kind == "state" + ] needs_age = "age" in accepted needs_period = "period" in accepted diff --git a/src/lcm/simulation/result.py b/src/lcm/simulation/result.py index 82ce2cf85..4b73dab26 100644 --- a/src/lcm/simulation/result.py +++ b/src/lcm/simulation/result.py @@ -314,8 +314,8 @@ def _compute_metadata( for regime_name, regime in internal_regimes.items(): vi = regime.variable_info - states = tuple(vi.query("is_state").index.tolist()) - actions = tuple(vi.query("is_action").index.tolist()) + states = tuple(name for name, info in vi.items() if info.kind == "state") + actions = tuple(name for name, info in vi.items() if info.kind == "action") regime_to_states[regime_name] = states regime_to_actions[regime_name] = actions all_states.update(states) diff --git a/src/lcm/simulation/transitions.py b/src/lcm/simulation/transitions.py index 1a4b299eb..ebe864a02 100644 --- a/src/lcm/simulation/transitions.py +++ b/src/lcm/simulation/transitions.py @@ -55,7 +55,11 @@ def create_regime_state_action_space( """ base = internal_regime.state_action_space(regime_params=regime_params) - relevant_state_names = internal_regime.variable_info.query("is_state").index + relevant_state_names = [ + name + for name, info in internal_regime.variable_info.items() + if info.kind == "state" + ] states_for_state_action_space = { sn: regime_states[sn] for sn in relevant_state_names } diff --git a/src/lcm/state_action_space.py b/src/lcm/state_action_space.py index 7c88e1658..6ed036777 100644 --- a/src/lcm/state_action_space.py +++ b/src/lcm/state_action_space.py @@ -1,17 +1,16 @@ from types import MappingProxyType import jax.numpy as jnp -import pandas as pd from jax import Array from lcm.grids import Grid, IrregSpacedGrid -from lcm.interfaces import StateActionSpace +from lcm.interfaces import StateActionSpace, VariableInfoMapping from lcm.typing import StateName, StateOrActionName def create_state_action_space( *, - variable_info: pd.DataFrame, + variable_info: VariableInfoMapping, grids: MappingProxyType[StateOrActionName, Grid], states: dict[StateName, Array] | None = None, ) -> StateActionSpace: @@ -21,7 +20,8 @@ def create_state_action_space( simulation, states must be provided. Args: - variable_info: The variable info table as returned by get_variable_info. + variable_info: Variable kind/topology/shock tags, as returned by + `get_variable_info`. grids: Immutable mapping of variable names to Grid spec objects. states: A dictionary of states. If None, the grids as specified in the regime are used. @@ -29,31 +29,33 @@ def create_state_action_space( Returns: A state-action-space. Contains the grids of the discrete and continuous actions, the states, and the names of the state and action variables in the order they - appear in the variable info table. + appear in the variable info mapping. """ + state_names = [name for name, info in variable_info.items() if info.kind == "state"] if states is None: - _states = { - sn: _grid_to_jax_or_placeholder(grids[sn]) - for sn in variable_info.query("is_state").index - } + _states = {sn: _grid_to_jax_or_placeholder(grids[sn]) for sn in state_names} else: _validate_all_states_present( provided_states=states, - required_state_names=set(variable_info.query("is_state").index), + required_state_names=set(state_names), ) _states = states discrete_actions = { name: grids[name].to_jax() - for name in variable_info.query("is_action & is_discrete").index + for name, info in variable_info.items() + if info.kind == "action" and info.topology == "discrete" } continuous_actions = { name: _grid_to_jax_or_placeholder(grids[name]) - for name in variable_info.query("is_action & is_continuous").index + for name, info in variable_info.items() + if info.kind == "action" and info.topology == "continuous" } state_and_discrete_action_names = tuple( - variable_info.query("is_state | is_discrete").index + name + for name, info in variable_info.items() + if info.kind == "state" or info.topology == "discrete" ) return StateActionSpace( diff --git a/src/lcm/utils/error_handling.py b/src/lcm/utils/error_handling.py index 5d89a0545..6aa2b5bb6 100644 --- a/src/lcm/utils/error_handling.py +++ b/src/lcm/utils/error_handling.py @@ -561,7 +561,11 @@ def _validate_no_reachable_incomplete_targets( for target_regime_name in active_regimes_next_period: target_regime = internal_regimes[target_regime_name] - target_state_names = tuple(target_regime.variable_info.query("is_state").index) + target_state_names = tuple( + name + for name, info in target_regime.variable_info.items() + if info.kind == "state" + ) needs = { f"next_{s}" for s in target_state_names if f"next_{s}" in stochastic_names } diff --git a/tests/regime_building/test_regime_processing.py b/tests/regime_building/test_regime_processing.py index 6801c9e10..4f9d7732b 100644 --- a/tests/regime_building/test_regime_processing.py +++ b/tests/regime_building/test_regime_processing.py @@ -2,16 +2,13 @@ from types import MappingProxyType import jax.numpy as jnp -import numpy as np -import pandas as pd import pytest from numpy.testing import assert_array_equal -from pandas.testing import assert_frame_equal from lcm import Regime, categorical from lcm.ages import AgeGrid from lcm.grids import DiscreteGrid, LinSpacedGrid -from lcm.interfaces import InternalRegime +from lcm.interfaces import InternalRegime, VariableInfo from lcm.regime_building.processing import ( _rename_params_to_qnames, process_regimes, @@ -41,18 +38,11 @@ def next_c(a, b): ) got = get_variable_info(regime_mock) # ty: ignore[invalid-argument-type] - exp = pd.DataFrame( - { - "is_state": [False, True], - "is_shock": [False, False], - "is_action": [True, False], - "is_continuous": [False, False], - "is_discrete": [True, True], - }, - index=pd.Index(["a", "c"]), - ) - assert_frame_equal(got.loc[exp.index], exp) # we don't care about the id order here + assert isinstance(got, MappingProxyType) + assert set(got) == {"a", "c"} + assert got["a"] == VariableInfo(kind="action", topology="discrete", is_shock=False) + assert got["c"] == VariableInfo(kind="state", topology="discrete", is_shock=False) def test_get_grids(binary_category_class): @@ -124,15 +114,16 @@ def test_process_regimes(): internal_working_regime = internal_regimes["working_life"] # Variable Info - assert ( - internal_working_regime.variable_info["is_state"].to_numpy() - == np.array([True, False, False]) - ).all() - - assert ( - internal_working_regime.variable_info["is_continuous"].to_numpy() - == np.array([True, False, True]) - ).all() + vi = internal_working_regime.variable_info + assert vi["wealth"] == VariableInfo( + kind="state", topology="continuous", is_shock=False + ) + assert vi["labor_supply"] == VariableInfo( + kind="action", topology="discrete", is_shock=False + ) + assert vi["consumption"] == VariableInfo( + kind="action", topology="continuous", is_shock=False + ) # Grids — compare the grid objects (which now include transition attributes) assert internal_working_regime.grids["wealth"] == working_life.states["wealth"] @@ -168,7 +159,9 @@ def test_process_regimes(): assert "utility" in internal_working_regime.solve_functions.functions -def test_variable_info_with_continuous_constraint_has_unique_index(): +def test_variable_info_excludes_constraint_names(): + """Constraint functions do not appear as variables in variable_info.""" + def wealth_constraint(wealth): return wealth > 200 @@ -178,7 +171,7 @@ def wealth_constraint(wealth): ) got = get_variable_info(working_copy) - assert got.index.is_unique + assert "wealth_constraint" not in got @pytest.fixture(name="two_non_terminal_internal_regimes") diff --git a/tests/test_next_state.py b/tests/test_next_state.py index 1fcc3eb81..3e2dac694 100644 --- a/tests/test_next_state.py +++ b/tests/test_next_state.py @@ -1,10 +1,10 @@ from types import MappingProxyType import jax.numpy as jnp -import pandas as pd from lcm.ages import AgeGrid from lcm.grids import DiscreteGrid, categorical +from lcm.interfaces import VariableInfo from lcm.regime_building import process_regimes from lcm.regime_building.next_state import ( _create_discrete_stochastic_next_func, @@ -76,7 +76,9 @@ class MockCategory: all_grids = MappingProxyType( {"mock": MappingProxyType({"b": DiscreteGrid(MockCategory)})} ) - variable_info = pd.DataFrame({"is_shock": [False]}, index=["b"]) + variable_info = MappingProxyType( + {"b": VariableInfo(kind="state", topology="discrete", is_shock=False)} + ) transitions = MappingProxyType( {"mock": MappingProxyType({"next_a": f_a, "next_b": f_b})} ) diff --git a/tests/test_state_action_space.py b/tests/test_state_action_space.py index e70d7e319..27201b00a 100644 --- a/tests/test_state_action_space.py +++ b/tests/test_state_action_space.py @@ -1,11 +1,10 @@ from types import MappingProxyType import jax.numpy as jnp -import pandas as pd from numpy.testing import assert_array_equal from lcm.grids import DiscreteGrid, IrregSpacedGrid, LinSpacedGrid, categorical -from lcm.interfaces import StateActionSpace +from lcm.interfaces import StateActionSpace, VariableInfo, VariableInfoMapping from lcm.regime import Regime from lcm.regime_building.V import VInterpolationInfo, create_v_interpolation_info from lcm.state_action_space import create_state_action_space @@ -17,16 +16,17 @@ def _create_variable_info( continuous_states: list[str], discrete_actions: list[str], continuous_actions: list[str], -) -> pd.DataFrame: - ordered_vars = ( - discrete_states + discrete_actions + continuous_states + continuous_actions - ) - info = pd.DataFrame(index=pd.Index(ordered_vars)) - info["is_state"] = info.index.isin(discrete_states + continuous_states) - info["is_action"] = ~info["is_state"] - info["is_discrete"] = info.index.isin(discrete_states + discrete_actions) - info["is_continuous"] = ~info["is_discrete"] - return info +) -> VariableInfoMapping: + info: dict[str, VariableInfo] = {} + for name in discrete_states: + info[name] = VariableInfo(kind="state", topology="discrete", is_shock=False) + for name in discrete_actions: + info[name] = VariableInfo(kind="action", topology="discrete", is_shock=False) + for name in continuous_states: + info[name] = VariableInfo(kind="state", topology="continuous", is_shock=False) + for name in continuous_actions: + info[name] = VariableInfo(kind="action", topology="continuous", is_shock=False) + return MappingProxyType(info) def test_create_state_action_space_solution_discrete_action_continuous_state(): From 1ad392a062e49cff5f82f5d367d23e6423134591 Mon Sep 17 00:00:00 2001 From: Hans-Martin von Gaudecker Date: Wed, 13 May 2026 15:23:39 +0200 Subject: [PATCH 13/77] Promote variable_info to a Variables container with named views Wrap the per-regime states+actions in a frozen `Variables(Mapping[..., VariableInfo])` class with pre-computed name-tuple views (state_names, shock_names, discrete_state_names, ...). Replaces the 16 sites that did `[name for name, info in variable_info.items() if info.kind == "state"]` with single attribute accesses. `InternalRegime.variable_info` becomes `InternalRegime.variables`. `get_variable_info(regime)` becomes the classmethod `Variables.from_regime(regime)`. The intermediate file `regime_building/variable_info.py` is gone; `get_grids` moves to `src/lcm/variables.py` next to the class. --- src/lcm/interfaces.py | 31 +- src/lcm/regime_building/V.py | 17 +- src/lcm/regime_building/next_state.py | 17 +- src/lcm/regime_building/processing.py | 52 ++-- src/lcm/regime_building/variable_info.py | 96 ------- src/lcm/simulation/initial_conditions.py | 22 +- src/lcm/simulation/result.py | 5 +- src/lcm/simulation/transitions.py | 6 +- src/lcm/state_action_space.py | 31 +- src/lcm/utils/error_handling.py | 6 +- src/lcm/variables.py | 264 ++++++++++++++++++ .../regime_building/test_regime_processing.py | 24 +- tests/test_next_state.py | 10 +- tests/test_state_action_space.py | 30 +- tests/test_variables.py | 192 +++++++++++++ 15 files changed, 554 insertions(+), 249 deletions(-) delete mode 100644 src/lcm/regime_building/variable_info.py create mode 100644 src/lcm/variables.py create mode 100644 tests/test_variables.py diff --git a/src/lcm/interfaces.py b/src/lcm/interfaces.py index 08b9e48a3..98b7f8316 100644 --- a/src/lcm/interfaces.py +++ b/src/lcm/interfaces.py @@ -1,7 +1,7 @@ import dataclasses from collections.abc import Callable from types import MappingProxyType -from typing import Literal, cast +from typing import cast from jax import Array @@ -28,6 +28,7 @@ VmappedRegimeTransitionFunction, ) from lcm.utils.containers import first_non_none +from lcm.variables import Variables @dataclasses.dataclass(frozen=True) @@ -206,30 +207,6 @@ class SimulateFunctions: """Compiled function to compute next-period states.""" -@dataclasses.dataclass(frozen=True) -class VariableInfo: - """Kind/topology/shock tags for one state or action variable.""" - - kind: Literal["state", "action"] - """Whether the variable is a state or an action.""" - - topology: Literal["continuous", "discrete"] - """Topology as treated by pylcm's solve/simulate machinery. - - Shocks have topology `"discrete"` because their value space is - approximated by a finite grid of nodes, even though the underlying - random variable is mathematically continuous. Combine with `is_shock` - when the distinction matters. - - """ - - is_shock: bool - """Whether the variable is a shock (always a state).""" - - -type VariableInfoMapping = MappingProxyType[StateOrActionName, VariableInfo] - - @dataclasses.dataclass(frozen=True, kw_only=True) class InternalRegime: """Internal representation of a user regime.""" @@ -243,8 +220,8 @@ class InternalRegime: grids: MappingProxyType[StateOrActionName, Grid] """Immutable mapping of variable names to grid objects.""" - variable_info: VariableInfoMapping - """Immutable mapping of variable names to their kind/topology/shock tags.""" + variables: Variables + """States and actions of the regime, with kind/topology/shock tags.""" active_periods: tuple[int, ...] """Period indices during which this regime is active.""" diff --git a/src/lcm/regime_building/V.py b/src/lcm/regime_building/V.py index ff0ba46b2..30ede1788 100644 --- a/src/lcm/regime_building/V.py +++ b/src/lcm/regime_building/V.py @@ -14,6 +14,7 @@ from lcm.shocks import _ShockGrid from lcm.typing import FloatND, ScalarFloat, StateName from lcm.utils.functools import all_as_kwargs +from lcm.variables import Variables, get_grids @dataclasses.dataclass(frozen=True, kw_only=True) @@ -45,33 +46,27 @@ def create_v_interpolation_info(regime: Regime) -> VInterpolationInfo: State space information for the regime. """ - from lcm.regime_building.variable_info import ( # noqa: PLC0415 - get_grids, - get_variable_info, - ) - - vi = get_variable_info(regime) + variables = Variables.from_regime(regime) grids = get_grids(regime) - state_names = [name for name, info in vi.items() if info.kind == "state"] - + state_names_set = set(variables.state_names) discrete_states = { name: grid_spec for name, grid_spec in grids.items() - if (name in state_names and isinstance(grid_spec, DiscreteGrid)) + if (name in state_names_set and isinstance(grid_spec, DiscreteGrid)) or isinstance(grid_spec, _ShockGrid) } continuous_states = { name: grid_spec for name, grid_spec in grids.items() - if name in state_names + if name in state_names_set and isinstance(grid_spec, ContinuousGrid) and not isinstance(grid_spec, _ShockGrid) } return VInterpolationInfo( - state_names=tuple(state_names), + state_names=variables.state_names, discrete_states=MappingProxyType(discrete_states), continuous_states=MappingProxyType(continuous_states), ) diff --git a/src/lcm/regime_building/next_state.py b/src/lcm/regime_building/next_state.py index 539e628df..1e81f0f23 100644 --- a/src/lcm/regime_building/next_state.py +++ b/src/lcm/regime_building/next_state.py @@ -9,7 +9,6 @@ from jax import Array from lcm.grids import Grid -from lcm.interfaces import VariableInfoMapping from lcm.shocks import _ShockGrid from lcm.shocks.ar1 import _ShockGridAR1 from lcm.shocks.iid import _ShockGridIID @@ -27,6 +26,7 @@ TransitionFunctionName, TransitionFunctionsMapping, ) +from lcm.variables import Variables def get_next_state_function_for_solution( @@ -63,7 +63,7 @@ def get_next_state_function_for_simulation( transitions: TransitionFunctionsMapping, functions: FunctionsMapping, all_grids: MappingProxyType[RegimeName, MappingProxyType[StateOrActionName, Grid]], - variable_info: VariableInfoMapping, + variables: Variables, stochastic_transition_names: frozenset[TransitionFunctionName] = frozenset(), ) -> NextStateSimulationFunction: """Get function that computes the next states during the simulation. @@ -83,7 +83,7 @@ def get_next_state_function_for_simulation( transitions: Nested mapping of target regime names to transition functions. functions: Immutable mapping of auxiliary functions of a regime. all_grids: Immutable mapping of regime names to Grid spec objects. - variable_info: Variable info of a regime. + variables: States and actions of the regime with kind/topology/shock tags. stochastic_transition_names: Frozenset of stochastic transition function names. Returns: @@ -99,7 +99,7 @@ def get_next_state_function_for_simulation( target=target, target_transitions=target_transitions, all_grids=all_grids, - variable_info=variable_info, + variables=variables, stochastic_transition_names=stochastic_transition_names, ) per_target_funcs[target] = concatenate_functions( @@ -157,7 +157,7 @@ def _extend_target_transitions_for_simulation( target: RegimeName, target_transitions: MappingProxyType[TransitionFunctionName, Callable[..., Array]], all_grids: MappingProxyType[RegimeName, MappingProxyType[StateOrActionName, Grid]], - variable_info: VariableInfoMapping, + variables: Variables, stochastic_transition_names: frozenset[TransitionFunctionName], ) -> dict[TransitionFunctionName, Callable[..., Array]]: """Replace stochastic transitions for one target with realisation wrappers. @@ -176,16 +176,15 @@ def _extend_target_transitions_for_simulation( target_transitions: Mapping of unqualified `next_` transition names to functions, restricted to one target regime. all_grids: Immutable mapping of regime names to Grid spec objects. - variable_info: Variable info of the current regime. + variables: States and actions of the current regime with + kind/topology/shock tags. stochastic_transition_names: Frozenset of stochastic transition function names. Returns: Extended transitions dictionary keyed by unqualified `next_` names. """ - shock_names: set[ShockName] = { - name for name, info in variable_info.items() if info.is_shock - } + shock_names: frozenset[ShockName] = frozenset(variables.shock_names) extended: dict[TransitionFunctionName, Callable[..., Array]] = dict( target_transitions ) diff --git a/src/lcm/regime_building/processing.py b/src/lcm/regime_building/processing.py index 5b1733313..0b0c78945 100644 --- a/src/lcm/regime_building/processing.py +++ b/src/lcm/regime_building/processing.py @@ -22,7 +22,6 @@ SolveFunctions, SolveSimulateFunctionPair, StateActionSpace, - VariableInfoMapping, ) from lcm.params.processing import get_flat_param_names from lcm.params.regime_template import create_regime_params_template @@ -41,7 +40,6 @@ ) from lcm.regime_building.V import VInterpolationInfo, create_v_interpolation_info from lcm.regime_building.validation import collect_state_transitions -from lcm.regime_building.variable_info import get_grids, get_variable_info from lcm.shocks import _ShockGrid from lcm.state_action_space import create_state_action_space from lcm.typing import ( @@ -68,6 +66,7 @@ from lcm.utils.containers import ensure_containers_are_immutable from lcm.utils.dispatchers import simulation_spacemap, vmap_1d from lcm.utils.namespace import flatten_regime_namespace, unflatten_regime_namespace +from lcm.variables import Variables, get_grids def process_regimes( @@ -113,9 +112,9 @@ def process_regimes( ) _validate_categoricals(regimes) - variable_info = MappingProxyType( + regime_to_variables = MappingProxyType( { - regime_name: get_variable_info(regime) + regime_name: Variables.from_regime(regime) for regime_name, regime in regimes.items() } ) @@ -134,7 +133,7 @@ def process_regimes( state_action_spaces = MappingProxyType( { regime_name: create_state_action_space( - variable_info=variable_info[regime_name], + variables=regime_to_variables[regime_name], grids=all_grids[regime_name], ) for regime_name in regimes @@ -158,7 +157,7 @@ def process_regimes( all_grids=all_grids, regime_params_template=regime_params_template, regime_names_to_ids=regime_names_to_ids, - variable_info=variable_info[regime_name], + variables=regime_to_variables[regime_name], regimes_to_active_periods=regimes_to_active_periods, regime_to_v_interpolation_info=regime_to_v_interpolation_info, state_action_space=state_action_spaces[regime_name], @@ -173,7 +172,7 @@ def process_regimes( all_grids=all_grids, regime_params_template=regime_params_template, regime_names_to_ids=regime_names_to_ids, - variable_info=variable_info[regime_name], + variables=regime_to_variables[regime_name], regimes_to_active_periods=regimes_to_active_periods, regime_to_v_interpolation_info=regime_to_v_interpolation_info, state_action_space=state_action_spaces[regime_name], @@ -188,7 +187,7 @@ def process_regimes( name=regime_name, terminal=regime.terminal, grids=all_grids[regime_name], - variable_info=variable_info[regime_name], + variables=regime_to_variables[regime_name], active_periods=tuple(regimes_to_active_periods[regime_name]), regime_params_template=regime_params_template, solve_functions=solve_functions, @@ -210,7 +209,7 @@ def _build_solve_functions( all_grids: MappingProxyType[RegimeName, MappingProxyType[StateOrActionName, Grid]], regime_params_template: RegimeParamsTemplate, regime_names_to_ids: RegimeNamesToIds, - variable_info: VariableInfoMapping, + variables: Variables, regimes_to_active_periods: MappingProxyType[RegimeName, tuple[int, ...]], regime_to_v_interpolation_info: MappingProxyType[RegimeName, VInterpolationInfo], state_action_space: StateActionSpace, @@ -226,7 +225,7 @@ def _build_solve_functions( all_grids: Immutable mapping of regime names to Grid spec objects. regime_params_template: The regime's parameter template. regime_names_to_ids: Immutable mapping of regime names to integer indices. - variable_info: Variable info of the regime. + variables: States and actions of the regime with kind/topology/shock tags. regimes_to_active_periods: Mapping of regime names to active period tuples. regime_to_v_interpolation_info: Mapping of regime names to state space info. state_action_space: The state-action space for this regime. @@ -242,7 +241,7 @@ def _build_solve_functions( nested_transitions=nested_transitions, all_grids=all_grids, regime_params_template=regime_params_template, - variable_info=variable_info, + variables=variables, phase="solve", ) @@ -325,7 +324,7 @@ def _build_simulate_functions( all_grids: MappingProxyType[RegimeName, MappingProxyType[StateOrActionName, Grid]], regime_params_template: RegimeParamsTemplate, regime_names_to_ids: RegimeNamesToIds, - variable_info: VariableInfoMapping, + variables: Variables, regimes_to_active_periods: MappingProxyType[RegimeName, tuple[int, ...]], regime_to_v_interpolation_info: MappingProxyType[RegimeName, VInterpolationInfo], state_action_space: StateActionSpace, @@ -351,7 +350,7 @@ def _build_simulate_functions( all_grids: Immutable mapping of regime names to Grid spec objects. regime_params_template: The regime's parameter template. regime_names_to_ids: Immutable mapping of regime names to integer indices. - variable_info: Variable info of the regime. + variables: States and actions of the regime with kind/topology/shock tags. regimes_to_active_periods: Mapping of regime names to active period tuples. regime_to_v_interpolation_info: Mapping of regime names to state space info. state_action_space: The state-action space for this regime. @@ -372,7 +371,7 @@ def _build_simulate_functions( nested_transitions=nested_transitions, all_grids=all_grids, regime_params_template=regime_params_template, - variable_info=variable_info, + variables=variables, phase="simulate", ) # Only functions/constraints vary by phase; core.transitions and @@ -432,7 +431,7 @@ def _build_simulate_functions( transitions=solve_transitions, stochastic_transition_names=solve_stochastic_transition_names, all_grids=all_grids, - variable_info=variable_info, + variables=variables, regime_params_template=regime_params_template, enable_jit=enable_jit, ) @@ -477,7 +476,7 @@ def _process_regime_core( ], all_grids: MappingProxyType[RegimeName, MappingProxyType[StateOrActionName, Grid]], regime_params_template: RegimeParamsTemplate, - variable_info: VariableInfoMapping, + variables: Variables, phase: Literal["solve", "simulate"], ) -> _CoreResult: """Process regime functions and transitions for a single phase. @@ -490,7 +489,7 @@ def _process_regime_core( nested_transitions: Nested transitions dict for internal processing. all_grids: Immutable mapping of regime names to Grid spec objects. regime_params_template: The regime's parameter template. - variable_info: Variable info of the regime. + variables: States and actions of the regime with kind/topology/shock tags. phase: Which phase variant to use for function pairs. Returns: @@ -525,7 +524,7 @@ def _process_regime_core( ) stochastic_transition_names = _get_stochastic_transition_names( - regime=regime, variable_info=variable_info + regime=regime, variables=variables ) stochastic_transition_functions = { @@ -582,7 +581,7 @@ def _process_regime_core( # next functions for reachable target regimes from each target's grid. # Scope to targets already present in non-shock transitions to avoid # spurious entries for unreachable regimes. - shock_names = [name for name, info in variable_info.items() if info.is_shock] + shock_names = variables.shock_names reachable_targets = { tree_path_from_qname(k)[0] for k in flat_nested_transitions @@ -776,13 +775,13 @@ def _wrap_transitions( def _get_stochastic_transition_names( *, regime: Regime, - variable_info: VariableInfoMapping, + variables: Variables, ) -> frozenset[TransitionFunctionName]: """Compute stochastic transition names from regime state transitions and shocks. Args: regime: The user regime. - variable_info: Variable info of the regime. + variables: States and actions of the regime with kind/topology/shock tags. Returns: Frozenset of stochastic transition function names (e.g., "next_health"). @@ -796,10 +795,9 @@ def _get_stochastic_transition_names( and any(isinstance(v, MarkovTransition) for v in raw.values()) ): markov_state_names.add(name) - shock_state_names: set[ShockName] = { - name for name, info in variable_info.items() if info.is_shock - } - return frozenset(f"next_{name}" for name in markov_state_names | shock_state_names) + return frozenset( + f"next_{name}" for name in markov_state_names | set(variables.shock_names) + ) def _rename_params_to_qnames( @@ -1477,7 +1475,7 @@ def _build_next_state_vmapped( transitions: TransitionFunctionsMapping, stochastic_transition_names: frozenset[TransitionFunctionName], all_grids: MappingProxyType[RegimeName, MappingProxyType[StateOrActionName, Grid]], - variable_info: VariableInfoMapping, + variables: Variables, regime_params_template: RegimeParamsTemplate, enable_jit: bool, ) -> NextStateSimulationFunction: @@ -1487,7 +1485,7 @@ def _build_next_state_vmapped( transitions=transitions, stochastic_transition_names=stochastic_transition_names, all_grids=all_grids, - variable_info=variable_info, + variables=variables, ) sig_args = tuple(inspect.signature(next_state).parameters) diff --git a/src/lcm/regime_building/variable_info.py b/src/lcm/regime_building/variable_info.py deleted file mode 100644 index aeff569a3..000000000 --- a/src/lcm/regime_building/variable_info.py +++ /dev/null @@ -1,96 +0,0 @@ -import math -from types import MappingProxyType - -from lcm.grids import ContinuousGrid, Grid -from lcm.interfaces import VariableInfo, VariableInfoMapping -from lcm.regime import Regime -from lcm.shocks import _ShockGrid -from lcm.typing import StateOrActionName - - -def get_variable_info(regime: Regime) -> VariableInfoMapping: - """Derive kind/topology/shock tags for every variable in the regime. - - Args: - regime: The regime as provided by the user. - - Returns: - Immutable mapping from variable name to its `VariableInfo`. Iteration - order is: discrete states (by batch size), continuous states (by batch - size), then actions. - - """ - variables = dict(regime.states) | dict(regime.actions) - raw: dict[StateOrActionName, VariableInfo] = {} - for name, spec in variables.items(): - is_state = name in regime.states - is_shock = isinstance(spec, _ShockGrid) - is_continuous = isinstance(spec, ContinuousGrid) and not is_shock - raw[name] = VariableInfo( - kind="state" if is_state else "action", - topology="continuous" if is_continuous else "discrete", - is_shock=is_shock, - ) - - ordered = _ordered_state_action_names(regime, raw) - return MappingProxyType({name: raw[name] for name in ordered}) - - -def get_grids( - regime: Regime, -) -> MappingProxyType[StateOrActionName, Grid]: - """Create a mapping of grid objects for each variable in the regime. - - Args: - regime: The regime as provided by the user. - - Returns: - Immutable mapping of state and action variable names to their grid objects. - The values describe which values the variable can take. For discrete variables - these are the codes. For continuous variables this is information about how to - build the grids. - - """ - variable_info = get_variable_info(regime) - - raw_variables = dict(regime.states) | dict(regime.actions) - return MappingProxyType({name: raw_variables[name] for name in variable_info}) - - -def _ordered_state_action_names( - regime: Regime, - raw: dict[StateOrActionName, VariableInfo], -) -> list[StateOrActionName]: - """Order variables: discrete states, continuous states, actions. - - States are sorted by `batch_size` within each topology group; batch size 0 - sorts last (treated as +inf). - - """ - - def state_batch_size(name: StateOrActionName) -> float: - batch_size = regime.states[name].batch_size - return batch_size if batch_size != 0 else math.inf - - discrete_states = sorted( - ( - name - for name, info in raw.items() - if info.kind == "state" and info.topology == "discrete" - ), - key=state_batch_size, - ) - continuous_states = sorted( - ( - name - for name, info in raw.items() - if info.kind == "state" and info.topology == "continuous" - ), - key=state_batch_size, - ) - actions = [name for name, info in raw.items() if info.kind == "action"] - - ordered = [*discrete_states, *continuous_states, *actions] - if set(ordered) != set(raw): - raise ValueError("Order and index do not match.") - return ordered diff --git a/src/lcm/simulation/initial_conditions.py b/src/lcm/simulation/initial_conditions.py index a10dd1ff4..708a95662 100644 --- a/src/lcm/simulation/initial_conditions.py +++ b/src/lcm/simulation/initial_conditions.py @@ -201,11 +201,7 @@ def _get_regime_state_names( Set of state variable names. """ - return { - name - for name, info in internal_regime.variable_info.items() - if info.kind == "state" - } + return set(internal_regime.variables.state_names) def _format_missing_states_message(missing: set[str], required: set[str]) -> str: @@ -472,9 +468,7 @@ def _validate_discrete_state_values( discrete_info: dict[str, tuple[set[int], set[int]]] = {} for regime_name, internal_regime in internal_regimes.items(): regime_id = int(regime_names_to_ids[regime_name]) - for state_name, info in internal_regime.variable_info.items(): - if not (info.kind == "state" and info.topology == "discrete"): - continue + for state_name in internal_regime.variables.discrete_state_names: grid = internal_regime.grids[state_name] if isinstance(grid, DiscreteGrid): codes, regime_ids = discrete_info.get(state_name, (set(), set())) @@ -606,11 +600,7 @@ def _check_regime_feasibility( # noqa: C901 ) accepted = get_union_of_args([feasibility_func]) - action_names = [ - name - for name, info in internal_regime.variable_info.items() - if info.kind == "action" - ] + action_names = list(internal_regime.variables.action_names) if not action_names: return None @@ -631,11 +621,7 @@ def _check_regime_feasibility( # noqa: C901 ) filtered_params = {k: v for k, v in regime_params.items() if k in accepted} - state_names = [ - name - for name, info in internal_regime.variable_info.items() - if info.kind == "state" - ] + state_names = list(internal_regime.variables.state_names) needs_age = "age" in accepted needs_period = "period" in accepted diff --git a/src/lcm/simulation/result.py b/src/lcm/simulation/result.py index 4b73dab26..16bef079b 100644 --- a/src/lcm/simulation/result.py +++ b/src/lcm/simulation/result.py @@ -313,9 +313,8 @@ def _compute_metadata( regime_to_actions: dict[RegimeName, tuple[ActionName, ...]] = {} for regime_name, regime in internal_regimes.items(): - vi = regime.variable_info - states = tuple(name for name, info in vi.items() if info.kind == "state") - actions = tuple(name for name, info in vi.items() if info.kind == "action") + states = regime.variables.state_names + actions = regime.variables.action_names regime_to_states[regime_name] = states regime_to_actions[regime_name] = actions all_states.update(states) diff --git a/src/lcm/simulation/transitions.py b/src/lcm/simulation/transitions.py index ebe864a02..dacbcddee 100644 --- a/src/lcm/simulation/transitions.py +++ b/src/lcm/simulation/transitions.py @@ -55,11 +55,7 @@ def create_regime_state_action_space( """ base = internal_regime.state_action_space(regime_params=regime_params) - relevant_state_names = [ - name - for name, info in internal_regime.variable_info.items() - if info.kind == "state" - ] + relevant_state_names = internal_regime.variables.state_names states_for_state_action_space = { sn: regime_states[sn] for sn in relevant_state_names } diff --git a/src/lcm/state_action_space.py b/src/lcm/state_action_space.py index 6ed036777..e5e25c82d 100644 --- a/src/lcm/state_action_space.py +++ b/src/lcm/state_action_space.py @@ -4,13 +4,14 @@ from jax import Array from lcm.grids import Grid, IrregSpacedGrid -from lcm.interfaces import StateActionSpace, VariableInfoMapping +from lcm.interfaces import StateActionSpace from lcm.typing import StateName, StateOrActionName +from lcm.variables import Variables def create_state_action_space( *, - variable_info: VariableInfoMapping, + variables: Variables, grids: MappingProxyType[StateOrActionName, Grid], states: dict[StateName, Array] | None = None, ) -> StateActionSpace: @@ -20,8 +21,7 @@ def create_state_action_space( simulation, states must be provided. Args: - variable_info: Variable kind/topology/shock tags, as returned by - `get_variable_info`. + variables: States and actions of the regime with kind/topology/shock tags. grids: Immutable mapping of variable names to Grid spec objects. states: A dictionary of states. If None, the grids as specified in the regime are used. @@ -29,40 +29,33 @@ def create_state_action_space( Returns: A state-action-space. Contains the grids of the discrete and continuous actions, the states, and the names of the state and action variables in the order they - appear in the variable info mapping. + appear in the variables container. """ - state_names = [name for name, info in variable_info.items() if info.kind == "state"] if states is None: - _states = {sn: _grid_to_jax_or_placeholder(grids[sn]) for sn in state_names} + _states = { + sn: _grid_to_jax_or_placeholder(grids[sn]) for sn in variables.state_names + } else: _validate_all_states_present( provided_states=states, - required_state_names=set(state_names), + required_state_names=set(variables.state_names), ) _states = states discrete_actions = { - name: grids[name].to_jax() - for name, info in variable_info.items() - if info.kind == "action" and info.topology == "discrete" + name: grids[name].to_jax() for name in variables.discrete_action_names } continuous_actions = { name: _grid_to_jax_or_placeholder(grids[name]) - for name, info in variable_info.items() - if info.kind == "action" and info.topology == "continuous" + for name in variables.continuous_action_names } - state_and_discrete_action_names = tuple( - name - for name, info in variable_info.items() - if info.kind == "state" or info.topology == "discrete" - ) return StateActionSpace( states=MappingProxyType(_states), discrete_actions=MappingProxyType(discrete_actions), continuous_actions=MappingProxyType(continuous_actions), - state_and_discrete_action_names=state_and_discrete_action_names, + state_and_discrete_action_names=variables.state_and_discrete_action_names, ) diff --git a/src/lcm/utils/error_handling.py b/src/lcm/utils/error_handling.py index 6aa2b5bb6..7cd79e143 100644 --- a/src/lcm/utils/error_handling.py +++ b/src/lcm/utils/error_handling.py @@ -561,11 +561,7 @@ def _validate_no_reachable_incomplete_targets( for target_regime_name in active_regimes_next_period: target_regime = internal_regimes[target_regime_name] - target_state_names = tuple( - name - for name, info in target_regime.variable_info.items() - if info.kind == "state" - ) + target_state_names = target_regime.variables.state_names needs = { f"next_{s}" for s in target_state_names if f"next_{s}" in stochastic_names } diff --git a/src/lcm/variables.py b/src/lcm/variables.py new file mode 100644 index 000000000..5668e87ac --- /dev/null +++ b/src/lcm/variables.py @@ -0,0 +1,264 @@ +"""Per-regime states + actions, with pre-computed name-tuple views. + +`Variables` wraps an immutable `MappingProxyType[StateOrActionName, +VariableInfo]` and exposes 8 named tuples covering every kind/topology +cross-section that consumers need (`state_names`, `discrete_action_names`, +`shock_names`, etc.). Callers that need ad-hoc filters can iterate via the +`Mapping` interface. + +Iteration order (set by `Variables.from_regime`): discrete states sorted by +`batch_size`, then continuous states sorted by `batch_size`, then actions. +Batch size 0 sorts last within its group (treated as +∞). Every named view +preserves this order. + +""" + +import dataclasses +import math +from collections.abc import Iterator, Mapping +from types import MappingProxyType +from typing import TYPE_CHECKING, Literal, Self + +from lcm.grids import ContinuousGrid, Grid +from lcm.shocks import _ShockGrid +from lcm.typing import StateOrActionName + +if TYPE_CHECKING: + from lcm.regime import Regime + + +@dataclasses.dataclass(frozen=True) +class VariableInfo: + """Kind/topology/shock tags for one state or action variable.""" + + kind: Literal["state", "action"] + """Whether the variable is a state or an action.""" + + topology: Literal["continuous", "discrete"] + """Topology as treated by pylcm's solve/simulate machinery. + + Shocks have topology `"discrete"` because their value space is + approximated by a finite grid of nodes, even though the underlying + random variable is mathematically continuous. Combine with `is_shock` + when the distinction matters. + + """ + + is_shock: bool + """Whether the variable is a shock (always a state).""" + + +@dataclasses.dataclass(frozen=True) +class Variables(Mapping[StateOrActionName, VariableInfo]): + """States + actions of a regime, with pre-computed name-tuple views. + + Mapping access by variable name returns the per-variable `VariableInfo`. + Named accessors return tuples of names in iteration order. Use + `Variables.from_regime` to construct from a regime; pass `info` directly + only when names are already in the desired order. + + """ + + info: MappingProxyType[StateOrActionName, VariableInfo] + """Immutable mapping of variable name to its `VariableInfo`.""" + + state_names: tuple[StateOrActionName, ...] = dataclasses.field(init=False) + """Names of variables with kind='state'.""" + + action_names: tuple[StateOrActionName, ...] = dataclasses.field(init=False) + """Names of variables with kind='action'.""" + + discrete_state_names: tuple[StateOrActionName, ...] = dataclasses.field(init=False) + """Names of states with topology='discrete' (includes shocks).""" + + continuous_state_names: tuple[StateOrActionName, ...] = dataclasses.field( + init=False + ) + """Names of states with topology='continuous'.""" + + discrete_action_names: tuple[StateOrActionName, ...] = dataclasses.field(init=False) + """Names of actions with topology='discrete'.""" + + continuous_action_names: tuple[StateOrActionName, ...] = dataclasses.field( + init=False + ) + """Names of actions with topology='continuous'.""" + + state_and_discrete_action_names: tuple[StateOrActionName, ...] = dataclasses.field( + init=False + ) + """Every state plus every discrete action — the gridded variable set.""" + + shock_names: tuple[StateOrActionName, ...] = dataclasses.field(init=False) + """Names of variables with `is_shock=True`.""" + + def __post_init__(self) -> None: + items = tuple(self.info.items()) + # `object.__setattr__` is required to bypass the frozen guard. + object.__setattr__( + self, + "state_names", + tuple(name for name, info in items if info.kind == "state"), + ) + object.__setattr__( + self, + "action_names", + tuple(name for name, info in items if info.kind == "action"), + ) + object.__setattr__( + self, + "discrete_state_names", + tuple( + name + for name, info in items + if info.kind == "state" and info.topology == "discrete" + ), + ) + object.__setattr__( + self, + "continuous_state_names", + tuple( + name + for name, info in items + if info.kind == "state" and info.topology == "continuous" + ), + ) + object.__setattr__( + self, + "discrete_action_names", + tuple( + name + for name, info in items + if info.kind == "action" and info.topology == "discrete" + ), + ) + object.__setattr__( + self, + "continuous_action_names", + tuple( + name + for name, info in items + if info.kind == "action" and info.topology == "continuous" + ), + ) + object.__setattr__( + self, + "state_and_discrete_action_names", + tuple( + name + for name, info in items + if info.kind == "state" or info.topology == "discrete" + ), + ) + object.__setattr__( + self, + "shock_names", + tuple(name for name, info in items if info.is_shock), + ) + + def __getitem__(self, key: StateOrActionName) -> VariableInfo: + return self.info[key] + + def __iter__(self) -> Iterator[StateOrActionName]: + return iter(self.info) + + def __len__(self) -> int: + return len(self.info) + + @classmethod + def from_regime(cls, regime: Regime) -> Self: + """Build `Variables` from a regime, ordering names canonically. + + Order: discrete states (by `batch_size`), continuous states (by + `batch_size`), then actions in declaration order. Within each topology + group, `batch_size == 0` sorts last. + + Args: + regime: The regime as provided by the user. + + Returns: + A `Variables` instance whose iteration order matches the canonical + ordering described above. + + """ + raw_info = _raw_variable_info(regime) + ordered_names = _ordered_state_action_names(regime, raw_info) + return cls( + info=MappingProxyType({name: raw_info[name] for name in ordered_names}) + ) + + +def get_grids( + regime: Regime, +) -> MappingProxyType[StateOrActionName, Grid]: + """Create a mapping of grid objects for each variable in the regime. + + Args: + regime: The regime as provided by the user. + + Returns: + Immutable mapping of state and action variable names to their grid objects, + in the canonical order used by `Variables.from_regime` (discrete states, + continuous states, then actions). + + """ + variables = Variables.from_regime(regime) + raw_variables = dict(regime.states) | dict(regime.actions) + return MappingProxyType({name: raw_variables[name] for name in variables}) + + +def _raw_variable_info( + regime: Regime, +) -> dict[StateOrActionName, VariableInfo]: + """Derive `VariableInfo` for every state and action variable.""" + variables = dict(regime.states) | dict(regime.actions) + info: dict[StateOrActionName, VariableInfo] = {} + for name, spec in variables.items(): + is_state = name in regime.states + is_shock = isinstance(spec, _ShockGrid) + is_continuous = isinstance(spec, ContinuousGrid) and not is_shock + info[name] = VariableInfo( + kind="state" if is_state else "action", + topology="continuous" if is_continuous else "discrete", + is_shock=is_shock, + ) + return info + + +def _ordered_state_action_names( + regime: Regime, + info: dict[StateOrActionName, VariableInfo], +) -> list[StateOrActionName]: + """Order variables: discrete states, continuous states, actions. + + States are sorted by `batch_size` within each topology group; batch size 0 + sorts last (treated as +inf). Actions keep declaration order. + + """ + + def state_batch_size(name: StateOrActionName) -> float: + batch_size = regime.states[name].batch_size + return batch_size if batch_size != 0 else math.inf + + discrete_states = sorted( + ( + name + for name, var_info in info.items() + if var_info.kind == "state" and var_info.topology == "discrete" + ), + key=state_batch_size, + ) + continuous_states = sorted( + ( + name + for name, var_info in info.items() + if var_info.kind == "state" and var_info.topology == "continuous" + ), + key=state_batch_size, + ) + actions = [name for name, var_info in info.items() if var_info.kind == "action"] + + ordered = [*discrete_states, *continuous_states, *actions] + if set(ordered) != set(info): + raise ValueError("Order and index do not match.") + return ordered diff --git a/tests/regime_building/test_regime_processing.py b/tests/regime_building/test_regime_processing.py index 4f9d7732b..7c60a9a51 100644 --- a/tests/regime_building/test_regime_processing.py +++ b/tests/regime_building/test_regime_processing.py @@ -8,18 +8,18 @@ from lcm import Regime, categorical from lcm.ages import AgeGrid from lcm.grids import DiscreteGrid, LinSpacedGrid -from lcm.interfaces import InternalRegime, VariableInfo +from lcm.interfaces import InternalRegime from lcm.regime_building.processing import ( _rename_params_to_qnames, process_regimes, ) -from lcm.regime_building.variable_info import get_grids, get_variable_info from lcm.typing import ScalarInt +from lcm.variables import VariableInfo, Variables, get_grids from tests.regime_mock import RegimeMock from tests.test_models.deterministic.base import dead, working_life -def test_get_variable_info(binary_category_class): +def test_variables_from_regime_tags_kind_and_topology(binary_category_class): def utility(c): pass @@ -37,9 +37,9 @@ def next_c(a, b): functions={"utility": utility}, ) - got = get_variable_info(regime_mock) # ty: ignore[invalid-argument-type] + got = Variables.from_regime(regime_mock) # ty: ignore[invalid-argument-type] - assert isinstance(got, MappingProxyType) + assert isinstance(got, Variables) assert set(got) == {"a", "c"} assert got["a"] == VariableInfo(kind="action", topology="discrete", is_shock=False) assert got["c"] == VariableInfo(kind="state", topology="discrete", is_shock=False) @@ -114,14 +114,14 @@ def test_process_regimes(): internal_working_regime = internal_regimes["working_life"] # Variable Info - vi = internal_working_regime.variable_info - assert vi["wealth"] == VariableInfo( + variables = internal_working_regime.variables + assert variables["wealth"] == VariableInfo( kind="state", topology="continuous", is_shock=False ) - assert vi["labor_supply"] == VariableInfo( + assert variables["labor_supply"] == VariableInfo( kind="action", topology="discrete", is_shock=False ) - assert vi["consumption"] == VariableInfo( + assert variables["consumption"] == VariableInfo( kind="action", topology="continuous", is_shock=False ) @@ -159,8 +159,8 @@ def test_process_regimes(): assert "utility" in internal_working_regime.solve_functions.functions -def test_variable_info_excludes_constraint_names(): - """Constraint functions do not appear as variables in variable_info.""" +def test_variables_excludes_constraint_names(): + """Constraint functions do not appear as variables in the Variables container.""" def wealth_constraint(wealth): return wealth > 200 @@ -170,7 +170,7 @@ def wealth_constraint(wealth): | {"wealth_constraint": wealth_constraint} ) - got = get_variable_info(working_copy) + got = Variables.from_regime(working_copy) assert "wealth_constraint" not in got diff --git a/tests/test_next_state.py b/tests/test_next_state.py index 3e2dac694..f89bad620 100644 --- a/tests/test_next_state.py +++ b/tests/test_next_state.py @@ -4,7 +4,6 @@ from lcm.ages import AgeGrid from lcm.grids import DiscreteGrid, categorical -from lcm.interfaces import VariableInfo from lcm.regime_building import process_regimes from lcm.regime_building.next_state import ( _create_discrete_stochastic_next_func, @@ -12,6 +11,7 @@ get_next_state_function_for_solution, ) from lcm.typing import ContinuousState, ScalarInt +from lcm.variables import VariableInfo, Variables from tests.test_models.deterministic.regression import dead, working_life @@ -76,8 +76,10 @@ class MockCategory: all_grids = MappingProxyType( {"mock": MappingProxyType({"b": DiscreteGrid(MockCategory)})} ) - variable_info = MappingProxyType( - {"b": VariableInfo(kind="state", topology="discrete", is_shock=False)} + variables = Variables( + info=MappingProxyType( + {"b": VariableInfo(kind="state", topology="discrete", is_shock=False)} + ) ) transitions = MappingProxyType( {"mock": MappingProxyType({"next_a": f_a, "next_b": f_b})} @@ -88,7 +90,7 @@ class MockCategory: transitions=transitions, # ty: ignore[invalid-argument-type] functions=functions, # ty: ignore[invalid-argument-type] all_grids=all_grids, - variable_info=variable_info, + variables=variables, ) got = got_func(state=jnp.array([1.0, 2.0])) diff --git a/tests/test_state_action_space.py b/tests/test_state_action_space.py index 27201b00a..63cef957d 100644 --- a/tests/test_state_action_space.py +++ b/tests/test_state_action_space.py @@ -4,29 +4,33 @@ from numpy.testing import assert_array_equal from lcm.grids import DiscreteGrid, IrregSpacedGrid, LinSpacedGrid, categorical -from lcm.interfaces import StateActionSpace, VariableInfo, VariableInfoMapping +from lcm.interfaces import StateActionSpace from lcm.regime import Regime from lcm.regime_building.V import VInterpolationInfo, create_v_interpolation_info from lcm.state_action_space import create_state_action_space from lcm.typing import ScalarInt +from lcm.variables import VariableInfo, Variables -def _create_variable_info( +def _create_variables( discrete_states: list[str], continuous_states: list[str], discrete_actions: list[str], continuous_actions: list[str], -) -> VariableInfoMapping: +) -> Variables: info: dict[str, VariableInfo] = {} + # Order matches Variables.from_regime ordering: discrete states, continuous + # states, then actions (discrete then continuous within actions in original + # declaration order). for name in discrete_states: info[name] = VariableInfo(kind="state", topology="discrete", is_shock=False) - for name in discrete_actions: - info[name] = VariableInfo(kind="action", topology="discrete", is_shock=False) for name in continuous_states: info[name] = VariableInfo(kind="state", topology="continuous", is_shock=False) + for name in discrete_actions: + info[name] = VariableInfo(kind="action", topology="discrete", is_shock=False) for name in continuous_actions: info[name] = VariableInfo(kind="action", topology="continuous", is_shock=False) - return MappingProxyType(info) + return Variables(info=MappingProxyType(info)) def test_create_state_action_space_solution_discrete_action_continuous_state(): @@ -35,7 +39,7 @@ class WorkChoice: no_work: ScalarInt work: ScalarInt - variable_info = _create_variable_info( + variables = _create_variables( continuous_states=["wealth"], discrete_actions=["work"], discrete_states=[], @@ -49,7 +53,7 @@ class WorkChoice: ) space = create_state_action_space( - variable_info=variable_info, + variables=variables, grids=grids, ) @@ -57,11 +61,11 @@ class WorkChoice: assert_array_equal(space.states["wealth"], grids["wealth"].to_jax()) assert_array_equal(space.discrete_actions["work"], grids["work"].to_jax()) assert space.continuous_actions == {} - assert space.state_and_discrete_action_names == ("work", "wealth") + assert space.state_and_discrete_action_names == ("wealth", "work") def test_create_state_action_space_solution_continuous_action(): - variable_info = _create_variable_info( + variables = _create_variables( continuous_states=["wealth"], continuous_actions=["consumption"], discrete_states=[], @@ -75,7 +79,7 @@ def test_create_state_action_space_solution_continuous_action(): ) space = create_state_action_space( - variable_info=variable_info, + variables=variables, grids=grids, ) @@ -94,7 +98,7 @@ class WorkChoice: no_work: ScalarInt work: ScalarInt - variable_info = _create_variable_info( + variables = _create_variables( continuous_states=["wealth"], discrete_actions=["work"], discrete_states=[], @@ -108,7 +112,7 @@ class WorkChoice: ) space = create_state_action_space( - variable_info=variable_info, + variables=variables, grids=grids, states={"wealth": jnp.array([10.0, 20.0])}, ) diff --git a/tests/test_variables.py b/tests/test_variables.py new file mode 100644 index 000000000..02998948f --- /dev/null +++ b/tests/test_variables.py @@ -0,0 +1,192 @@ +"""Tests for `Variables` — per-regime states + actions with named accessors.""" + +from collections.abc import Mapping +from dataclasses import FrozenInstanceError +from types import MappingProxyType + +import pytest + +from lcm.grids import DiscreteGrid, LinSpacedGrid +from lcm.variables import VariableInfo, Variables +from tests.regime_mock import RegimeMock + + +@pytest.fixture(name="variables") +def _variables() -> Variables: + """Variables covering every kind/topology combination, including one shock.""" + return Variables( + info=MappingProxyType( + { + "edu": VariableInfo(kind="state", topology="discrete", is_shock=False), + "wealth": VariableInfo( + kind="state", topology="continuous", is_shock=False + ), + "wage_shock": VariableInfo( + kind="state", topology="discrete", is_shock=True + ), + "labor_supply": VariableInfo( + kind="action", topology="discrete", is_shock=False + ), + "consumption": VariableInfo( + kind="action", topology="continuous", is_shock=False + ), + } + ) + ) + + +def test_state_names_filters_kind(variables: Variables) -> None: + """`state_names` returns every variable with kind='state', in iteration order.""" + assert variables.state_names == ("edu", "wealth", "wage_shock") + + +def test_action_names_filters_kind(variables: Variables) -> None: + """`action_names` returns every variable with kind='action', in iteration order.""" + assert variables.action_names == ("labor_supply", "consumption") + + +def test_discrete_state_names_filters_kind_and_topology( + variables: Variables, +) -> None: + """`discrete_state_names` is states with topology='discrete' (includes shocks).""" + assert variables.discrete_state_names == ("edu", "wage_shock") + + +def test_continuous_state_names_filters_kind_and_topology( + variables: Variables, +) -> None: + """`continuous_state_names` is states with topology='continuous'.""" + assert variables.continuous_state_names == ("wealth",) + + +def test_discrete_action_names_filters_kind_and_topology( + variables: Variables, +) -> None: + """`discrete_action_names` is actions with topology='discrete'.""" + assert variables.discrete_action_names == ("labor_supply",) + + +def test_continuous_action_names_filters_kind_and_topology( + variables: Variables, +) -> None: + """`continuous_action_names` is actions with topology='continuous'.""" + assert variables.continuous_action_names == ("consumption",) + + +def test_state_and_discrete_action_names_is_gridded_set( + variables: Variables, +) -> None: + """`state_and_discrete_action_names` is every state plus every discrete action.""" + assert variables.state_and_discrete_action_names == ( + "edu", + "wealth", + "wage_shock", + "labor_supply", + ) + + +def test_shock_names_filters_is_shock(variables: Variables) -> None: + """`shock_names` is every variable with `is_shock=True`.""" + assert variables.shock_names == ("wage_shock",) + + +def test_getitem_returns_variable_info(variables: Variables) -> None: + """`vars[name]` returns the underlying `VariableInfo` record.""" + assert variables["wealth"] == VariableInfo( + kind="state", topology="continuous", is_shock=False + ) + + +def test_iter_yields_names_in_definition_order(variables: Variables) -> None: + """Iteration yields variable names in the order the info dict supplies.""" + assert list(variables) == [ + "edu", + "wealth", + "wage_shock", + "labor_supply", + "consumption", + ] + + +def test_len_equals_variable_count(variables: Variables) -> None: + """`len(vars)` equals the number of variables.""" + assert len(variables) == 5 + + +def test_contains_returns_true_for_known_variable(variables: Variables) -> None: + """`name in vars` works through the Mapping interface.""" + assert "edu" in variables + + +def test_is_mapping_instance(variables: Variables) -> None: + """`Variables` registers as a `collections.abc.Mapping`.""" + assert isinstance(variables, Mapping) + + +def test_items_yields_name_info_pairs(variables: Variables) -> None: + """`vars.items()` yields (name, VariableInfo) pairs.""" + items = list(variables.items()) + assert items[0] == ( + "edu", + VariableInfo(kind="state", topology="discrete", is_shock=False), + ) + + +def test_frozen_dataclass_rejects_field_assignment( + variables: Variables, +) -> None: + """`Variables` is immutable — assigning to a field raises `FrozenInstanceError`.""" + with pytest.raises(FrozenInstanceError): + variables.state_names = () # ty: ignore[invalid-assignment] + + +def test_from_regime_orders_discrete_states_continuous_states_actions( + binary_category_class, +) -> None: + """`Variables.from_regime` orders discrete states → continuous states → actions.""" + + def next_state(x): + return x + + regime = RegimeMock( + states={ + "a_discrete": DiscreteGrid(binary_category_class), + "b_continuous": LinSpacedGrid(start=0, stop=1, n_points=5), + }, + state_transitions={ + "a_discrete": next_state, + "b_continuous": next_state, + }, + actions={ + "c_action": DiscreteGrid(binary_category_class), + }, + functions={"utility": lambda c_action: 0}, # noqa: ARG005 + ) + variables = Variables.from_regime(regime) # ty: ignore[invalid-argument-type] + assert list(variables) == ["a_discrete", "b_continuous", "c_action"] + + +def test_from_regime_within_states_orders_by_batch_size( + binary_category_class, +) -> None: + """Within a topology group, states are ordered by batch_size (0 sorts last).""" + + def next_state(x): + return x + + regime = RegimeMock( + states={ + "third": DiscreteGrid(binary_category_class), + "first": DiscreteGrid(binary_category_class, batch_size=1), + "second": DiscreteGrid(binary_category_class, batch_size=2), + }, + state_transitions={ + "first": next_state, + "second": next_state, + "third": next_state, + }, + actions={"a": DiscreteGrid(binary_category_class)}, + functions={"utility": lambda a: 0}, # noqa: ARG005 + ) + variables = Variables.from_regime(regime) # ty: ignore[invalid-argument-type] + assert variables.discrete_state_names == ("first", "second", "third") From d503dae033676165b5ff2402fc699d8832339f92 Mon Sep 17 00:00:00 2001 From: Hans-Martin von Gaudecker Date: Wed, 13 May 2026 15:35:38 +0200 Subject: [PATCH 14/77] V.py: use Variables.{discrete,continuous}_state_names directly MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The earlier rewrite kept isinstance checks under `name in state_names_set`, mirroring the pre-Variables logic. The named views already filter on `kind == "state"` plus the matching topology, so the isinstance branches become redundant — shocks live in `discrete_state_names` (topology `"discrete"`), non-shock ContinuousGrid states in `continuous_state_names`. --- src/lcm/regime_building/V.py | 23 ++++++----------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/src/lcm/regime_building/V.py b/src/lcm/regime_building/V.py index 30ede1788..31521a5c8 100644 --- a/src/lcm/regime_building/V.py +++ b/src/lcm/regime_building/V.py @@ -49,26 +49,15 @@ def create_v_interpolation_info(regime: Regime) -> VInterpolationInfo: variables = Variables.from_regime(regime) grids = get_grids(regime) - state_names_set = set(variables.state_names) - discrete_states = { - name: grid_spec - for name, grid_spec in grids.items() - if (name in state_names_set and isinstance(grid_spec, DiscreteGrid)) - or isinstance(grid_spec, _ShockGrid) - } - - continuous_states = { - name: grid_spec - for name, grid_spec in grids.items() - if name in state_names_set - and isinstance(grid_spec, ContinuousGrid) - and not isinstance(grid_spec, _ShockGrid) - } + discrete_states = {name: grids[name] for name in variables.discrete_state_names} + continuous_states = {name: grids[name] for name in variables.continuous_state_names} return VInterpolationInfo( state_names=variables.state_names, - discrete_states=MappingProxyType(discrete_states), - continuous_states=MappingProxyType(continuous_states), + # `variables.{discrete,continuous}_state_names` filter on + # topology/shock; ty can't see through that to narrow grid types. + discrete_states=MappingProxyType(discrete_states), # ty: ignore[invalid-argument-type] + continuous_states=MappingProxyType(continuous_states), # ty: ignore[invalid-argument-type] ) From 607dba5a0fa8cf1a32cbdbd502d92e7c0739bb99 Mon Sep 17 00:00:00 2001 From: Hans-Martin von Gaudecker Date: Wed, 13 May 2026 15:49:09 +0200 Subject: [PATCH 15/77] Drop intermediate variables and one-call helpers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - transitions.py / result.py: inline `state_names` / `action_names` / `relevant_state_names` rebinds — `internal_regime.variables.state_names` is a cached attribute, so the rebind buys nothing. - error_handling.py: same for `target_state_names`. - initial_conditions.py: drop `_get_regime_state_names` — three callers, each one line shorter with the attribute access inlined. - variables.py: collapse two `def get_grids(\n regime: Regime,\n)` / `_raw_variable_info` signatures to one line; both fit comfortably. --- src/lcm/simulation/initial_conditions.py | 21 +++------------------ src/lcm/simulation/result.py | 10 ++++------ src/lcm/simulation/transitions.py | 5 ++--- src/lcm/utils/error_handling.py | 7 ++++--- src/lcm/variables.py | 8 ++------ 5 files changed, 15 insertions(+), 36 deletions(-) diff --git a/src/lcm/simulation/initial_conditions.py b/src/lcm/simulation/initial_conditions.py index 708a95662..30453e38b 100644 --- a/src/lcm/simulation/initial_conditions.py +++ b/src/lcm/simulation/initial_conditions.py @@ -67,7 +67,7 @@ def build_initial_states( for regime_name, internal_regime in internal_regimes.items(): regime_states: dict[StateName, Array] = {} - for state_name in _get_regime_state_names(internal_regime): + for state_name in internal_regime.variables.state_names: grid = internal_regime.grids[state_name] if isinstance(grid, DiscreteGrid): # Cast user-supplied discrete states to the grid's index @@ -189,21 +189,6 @@ def validate_initial_conditions( raise InvalidInitialConditionsError(format_messages(feasibility_errors)) -def _get_regime_state_names( - internal_regime: InternalRegime, -) -> set[str]: - """Get state names from an internal regime's variable info. - - Args: - internal_regime: The internal regime instance. - - Returns: - Set of state variable names. - - """ - return set(internal_regime.variables.state_names) - - def _format_missing_states_message(missing: set[str], required: set[str]) -> str: """Format an error message for missing initial states. @@ -262,7 +247,7 @@ def _collect_state_name_errors( # All known states (union across all regimes) — used for the "extra" check all_known_states: set[str] = set(PSEUDO_STATE_NAMES) for internal_regime in internal_regimes.values(): - all_known_states.update(_get_regime_state_names(internal_regime)) + all_known_states.update(internal_regime.variables.state_names) # Required states — only from regimes subjects actually start in required_states: set[str] = set(PSEUDO_STATE_NAMES) @@ -271,7 +256,7 @@ def _collect_state_name_errors( regime_ids_to_names[int(i)] for i in used_ids if int(i) in regime_ids_to_names } & valid_regime_names for regime_name in used_regime_names: - required_states.update(_get_regime_state_names(internal_regimes[regime_name])) + required_states.update(internal_regimes[regime_name].variables.state_names) provided_states = set(initial_states.keys()) diff --git a/src/lcm/simulation/result.py b/src/lcm/simulation/result.py index 16bef079b..e5f8c0cb4 100644 --- a/src/lcm/simulation/result.py +++ b/src/lcm/simulation/result.py @@ -313,12 +313,10 @@ def _compute_metadata( regime_to_actions: dict[RegimeName, tuple[ActionName, ...]] = {} for regime_name, regime in internal_regimes.items(): - states = regime.variables.state_names - actions = regime.variables.action_names - regime_to_states[regime_name] = states - regime_to_actions[regime_name] = actions - all_states.update(states) - all_actions.update(actions) + regime_to_states[regime_name] = regime.variables.state_names + regime_to_actions[regime_name] = regime.variables.action_names + all_states.update(regime.variables.state_names) + all_actions.update(regime.variables.action_names) # Extract categories and ordered flags from simulation_output_dtypes discrete_categories: dict[str, tuple[str, ...]] = {} diff --git a/src/lcm/simulation/transitions.py b/src/lcm/simulation/transitions.py index dacbcddee..484fe9f12 100644 --- a/src/lcm/simulation/transitions.py +++ b/src/lcm/simulation/transitions.py @@ -55,13 +55,12 @@ def create_regime_state_action_space( """ base = internal_regime.state_action_space(regime_params=regime_params) - relevant_state_names = internal_regime.variables.state_names states_for_state_action_space = { - sn: regime_states[sn] for sn in relevant_state_names + sn: regime_states[sn] for sn in internal_regime.variables.state_names } _validate_all_states_present( provided_states=states_for_state_action_space, - required_state_names=set(relevant_state_names), + required_state_names=set(internal_regime.variables.state_names), ) return base.replace(states=MappingProxyType(states_for_state_action_space)) diff --git a/src/lcm/utils/error_handling.py b/src/lcm/utils/error_handling.py index 7cd79e143..b9c7e9b25 100644 --- a/src/lcm/utils/error_handling.py +++ b/src/lcm/utils/error_handling.py @@ -561,9 +561,10 @@ def _validate_no_reachable_incomplete_targets( for target_regime_name in active_regimes_next_period: target_regime = internal_regimes[target_regime_name] - target_state_names = target_regime.variables.state_names needs = { - f"next_{s}" for s in target_state_names if f"next_{s}" in stochastic_names + f"next_{s}" + for s in target_regime.variables.state_names + if f"next_{s}" in stochastic_names } if not needs: continue @@ -575,7 +576,7 @@ def _validate_no_reachable_incomplete_targets( continue missing = sorted(needs - set(transitions.get(target_regime_name, {}))) if target_regime_name not in transitions: - missing = sorted(f"next_{s}" for s in target_state_names) + missing = sorted(f"next_{s}" for s in target_regime.variables.state_names) raise InvalidRegimeTransitionProbabilitiesError( f"Regime '{regime_name}' at age {age} has positive transition " f"probability to '{target_regime_name}', but '{regime_name}' " diff --git a/src/lcm/variables.py b/src/lcm/variables.py index 5668e87ac..64eec92cb 100644 --- a/src/lcm/variables.py +++ b/src/lcm/variables.py @@ -188,9 +188,7 @@ def from_regime(cls, regime: Regime) -> Self: ) -def get_grids( - regime: Regime, -) -> MappingProxyType[StateOrActionName, Grid]: +def get_grids(regime: Regime) -> MappingProxyType[StateOrActionName, Grid]: """Create a mapping of grid objects for each variable in the regime. Args: @@ -207,9 +205,7 @@ def get_grids( return MappingProxyType({name: raw_variables[name] for name in variables}) -def _raw_variable_info( - regime: Regime, -) -> dict[StateOrActionName, VariableInfo]: +def _raw_variable_info(regime: Regime) -> dict[StateOrActionName, VariableInfo]: """Derive `VariableInfo` for every state and action variable.""" variables = dict(regime.states) | dict(regime.actions) info: dict[StateOrActionName, VariableInfo] = {} From 80d6095b710adb9e464e613e91acaf2a80f176ba Mon Sep 17 00:00:00 2001 From: Hans-Martin von Gaudecker Date: Wed, 13 May 2026 13:49:44 +0200 Subject: [PATCH 16/77] Add beartype dep + per-exception BeartypeConf helpers `src/lcm/_beartype_conf.py` exposes five module-level `BeartypeConf` instances, each configured to raise the existing project exception class on parameter-type violations. Subsequent commits decorate user-facing constructors and runtime methods with these configs. Co-Authored-By: Claude Opus 4.7 --- pixi.lock | 129 +++++++++++++++++++++++++++++++++++++- pyproject.toml | 1 + src/lcm/_beartype_conf.py | 37 +++++++++++ 3 files changed, 165 insertions(+), 2 deletions(-) create mode 100644 src/lcm/_beartype_conf.py diff --git a/pixi.lock b/pixi.lock index a73e96fba..05c13a7af 100644 --- a/pixi.lock +++ b/pixi.lock @@ -272,6 +272,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda - pypi: git+https://github.com/OpenSourceEconomics/aca-model.git?rev=b807b286175e00f8a721f039f4afd657bbb10e2f#b807b286175e00f8a721f039f4afd657bbb10e2f - pypi: https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ae/44/c1221527f6a71a01ec6fbad7fa78f1d50dfa02217385cf0fa3eec7087d59/click-8.3.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2c/1a/aff8bb287a4b1400f69e09a53bd65de96aa5cee5691925b38731c67fc695/click_default_group-1.2.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2c/c1/a662f0a8f6e024fca239d493f278d9adf5de1c8408af46a53a76beb13534/dags-0.5.1-py3-none-any.whl @@ -586,6 +587,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-1.3.2-h25fd6f3_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda + - pypi: https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2c/c1/a662f0a8f6e024fca239d493f278d9adf5de1c8408af46a53a76beb13534/dags-0.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/f5/ee39c6e92acc742c052f137b47c210cd0a1b72dcd3f98495528bb4d27761/flatten_dict-0.4.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/70/aa/dfac6d72cc35bc07e7587115b6946e333ef4ccb2e6cd26ecf639438c5d26/jax-0.10.0-py3-none-any.whl @@ -885,6 +887,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-1.3.2-h25fd6f3_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda + - pypi: https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2c/c1/a662f0a8f6e024fca239d493f278d9adf5de1c8408af46a53a76beb13534/dags-0.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/f5/ee39c6e92acc742c052f137b47c210cd0a1b72dcd3f98495528bb4d27761/flatten_dict-0.4.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/70/aa/dfac6d72cc35bc07e7587115b6946e333ef4ccb2e6cd26ecf639438c5d26/jax-0.10.0-py3-none-any.whl @@ -1154,6 +1157,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-1.3.2-h25fd6f3_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda + - pypi: https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2c/c1/a662f0a8f6e024fca239d493f278d9adf5de1c8408af46a53a76beb13534/dags-0.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/f5/ee39c6e92acc742c052f137b47c210cd0a1b72dcd3f98495528bb4d27761/flatten_dict-0.4.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/70/aa/dfac6d72cc35bc07e7587115b6946e333ef4ccb2e6cd26ecf639438c5d26/jax-0.10.0-py3-none-any.whl @@ -1394,6 +1398,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zlib-1.3.2-h8088a28_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda + - pypi: https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2c/c1/a662f0a8f6e024fca239d493f278d9adf5de1c8408af46a53a76beb13534/dags-0.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/f5/ee39c6e92acc742c052f137b47c210cd0a1b72dcd3f98495528bb4d27761/flatten_dict-0.4.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/70/aa/dfac6d72cc35bc07e7587115b6946e333ef4ccb2e6cd26ecf639438c5d26/jax-0.10.0-py3-none-any.whl @@ -1631,6 +1636,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zlib-1.3.2-hfd05255_2.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_6.conda + - pypi: https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2c/c1/a662f0a8f6e024fca239d493f278d9adf5de1c8408af46a53a76beb13534/dags-0.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/f5/ee39c6e92acc742c052f137b47c210cd0a1b72dcd3f98495528bb4d27761/flatten_dict-0.4.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/70/aa/dfac6d72cc35bc07e7587115b6946e333ef4ccb2e6cd26ecf639438c5d26/jax-0.10.0-py3-none-any.whl @@ -1888,6 +1894,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-1.3.2-h25fd6f3_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda + - pypi: https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2c/c1/a662f0a8f6e024fca239d493f278d9adf5de1c8408af46a53a76beb13534/dags-0.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/f5/ee39c6e92acc742c052f137b47c210cd0a1b72dcd3f98495528bb4d27761/flatten_dict-0.4.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/70/aa/dfac6d72cc35bc07e7587115b6946e333ef4ccb2e6cd26ecf639438c5d26/jax-0.10.0-py3-none-any.whl @@ -2138,6 +2145,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zlib-1.3.2-h8088a28_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda + - pypi: https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2c/c1/a662f0a8f6e024fca239d493f278d9adf5de1c8408af46a53a76beb13534/dags-0.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/f5/ee39c6e92acc742c052f137b47c210cd0a1b72dcd3f98495528bb4d27761/flatten_dict-0.4.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/70/aa/dfac6d72cc35bc07e7587115b6946e333ef4ccb2e6cd26ecf639438c5d26/jax-0.10.0-py3-none-any.whl @@ -2384,6 +2392,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zlib-1.3.2-hfd05255_2.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_6.conda + - pypi: https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2c/c1/a662f0a8f6e024fca239d493f278d9adf5de1c8408af46a53a76beb13534/dags-0.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/f5/ee39c6e92acc742c052f137b47c210cd0a1b72dcd3f98495528bb4d27761/flatten_dict-0.4.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/70/aa/dfac6d72cc35bc07e7587115b6946e333ef4ccb2e6cd26ecf639438c5d26/jax-0.10.0-py3-none-any.whl @@ -2638,6 +2647,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zlib-1.3.2-h8088a28_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda + - pypi: https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2c/c1/a662f0a8f6e024fca239d493f278d9adf5de1c8408af46a53a76beb13534/dags-0.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/f5/ee39c6e92acc742c052f137b47c210cd0a1b72dcd3f98495528bb4d27761/flatten_dict-0.4.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/70/aa/dfac6d72cc35bc07e7587115b6946e333ef4ccb2e6cd26ecf639438c5d26/jax-0.10.0-py3-none-any.whl @@ -2900,6 +2910,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-1.3.2-h25fd6f3_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda + - pypi: https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2c/c1/a662f0a8f6e024fca239d493f278d9adf5de1c8408af46a53a76beb13534/dags-0.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/f5/ee39c6e92acc742c052f137b47c210cd0a1b72dcd3f98495528bb4d27761/flatten_dict-0.4.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/70/aa/dfac6d72cc35bc07e7587115b6946e333ef4ccb2e6cd26ecf639438c5d26/jax-0.10.0-py3-none-any.whl @@ -3154,6 +3165,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zlib-1.3.2-h8088a28_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda + - pypi: https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2c/c1/a662f0a8f6e024fca239d493f278d9adf5de1c8408af46a53a76beb13534/dags-0.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/f5/ee39c6e92acc742c052f137b47c210cd0a1b72dcd3f98495528bb4d27761/flatten_dict-0.4.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/70/aa/dfac6d72cc35bc07e7587115b6946e333ef4ccb2e6cd26ecf639438c5d26/jax-0.10.0-py3-none-any.whl @@ -3404,6 +3416,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zlib-1.3.2-hfd05255_2.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_6.conda + - pypi: https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2c/c1/a662f0a8f6e024fca239d493f278d9adf5de1c8408af46a53a76beb13534/dags-0.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/f5/ee39c6e92acc742c052f137b47c210cd0a1b72dcd3f98495528bb4d27761/flatten_dict-0.4.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/70/aa/dfac6d72cc35bc07e7587115b6946e333ef4ccb2e6cd26ecf639438c5d26/jax-0.10.0-py3-none-any.whl @@ -3702,6 +3715,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-1.3.2-h25fd6f3_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda + - pypi: https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2c/c1/a662f0a8f6e024fca239d493f278d9adf5de1c8408af46a53a76beb13534/dags-0.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/f5/ee39c6e92acc742c052f137b47c210cd0a1b72dcd3f98495528bb4d27761/flatten_dict-0.4.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/70/aa/dfac6d72cc35bc07e7587115b6946e333ef4ccb2e6cd26ecf639438c5d26/jax-0.10.0-py3-none-any.whl @@ -4014,6 +4028,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-1.3.2-h25fd6f3_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda + - pypi: https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2c/c1/a662f0a8f6e024fca239d493f278d9adf5de1c8408af46a53a76beb13534/dags-0.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/f5/ee39c6e92acc742c052f137b47c210cd0a1b72dcd3f98495528bb4d27761/flatten_dict-0.4.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/70/aa/dfac6d72cc35bc07e7587115b6946e333ef4ccb2e6cd26ecf639438c5d26/jax-0.10.0-py3-none-any.whl @@ -4292,6 +4307,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zlib-1.3.2-h8088a28_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda + - pypi: https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2c/c1/a662f0a8f6e024fca239d493f278d9adf5de1c8408af46a53a76beb13534/dags-0.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/f5/ee39c6e92acc742c052f137b47c210cd0a1b72dcd3f98495528bb4d27761/flatten_dict-0.4.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/70/aa/dfac6d72cc35bc07e7587115b6946e333ef4ccb2e6cd26ecf639438c5d26/jax-0.10.0-py3-none-any.whl @@ -4656,6 +4672,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-1.3.2-h25fd6f3_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-ng-2.3.3-hceb46e0_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda + - pypi: https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2c/c1/a662f0a8f6e024fca239d493f278d9adf5de1c8408af46a53a76beb13534/dags-0.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/f5/ee39c6e92acc742c052f137b47c210cd0a1b72dcd3f98495528bb4d27761/flatten_dict-0.4.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/70/aa/dfac6d72cc35bc07e7587115b6946e333ef4ccb2e6cd26ecf639438c5d26/jax-0.10.0-py3-none-any.whl @@ -4946,6 +4963,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zlib-1.3.2-h8088a28_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zlib-ng-2.3.3-hed4e4f5_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda + - pypi: https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2c/c1/a662f0a8f6e024fca239d493f278d9adf5de1c8408af46a53a76beb13534/dags-0.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/f5/ee39c6e92acc742c052f137b47c210cd0a1b72dcd3f98495528bb4d27761/flatten_dict-0.4.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/70/aa/dfac6d72cc35bc07e7587115b6946e333ef4ccb2e6cd26ecf639438c5d26/jax-0.10.0-py3-none-any.whl @@ -5255,6 +5273,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/zlib-1.3.2-hfd05255_2.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zlib-ng-2.3.3-h0261ad2_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_6.conda + - pypi: https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2c/c1/a662f0a8f6e024fca239d493f278d9adf5de1c8408af46a53a76beb13534/dags-0.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/f5/ee39c6e92acc742c052f137b47c210cd0a1b72dcd3f98495528bb4d27761/flatten_dict-0.4.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/70/aa/dfac6d72cc35bc07e7587115b6946e333ef4ccb2e6cd26ecf639438c5d26/jax-0.10.0-py3-none-any.whl @@ -6377,6 +6396,111 @@ packages: purls: [] size: 7514 timestamp: 1767044983590 +- pypi: https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl + name: beartype + version: 0.22.9 + sha256: d16c9bbc61ea14637596c5f6fbff2ee99cbe3573e46a716401734ef50c3060c2 + requires_dist: + - autoapi>=0.9.0 ; extra == 'dev' + - celery ; extra == 'dev' + - click ; extra == 'dev' + - coverage>=5.5 ; extra == 'dev' + - docutils>=0.22.0 ; extra == 'dev' + - equinox ; python_full_version < '3.15' and sys_platform == 'linux' and extra == 'dev' + - fastmcp ; python_full_version < '3.14' and extra == 'dev' + - jax[cpu] ; python_full_version < '3.15' and sys_platform == 'linux' and extra == 'dev' + - jaxtyping ; sys_platform == 'linux' and extra == 'dev' + - langchain ; python_full_version < '3.14' and platform_python_implementation != 'PyPy' and sys_platform != 'darwin' and extra == 'dev' + - mypy>=0.800 ; platform_python_implementation != 'PyPy' and extra == 'dev' + - nuitka>=1.2.6 ; python_full_version < '3.14' and sys_platform == 'linux' and extra == 'dev' + - numba ; python_full_version < '3.14' and extra == 'dev' + - numpy ; python_full_version < '3.15' and platform_python_implementation != 'PyPy' and sys_platform != 'darwin' and extra == 'dev' + - pandera>=0.26.0 ; python_full_version < '3.14' and extra == 'dev' + - poetry ; extra == 'dev' + - polars ; python_full_version < '3.14' and extra == 'dev' + - pydata-sphinx-theme<=0.7.2 ; extra == 'dev' + - pygments ; extra == 'dev' + - pyinstaller ; extra == 'dev' + - pyright>=1.1.370 ; extra == 'dev' + - pytest>=6.2.0 ; extra == 'dev' + - redis ; extra == 'dev' + - rich-click ; extra == 'dev' + - setuptools ; extra == 'dev' + - sphinx ; extra == 'dev' + - sphinx>=4.2.0,<6.0.0 ; extra == 'dev' + - sphinxext-opengraph>=0.7.5 ; extra == 'dev' + - sqlalchemy ; extra == 'dev' + - torch ; python_full_version < '3.14' and sys_platform == 'linux' and extra == 'dev' + - tox>=3.20.1 ; extra == 'dev' + - typer ; extra == 'dev' + - typing-extensions>=3.10.0.0 ; extra == 'dev' + - xarray ; python_full_version < '3.15' and extra == 'dev' + - mkdocs-material[imaging]>=9.6.0 ; extra == 'doc-ghp' + - mkdocstrings-python-xref>=1.16.0 ; extra == 'doc-ghp' + - mkdocstrings-python>=1.16.0 ; extra == 'doc-ghp' + - autoapi>=0.9.0 ; extra == 'doc-rtd' + - pydata-sphinx-theme<=0.7.2 ; extra == 'doc-rtd' + - setuptools ; extra == 'doc-rtd' + - sphinx>=4.2.0,<6.0.0 ; extra == 'doc-rtd' + - sphinxext-opengraph>=0.7.5 ; extra == 'doc-rtd' + - celery ; extra == 'test' + - click ; extra == 'test' + - coverage>=5.5 ; extra == 'test' + - docutils>=0.22.0 ; extra == 'test' + - equinox ; python_full_version < '3.15' and sys_platform == 'linux' and extra == 'test' + - fastmcp ; python_full_version < '3.14' and extra == 'test' + - jax[cpu] ; python_full_version < '3.15' and sys_platform == 'linux' and extra == 'test' + - jaxtyping ; sys_platform == 'linux' and extra == 'test' + - langchain ; python_full_version < '3.14' and platform_python_implementation != 'PyPy' and sys_platform != 'darwin' and extra == 'test' + - mypy>=0.800 ; platform_python_implementation != 'PyPy' and extra == 'test' + - nuitka>=1.2.6 ; python_full_version < '3.14' and sys_platform == 'linux' and extra == 'test' + - numba ; python_full_version < '3.14' and extra == 'test' + - numpy ; python_full_version < '3.15' and platform_python_implementation != 'PyPy' and sys_platform != 'darwin' and extra == 'test' + - pandera>=0.26.0 ; python_full_version < '3.14' and extra == 'test' + - poetry ; extra == 'test' + - polars ; python_full_version < '3.14' and extra == 'test' + - pygments ; extra == 'test' + - pyinstaller ; extra == 'test' + - pyright>=1.1.370 ; extra == 'test' + - pytest>=6.2.0 ; extra == 'test' + - redis ; extra == 'test' + - rich-click ; extra == 'test' + - sphinx ; extra == 'test' + - sqlalchemy ; extra == 'test' + - torch ; python_full_version < '3.14' and sys_platform == 'linux' and extra == 'test' + - tox>=3.20.1 ; extra == 'test' + - typer ; extra == 'test' + - typing-extensions>=3.10.0.0 ; extra == 'test' + - xarray ; python_full_version < '3.15' and extra == 'test' + - celery ; extra == 'test-tox' + - click ; extra == 'test-tox' + - docutils>=0.22.0 ; extra == 'test-tox' + - equinox ; python_full_version < '3.15' and sys_platform == 'linux' and extra == 'test-tox' + - fastmcp ; python_full_version < '3.14' and extra == 'test-tox' + - jax[cpu] ; python_full_version < '3.15' and sys_platform == 'linux' and extra == 'test-tox' + - jaxtyping ; sys_platform == 'linux' and extra == 'test-tox' + - langchain ; python_full_version < '3.14' and platform_python_implementation != 'PyPy' and sys_platform != 'darwin' and extra == 'test-tox' + - mypy>=0.800 ; platform_python_implementation != 'PyPy' and extra == 'test-tox' + - nuitka>=1.2.6 ; python_full_version < '3.14' and sys_platform == 'linux' and extra == 'test-tox' + - numba ; python_full_version < '3.14' and extra == 'test-tox' + - numpy ; python_full_version < '3.15' and platform_python_implementation != 'PyPy' and sys_platform != 'darwin' and extra == 'test-tox' + - pandera>=0.26.0 ; python_full_version < '3.14' and extra == 'test-tox' + - poetry ; extra == 'test-tox' + - polars ; python_full_version < '3.14' and extra == 'test-tox' + - pygments ; extra == 'test-tox' + - pyinstaller ; extra == 'test-tox' + - pyright>=1.1.370 ; extra == 'test-tox' + - pytest>=6.2.0 ; extra == 'test-tox' + - redis ; extra == 'test-tox' + - rich-click ; extra == 'test-tox' + - sphinx ; extra == 'test-tox' + - sqlalchemy ; extra == 'test-tox' + - torch ; python_full_version < '3.14' and sys_platform == 'linux' and extra == 'test-tox' + - typer ; extra == 'test-tox' + - typing-extensions>=3.10.0.0 ; extra == 'test-tox' + - xarray ; python_full_version < '3.15' and extra == 'test-tox' + - coverage>=5.5 ; extra == 'test-tox-coverage' + requires_python: '>=3.10' - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.14.3-pyha770c72_0.conda sha256: bf1e71c3c0a5b024e44ff928225a0874fc3c3356ec1a0b6fe719108e6d1288f6 md5: 5267bef8efea4127aacd1f4e1f149b6e @@ -13962,9 +14086,10 @@ packages: timestamp: 1774796815820 - pypi: ./ name: pylcm - version: 0.0.2.dev131+g4a05a8283.d20260511 - sha256: 4f354caf09e81582a5adb942f82bd7f91aafbac5a9eeaacb931f28bd1d0499dc + version: 0.0.2.dev136+ga5d932a48.d20260513 + sha256: fc3e3cff622b9db6e1f5a01c6cfad41879d5d1efdb982743f18b8ca3edf585a7 requires_dist: + - beartype>=0.21 - cloudpickle>=3.1.2 - dags>=0.5.1 - h5py>=3.12 diff --git a/pyproject.toml b/pyproject.toml index 89846aec9..3651a96a5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,6 +29,7 @@ classifiers = [ ] dynamic = [ "version" ] dependencies = [ + "beartype>=0.21", "cloudpickle>=3.1.2", "dags>=0.5.1", "h5py>=3.12", diff --git a/src/lcm/_beartype_conf.py b/src/lcm/_beartype_conf.py new file mode 100644 index 000000000..9740aae80 --- /dev/null +++ b/src/lcm/_beartype_conf.py @@ -0,0 +1,37 @@ +"""Per-exception `BeartypeConf` instances used at the pylcm perimeter. + +Decorators at user-facing entry points configure beartype to raise +the existing project exception class on parameter-type violations, +preserving the documented exception hierarchy. + +""" + +from beartype import BeartypeConf + +from lcm.exceptions import ( + CategoricalDefinitionError, + GridInitializationError, + InvalidParamsError, + ModelInitializationError, + RegimeInitializationError, +) + + +def _conf(exc: type[Exception]) -> BeartypeConf: + return BeartypeConf(violation_param_type=exc) + + +# Used on `Regime` and `MarkovTransition`. +REGIME_CONF = _conf(RegimeInitializationError) + +# Used on `Model`. +MODEL_CONF = _conf(ModelInitializationError) + +# Used on all grid and shock-grid constructors. +GRID_CONF = _conf(GridInitializationError) + +# Used on the `categorical` decorator factory. +CATEGORICAL_CONF = _conf(CategoricalDefinitionError) + +# Used on `Model.solve` and `Model.simulate`. +PARAMS_CONF = _conf(InvalidParamsError) From 3e0d62172e6083b8d70dfbebc51b13a6c3339544 Mon Sep 17 00:00:00 2001 From: Hans-Martin von Gaudecker Date: Wed, 13 May 2026 14:02:42 +0200 Subject: [PATCH 17/77] Decorate Regime + MarkovTransition with @beartype MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `@beartype(conf=REGIME_CONF)` on `Regime` and `MarkovTransition` catches parameter-type violations at construction time and raises `RegimeInitializationError` (via `BeartypeConf(violation_param_type=...)`) preserving the existing exception contract. - `validate_attribute_types` in `regime_building/validation.py` is replaced by `validate_mapping_contents`, a slim aggregator that covers what beartype can't deep-check: exhaustive iteration of `Mapping[..., Callable]` and `Mapping[..., Protocol]` value types. Beartype's `O(n)` strategy still only samples Mapping entries when the value is a Protocol/Callable. - `MarkovTransition.__post_init__`'s manual `callable(self.func)` check is dropped — beartype covers it. - Protocols in `lcm.typing` (`UserFunction`, `ActiveFunction`, etc.) are marked `@runtime_checkable` so beartype's isinstance checks succeed. - Strategy bumped to `BeartypeStrategy.On` for linear-time container validation (cheap at construction sites). - 8 tests in `test_model.py` / `test_regime.py` updated from message-text matching to parameter-name matching. Co-Authored-By: Claude Opus 4.7 --- src/lcm/_beartype_conf.py | 8 +++- src/lcm/regime.py | 14 +++--- src/lcm/regime_building/validation.py | 67 +++++++++------------------ src/lcm/typing.py | 15 +++++- tests/test_model.py | 35 ++++---------- tests/test_regime.py | 7 +-- 6 files changed, 60 insertions(+), 86 deletions(-) diff --git a/src/lcm/_beartype_conf.py b/src/lcm/_beartype_conf.py index 9740aae80..23b3d6112 100644 --- a/src/lcm/_beartype_conf.py +++ b/src/lcm/_beartype_conf.py @@ -6,7 +6,7 @@ """ -from beartype import BeartypeConf +from beartype import BeartypeConf, BeartypeStrategy from lcm.exceptions import ( CategoricalDefinitionError, @@ -18,7 +18,11 @@ def _conf(exc: type[Exception]) -> BeartypeConf: - return BeartypeConf(violation_param_type=exc) + # Full O(n) container validation so every bad entry in a mapping/sequence + # gets reported, not just one sampled element. The decorated entry points + # are called rarely (construction, solve, simulate), so per-call cost is + # invisible. + return BeartypeConf(violation_param_type=exc, strategy=BeartypeStrategy.On) # Used on `Regime` and `MarkovTransition`. diff --git a/src/lcm/regime.py b/src/lcm/regime.py index 6b4424a96..13a8734ec 100644 --- a/src/lcm/regime.py +++ b/src/lcm/regime.py @@ -5,6 +5,9 @@ from types import MappingProxyType from typing import Any, Literal, TypeAliasType, cast, overload +from beartype import beartype + +from lcm._beartype_conf import REGIME_CONF from lcm.exceptions import RegimeInitializationError from lcm.grids import DiscreteGrid, Grid from lcm.interfaces import SolveSimulateFunctionPair @@ -24,6 +27,7 @@ ) +@beartype(conf=REGIME_CONF) @dataclass(frozen=True) class MarkovTransition: """Wrapper marking a transition function as stochastic (Markov). @@ -48,11 +52,6 @@ class MarkovTransition: """The transition function returning a probability distribution.""" def __post_init__(self) -> None: - if not callable(self.func): - raise RegimeInitializationError( - f"MarkovTransition requires a callable, " - f"but got {type(self.func).__name__}: {self.func!r}" - ) # Copy __wrapped__ and __annotations__ from the wrapped function so # that inspect.signature and dags see the original signature. We use # object.__setattr__ because the dataclass is frozen. @@ -104,6 +103,7 @@ def __call__( return kwargs[self._state_name] +@beartype(conf=REGIME_CONF) @dataclass(frozen=True, kw_only=True) class Regime: """A user regime which can be processed into an internal regime. @@ -184,11 +184,11 @@ def stochastic_regime_transition(self) -> bool: def __post_init__(self) -> None: from lcm.regime_building.validation import ( # noqa: PLC0415 - validate_attribute_types, validate_logical_consistency, + validate_mapping_contents, ) - validate_attribute_types(self) + validate_mapping_contents(self) validate_logical_consistency(self) def make_immutable(name: str) -> None: diff --git a/src/lcm/regime_building/validation.py b/src/lcm/regime_building/validation.py index 92656692d..e7bc57867 100644 --- a/src/lcm/regime_building/validation.py +++ b/src/lcm/regime_building/validation.py @@ -30,53 +30,32 @@ ) -def validate_attribute_types(regime: Regime) -> None: # noqa: C901, PLR0912 - """Validate the types of the regime attributes.""" - error_messages = [] - - # Validate types of states and actions - for attr_name in ("actions", "states"): - attr = getattr(regime, attr_name) - if isinstance(attr, Mapping): - for k, v in attr.items(): - if not isinstance(k, str): - error_messages.append(f"{attr_name} key {k} must be a string.") - if not isinstance(v, Grid): - error_messages.append(f"{attr_name} value {v} must be an LCM grid.") - else: - error_messages.append(f"{attr_name} must be a mapping.") +def validate_mapping_contents(regime: Regime) -> None: + """Exhaustively check key/value types of `regime`'s mapping fields. - # Validate types of function mappings (constraints and functions) - function_collections = [ - regime.constraints, - regime.functions, - ] - for func_collection in function_collections: - if isinstance(func_collection, Mapping): - for k, v in func_collection.items(): - if not isinstance(k, str): - error_messages.append( - f"function keys must be a strings, but is {k}." - ) - if not callable(v) and not isinstance(v, SolveSimulateFunctionPair): - error_messages.append( - f"function values must be a callable, but is {v}." - ) - else: - error_messages.append( - "constraints and functions must each be a mapping of callables." - ) + Beartype on `Regime` catches top-level type mismatches and a sampled + Mapping entry, but does not deep-check every key/value of a Mapping + parameter — especially when the value type is a `Callable`/`Protocol`, + which beartype skips entirely. This function fills that gap by + iterating each entry and reporting all type violations together via + the standard `error_messages` aggregator. - # Validate state_transitions is a mapping - if not isinstance(regime.state_transitions, Mapping): - error_messages.append("state_transitions must be a mapping.") + """ + error_messages: list[str] = [] - # Validate regime transition is callable or None - if not regime.terminal and not callable(regime.transition): - error_messages.append( - "transition must be callable or None, " - f"but is {type(regime.transition).__name__}." - ) + for attr_name in ("states", "actions"): + for k, v in getattr(regime, attr_name).items(): + if not isinstance(k, str): + error_messages.append(f"{attr_name} key {k!r} must be a string.") + if not isinstance(v, Grid): + error_messages.append(f"{attr_name} value {v!r} must be an LCM grid.") + + for attr_name in ("functions", "constraints"): + for k, v in getattr(regime, attr_name).items(): + if not isinstance(k, str): + error_messages.append(f"{attr_name} key {k!r} must be a string.") + if not callable(v) and not isinstance(v, SolveSimulateFunctionPair): + error_messages.append(f"{attr_name} value {v!r} must be a callable.") if error_messages: msg = format_messages(error_messages) diff --git a/src/lcm/typing.py b/src/lcm/typing.py index f93b70e52..3108dec7f 100644 --- a/src/lcm/typing.py +++ b/src/lcm/typing.py @@ -1,6 +1,6 @@ from collections.abc import Mapping from types import MappingProxyType -from typing import Any, Protocol +from typing import Any, Protocol, runtime_checkable import pandas as pd from jax import Array @@ -72,16 +72,19 @@ type PeriodToRegimeToVArr = MappingProxyType[int, MappingProxyType[RegimeName, FloatND]] +@runtime_checkable class UserFunction(Protocol): """A function provided by the user. - Only used for type checking. + Used for both type checking and beartype runtime checks on perimeter + constructors. Any callable satisfies this protocol structurally. """ def __call__(self, *args: Any, **kwargs: Any) -> Any: ... # noqa: ANN401 +@runtime_checkable class InternalUserFunction(Protocol): """The internal representation of a function provided by the user. @@ -96,6 +99,7 @@ def __call__( ) -> Array: ... +@runtime_checkable class RegimeTransitionFunction(Protocol): """The regime transition function provided by the user. @@ -112,6 +116,7 @@ def __call__( ) -> Float1D: ... +@runtime_checkable class VmappedRegimeTransitionFunction(Protocol): """The vmapped regime transition function. @@ -128,6 +133,7 @@ def __call__( ) -> FloatND: ... +@runtime_checkable class QAndFFunction(Protocol): """The function that computes Q and F. @@ -145,6 +151,7 @@ def __call__( ) -> tuple[FloatND, BoolND]: ... +@runtime_checkable class MaxQOverAFunction(Protocol): """The function that maximizes Q over all actions. @@ -162,6 +169,7 @@ def __call__( ) -> Array: ... +@runtime_checkable class ArgmaxQOverAFunction(Protocol): """The function that finds the argmax of Q over all actions. @@ -179,6 +187,7 @@ def __call__( ) -> tuple[Array, Array]: ... +@runtime_checkable class StochasticNextFunction(Protocol): """The function that simulates the next state of a stochastic variable. @@ -189,6 +198,7 @@ class StochasticNextFunction(Protocol): def __call__(self, **kwargs: Array) -> Array: ... +@runtime_checkable class NextStateSimulationFunction(Protocol): """The function that computes the next states during the simulation. @@ -205,6 +215,7 @@ def __call__( ]: ... +@runtime_checkable class ActiveFunction(Protocol): """Function that determines if a regime is active at a given age. diff --git a/tests/test_model.py b/tests/test_model.py index 8897fe431..53f5ad910 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -23,7 +23,7 @@ def test_regime_invalid_states(): """Regime rejects non-dict states argument.""" - with pytest.raises(RegimeInitializationError, match="states must be a mapping"): + with pytest.raises(RegimeInitializationError, match="states"): Regime( transition=lambda: 0, states="health", # ty: ignore[invalid-argument-type] @@ -35,7 +35,7 @@ def test_regime_invalid_states(): def test_regime_invalid_actions(): """Regime rejects non-dict actions argument.""" - with pytest.raises(RegimeInitializationError, match="actions must be a mapping"): + with pytest.raises(RegimeInitializationError, match="actions"): Regime( transition=lambda: 0, states={}, @@ -47,9 +47,7 @@ def test_regime_invalid_actions(): def test_regime_invalid_functions(): """Regime rejects non-dict functions argument.""" - with pytest.raises( - RegimeInitializationError, match="functions must each be a mapping" - ): + with pytest.raises(RegimeInitializationError, match="functions"): Regime( transition=lambda: 0, states={}, @@ -61,10 +59,7 @@ def test_regime_invalid_functions(): def test_regime_invalid_functions_values(): """Regime rejects non-callable function values.""" - with pytest.raises( - RegimeInitializationError, - match=r"function values must be a callable, but is 0.", - ): + with pytest.raises(RegimeInitializationError, match="functions"): Regime( states={}, actions={}, @@ -76,9 +71,7 @@ def test_regime_invalid_functions_values(): def test_regime_invalid_functions_keys(): """Regime rejects non-string function keys.""" - with pytest.raises( - RegimeInitializationError, match=r"function keys must be a strings, but is 0." - ): + with pytest.raises(RegimeInitializationError, match="functions"): Regime( states={}, actions={}, @@ -90,9 +83,7 @@ def test_regime_invalid_functions_keys(): def test_regime_invalid_actions_values(): """Regime rejects non-grid action values.""" - with pytest.raises( - RegimeInitializationError, match=r"actions value 0 must be an LCM grid." - ): + with pytest.raises(RegimeInitializationError, match="actions"): Regime( states={}, actions={"exercise": 0}, # ty: ignore[invalid-argument-type] @@ -104,9 +95,7 @@ def test_regime_invalid_actions_values(): def test_regime_invalid_states_values(): """Regime rejects non-grid state values.""" - with pytest.raises( - RegimeInitializationError, match=r"states value 0 must be an LCM grid." - ): + with pytest.raises(RegimeInitializationError, match="states"): Regime( states={"health": 0}, # ty: ignore[invalid-argument-type] actions={}, @@ -118,10 +107,7 @@ def test_regime_invalid_states_values(): def test_regime_invalid_utility(): """Regime rejects non-callable utility argument.""" - with pytest.raises( - RegimeInitializationError, - match=(r"function values must be a callable, but is 0"), - ): + with pytest.raises(RegimeInitializationError, match="functions"): Regime( states={}, actions={}, @@ -151,10 +137,7 @@ def test_regime_overlapping_states_actions(binary_category_class): def test_regime_transition_must_be_callable(): """Regime rejects non-callable transition.""" - with pytest.raises( - RegimeInitializationError, - match="transition must be callable or None", - ): + with pytest.raises(RegimeInitializationError, match="transition"): Regime( states={}, actions={}, diff --git a/tests/test_regime.py b/tests/test_regime.py index 94b9e768b..723d718d9 100644 --- a/tests/test_regime.py +++ b/tests/test_regime.py @@ -167,7 +167,7 @@ def test_regime_requires_utility_in_functions(): def test_active_validation_rejects_non_callable(): """Active attribute must be a callable.""" - with pytest.raises(RegimeInitializationError, match="must be a callable"): + with pytest.raises(RegimeInitializationError, match="active"): Regime( transition=next_wealth, functions={"utility": utility}, @@ -179,10 +179,7 @@ def test_active_validation_rejects_non_callable(): def test_markov_transition_rejects_non_callable(): - with pytest.raises( - RegimeInitializationError, - match="MarkovTransition requires a callable", - ): + with pytest.raises(RegimeInitializationError, match="func"): MarkovTransition(func=42) # ty: ignore[invalid-argument-type] From 4799dffab97a7a083153be784608369144ddc5de Mon Sep 17 00:00:00 2001 From: Hans-Martin von Gaudecker Date: Wed, 13 May 2026 14:09:44 +0200 Subject: [PATCH 18/77] Decorate Model with @beartype; trim type-check block of validate_model_inputs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `@beartype(conf=MODEL_CONF)` on `Model.__init__` raises `ModelInitializationError` on parameter-type violations. - `validate_model_inputs` drops the `isinstance(n_periods, int)` check (n_periods is structurally an `int` from `ages.n_periods`) and the "All items in regimes must be instances of lcm.Regime" early exit (beartype on `regimes: Mapping[RegimeName, Regime]` covers it). The value and cross-field checks below (n_periods >= 2, regime name separator, terminal/non-terminal counts, etc.) remain. - `_ParamsLeaf` widened to include `int` — beartype caught an annotation drift where the runtime accepted ints but the type declared only `bool | float | ...`. - `test_n_subjects_validation_rejects_non_int` expects `ModelInitializationError` instead of `TypeError`, aligning with the rest of `Model.__init__`'s error contract. Co-Authored-By: Claude Opus 4.7 --- src/lcm/model.py | 3 +++ src/lcm/model_processing.py | 19 +++++++++---------- src/lcm/typing.py | 2 +- tests/simulation/test_simulate_aot.py | 5 +++-- 4 files changed, 16 insertions(+), 13 deletions(-) diff --git a/src/lcm/model.py b/src/lcm/model.py index 01b76e01f..3450a6d68 100644 --- a/src/lcm/model.py +++ b/src/lcm/model.py @@ -8,8 +8,10 @@ from types import MappingProxyType import pandas as pd +from beartype import beartype from jax import Array +from lcm._beartype_conf import MODEL_CONF from lcm.ages import AgeGrid from lcm.exceptions import InvalidValueFunctionError, ModelInitializationError from lcm.grids import DiscreteGrid @@ -128,6 +130,7 @@ class Model: simulate() calls don't serialise on logging I/O. """ + @beartype(conf=MODEL_CONF) def __init__( self, *, diff --git a/src/lcm/model_processing.py b/src/lcm/model_processing.py index 4f149736a..6df0fb5f9 100644 --- a/src/lcm/model_processing.py +++ b/src/lcm/model_processing.py @@ -151,20 +151,19 @@ def validate_model_inputs( regime_id_class: type, n_subjects: int | None = None, ) -> None: - """Validate model constructor inputs.""" - _fail_if_invalid_n_subjects(n_subjects=n_subjects) + """Validate model constructor inputs. - # Early exit if regimes are not lcm.Regime instances - if not all(isinstance(regime, Regime) for regime in regimes.values()): - raise ModelInitializationError( - "All items in regimes must be instances of lcm.Regime." - ) + `n_periods` is derived from `Model.__init__`'s `ages: AgeGrid` and + `regimes` is typed via beartype on `Model.__init__`; both reach this + function with their declared types. This function focuses on value and + cross-field rules. + + """ + _fail_if_invalid_n_subjects(n_subjects=n_subjects) error_messages: list[str] = [] - if not isinstance(n_periods, int): - error_messages.append("n_periods must be an integer.") - elif n_periods <= 1: + if n_periods <= 1: error_messages.append("n_periods must be at least 2.") if not regimes: diff --git a/src/lcm/typing.py b/src/lcm/typing.py index 3108dec7f..6a340097f 100644 --- a/src/lcm/typing.py +++ b/src/lcm/typing.py @@ -49,7 +49,7 @@ type StatesPerRegime = MappingProxyType[RegimeName, RegimeStates] -type _ParamsLeaf = bool | float | Array | pd.Series | MappingLeaf | SequenceLeaf +type _ParamsLeaf = bool | int | float | Array | pd.Series | MappingLeaf | SequenceLeaf type UserParams = Mapping[ str, _ParamsLeaf | Mapping[str, _ParamsLeaf | Mapping[str, _ParamsLeaf]], diff --git a/tests/simulation/test_simulate_aot.py b/tests/simulation/test_simulate_aot.py index 698256e4e..f778722e9 100644 --- a/tests/simulation/test_simulate_aot.py +++ b/tests/simulation/test_simulate_aot.py @@ -21,6 +21,7 @@ from lcm import Model from lcm.ages import AgeGrid +from lcm.exceptions import ModelInitializationError from tests.test_models.deterministic.regression import ( RegimeId, dead, @@ -63,8 +64,8 @@ def test_n_subjects_validation_rejects_non_positive(invalid: int) -> None: def test_n_subjects_validation_rejects_non_int() -> None: - """`Model(n_subjects=1.5)` raises `TypeError`.""" - with pytest.raises(TypeError, match="n_subjects"): + """`Model(n_subjects=1.5)` raises `ModelInitializationError`.""" + with pytest.raises(ModelInitializationError, match="n_subjects"): _build_test_model(n_periods=3, n_subjects=1.5) # ty: ignore[invalid-argument-type] From 24029ecfde8f9b7c4dad4811af1974189e61f47f Mon Sep 17 00:00:00 2001 From: Hans-Martin von Gaudecker Date: Wed, 13 May 2026 14:28:45 +0200 Subject: [PATCH 19/77] Decorate grids + shocks + categorical with @beartype_init MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `@beartype_init(GRID_CONF)` on 6 grids and 7 shock grids checks parameter types at construction time and raises `GridInitializationError` while leaving each class's other methods unwrapped. Bare `@beartype` on a class wraps every method, which surfaces annotation drift in helpers like `compute_gridpoints(**kwargs: float)` where runtime kwargs are JAX arrays. - `_beartype_conf.beartype_init` is the class decorator that only wraps `__init__`. - Decorated classes: `DiscreteGrid`, `LinSpacedGrid`, `LogSpacedGrid`, `IrregSpacedGrid`, `PiecewiseLinSpacedGrid`, `PiecewiseLogSpacedGrid`; `Uniform`, `Normal`, `LogNormal`, `NormalMixture`, `Tauchen`, `Rouwenhorst`, `TauchenNormalMixture`. `AgeGrid.__init__` (a plain method, not a dataclass) gets a direct `@beartype(conf=GRID_CONF)`. - `categorical` decorator factory gets `@beartype(conf=CATEGORICAL_CONF)`. - `BeartypeConf` flipped to `is_pep484_tower=True` so `int` satisfies `float`-typed parameters, matching Python's numeric tower and ruff's PYI041 — without this, `LinSpacedGrid(start=1, ...)` failed because `1: int` is not `float` at the type level. - Updated 6 test sites in `tests/test_grids.py` from message-text matching to parameter-name matching, plus type from `TypeError` to `GridInitializationError` where applicable. Co-Authored-By: Claude Opus 4.7 --- src/lcm/_beartype_conf.py | 39 ++++++++++++++++++++++++++++++------ src/lcm/ages.py | 3 +++ src/lcm/grids/categorical.py | 3 +++ src/lcm/grids/continuous.py | 4 ++++ src/lcm/grids/discrete.py | 2 ++ src/lcm/grids/piecewise.py | 3 +++ src/lcm/shocks/ar1.py | 34 +++++++++++++++++-------------- src/lcm/shocks/iid.py | 33 +++++++++++++++++------------- tests/test_grids.py | 18 ++++++++--------- 9 files changed, 95 insertions(+), 44 deletions(-) diff --git a/src/lcm/_beartype_conf.py b/src/lcm/_beartype_conf.py index 23b3d6112..daf276120 100644 --- a/src/lcm/_beartype_conf.py +++ b/src/lcm/_beartype_conf.py @@ -6,7 +6,9 @@ """ -from beartype import BeartypeConf, BeartypeStrategy +from collections.abc import Callable + +from beartype import BeartypeConf, BeartypeStrategy, beartype from lcm.exceptions import ( CategoricalDefinitionError, @@ -18,11 +20,36 @@ def _conf(exc: type[Exception]) -> BeartypeConf: - # Full O(n) container validation so every bad entry in a mapping/sequence - # gets reported, not just one sampled element. The decorated entry points - # are called rarely (construction, solve, simulate), so per-call cost is - # invisible. - return BeartypeConf(violation_param_type=exc, strategy=BeartypeStrategy.On) + # `On` strategy: full O(n) container validation so every bad entry in a + # mapping/sequence is reported, not just one sampled element. The + # decorated entry points are called rarely (construction, solve, + # simulate), so per-call cost is invisible. + # `is_pep484_tower=True`: respect the PEP-484 numeric tower so `int` + # satisfies `float`-typed parameters (matches the implicit numeric + # conversion that Python and ruff's PYI041 both assume). + return BeartypeConf( + violation_param_type=exc, + strategy=BeartypeStrategy.On, + is_pep484_tower=True, + ) + + +def beartype_init[C](conf: BeartypeConf) -> Callable[[type[C]], type[C]]: + """Class decorator that beartype-checks `__init__` only. + + Bare `@beartype` on a class wraps every method, which surfaces + annotation drift in helpers like `compute_gridpoints(**kwargs: float)` + where runtime kwargs are actually JAX arrays. Restricting decoration + to `__init__` keeps the perimeter check (parameter types at + construction) without policing every method's runtime types. + + """ + + def deco(cls: type[C]) -> type[C]: + cls.__init__ = beartype(conf=conf)(cls.__init__) # ty: ignore[invalid-assignment] + return cls + + return deco # Used on `Regime` and `MarkovTransition`. diff --git a/src/lcm/ages.py b/src/lcm/ages.py index 11d2c88c7..0fc41141a 100644 --- a/src/lcm/ages.py +++ b/src/lcm/ages.py @@ -8,7 +8,9 @@ from typing import overload import jax.numpy as jnp +from beartype import beartype +from lcm._beartype_conf import GRID_CONF from lcm.exceptions import GridInitializationError, format_messages from lcm.typing import Float1D, Int1D @@ -50,6 +52,7 @@ def __init__( exact_values: Iterable[int | Fraction], ) -> None: ... + @beartype(conf=GRID_CONF) def __init__( self, *, diff --git a/src/lcm/grids/categorical.py b/src/lcm/grids/categorical.py index b45834424..618925b81 100644 --- a/src/lcm/grids/categorical.py +++ b/src/lcm/grids/categorical.py @@ -5,7 +5,9 @@ import jax import jax.numpy as jnp import pandas as pd +from beartype import beartype +from lcm._beartype_conf import CATEGORICAL_CONF from lcm.exceptions import ( CategoricalDefinitionError, GridInitializationError, @@ -15,6 +17,7 @@ from lcm.utils.containers import find_duplicates, get_field_names_and_values +@beartype(conf=CATEGORICAL_CONF) def categorical[T](*, ordered: bool) -> Callable[[type[T]], type[T]]: """Create a categorical class with auto-assigned `ScalarInt` values. diff --git a/src/lcm/grids/continuous.py b/src/lcm/grids/continuous.py index 839d4cefb..22fa7ce05 100644 --- a/src/lcm/grids/continuous.py +++ b/src/lcm/grids/continuous.py @@ -7,6 +7,7 @@ import jax.numpy as jnp from jax import Array +from lcm._beartype_conf import GRID_CONF, beartype_init from lcm.dtypes import canonical_float_dtype from lcm.exceptions import GridInitializationError, format_messages from lcm.grids import coordinates as grid_coordinates @@ -104,6 +105,7 @@ def replace(self, **kwargs: float) -> UniformContinuousGrid: ) from e +@beartype_init(GRID_CONF) class LinSpacedGrid(UniformContinuousGrid): """A linearly spaced grid of continuous values. @@ -133,6 +135,7 @@ def get_coordinate(self, value: ScalarFloat | Array) -> ScalarFloat | Array: ) +@beartype_init(GRID_CONF) class LogSpacedGrid(UniformContinuousGrid): """A logarithmically spaced grid of continuous values. @@ -215,6 +218,7 @@ def _init_uniform_grid( object.__setattr__(grid, "batch_size", batch_size) +@beartype_init(GRID_CONF) @dataclass(frozen=True, kw_only=True, init=False) class IrregSpacedGrid(ContinuousGrid): """A grid of continuous values at irregular (user-specified) points. diff --git a/src/lcm/grids/discrete.py b/src/lcm/grids/discrete.py index 573c9d9b4..a96c32952 100644 --- a/src/lcm/grids/discrete.py +++ b/src/lcm/grids/discrete.py @@ -1,11 +1,13 @@ import jax.numpy as jnp +from lcm._beartype_conf import GRID_CONF, beartype_init from lcm.grids.base import Grid from lcm.grids.categorical import _validate_discrete_grid from lcm.typing import Int1D from lcm.utils.containers import get_field_names_and_values +@beartype_init(GRID_CONF) class DiscreteGrid(Grid): """A discrete grid defining the outcome space of a categorical variable. diff --git a/src/lcm/grids/piecewise.py b/src/lcm/grids/piecewise.py index 43ff2a91e..97e3f26d5 100644 --- a/src/lcm/grids/piecewise.py +++ b/src/lcm/grids/piecewise.py @@ -6,6 +6,7 @@ import portion from jax import Array +from lcm._beartype_conf import GRID_CONF, beartype_init from lcm.exceptions import GridInitializationError, format_messages from lcm.grids import coordinates as grid_coordinates from lcm.grids.continuous import ContinuousGrid @@ -44,6 +45,7 @@ def __init__( object.__setattr__(self, "n_points", jnp.int32(n_points)) +@beartype_init(GRID_CONF) @dataclass(frozen=True, kw_only=True) class PiecewiseLinSpacedGrid(ContinuousGrid): """A piecewise linearly spaced grid with multiple segments. @@ -111,6 +113,7 @@ def get_coordinate(self, value: ScalarFloat | Array) -> ScalarFloat | Array: return self._cumulative_offsets[piece_idx] + local_coord +@beartype_init(GRID_CONF) @dataclass(frozen=True, kw_only=True) class PiecewiseLogSpacedGrid(ContinuousGrid): """A piecewise logarithmically spaced grid with multiple segments. diff --git a/src/lcm/shocks/ar1.py b/src/lcm/shocks/ar1.py index a42b39455..5ca8227ec 100644 --- a/src/lcm/shocks/ar1.py +++ b/src/lcm/shocks/ar1.py @@ -7,6 +7,7 @@ import jax.numpy as jnp from jax.scipy.stats.norm import cdf +from lcm._beartype_conf import GRID_CONF, beartype_init from lcm.shocks._base import ( _gauss_hermite_normal, _mixture_cdf, @@ -29,6 +30,7 @@ def draw_shock( ) -> Float1D: ... +@beartype_init(GRID_CONF) @dataclass(frozen=True, kw_only=True) class Tauchen(_ShockGridAR1): r"""AR(1) shock discretized via Tauchen (1986). @@ -49,16 +51,16 @@ class Tauchen(_ShockGridAR1): gauss_hermite: bool """Use Gauss-Hermite quadrature nodes and weights.""" - rho: float | None = None + rho: float | int | None = None """Persistence parameter of the AR(1) process.""" - sigma: float | None = None + sigma: float | int | None = None """Standard deviation of the innovation.""" - mu: float | None = None + mu: float | int | None = None """Intercept (drift) of the AR(1) process.""" - n_std: float | None = None + n_std: float | int | None = None """Number of standard deviations for the grid boundary.""" def __post_init__(self) -> None: @@ -128,6 +130,7 @@ def draw_shock( ) +@beartype_init(GRID_CONF) @dataclass(frozen=True, kw_only=True) class Rouwenhorst(_ShockGridAR1): r"""AR(1) shock discretized via Rouwenhorst (1995). @@ -140,13 +143,13 @@ class Rouwenhorst(_ShockGridAR1): """ - rho: float | None = None + rho: float | int | None = None """Persistence parameter of the AR(1) process.""" - sigma: float | None = None + sigma: float | int | None = None """Standard deviation of the innovation.""" - mu: float | None = None + mu: float | int | None = None """Intercept (drift) of the AR(1) process.""" def compute_gridpoints(self, **kwargs: float) -> Float1D: @@ -196,6 +199,7 @@ def draw_shock( ) +@beartype_init(GRID_CONF) @dataclass(frozen=True, kw_only=True) class TauchenNormalMixture(_ShockGridAR1): r"""AR(1) shock with mixture-of-normals innovations, discretized via Tauchen. @@ -211,28 +215,28 @@ class TauchenNormalMixture(_ShockGridAR1): """ - rho: float | None = None + rho: float | int | None = None """Persistence parameter of the AR(1) process.""" - mu: float | None = None + mu: float | int | None = None """Intercept (drift) of the AR(1) process.""" - n_std: float | None = None + n_std: float | int | None = None """Number of unconditional standard deviations for the grid boundary.""" - p1: float | None = None + p1: float | int | None = None """Probability of the first mixture component.""" - mu1: float | None = None + mu1: float | int | None = None """Mean of the first mixture component.""" - sigma1: float | None = None + sigma1: float | int | None = None """Standard deviation of the first mixture component.""" - mu2: float | None = None + mu2: float | int | None = None """Mean of the second mixture component.""" - sigma2: float | None = None + sigma2: float | int | None = None """Standard deviation of the second mixture component.""" @staticmethod diff --git a/src/lcm/shocks/iid.py b/src/lcm/shocks/iid.py index 506057389..b18265d6f 100644 --- a/src/lcm/shocks/iid.py +++ b/src/lcm/shocks/iid.py @@ -6,6 +6,7 @@ import jax.numpy as jnp from jax.scipy.stats.norm import cdf +from lcm._beartype_conf import GRID_CONF, beartype_init from lcm.shocks._base import ( _gauss_hermite_normal, _mixture_cdf, @@ -27,6 +28,7 @@ def draw_shock( ) -> Float1D: ... +@beartype_init(GRID_CONF) @dataclass(frozen=True, kw_only=True) class Uniform(_ShockGridIID): r"""Discretized iid uniform shock: $U(\text{start}, \text{stop})$. @@ -36,10 +38,10 @@ class Uniform(_ShockGridIID): """ - start: float | None = None + start: float | int | None = None """Lower bound of the uniform distribution.""" - stop: float | None = None + stop: float | int | None = None """Upper bound of the uniform distribution.""" def compute_gridpoints(self, **kwargs: float) -> Float1D: @@ -61,6 +63,7 @@ def draw_shock( ) +@beartype_init(GRID_CONF) @dataclass(frozen=True, kw_only=True) class Normal(_ShockGridIID): r"""Discretized iid normal shock: $N(\mu_\varepsilon, \sigma_\varepsilon^2)$. @@ -75,13 +78,13 @@ class Normal(_ShockGridIID): gauss_hermite: bool """Use Gauss-Hermite quadrature nodes and weights.""" - mu: float | None = None + mu: float | int | None = None """Mean of the shock distribution.""" - sigma: float | None = None + sigma: float | int | None = None """Standard deviation of the shock distribution.""" - n_std: float | None = None + n_std: float | int | None = None """Number of standard deviations from the mean to the grid boundary.""" def __post_init__(self) -> None: @@ -135,6 +138,7 @@ def draw_shock( return params["mu"] + params["sigma"] * jax.random.normal(key=key) +@beartype_init(GRID_CONF) @dataclass(frozen=True, kw_only=True) class LogNormal(_ShockGridIID): r"""Discretized iid log-normal shock: $\ln X \sim N(\mu, \sigma^2)$.""" @@ -142,13 +146,13 @@ class LogNormal(_ShockGridIID): gauss_hermite: bool """Use Gauss-Hermite quadrature nodes and weights.""" - mu: float | None = None + mu: float | int | None = None """Mean of the underlying normal distribution ($E[\\ln X]$).""" - sigma: float | None = None + sigma: float | int | None = None """Standard deviation of the underlying normal distribution.""" - n_std: float | None = None + n_std: float | int | None = None """Number of standard deviations in log-space for the grid boundary.""" def __post_init__(self) -> None: @@ -200,6 +204,7 @@ def draw_shock( return jnp.exp(params["mu"] + params["sigma"] * jax.random.normal(key=key)) +@beartype_init(GRID_CONF) @dataclass(frozen=True, kw_only=True) class NormalMixture(_ShockGridIID): r"""Discretized IID normal-mixture shock. @@ -215,22 +220,22 @@ class NormalMixture(_ShockGridIID): """ - n_std: float | None = None + n_std: float | int | None = None """Number of mixture standard deviations for the grid boundary.""" - p1: float | None = None + p1: float | int | None = None """Probability of the first mixture component.""" - mu1: float | None = None + mu1: float | int | None = None """Mean of the first mixture component.""" - sigma1: float | None = None + sigma1: float | int | None = None """Standard deviation of the first mixture component.""" - mu2: float | None = None + mu2: float | int | None = None """Mean of the second mixture component.""" - sigma2: float | None = None + sigma2: float | int | None = None """Standard deviation of the second mixture component.""" def compute_gridpoints(self, **kwargs: float) -> Float1D: diff --git a/tests/test_grids.py b/tests/test_grids.py index ec652503c..560b3e87e 100644 --- a/tests/test_grids.py +++ b/tests/test_grids.py @@ -252,20 +252,20 @@ class Cat: def test_lin_spaced_grid_rejects_non_numeric_start(): - """Non-numeric `start` is rejected at the boundary cast.""" - with pytest.raises((TypeError, ValueError)): + """Non-numeric `start` raises `GridInitializationError`.""" + with pytest.raises(GridInitializationError, match="start"): LinSpacedGrid(start="a", stop=1, n_points=10) # ty: ignore[invalid-argument-type] def test_lin_spaced_grid_rejects_non_numeric_stop(): - """Non-numeric `stop` is rejected at the boundary cast.""" - with pytest.raises((TypeError, ValueError)): + """Non-numeric `stop` raises `GridInitializationError`.""" + with pytest.raises(GridInitializationError, match="stop"): LinSpacedGrid(start=1, stop="a", n_points=10) # ty: ignore[invalid-argument-type] def test_lin_spaced_grid_rejects_non_numeric_n_points(): - """Non-numeric `n_points` is rejected at the boundary cast.""" - with pytest.raises((TypeError, ValueError)): + """Non-numeric `n_points` raises `GridInitializationError`.""" + with pytest.raises(GridInitializationError, match="n_points"): LinSpacedGrid(start=1, stop=2, n_points="a") # ty: ignore[invalid-argument-type] @@ -363,7 +363,7 @@ def test_irreg_spaced_grid_invalid_too_few_points(): def test_irreg_spaced_grid_invalid_non_numeric(): - with pytest.raises(GridInitializationError, match="must be int or float"): + with pytest.raises(GridInitializationError, match="points"): IrregSpacedGrid(points=(1.0, "a", 3.0)) # ty: ignore[invalid-argument-type] @@ -559,13 +559,13 @@ def test_piecewise_lin_spaced_grid_invalid_numeric_gap(): def test_piecewise_lin_spaced_grid_invalid_not_tuple(): """Pieces must be a tuple.""" - with pytest.raises(GridInitializationError, match="must be a tuple"): + with pytest.raises(GridInitializationError, match="pieces"): PiecewiseLinSpacedGrid(pieces=[Piece(interval="[1, 4]", n_points=3)]) # ty: ignore[invalid-argument-type] def test_piecewise_lin_spaced_grid_invalid_not_piece(): """Each element in pieces must be a Piece object.""" - with pytest.raises(GridInitializationError, match="must be a Piece"): + with pytest.raises(GridInitializationError, match="pieces"): PiecewiseLinSpacedGrid( pieces=({"interval": "[1, 4]", "n_points": 3},) # ty: ignore[invalid-argument-type] ) From b0f9fb86d22a8c4101cde69bf84a55dea322861f Mon Sep 17 00:00:00 2001 From: Hans-Martin von Gaudecker Date: Wed, 13 May 2026 14:31:25 +0200 Subject: [PATCH 20/77] Decorate Model.solve + Model.simulate with @beartype `@beartype(conf=PARAMS_CONF)` on the two runtime entry points catches parameter-type violations (bad `params` structure, wrong `initial_conditions` types, malformed `period_to_regime_to_V_arr`) and raises `InvalidParamsError`. Per-call cost is invisible at the construction/run cadence these methods see. Co-Authored-By: Claude Opus 4.7 --- src/lcm/model.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/lcm/model.py b/src/lcm/model.py index 3450a6d68..efcb9ce75 100644 --- a/src/lcm/model.py +++ b/src/lcm/model.py @@ -11,7 +11,7 @@ from beartype import beartype from jax import Array -from lcm._beartype_conf import MODEL_CONF +from lcm._beartype_conf import MODEL_CONF, PARAMS_CONF from lcm.ages import AgeGrid from lcm.exceptions import InvalidValueFunctionError, ModelInitializationError from lcm.grids import DiscreteGrid @@ -242,6 +242,7 @@ def get_params_template(self) -> UserFacingParamsTemplate: for regime, funcs in mutable.items() } + @beartype(conf=PARAMS_CONF) def solve( self, *, @@ -371,6 +372,7 @@ def _resolve_simulate_internal_regimes( with self._simulate_compile_lock: return self._simulate_compile_cache[self.n_subjects] + @beartype(conf=PARAMS_CONF) def simulate( self, *, From ca69bbddae63faf6a9392d59167b1bee1f02a2fc Mon Sep 17 00:00:00 2001 From: mj023 Date: Wed, 13 May 2026 15:59:18 +0200 Subject: [PATCH 21/77] Dont run dist code if no grid distributed --- src/lcm/simulation/initial_conditions.py | 23 +++--- src/lcm/solution/solve_brute.py | 97 ++++++++++++++++-------- tests/solution/test_solve_brute.py | 6 +- tests/test_distributed.py | 8 +- 4 files changed, 87 insertions(+), 47 deletions(-) diff --git a/src/lcm/simulation/initial_conditions.py b/src/lcm/simulation/initial_conditions.py index fa2e9f5ca..ab044387e 100644 --- a/src/lcm/simulation/initial_conditions.py +++ b/src/lcm/simulation/initial_conditions.py @@ -52,7 +52,9 @@ def build_initial_states( """Build the regime-keyed state carrier from user-provided initial states. For each regime, copies provided states and fills missing ones with - `jnp.nan` (continuous) or `MISSING_CAT_CODE` (discrete). + `jnp.nan` (continuous) or `MISSING_CAT_CODE` (discrete). If a state has been + declared as distributed, the initial states will also be distributed over the + available devices. Args: initial_states: Mapping of state names to arrays. @@ -71,16 +73,17 @@ def build_initial_states( # Logic for distribution of subjects over devices distributed = any(grid.distributed for grid in internal_regime.grids.values()) devices = jax.devices() - if distributed and n_subjects % len(devices) != 0: - raise PyLCMError( - "When using distributed grids, the number of subjects during the" - " simulation needs to be a multiple of the available devices. Subjects:" - f" {n_subjects} Available Devices: {len(devices)}" + if distributed: + if n_subjects % len(devices) != 0: + raise PyLCMError( + "When using distributed grids, the number of subjects during the" + " simulation needs to be a multiple of the available devices. " + f"Subjects: {n_subjects} Available Devices: {len(devices)}" + ) + mesh = jax.make_mesh( + (len(devices),), ("X"), (jax.sharding.AxisType.Auto,), devices=devices ) - mesh = jax.make_mesh( - (len(devices),), ("X"), (jax.sharding.AxisType.Auto,), devices=devices - ) - sharding = jax.NamedSharding(mesh=mesh, spec=jax.P("X")) + sharding = jax.NamedSharding(mesh=mesh, spec=jax.P("X")) for state_name in _get_regime_state_names(internal_regime): grid = internal_regime.grids[state_name] if isinstance(grid, DiscreteGrid): diff --git a/src/lcm/solution/solve_brute.py b/src/lcm/solution/solve_brute.py index 47d8c6eb4..3b971b391 100644 --- a/src/lcm/solution/solve_brute.py +++ b/src/lcm/solution/solve_brute.py @@ -49,15 +49,24 @@ def solve( # Compute V array shapes and build a consistent next_regime_to_V_arr # template. Using the same pytree structure (keys and shapes) across # all periods avoids JIT re-compilation from pytree mismatches. - regime_V_shapes = _get_regime_V_shapes_and_shardings( + regime_V_shapes = _get_regime_V_shapes( + internal_regimes=internal_regimes, + internal_params=internal_params, + ) + + regime_V_shardings = _get_regime_V_shardings( internal_regimes=internal_regimes, internal_params=internal_params, ) next_regime_to_V_arr = MappingProxyType( { - regime_name: jax.device_put(jnp.zeros(shape), sharding) - for regime_name, (shape, sharding) in regime_V_shapes.items() + regime_name: jax.device_put( + jnp.zeros(shape), regime_V_shardings[regime_name] + ) + if regime_V_shardings + else jnp.zeros(shape) + for regime_name, shape in regime_V_shapes.items() } ) @@ -386,11 +395,11 @@ def _func_dedup_key(*, func: Callable) -> Hashable: return id(func) -def _get_regime_V_shapes_and_shardings( +def _get_regime_V_shapes( *, internal_regimes: MappingProxyType[RegimeName, InternalRegime], internal_params: InternalParams, -) -> dict[RegimeName, tuple[tuple[int, ...], jax.NamedSharding]]: +) -> dict[RegimeName, tuple[int, ...]]: """Compute value function array shapes for all regimes. The V array has one dimension per state variable, with size equal to @@ -404,35 +413,59 @@ def _get_regime_V_shapes_and_shardings( Dict of regime names to V array shapes. """ - shapes_and_shardings: dict[ - RegimeName, tuple[tuple[int, ...], jax.NamedSharding] - ] = {} - avail_devices = jax.devices() + shapes: dict[RegimeName, tuple[int, ...]] = {} for regime_name, regime in internal_regimes.items(): state_action_space = regime.state_action_space( regime_params=internal_params[regime_name], ) - spec = [] - for name in state_action_space.states: - if name in state_action_space.distributed_states: - spec.append(name) - else: - spec.append(None) - shape = tuple(len(v) for v in state_action_space.states.values()) - dist_shape = tuple( - len(v) for v in state_action_space.distributed_states.values() - ) - mesh = jax.make_mesh( - dist_shape, - tuple(name for name in spec if name is not None), - axis_types=tuple( - jax.sharding.AxisType.Auto for i in range(len(dist_shape)) - ), - devices=avail_devices, - ) - sharding = jax.sharding.NamedSharding(mesh=mesh, spec=jax.P(*spec)) - shapes_and_shardings[regime_name] = (shape, sharding) - return shapes_and_shardings + shapes[regime_name] = tuple(len(v) for v in state_action_space.states.values()) + return shapes + + +def _get_regime_V_shardings( + *, + internal_regimes: MappingProxyType[RegimeName, InternalRegime], + internal_params: InternalParams, +) -> dict[RegimeName, jax.NamedSharding]: + """Compute value function array shardings for all regimes. + + The V-Array is distributed over all axes that belong to a distributed state. + + Args: + internal_regimes: The internal regimes. + internal_params: Regime parameters (needed for runtime grid shapes). + + Returns: + Dict of regime names to V array shardings. Empty if no state is distributed. + + """ + shardings: dict[RegimeName, jax.NamedSharding] = {} + avail_devices = jax.devices() + for regime_name, regime in internal_regimes.items(): + if any(grid.distributed for grid in regime.grids.values()): + state_action_space = regime.state_action_space( + regime_params=internal_params[regime_name], + ) + spec = [] + for name in state_action_space.states: + if name in state_action_space.distributed_states: + spec.append(name) + else: + spec.append(None) + dist_shape = tuple( + len(v) for v in state_action_space.distributed_states.values() + ) + mesh = jax.make_mesh( + dist_shape, + tuple(name for name in spec if name is not None), + axis_types=tuple( + jax.sharding.AxisType.Auto for i in range(len(dist_shape)) + ), + devices=avail_devices, + ) + sharding = jax.sharding.NamedSharding(mesh=mesh, spec=jax.P(*spec)) + shardings[regime_name] = sharding + return shardings @dataclass(frozen=True) @@ -583,13 +616,13 @@ def _reconstruct_next_regime_to_V_arr( the regime's state-action space at the supplied params — identical to what `_get_regime_V_shapes_and_shardings` saw during solve setup. """ - regime_V_shapes_and_shardings = _get_regime_V_shapes_and_shardings( + regime_V_shapes_and_shardings = _get_regime_V_shapes( internal_regimes=internal_regimes, internal_params=internal_params, ) later_periods = sorted(p for p in solution if p > period) result: dict[RegimeName, FloatND] = {} - for regime_name, (shape, _sharding) in regime_V_shapes_and_shardings.items(): + for regime_name, shape in regime_V_shapes_and_shardings.items(): rolled: FloatND | None = None for q in later_periods: if regime_name in solution[q]: diff --git a/tests/solution/test_solve_brute.py b/tests/solution/test_solve_brute.py index 15caab23f..629472b5d 100644 --- a/tests/solution/test_solve_brute.py +++ b/tests/solution/test_solve_brute.py @@ -6,11 +6,12 @@ from numpy.testing import assert_array_almost_equal as aaae from lcm.ages import AgeGrid +from lcm.grids import Grid from lcm.interfaces import StateActionSpace from lcm.regime_building.max_Q_over_a import get_max_Q_over_a from lcm.regime_building.ndimage import map_coordinates from lcm.solution.solve_brute import solve -from lcm.typing import MaxQOverAFunction +from lcm.typing import MaxQOverAFunction, StateOrActionName from lcm.utils.logging import get_logger @@ -36,6 +37,7 @@ class InternalRegimeMock: _base_state_action_space: StateActionSpace solve_functions: SolveFunctionsMock active_periods: list[int] + grids: MappingProxyType[StateOrActionName, Grid] def state_action_space(self, regime_params): # noqa: ARG002 return self._base_state_action_space @@ -127,6 +129,7 @@ def _Q_and_F( max_Q_over_a={0: max_Q_over_a, 1: max_Q_over_a}, ), active_periods=[0, 1], + grids=MappingProxyType({}), ) solution = solve( @@ -188,6 +191,7 @@ def _Q_and_F(a, c, b, d, next_regime_to_V_arr, period, age): # noqa: ARG001 max_Q_over_a={0: max_Q_over_a, 1: max_Q_over_a}, ), active_periods=[0, 1], + grids=MappingProxyType({}), ) got = solve( diff --git a/tests/test_distributed.py b/tests/test_distributed.py index 976d0aaef..b7de4919e 100644 --- a/tests/test_distributed.py +++ b/tests/test_distributed.py @@ -146,7 +146,7 @@ class Type: @_skip_pytest_parallel def test_solution_running_on_multiple_cpus(correct_distributed_model): - """Test that distribution over multiple CPU's works.""" + """Test that distribution over multiple CPU's works for solution.""" period_to_regime_to_V_arr = correct_distributed_model.solve( params={"discount_factor": 0.95}, @@ -157,7 +157,7 @@ def test_solution_running_on_multiple_cpus(correct_distributed_model): @_skip_pytest_parallel def test_simulation_running_on_multiple_cpus(correct_distributed_model): - """Test that distribution over multiple CPU's works.""" + """Test that distribution over multiple CPU's works for simulation.""" res = correct_distributed_model.simulate( params={"discount_factor": 0.95}, @@ -181,7 +181,7 @@ def test_simulation_running_on_multiple_cpus(correct_distributed_model): @_skip_pytest_parallel def test_solution_error_if_not_multiple(wrong_distributed_model): - """Test that distribution over multiple CPU's works.""" + """Test that solution throws error if too many states for num cpus.""" with pytest.raises(PyLCMError, match="smaller than the number"): wrong_distributed_model.solve( @@ -191,7 +191,7 @@ def test_solution_error_if_not_multiple(wrong_distributed_model): @_skip_pytest_parallel def test_simulation_error_if_not_multiple(correct_distributed_model): - """Test that distribution over multiple CPU's works.""" + """Test that simulation throws error if too many subjects for num cpus.""" with pytest.raises(PyLCMError, match="multiple"): correct_distributed_model.simulate( From 67e64d3a2739fc7cb7c0429ee50b034f5ae0a738 Mon Sep 17 00:00:00 2001 From: mj023 Date: Wed, 13 May 2026 16:19:31 +0200 Subject: [PATCH 22/77] Remove distributed states from state space --- src/lcm/grids/continuous.py | 2 +- src/lcm/interfaces.py | 3 --- src/lcm/solution/solve_brute.py | 9 ++++++--- src/lcm/state_action_space.py | 9 --------- tests/solution/test_solve_brute.py | 2 -- tests/test_nan_diagnostics.py | 1 - 6 files changed, 7 insertions(+), 19 deletions(-) diff --git a/src/lcm/grids/continuous.py b/src/lcm/grids/continuous.py index 1a0744bda..9a7678eb4 100644 --- a/src/lcm/grids/continuous.py +++ b/src/lcm/grids/continuous.py @@ -30,7 +30,7 @@ class ContinuousGrid(Grid): batch_size: int = 0 """Size of the batches that are looped over during the solution.""" distributed: bool = False - """Size of the batches that are looped over during the solution.""" + """Whether to distribute the grid over the available devices.""" @overload def get_coordinate(self, value: ScalarFloat) -> ScalarFloat: ... diff --git a/src/lcm/interfaces.py b/src/lcm/interfaces.py index 753886c3b..4ab7a5626 100644 --- a/src/lcm/interfaces.py +++ b/src/lcm/interfaces.py @@ -63,9 +63,6 @@ class StateActionSpace: states: MappingProxyType[StateName, ContinuousState | DiscreteState] """Immutable mapping of state variable names to their values.""" - distributed_states: MappingProxyType[StateName, ContinuousState | DiscreteState] - """Immutable mapping of distributed state variable names to their values.""" - discrete_actions: MappingProxyType[ActionName, DiscreteAction] """Immutable mapping of discrete action variable names to their values.""" diff --git a/src/lcm/solution/solve_brute.py b/src/lcm/solution/solve_brute.py index 3b971b391..0b86b722a 100644 --- a/src/lcm/solution/solve_brute.py +++ b/src/lcm/solution/solve_brute.py @@ -442,18 +442,21 @@ def _get_regime_V_shardings( shardings: dict[RegimeName, jax.NamedSharding] = {} avail_devices = jax.devices() for regime_name, regime in internal_regimes.items(): - if any(grid.distributed for grid in regime.grids.values()): + curr_regime_grids = regime.grids + if any(grid.distributed for grid in curr_regime_grids.values()): state_action_space = regime.state_action_space( regime_params=internal_params[regime_name], ) spec = [] for name in state_action_space.states: - if name in state_action_space.distributed_states: + if curr_regime_grids[name].distributed: spec.append(name) else: spec.append(None) dist_shape = tuple( - len(v) for v in state_action_space.distributed_states.values() + len(v) + for name, v in state_action_space.states.items() + if curr_regime_grids[name].distributed ) mesh = jax.make_mesh( dist_shape, diff --git a/src/lcm/state_action_space.py b/src/lcm/state_action_space.py index 9faa24165..9b23c18ec 100644 --- a/src/lcm/state_action_space.py +++ b/src/lcm/state_action_space.py @@ -37,20 +37,12 @@ def create_state_action_space( sn: _grid_to_jax_or_placeholder(grids[sn]) for sn in variable_info.query("is_state").index } - distributed_states = { - sn: _grid_to_jax_or_placeholder(grids[sn]) - for sn in variable_info.query("is_state").index - if grids[sn].distributed - } else: _validate_all_states_present( provided_states=states, required_state_names=set(variable_info.query("is_state").index), ) _states = states - distributed_states = { - sn: state for sn, state in states.items() if grids[sn].distributed - } discrete_actions = { name: grids[name].to_jax() for name in variable_info.query("is_action & is_discrete").index @@ -65,7 +57,6 @@ def create_state_action_space( return StateActionSpace( states=MappingProxyType(_states), - distributed_states=MappingProxyType(distributed_states), discrete_actions=MappingProxyType(discrete_actions), continuous_actions=MappingProxyType(continuous_actions), state_and_discrete_action_names=state_and_discrete_action_names, diff --git a/tests/solution/test_solve_brute.py b/tests/solution/test_solve_brute.py index 629472b5d..5051c751c 100644 --- a/tests/solution/test_solve_brute.py +++ b/tests/solution/test_solve_brute.py @@ -79,7 +79,6 @@ def test_solve_brute(): "wealth": jnp.array([0.0, 1.0, 2.0]), } ), - distributed_states=MappingProxyType({}), state_and_discrete_action_names=("lazy", "labor_supply", "wealth"), ) # ================================================================================== @@ -163,7 +162,6 @@ def test_solve_brute_single_period_Qc_arr(): } ), states=MappingProxyType({}), - distributed_states=MappingProxyType({}), state_and_discrete_action_names=("a", "b", "c"), ) diff --git a/tests/test_nan_diagnostics.py b/tests/test_nan_diagnostics.py index cc25e063f..dd6e0b5f9 100644 --- a/tests/test_nan_diagnostics.py +++ b/tests/test_nan_diagnostics.py @@ -30,7 +30,6 @@ def _make_state_action_space( states=MappingProxyType( {"wealth": jnp.linspace(1.0, 5.0, n_wealth)}, ), - distributed_states=MappingProxyType({}), discrete_actions=MappingProxyType({}), continuous_actions=MappingProxyType( {"consumption": jnp.linspace(0.1, 2.0, n_consumption)}, From 45ea93a53941f8c871f5264b2ec2ac124e2408f8 Mon Sep 17 00:00:00 2001 From: Hans-Martin von Gaudecker Date: Wed, 13 May 2026 16:30:31 +0200 Subject: [PATCH 23/77] =?UTF-8?q?Annotation=20drift=20sweep=20=E2=80=94=20?= =?UTF-8?q?prep=20for=20whole-package=20beartype=20claw?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Targeted widenings + corrections that align type annotations with the values actually flowing through pylcm at runtime. None of these change behaviour today; together they cover what beartype's `On` strategy would flag if `beartype.claw.beartype_package("lcm", ...)` were enabled in `tests/conftest.py`. Categories addressed (one site per bullet unless noted): - `MappingProxyType` annotations on internal entry points widened to `Mapping`: `solve_brute.py:solve` (8 sites) and `params/processing.py:process_params`. Callers pass plain `dict` in several spots; the immutability discipline isn't load-bearing on these boundaries. - JAX scalar/array dtype drift on `grids/coordinates.py`: `get_linspace_coordinate` / `get_logspace_coordinate` widen `start: ScalarFloat` and `stop: ScalarFloat` to `ScalarFloat | Array` (the recursive log → linear path passes 0-d JAX arrays). - `shocks/_base.py:_gauss_hermite_normal`: add `ScalarFloat` to the `mu` / `sigma` unions to admit 0-d JAX scalars. - PRNG keys: `draw_shock` methods on every `Tauchen`, `Rouwenhorst`, `TauchenNormalMixture`, `Uniform`, `Normal`, `LogNormal` retype `key: FloatND` to `key: Array` — PRNG keys have `dtype=key`, not float. - `utils/dispatchers.py:batched_vmap`: retype `kwargs: FloatND` and `Float1D` to `Array`; the dispatcher routes int arrays as well as float arrays through the vmap chain. - `params/{mapping_leaf,sequence_leaf}.py:_unflatten`: `values: list[Any]` → `Sequence[Any]`; pytree unflatten receives the tuple from `_flatten`. - Beartype-unsupported PEP forms: - `simulation/initial_conditions.py:_raise_feasibility_type_error`: `Never` → `NoReturn` (beartype 0.22 rejects `typing.Never`). - `persistence.py`: define `_ModelOrNone` and `_SimulationResultOrNone` as `TYPE_CHECKING`-conditional aliases that resolve to `Any` at runtime. The bare-union string forward reference `'Model | None'` was unparseable by beartype's forward-ref machinery, but importing `Model` at the top causes a circular import. - Corrections discovered in passing: - `dtypes.py:canonical_float_dtype` return annotation `jnp.dtype` → `type` (the function returns `_ScalarMeta`, a class, not a numpy `dtype` instance). - `utils/containers.py:get_field_names_and_values` parameter `dc: type` → `dc: object` (the function accepts both dataclass classes and instances). --- src/lcm/dtypes.py | 8 +++++--- src/lcm/grids/coordinates.py | 24 ++++++++++++------------ src/lcm/params/mapping_leaf.py | 4 ++-- src/lcm/params/processing.py | 4 ++-- src/lcm/params/sequence_leaf.py | 2 +- src/lcm/persistence.py | 18 +++++++++++++++--- src/lcm/shocks/_base.py | 5 ++++- src/lcm/shocks/ar1.py | 9 +++++---- src/lcm/shocks/iid.py | 11 ++++++----- src/lcm/simulation/initial_conditions.py | 4 ++-- src/lcm/solution/solve_brute.py | 16 ++++++++-------- src/lcm/utils/containers.py | 9 ++++++--- src/lcm/utils/dispatchers.py | 8 ++++---- 13 files changed, 72 insertions(+), 50 deletions(-) diff --git a/src/lcm/dtypes.py b/src/lcm/dtypes.py index f2cf31708..bdc1b504e 100644 --- a/src/lcm/dtypes.py +++ b/src/lcm/dtypes.py @@ -18,12 +18,14 @@ _FLOAT32_MAX = float(np.finfo(np.float32).max) -def canonical_float_dtype() -> jnp.dtype: +def canonical_float_dtype() -> type: """Return pylcm's canonical float dtype, derived from `jax_enable_x64`. Returns `jnp.float64` if `jax.config.jax_enable_x64` is True, - otherwise `jnp.float32`. The value is read at call time, not at - import, so toggling the JAX config (e.g. between tests) is honoured. + otherwise `jnp.float32`. The return is a JAX scalar type, passable + wherever JAX/NumPy accept a dtype-like specifier. The value is read + at call time, not at import, so toggling the JAX config (e.g. + between tests) is honoured. """ return jnp.float64 if jax.config.read("jax_enable_x64") else jnp.float32 diff --git a/src/lcm/grids/coordinates.py b/src/lcm/grids/coordinates.py index dc95b40c5..804f4dde6 100644 --- a/src/lcm/grids/coordinates.py +++ b/src/lcm/grids/coordinates.py @@ -33,23 +33,23 @@ def linspace( def get_linspace_coordinate( *, value: ScalarFloat, - start: ScalarFloat, - stop: ScalarFloat, + start: ScalarFloat | Array, + stop: ScalarFloat | Array, n_points: ScalarInt, ) -> ScalarFloat: ... @overload def get_linspace_coordinate( *, value: Array, - start: ScalarFloat, - stop: ScalarFloat, + start: ScalarFloat | Array, + stop: ScalarFloat | Array, n_points: ScalarInt, ) -> Array: ... def get_linspace_coordinate( *, value: ScalarFloat | Array, - start: ScalarFloat, - stop: ScalarFloat, + start: ScalarFloat | Array, + stop: ScalarFloat | Array, n_points: ScalarInt, ) -> ScalarFloat | Array: """Map a value into the input needed for jax.scipy.ndimage.map_coordinates.""" @@ -87,23 +87,23 @@ def logspace( def get_logspace_coordinate( *, value: ScalarFloat, - start: ScalarFloat, - stop: ScalarFloat, + start: ScalarFloat | Array, + stop: ScalarFloat | Array, n_points: ScalarInt, ) -> ScalarFloat: ... @overload def get_logspace_coordinate( *, value: Array, - start: ScalarFloat, - stop: ScalarFloat, + start: ScalarFloat | Array, + stop: ScalarFloat | Array, n_points: ScalarInt, ) -> Array: ... def get_logspace_coordinate( *, value: ScalarFloat | Array, - start: ScalarFloat, - stop: ScalarFloat, + start: ScalarFloat | Array, + stop: ScalarFloat | Array, n_points: ScalarInt, ) -> ScalarFloat | Array: """Map a value into the input needed for jax.scipy.ndimage.map_coordinates.""" diff --git a/src/lcm/params/mapping_leaf.py b/src/lcm/params/mapping_leaf.py index 617c1e2f0..aaca3e211 100644 --- a/src/lcm/params/mapping_leaf.py +++ b/src/lcm/params/mapping_leaf.py @@ -1,6 +1,6 @@ """A Mapping wrapper that is a JAX pytree but not itself a Mapping.""" -from collections.abc import Mapping +from collections.abc import Mapping, Sequence from typing import Any import jax @@ -41,7 +41,7 @@ def _flatten(nmp: MappingLeaf) -> tuple[list[Any], tuple[str, ...]]: return values, keys -def _unflatten(keys: tuple[str, ...], values: list[Any]) -> MappingLeaf: +def _unflatten(keys: tuple[str, ...], values: Sequence[Any]) -> MappingLeaf: return MappingLeaf(dict(zip(keys, values, strict=True))) diff --git a/src/lcm/params/processing.py b/src/lcm/params/processing.py index d6afbccd6..a0d3d5ecf 100644 --- a/src/lcm/params/processing.py +++ b/src/lcm/params/processing.py @@ -52,7 +52,7 @@ def process_params( *, params: UserParams, - params_template: ParamsTemplate, + params_template: Mapping[RegimeName, RegimeParamsTemplate], ) -> InternalParams: """Process user-provided params into internal params. @@ -298,7 +298,7 @@ def _find_candidates( def create_params_template( # noqa: C901 - internal_regimes: MappingProxyType[RegimeName, InternalRegime], + internal_regimes: Mapping[RegimeName, InternalRegime], ) -> ParamsTemplate: """Create params_template from internal regimes and validate name uniqueness. diff --git a/src/lcm/params/sequence_leaf.py b/src/lcm/params/sequence_leaf.py index 363eb963e..83398a548 100644 --- a/src/lcm/params/sequence_leaf.py +++ b/src/lcm/params/sequence_leaf.py @@ -38,7 +38,7 @@ def _flatten(sl: SequenceLeaf) -> tuple[list[Any], None]: return list(sl.data), None -def _unflatten(_aux: None, values: list[Any]) -> SequenceLeaf: +def _unflatten(_aux: None, values: Sequence[Any]) -> SequenceLeaf: return SequenceLeaf(values) diff --git a/src/lcm/persistence.py b/src/lcm/persistence.py index c8498a199..99d32fc82 100644 --- a/src/lcm/persistence.py +++ b/src/lcm/persistence.py @@ -27,6 +27,18 @@ from lcm.model import Model from lcm.simulation.result import SimulationResult + # Type-checker view: full precision. + _ModelOrNone = Model | None + _SimulationResultOrNone = SimulationResult | None +else: + # Runtime view used by beartype's annotation evaluator. `Model` and + # `SimulationResult` cannot be imported here (circular), so collapse + # to `Any`. The snapshot dataclasses are serialization carriers; the + # API surface that needs strict checking is the `save_*_snapshot` + # callers, which beartype still polices via their own parameters. + _ModelOrNone = Any + _SimulationResultOrNone = Any + logger = logging.getLogger(__name__) @@ -34,7 +46,7 @@ class SolveSnapshot: """Snapshot of a solve run for offline reconstruction.""" - model: Model | None + model: _ModelOrNone """The Model instance.""" params: UserParams | None @@ -51,7 +63,7 @@ class SolveSnapshot: class SimulateSnapshot: """Snapshot of a simulate run for offline reconstruction.""" - model: Model | None + model: _ModelOrNone """The Model instance.""" params: UserParams | None @@ -63,7 +75,7 @@ class SimulateSnapshot: period_to_regime_to_V_arr: PeriodToRegimeToVArr | None """Immutable mapping of periods to regime value function arrays.""" - result: SimulationResult | None + result: _SimulationResultOrNone """SimulationResult object.""" platform: str diff --git a/src/lcm/shocks/_base.py b/src/lcm/shocks/_base.py index 55d4ac79b..ddd151963 100644 --- a/src/lcm/shocks/_base.py +++ b/src/lcm/shocks/_base.py @@ -15,7 +15,10 @@ def _gauss_hermite_normal( - *, n_points: int, mu: float | Float1D, sigma: float | Float1D + *, + n_points: int, + mu: float | ScalarFloat | Float1D, + sigma: float | ScalarFloat | Float1D, ) -> tuple[Float1D, Float1D]: """Compute Gauss-Hermite quadrature nodes and weights for $N(\\mu, \\sigma^2)$. diff --git a/src/lcm/shocks/ar1.py b/src/lcm/shocks/ar1.py index 5ca8227ec..e34debdee 100644 --- a/src/lcm/shocks/ar1.py +++ b/src/lcm/shocks/ar1.py @@ -5,6 +5,7 @@ import jax import jax.numpy as jnp +from jax import Array from jax.scipy.stats.norm import cdf from lcm._beartype_conf import GRID_CONF, beartype_init @@ -25,7 +26,7 @@ class _ShockGridAR1(_ShockGrid): def draw_shock( self, params: MappingProxyType[str, float | FloatND], - key: FloatND, + key: Array, current_value: Float1D, ) -> Float1D: ... @@ -120,7 +121,7 @@ def compute_transition_probs(self, **kwargs: float) -> FloatND: def draw_shock( self, params: MappingProxyType[str, float | FloatND], - key: FloatND, + key: Array, current_value: Float1D, ) -> Float1D: return ( @@ -189,7 +190,7 @@ def compute_transition_probs(self, **kwargs: float) -> FloatND: def draw_shock( self, params: MappingProxyType[str, float | FloatND], - key: FloatND, + key: Array, current_value: Float1D, ) -> Float1D: return ( @@ -303,7 +304,7 @@ def compute_transition_probs(self, **kwargs: float) -> FloatND: def draw_shock( self, params: MappingProxyType[str, float | FloatND], - key: FloatND, + key: Array, current_value: Float1D, ) -> Float1D: key1, key2 = jax.random.split(key) diff --git a/src/lcm/shocks/iid.py b/src/lcm/shocks/iid.py index b18265d6f..ddc4b5b13 100644 --- a/src/lcm/shocks/iid.py +++ b/src/lcm/shocks/iid.py @@ -4,6 +4,7 @@ import jax import jax.numpy as jnp +from jax import Array from jax.scipy.stats.norm import cdf from lcm._beartype_conf import GRID_CONF, beartype_init @@ -24,7 +25,7 @@ class _ShockGridIID(_ShockGrid): def draw_shock( self, params: MappingProxyType[str, float | FloatND], - key: FloatND, + key: Array, ) -> Float1D: ... @@ -56,7 +57,7 @@ def compute_transition_probs(self, **kwargs: float) -> FloatND: # noqa: ARG002 def draw_shock( self, params: MappingProxyType[str, float | FloatND], - key: FloatND, + key: Array, ) -> Float1D: return jax.random.uniform( key=key, minval=params["start"], maxval=params["stop"] @@ -133,7 +134,7 @@ def compute_transition_probs(self, **kwargs: float) -> FloatND: def draw_shock( self, params: MappingProxyType[str, float | FloatND], - key: FloatND, + key: Array, ) -> Float1D: return params["mu"] + params["sigma"] * jax.random.normal(key=key) @@ -199,7 +200,7 @@ def compute_transition_probs(self, **kwargs: float) -> FloatND: def draw_shock( self, params: MappingProxyType[str, float | FloatND], - key: FloatND, + key: Array, ) -> Float1D: return jnp.exp(params["mu"] + params["sigma"] * jax.random.normal(key=key)) @@ -284,7 +285,7 @@ def compute_transition_probs(self, **kwargs: float) -> FloatND: def draw_shock( self, params: MappingProxyType[str, float | FloatND], - key: FloatND, + key: Array, ) -> Float1D: key1, key2 = jax.random.split(key) component = jax.random.bernoulli(key1, params["p1"]) diff --git a/src/lcm/simulation/initial_conditions.py b/src/lcm/simulation/initial_conditions.py index 30453e38b..c48ac53cf 100644 --- a/src/lcm/simulation/initial_conditions.py +++ b/src/lcm/simulation/initial_conditions.py @@ -7,7 +7,7 @@ from collections.abc import Callable, Mapping, Sequence from types import MappingProxyType -from typing import Never, cast +from typing import NoReturn, cast import jax import numpy as np @@ -769,7 +769,7 @@ def _raise_feasibility_type_error( regime_name: RegimeName, internal_regime: InternalRegime, subject_states: dict[StateName, Array], -) -> Never: +) -> NoReturn: """Re-raise a TypeError from feasibility checking with diagnostic context. Args: diff --git a/src/lcm/solution/solve_brute.py b/src/lcm/solution/solve_brute.py index 77e63e24f..ac39ab943 100644 --- a/src/lcm/solution/solve_brute.py +++ b/src/lcm/solution/solve_brute.py @@ -2,7 +2,7 @@ import logging import os import time -from collections.abc import Callable, Hashable +from collections.abc import Callable, Hashable, Mapping from concurrent.futures import ThreadPoolExecutor, as_completed from dataclasses import dataclass from types import MappingProxyType @@ -25,7 +25,7 @@ def solve( *, internal_params: InternalParams, ages: AgeGrid, - internal_regimes: MappingProxyType[RegimeName, InternalRegime], + internal_regimes: Mapping[RegimeName, InternalRegime], logger: logging.Logger, enable_jit: bool, max_compilation_workers: int | None = None, @@ -234,7 +234,7 @@ def solve( def _compile_all_functions( *, - internal_regimes: MappingProxyType[RegimeName, InternalRegime], + internal_regimes: Mapping[RegimeName, InternalRegime], internal_params: InternalParams, ages: AgeGrid, next_regime_to_V_arr: MappingProxyType[RegimeName, FloatND], @@ -388,7 +388,7 @@ def _func_dedup_key(*, func: Callable) -> Hashable: def _get_regime_V_shapes( *, - internal_regimes: MappingProxyType[RegimeName, InternalRegime], + internal_regimes: Mapping[RegimeName, InternalRegime], internal_params: InternalParams, ) -> dict[RegimeName, tuple[int, ...]]: """Compute value function array shapes for all regimes. @@ -438,7 +438,7 @@ def _emit_post_loop_diagnostics( logger: logging.Logger, diagnostic_rows: list[_DiagnosticRow], solution: MappingProxyType[int, MappingProxyType[RegimeName, FloatND]], - internal_regimes: MappingProxyType[RegimeName, InternalRegime], + internal_regimes: Mapping[RegimeName, InternalRegime], internal_params: InternalParams, running_any_nan: FloatND, running_any_inf: FloatND, @@ -480,7 +480,7 @@ def _raise_first_nan_row( *, diagnostic_rows: list[_DiagnosticRow], solution: MappingProxyType[int, MappingProxyType[RegimeName, FloatND]], - internal_regimes: MappingProxyType[RegimeName, InternalRegime], + internal_regimes: Mapping[RegimeName, InternalRegime], internal_params: InternalParams, ) -> None: """Find the first NaN-bearing (regime, period) and raise. @@ -504,7 +504,7 @@ def _raise_at( *, row: _DiagnosticRow, solution: MappingProxyType[int, MappingProxyType[RegimeName, FloatND]], - internal_regimes: MappingProxyType[RegimeName, InternalRegime], + internal_regimes: Mapping[RegimeName, InternalRegime], internal_params: InternalParams, ) -> None: """Run the enriched NaN diagnostic on a single offending row and raise.""" @@ -546,7 +546,7 @@ def _raise_at( def _reconstruct_next_regime_to_V_arr( *, period: int, - internal_regimes: MappingProxyType[RegimeName, InternalRegime], + internal_regimes: Mapping[RegimeName, InternalRegime], internal_params: InternalParams, solution: MappingProxyType[int, MappingProxyType[RegimeName, FloatND]], ) -> MappingProxyType[RegimeName, FloatND]: diff --git a/src/lcm/utils/containers.py b/src/lcm/utils/containers.py index 0c1682d14..8b8189315 100644 --- a/src/lcm/utils/containers.py +++ b/src/lcm/utils/containers.py @@ -68,11 +68,11 @@ def find_duplicates(*containers: Iterable[T]) -> set[T]: return {v for v, count in counts.items() if count > 1} -def get_field_names_and_values(dc: type) -> MappingProxyType[str, Any]: +def get_field_names_and_values(dc: object) -> MappingProxyType[str, Any]: """Return the fields of a dataclass. Args: - dc: The dataclass to get the fields of. + dc: The dataclass class or instance to get the fields of. Returns: An immutable mapping with the field names as keys and the field values as @@ -80,7 +80,10 @@ def get_field_names_and_values(dc: type) -> MappingProxyType[str, Any]: """ return MappingProxyType( - {field.name: getattr(dc, field.name, None) for field in fields(dc)} + { + field.name: getattr(dc, field.name, None) + for field in fields(dc) # ty: ignore[invalid-argument-type] + } ) diff --git a/src/lcm/utils/dispatchers.py b/src/lcm/utils/dispatchers.py index 22367d641..91a30b1e0 100644 --- a/src/lcm/utils/dispatchers.py +++ b/src/lcm/utils/dispatchers.py @@ -9,7 +9,7 @@ from jax import Array, vmap from lcm.exceptions import FunctionDispatchError -from lcm.typing import ActionName, Float1D, FloatND, StateName +from lcm.typing import ActionName, FloatND, StateName from lcm.utils.containers import find_duplicates from lcm.utils.functools import allow_args, allow_only_kwargs @@ -246,7 +246,7 @@ def _base_productmap_batched( "is POSITIONAL_ONLY." ) - def batched_vmap(**kwargs: FloatND) -> FloatND: + def batched_vmap(**kwargs: Array) -> Array: non_array_kwargs = { key: val for key, val in kwargs.items() if key not in product_axes } @@ -259,8 +259,8 @@ def map_one_more( loop_func: FunctionWithArrayReturn, axis: str ) -> FunctionWithArrayReturn: def func_mapped_over_one_more_axis( - *already_mapped_args: Float1D, **already_mapped_kwargs: Float1D - ) -> FloatND: + *already_mapped_args: Array, **already_mapped_kwargs: Array + ) -> Array: return jax.lax.map( lambda axis_i: loop_func( *already_mapped_args, **{axis: axis_i}, **already_mapped_kwargs From 51a7b4b39f90692aa1c75d583c69847d2ee3e838 Mon Sep 17 00:00:00 2001 From: Hans-Martin von Gaudecker Date: Wed, 13 May 2026 18:25:52 +0200 Subject: [PATCH 24/77] Enable scoped beartype claw on lcm.grids/shocks/params MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Activate beartype's import-time claw via tests/conftest.py for the `lcm.grids`, `lcm.shocks`, and `lcm.params` packages, configured to raise the existing project exceptions (`GridInitializationError` / `InvalidParamsError`) on parameter-type violations. The claw catches real annotation drift that the previous decorator-only perimeter did not police — every shock-grid `draw_shock`, `compute_gridpoints`, and `compute_transition_probs` plus every grid `get_coordinate` now has runtime type checks. The drift fixes: - iid `draw_shock` returns `ScalarFloat` (not `Float1D`) — each call produces a 0-d sample. - ar1 `draw_shock` takes / returns `ScalarFloat` per element (matches what `next_state.py` feeds it under vmap). - shock-grid `compute_gridpoints` / `compute_transition_probs` accept `**kwargs: float | FloatND` — runtime kwargs from the params wrapper arrive as JAX tracers, not Python floats. - `_mixture_cdf` and `TauchenNormalMixture._innovation_variance` widen scalar params to `float | FloatND` so JAX-array shock params flow through. - `get_linspace_coordinate` / `get_logspace_coordinate` accept `n_points: ScalarInt | IntND` for the piecewise dispatch where `_piece_n_points[piece_idx]` is an `IntND` under vectorized lookup. - Piecewise + linspace/logspace coordinate methods accept `value: float | ScalarFloat | FloatND` (Python float was already used in tests; the contract just lacked the annotation). - Continuous-grid + shock-grid `get_coordinate` overloads use `FloatND` instead of bare `Array`. --- src/lcm/grids/continuous.py | 22 ++++++------ src/lcm/grids/coordinates.py | 67 ++++++++++++++++++------------------ src/lcm/grids/piecewise.py | 18 ++++++---- src/lcm/shocks/_base.py | 19 +++++----- src/lcm/shocks/ar1.py | 42 +++++++++++----------- src/lcm/shocks/iid.py | 28 +++++++-------- tests/conftest.py | 26 ++++++++++---- 7 files changed, 119 insertions(+), 103 deletions(-) diff --git a/src/lcm/grids/continuous.py b/src/lcm/grids/continuous.py index 22fa7ce05..7cc295d0a 100644 --- a/src/lcm/grids/continuous.py +++ b/src/lcm/grids/continuous.py @@ -5,7 +5,6 @@ from typing import overload import jax.numpy as jnp -from jax import Array from lcm._beartype_conf import GRID_CONF, beartype_init from lcm.dtypes import canonical_float_dtype @@ -14,6 +13,7 @@ from lcm.grids.base import Grid from lcm.typing import ( Float1D, + FloatND, ScalarFloat, ScalarInt, ) @@ -34,9 +34,9 @@ class ContinuousGrid(Grid): @overload def get_coordinate(self, value: ScalarFloat) -> ScalarFloat: ... @overload - def get_coordinate(self, value: Array) -> Array: ... + def get_coordinate(self, value: FloatND) -> FloatND: ... @abstractmethod - def get_coordinate(self, value: ScalarFloat | Array) -> ScalarFloat | Array: + def get_coordinate(self, value: ScalarFloat | FloatND) -> ScalarFloat | FloatND: """Return the generalized coordinate of a value in the grid.""" @@ -82,9 +82,9 @@ def to_jax(self) -> Float1D: @overload def get_coordinate(self, value: ScalarFloat) -> ScalarFloat: ... @overload - def get_coordinate(self, value: Array) -> Array: ... + def get_coordinate(self, value: FloatND) -> FloatND: ... @abstractmethod - def get_coordinate(self, value: ScalarFloat | Array) -> ScalarFloat | Array: + def get_coordinate(self, value: ScalarFloat | FloatND) -> ScalarFloat | FloatND: """Return the generalized coordinate of a value in the grid.""" def replace(self, **kwargs: float) -> UniformContinuousGrid: @@ -124,8 +124,8 @@ def to_jax(self) -> Float1D: @overload def get_coordinate(self, value: ScalarFloat) -> ScalarFloat: ... @overload - def get_coordinate(self, value: Array) -> Array: ... - def get_coordinate(self, value: ScalarFloat | Array) -> ScalarFloat | Array: + def get_coordinate(self, value: FloatND) -> FloatND: ... + def get_coordinate(self, value: ScalarFloat | FloatND) -> ScalarFloat | FloatND: """Return the generalized coordinate of a value in the grid.""" return grid_coordinates.get_linspace_coordinate( value=value, @@ -173,8 +173,8 @@ def to_jax(self) -> Float1D: @overload def get_coordinate(self, value: ScalarFloat) -> ScalarFloat: ... @overload - def get_coordinate(self, value: Array) -> Array: ... - def get_coordinate(self, value: ScalarFloat | Array) -> ScalarFloat | Array: + def get_coordinate(self, value: FloatND) -> FloatND: ... + def get_coordinate(self, value: ScalarFloat | FloatND) -> ScalarFloat | FloatND: """Return the generalized coordinate of a value in the grid.""" return grid_coordinates.get_logspace_coordinate( value=value, @@ -305,8 +305,8 @@ def to_jax(self) -> Float1D: @overload def get_coordinate(self, value: ScalarFloat) -> ScalarFloat: ... @overload - def get_coordinate(self, value: Array) -> Array: ... - def get_coordinate(self, value: ScalarFloat | Array) -> ScalarFloat | Array: + def get_coordinate(self, value: FloatND) -> FloatND: ... + def get_coordinate(self, value: ScalarFloat | FloatND) -> ScalarFloat | FloatND: """Return the generalized coordinate of a value in the grid.""" if self.points is None: raise GridInitializationError( diff --git a/src/lcm/grids/coordinates.py b/src/lcm/grids/coordinates.py index 804f4dde6..1541484c1 100644 --- a/src/lcm/grids/coordinates.py +++ b/src/lcm/grids/coordinates.py @@ -9,9 +9,8 @@ from typing import overload import jax.numpy as jnp -from jax import Array -from lcm.typing import Float1D, ScalarFloat, ScalarInt +from lcm.typing import Float1D, FloatND, IntND, ScalarFloat, ScalarInt def linspace( @@ -32,26 +31,26 @@ def linspace( @overload def get_linspace_coordinate( *, - value: ScalarFloat, - start: ScalarFloat | Array, - stop: ScalarFloat | Array, - n_points: ScalarInt, + value: float | ScalarFloat, + start: ScalarFloat | FloatND, + stop: ScalarFloat | FloatND, + n_points: ScalarInt | IntND, ) -> ScalarFloat: ... @overload def get_linspace_coordinate( *, - value: Array, - start: ScalarFloat | Array, - stop: ScalarFloat | Array, - n_points: ScalarInt, -) -> Array: ... + value: FloatND, + start: ScalarFloat | FloatND, + stop: ScalarFloat | FloatND, + n_points: ScalarInt | IntND, +) -> FloatND: ... def get_linspace_coordinate( *, - value: ScalarFloat | Array, - start: ScalarFloat | Array, - stop: ScalarFloat | Array, - n_points: ScalarInt, -) -> ScalarFloat | Array: + value: float | ScalarFloat | FloatND, + start: ScalarFloat | FloatND, + stop: ScalarFloat | FloatND, + n_points: ScalarInt | IntND, +) -> ScalarFloat | FloatND: """Map a value into the input needed for jax.scipy.ndimage.map_coordinates.""" step_length = (stop - start) / (n_points - 1) return (value - start) / step_length @@ -86,26 +85,26 @@ def logspace( @overload def get_logspace_coordinate( *, - value: ScalarFloat, - start: ScalarFloat | Array, - stop: ScalarFloat | Array, - n_points: ScalarInt, + value: float | ScalarFloat, + start: ScalarFloat | FloatND, + stop: ScalarFloat | FloatND, + n_points: ScalarInt | IntND, ) -> ScalarFloat: ... @overload def get_logspace_coordinate( *, - value: Array, - start: ScalarFloat | Array, - stop: ScalarFloat | Array, - n_points: ScalarInt, -) -> Array: ... + value: FloatND, + start: ScalarFloat | FloatND, + stop: ScalarFloat | FloatND, + n_points: ScalarInt | IntND, +) -> FloatND: ... def get_logspace_coordinate( *, - value: ScalarFloat | Array, - start: ScalarFloat | Array, - stop: ScalarFloat | Array, - n_points: ScalarInt, -) -> ScalarFloat | Array: + value: float | ScalarFloat | FloatND, + start: ScalarFloat | FloatND, + stop: ScalarFloat | FloatND, + n_points: ScalarInt | IntND, +) -> ScalarFloat | FloatND: """Map a value into the input needed for jax.scipy.ndimage.map_coordinates.""" # Transform start, stop, and value to linear scale start_linear = jnp.log(start) @@ -152,14 +151,14 @@ def get_irreg_coordinate( @overload def get_irreg_coordinate( *, - value: Array, + value: FloatND, points: Float1D, -) -> Array: ... +) -> FloatND: ... def get_irreg_coordinate( *, - value: ScalarFloat | Array, + value: ScalarFloat | FloatND, points: Float1D, -) -> ScalarFloat | Array: +) -> ScalarFloat | FloatND: """Return the generalized coordinate of a value in an irregularly spaced grid. Uses binary search (jnp.searchsorted) to find the position of the value among diff --git a/src/lcm/grids/piecewise.py b/src/lcm/grids/piecewise.py index 97e3f26d5..c7a1ac791 100644 --- a/src/lcm/grids/piecewise.py +++ b/src/lcm/grids/piecewise.py @@ -4,7 +4,6 @@ import jax.numpy as jnp import portion -from jax import Array from lcm._beartype_conf import GRID_CONF, beartype_init from lcm.exceptions import GridInitializationError, format_messages @@ -12,6 +11,7 @@ from lcm.grids.continuous import ContinuousGrid from lcm.typing import ( Float1D, + FloatND, Int1D, ScalarFloat, ScalarInt, @@ -98,10 +98,12 @@ def to_jax(self) -> Float1D: return jnp.concatenate(piece_arrays) @overload - def get_coordinate(self, value: ScalarFloat) -> ScalarFloat: ... + def get_coordinate(self, value: float | ScalarFloat) -> ScalarFloat: ... @overload - def get_coordinate(self, value: Array) -> Array: ... - def get_coordinate(self, value: ScalarFloat | Array) -> ScalarFloat | Array: + def get_coordinate(self, value: FloatND) -> FloatND: ... + def get_coordinate( + self, value: float | ScalarFloat | FloatND + ) -> ScalarFloat | FloatND: """Return the generalized coordinate of a value in the grid.""" piece_idx = jnp.searchsorted(self._breakpoints, value, side="right") local_coord = grid_coordinates.get_linspace_coordinate( @@ -170,10 +172,12 @@ def to_jax(self) -> Float1D: return jnp.concatenate(piece_arrays) @overload - def get_coordinate(self, value: ScalarFloat) -> ScalarFloat: ... + def get_coordinate(self, value: float | ScalarFloat) -> ScalarFloat: ... @overload - def get_coordinate(self, value: Array) -> Array: ... - def get_coordinate(self, value: ScalarFloat | Array) -> ScalarFloat | Array: + def get_coordinate(self, value: FloatND) -> FloatND: ... + def get_coordinate( + self, value: float | ScalarFloat | FloatND + ) -> ScalarFloat | FloatND: """Return the generalized coordinate of a value in the grid.""" piece_idx = jnp.searchsorted(self._breakpoints, value, side="right") local_coord = grid_coordinates.get_logspace_coordinate( diff --git a/src/lcm/shocks/_base.py b/src/lcm/shocks/_base.py index ddd151963..aa34af071 100644 --- a/src/lcm/shocks/_base.py +++ b/src/lcm/shocks/_base.py @@ -5,7 +5,6 @@ import jax.numpy as jnp import numpy as np -from jax import Array from jax.scipy.stats.norm import cdf from lcm.exceptions import GridInitializationError @@ -76,11 +75,11 @@ def is_fully_specified(self) -> bool: return not self.params_to_pass_at_runtime @abstractmethod - def compute_gridpoints(self, **kwargs: float) -> Float1D: + def compute_gridpoints(self, **kwargs: float | FloatND) -> Float1D: """Compute discretized gridpoints for the shock distribution.""" @abstractmethod - def compute_transition_probs(self, **kwargs: float) -> FloatND: + def compute_transition_probs(self, **kwargs: float | FloatND) -> FloatND: """Compute transition probability matrix for the shock distribution.""" def get_gridpoints(self) -> Float1D: @@ -112,8 +111,8 @@ def to_jax(self) -> Float1D: @overload def get_coordinate(self, value: ScalarFloat) -> ScalarFloat: ... @overload - def get_coordinate(self, value: Array) -> Array: ... - def get_coordinate(self, value: ScalarFloat | Array) -> ScalarFloat | Array: + def get_coordinate(self, value: FloatND) -> FloatND: ... + def get_coordinate(self, value: ScalarFloat | FloatND) -> ScalarFloat | FloatND: """Return the generalized coordinate of a value in the grid.""" if not self.is_fully_specified: raise GridInitializationError( @@ -144,11 +143,11 @@ def _validate_gauss_hermite_grid( def _mixture_cdf( *, x: FloatND, - p1: float, - mu1: float, - sigma1: float, - mu2: float, - sigma2: float, + p1: float | FloatND, + mu1: float | FloatND, + sigma1: float | FloatND, + mu2: float | FloatND, + sigma2: float | FloatND, ) -> FloatND: """Evaluate the CDF of a two-component normal mixture. diff --git a/src/lcm/shocks/ar1.py b/src/lcm/shocks/ar1.py index e34debdee..eb100e18e 100644 --- a/src/lcm/shocks/ar1.py +++ b/src/lcm/shocks/ar1.py @@ -15,7 +15,7 @@ _ShockGrid, _validate_gauss_hermite_grid, ) -from lcm.typing import Float1D, FloatND +from lcm.typing import Float1D, FloatND, ScalarFloat @dataclass(frozen=True, kw_only=True) @@ -27,8 +27,8 @@ def draw_shock( self, params: MappingProxyType[str, float | FloatND], key: Array, - current_value: Float1D, - ) -> Float1D: ... + current_value: ScalarFloat, + ) -> ScalarFloat: ... @beartype_init(GRID_CONF) @@ -78,7 +78,7 @@ def _param_field_names(self) -> tuple[str, ...]: exclude.add("n_std") return tuple(f.name for f in fields(self) if f.name not in exclude) - def compute_gridpoints(self, **kwargs: float) -> Float1D: + def compute_gridpoints(self, **kwargs: float | FloatND) -> Float1D: n_points = self.n_points rho, sigma, mu = kwargs["rho"], kwargs["sigma"], kwargs["mu"] std_y = jnp.sqrt(sigma**2 / (1 - rho**2)) @@ -91,7 +91,7 @@ def compute_gridpoints(self, **kwargs: float) -> Float1D: x = jnp.linspace(-x_max, x_max, n_points) return x + mu / (1 - rho) - def compute_transition_probs(self, **kwargs: float) -> FloatND: + def compute_transition_probs(self, **kwargs: float | FloatND) -> FloatND: n_points = self.n_points rho, sigma = kwargs["rho"], kwargs["sigma"] std_y = jnp.sqrt(sigma**2 / (1 - rho**2)) @@ -122,8 +122,8 @@ def draw_shock( self, params: MappingProxyType[str, float | FloatND], key: Array, - current_value: Float1D, - ) -> Float1D: + current_value: ScalarFloat, + ) -> ScalarFloat: return ( params["mu"] + params["rho"] * current_value @@ -153,14 +153,14 @@ class Rouwenhorst(_ShockGridAR1): mu: float | int | None = None """Intercept (drift) of the AR(1) process.""" - def compute_gridpoints(self, **kwargs: float) -> Float1D: + def compute_gridpoints(self, **kwargs: float | FloatND) -> Float1D: n_points = self.n_points rho, sigma, mu = kwargs["rho"], kwargs["sigma"], kwargs["mu"] nu = jnp.sqrt((n_points - 1) / (1 - rho**2)) * sigma long_run_mean = mu / (1.0 - rho) return jnp.linspace(long_run_mean - nu, long_run_mean + nu, n_points) - def compute_transition_probs(self, **kwargs: float) -> FloatND: + def compute_transition_probs(self, **kwargs: float | FloatND) -> FloatND: n_points = self.n_points rho = kwargs["rho"] q = (rho + 1) / 2 @@ -191,8 +191,8 @@ def draw_shock( self, params: MappingProxyType[str, float | FloatND], key: Array, - current_value: Float1D, - ) -> Float1D: + current_value: ScalarFloat, + ) -> ScalarFloat: return ( params["mu"] + params["rho"] * current_value @@ -243,17 +243,17 @@ class TauchenNormalMixture(_ShockGridAR1): @staticmethod def _innovation_variance( *, - p1: float, - mu1: float, - sigma1: float, - mu2: float, - sigma2: float, - ) -> float: + p1: float | FloatND, + mu1: float | FloatND, + sigma1: float | FloatND, + mu2: float | FloatND, + sigma2: float | FloatND, + ) -> float | FloatND: """Compute the variance of the mixture innovation.""" mean_eps = p1 * mu1 + (1 - p1) * mu2 return p1 * (sigma1**2 + mu1**2) + (1 - p1) * (sigma2**2 + mu2**2) - mean_eps**2 - def compute_gridpoints(self, **kwargs: float) -> Float1D: + def compute_gridpoints(self, **kwargs: float | FloatND) -> Float1D: n_points = self.n_points rho, mu = kwargs["rho"], kwargs["mu"] n_std = kwargs["n_std"] @@ -269,7 +269,7 @@ def compute_gridpoints(self, **kwargs: float) -> Float1D: x_max = n_std * std_y return jnp.linspace(long_run_mean - x_max, long_run_mean + x_max, n_points) - def compute_transition_probs(self, **kwargs: float) -> FloatND: + def compute_transition_probs(self, **kwargs: float | FloatND) -> FloatND: n_points = self.n_points rho, mu = kwargs["rho"], kwargs["mu"] n_std = kwargs["n_std"] @@ -305,8 +305,8 @@ def draw_shock( self, params: MappingProxyType[str, float | FloatND], key: Array, - current_value: Float1D, - ) -> Float1D: + current_value: ScalarFloat, + ) -> ScalarFloat: key1, key2 = jax.random.split(key) component = jax.random.bernoulli(key1, params["p1"]) normal = jax.random.normal(key2) diff --git a/src/lcm/shocks/iid.py b/src/lcm/shocks/iid.py index ddc4b5b13..c5e90b563 100644 --- a/src/lcm/shocks/iid.py +++ b/src/lcm/shocks/iid.py @@ -14,7 +14,7 @@ _ShockGrid, _validate_gauss_hermite_grid, ) -from lcm.typing import Float1D, FloatND +from lcm.typing import Float1D, FloatND, ScalarFloat @dataclass(frozen=True, kw_only=True) @@ -26,7 +26,7 @@ def draw_shock( self, params: MappingProxyType[str, float | FloatND], key: Array, - ) -> Float1D: ... + ) -> ScalarFloat: ... @beartype_init(GRID_CONF) @@ -45,12 +45,12 @@ class Uniform(_ShockGridIID): stop: float | int | None = None """Upper bound of the uniform distribution.""" - def compute_gridpoints(self, **kwargs: float) -> Float1D: + def compute_gridpoints(self, **kwargs: float | FloatND) -> Float1D: return jnp.linspace( start=kwargs["start"], stop=kwargs["stop"], num=self.n_points ) - def compute_transition_probs(self, **kwargs: float) -> FloatND: # noqa: ARG002 + def compute_transition_probs(self, **kwargs: float | FloatND) -> FloatND: # noqa: ARG002 n_points = self.n_points return jnp.full((n_points, n_points), fill_value=1 / n_points) @@ -58,7 +58,7 @@ def draw_shock( self, params: MappingProxyType[str, float | FloatND], key: Array, - ) -> Float1D: + ) -> ScalarFloat: return jax.random.uniform( key=key, minval=params["start"], maxval=params["stop"] ) @@ -100,7 +100,7 @@ def _param_field_names(self) -> tuple[str, ...]: exclude.add("n_std") return tuple(f.name for f in fields(self) if f.name not in exclude) - def compute_gridpoints(self, **kwargs: float) -> Float1D: + def compute_gridpoints(self, **kwargs: float | FloatND) -> Float1D: n_points = self.n_points mu, sigma = kwargs["mu"], kwargs["sigma"] if self.gauss_hermite: @@ -113,7 +113,7 @@ def compute_gridpoints(self, **kwargs: float) -> Float1D: x_max = mu + n_std * sigma return jnp.linspace(start=x_min, stop=x_max, num=n_points) - def compute_transition_probs(self, **kwargs: float) -> FloatND: + def compute_transition_probs(self, **kwargs: float | FloatND) -> FloatND: n_points = self.n_points mu, sigma = kwargs["mu"], kwargs["sigma"] if self.gauss_hermite: @@ -135,7 +135,7 @@ def draw_shock( self, params: MappingProxyType[str, float | FloatND], key: Array, - ) -> Float1D: + ) -> ScalarFloat: return params["mu"] + params["sigma"] * jax.random.normal(key=key) @@ -168,7 +168,7 @@ def _param_field_names(self) -> tuple[str, ...]: exclude.add("n_std") return tuple(f.name for f in fields(self) if f.name not in exclude) - def compute_gridpoints(self, **kwargs: float) -> Float1D: + def compute_gridpoints(self, **kwargs: float | FloatND) -> Float1D: n_points = self.n_points mu, sigma = kwargs["mu"], kwargs["sigma"] if self.gauss_hermite: @@ -179,7 +179,7 @@ def compute_gridpoints(self, **kwargs: float) -> Float1D: n_std = kwargs["n_std"] return jnp.exp(jnp.linspace(mu - n_std * sigma, mu + n_std * sigma, n_points)) - def compute_transition_probs(self, **kwargs: float) -> FloatND: + def compute_transition_probs(self, **kwargs: float | FloatND) -> FloatND: n_points = self.n_points mu, sigma = kwargs["mu"], kwargs["sigma"] if self.gauss_hermite: @@ -201,7 +201,7 @@ def draw_shock( self, params: MappingProxyType[str, float | FloatND], key: Array, - ) -> Float1D: + ) -> ScalarFloat: return jnp.exp(params["mu"] + params["sigma"] * jax.random.normal(key=key)) @@ -239,7 +239,7 @@ class NormalMixture(_ShockGridIID): sigma2: float | int | None = None """Standard deviation of the second mixture component.""" - def compute_gridpoints(self, **kwargs: float) -> Float1D: + def compute_gridpoints(self, **kwargs: float | FloatND) -> Float1D: n_points = self.n_points n_std = kwargs["n_std"] p1, mu1, sigma1 = kwargs["p1"], kwargs["mu1"], kwargs["sigma1"] @@ -254,7 +254,7 @@ def compute_gridpoints(self, **kwargs: float) -> Float1D: mean_eps - n_std * std_eps, mean_eps + n_std * std_eps, n_points ) - def compute_transition_probs(self, **kwargs: float) -> FloatND: + def compute_transition_probs(self, **kwargs: float | FloatND) -> FloatND: n_points = self.n_points n_std = kwargs["n_std"] p1, mu1, sigma1 = kwargs["p1"], kwargs["mu1"], kwargs["sigma1"] @@ -286,7 +286,7 @@ def draw_shock( self, params: MappingProxyType[str, float | FloatND], key: Array, - ) -> Float1D: + ) -> ScalarFloat: key1, key2 = jax.random.split(key) component = jax.random.bernoulli(key1, params["p1"]) normal = jax.random.normal(key2) diff --git a/tests/conftest.py b/tests/conftest.py index 2bc510b69..c95bd21a9 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,11 +1,25 @@ -from collections.abc import Iterator -from dataclasses import make_dataclass +# Beartype claw must instrument `lcm.grids`, `lcm.shocks`, and `lcm.params` +# before any submodule of those packages is imported. The registrations below +# install an import hook on `sys.meta_path` that transforms each matching +# module's AST at first import. Subsequent imports in this file must come +# after the registration, hence the `# noqa: E402` markers on them. -import jax.numpy as jnp -import pytest -from jax import config as jax_config +from beartype.claw import beartype_package -from lcm.typing import ScalarInt +from lcm._beartype_conf import GRID_CONF, PARAMS_CONF + +beartype_package("lcm.grids", conf=GRID_CONF) +beartype_package("lcm.shocks", conf=GRID_CONF) +beartype_package("lcm.params", conf=PARAMS_CONF) + +from collections.abc import Iterator # noqa: E402 +from dataclasses import make_dataclass # noqa: E402 + +import jax.numpy as jnp # noqa: E402 +import pytest # noqa: E402 +from jax import config as jax_config # noqa: E402 + +from lcm.typing import ScalarInt # noqa: E402 # Module-level precision settings (updated by pytest_configure based on --precision) X64_ENABLED: bool = True From c6d507889c2e595952fa7a42fa936c9df96e6bf8 Mon Sep 17 00:00:00 2001 From: Hans-Martin von Gaudecker Date: Wed, 13 May 2026 18:58:26 +0200 Subject: [PATCH 25/77] Fix sharding correctness in solve and forbid distributed actions Introduce a single per-regime sharding plan (`_build_regime_sharding` in `interfaces.py`) used by both `_distribute_states_to_devices` and the new `_get_regime_V_shapes_and_shardings` in `solve_brute.py`. State arrays and V-arrays share the same mesh, eliminating the device-set mismatch and the silent truncation in the multi-grid path. Reject `distributed=True` on action grids at `Regime` construction. Use a per-regime `_RegimeVTopology` so non-distributed regimes coexist with distributed ones without a `KeyError` on the sharding lookup. Co-Authored-By: Claude Opus 4.7 --- src/lcm/interfaces.py | 176 ++++++++++++++++++-------- src/lcm/regime_building/validation.py | 22 ++++ src/lcm/solution/solve_brute.py | 136 ++++++++------------ tests/test_distributed.py | 98 +++++++++++++- 4 files changed, 296 insertions(+), 136 deletions(-) diff --git a/src/lcm/interfaces.py b/src/lcm/interfaces.py index 4ab7a5626..b4faeea1c 100644 --- a/src/lcm/interfaces.py +++ b/src/lcm/interfaces.py @@ -1,7 +1,6 @@ import dataclasses from collections.abc import Callable -from functools import reduce -from operator import mul +from math import prod as math_prod from types import MappingProxyType from typing import cast @@ -308,71 +307,144 @@ def state_action_space(self, regime_params: FlatRegimeParams) -> StateActionSpac if action_replacements else dict(self._base_state_action_space.continuous_actions) ) - new_states = distribute_states_to_devices( - new_states=new_states, grids=self.grids + distributed_states = _distribute_states_to_devices( + states=MappingProxyType(new_states), grids=self.grids ) return self._base_state_action_space.replace( - states=MappingProxyType(new_states), + states=distributed_states, continuous_actions=MappingProxyType(new_continuous_actions), ) -def distribute_states_to_devices( - new_states: dict[StateName, ContinuousState], - grids: MappingProxyType[StateOrActionName, Grid], -) -> dict[StateName, ContinuousState]: +@dataclasses.dataclass(frozen=True) +class _RegimeSharding: + """Per-regime device-sharding plan for state and value-function arrays. + + The mesh has one axis per distributed state, named after the state. + `state_sharding` produces the 1-D sharding for a single state grid (or + array of subjects); `v_array_sharding` produces the multi-axis sharding + for the V-array given the order of states in the state-action space. + """ - distributed_states = new_states + mesh: jax.sharding.Mesh + """Device mesh whose axes are named after the distributed states.""" - avail_devices = jax.devices() + distributed_state_names: tuple[StateName, ...] + """Names of states whose axes appear in `mesh`.""" + + def state_sharding(self, state_name: StateName) -> jax.NamedSharding: + """Return the sharding for a single state's 1-D grid array.""" + return jax.NamedSharding(mesh=self.mesh, spec=jax.P(state_name)) + + def v_array_sharding(self, state_order: tuple[StateName, ...]) -> jax.NamedSharding: + """Return the sharding for a V-array whose axes are `state_order`.""" + spec = jax.P( + *( + name if name in self.distributed_state_names else None + for name in state_order + ) + ) + return jax.NamedSharding(mesh=self.mesh, spec=spec) + + +def _build_regime_sharding( + *, + grids: MappingProxyType[StateOrActionName, Grid], + n_devices: int, +) -> _RegimeSharding | None: + """Build a `_RegimeSharding` covering this regime's distributed grids. + + Returns `None` when no grid is distributed. Action grids are rejected at + `Regime` construction (see `regime_building.validation`); the helper + assumes any grid with `distributed=True` is a state grid. + + Sharding policy depends on the number of distributed grids: + - exactly one: build a 1-axis mesh with shape `(n_devices,)`, axis name + equal to the state name; the grid's axis is split into `n_devices` + chunks. Requires `n_points % n_devices == 0`. + - more than one: build a multi-axis mesh whose axes are the grid sizes + in iteration order, axis names equal to the state names; each state's + axis is scattered one element per device. Requires + `prod(grid_sizes) == n_devices` so every device is used exactly once. + + Args: + grids: Immutable mapping of state and action names to their grids. + n_devices: Number of available devices. + + Returns: + The regime's sharding plan, or `None` if no grid is distributed. + + """ distributed_grids = {name: grid for name, grid in grids.items() if grid.distributed} + if not distributed_grids: + return None + + state_names = tuple(distributed_grids.keys()) + grid_sizes = tuple(grid.to_jax().shape[0] for grid in distributed_grids.values()) + if len(distributed_grids) == 1: - state_name = next(iter(distributed_grids)) - n_points = distributed_grids[state_name].to_jax().shape[0] - if n_points % len(avail_devices) == 0: - mesh = jax.make_mesh( - (len(avail_devices),), - ("X"), - axis_types=(jax.sharding.AxisType.Auto,), - devices=avail_devices, - ) - distributed_states[state_name] = jax.device_put( - new_states[state_name], - jax.NamedSharding(mesh=mesh, spec=jax.P("X")), - ) - else: + n_points = grid_sizes[0] + if n_points % n_devices != 0: raise PyLCMError( - "When distributing over one grid, the number of points in the grid " - "needs to be a multiple of the available devices. Gridpoints: " - f" {n_points} Available Devices: {len(avail_devices)}" + "When distributing over one grid, the number of points must be " + "a multiple of the available devices. " + f"Gridpoints: {n_points} Available devices: {n_devices}" ) - if len(distributed_grids) > 1: - permutations = reduce( - mul, [grid.to_jax().shape[0] for grid in distributed_grids.values()] + mesh = jax.make_mesh( + (n_devices,), + state_names, + axis_types=(jax.sharding.AxisType.Auto,), + devices=jax.devices(), ) - if permutations <= len(avail_devices): - avail_devices = avail_devices[:permutations] - mesh = jax.make_mesh( - tuple(len(grid.to_jax()) for grid in distributed_grids.values()), - tuple(distributed_grids.keys()), - axis_types=tuple( - jax.sharding.AxisType.Auto for grid in distributed_grids - ), - devices=avail_devices, - ) - for state_name in distributed_grids: - distributed_states[state_name] = jax.device_put( - new_states[state_name], - jax.NamedSharding(mesh=mesh, spec=jax.P(state_name)), - ) - else: + else: + product = math_prod(grid_sizes) + if product != n_devices: raise PyLCMError( - "When distributing over multiple grids, the product of the" - " number of points of the grids needs to be smaller than the number" - f" of available devices. Gridpoints: {permutations} Available" - f"Devices: {len(avail_devices)}" + "When distributing over multiple grids, the product of the " + "number of points in the grids must equal the number of " + f"available devices. Gridpoints product: {product} " + f"Available devices: {n_devices}" ) - return distributed_states + mesh = jax.make_mesh( + grid_sizes, + state_names, + axis_types=tuple(jax.sharding.AxisType.Auto for _ in distributed_grids), + devices=jax.devices(), + ) + + return _RegimeSharding(mesh=mesh, distributed_state_names=state_names) + + +def _distribute_states_to_devices( + *, + states: MappingProxyType[StateName, Array], + grids: MappingProxyType[StateOrActionName, Grid], +) -> MappingProxyType[StateName, Array]: + """Place each distributed state's array on its device mesh. + + States whose grid carries `distributed=True` are placed via + `jax.device_put` onto the per-regime mesh; other states pass through + unchanged. The input mapping is treated as immutable. + + Args: + states: Immutable mapping of state names to their 1-D arrays. + grids: Immutable mapping of state and action names to their grids. + + Returns: + Immutable mapping with distributed states placed on the mesh and + every other state untouched. + + """ + sharding_plan = _build_regime_sharding(grids=grids, n_devices=len(jax.devices())) + if sharding_plan is None: + return states + placed = dict(states) + for state_name in sharding_plan.distributed_state_names: + placed[state_name] = jax.device_put( + states[state_name], + sharding_plan.state_sharding(state_name), + ) + return MappingProxyType(placed) @dataclasses.dataclass(frozen=True) diff --git a/src/lcm/regime_building/validation.py b/src/lcm/regime_building/validation.py index 92656692d..b51a0fdad 100644 --- a/src/lcm/regime_building/validation.py +++ b/src/lcm/regime_building/validation.py @@ -129,6 +129,7 @@ def validate_logical_consistency(regime: Regime) -> None: error_messages.extend(_validate_active(regime.active)) error_messages.extend(_validate_state_transitions(regime)) error_messages.extend(_validate_function_output_grid_indexing(regime)) + error_messages.extend(_validate_distributed_grids(regime)) states_and_actions_overlap = set(regime.states) & set(regime.actions) if states_and_actions_overlap: @@ -142,6 +143,27 @@ def validate_logical_consistency(regime: Regime) -> None: raise RegimeInitializationError(msg) +def _validate_distributed_grids(regime: Regime) -> list[str]: + """Reject `distributed=True` on action grids. + + Distribution shards the V-array along state axes; an action grid has no + corresponding V-array axis, so marking one as distributed has no + consistent meaning. To shard an axis a user cares about, set + `distributed=True` on the matching state. + """ + offending_actions = [ + name for name, grid in regime.actions.items() if grid.distributed + ] + if not offending_actions: + return [] + return [ + "Action grids cannot be marked `distributed=True` — distribution " + "shards V-array axes, which come from states. Move `distributed=True` " + "to the corresponding state grid. Offending actions: " + f"{offending_actions}.", + ] + + def _validate_function_output_grid_indexing(regime: Regime) -> list[str]: """Detect the regime-function-output / discrete-grid-indexed-input name clash. diff --git a/src/lcm/solution/solve_brute.py b/src/lcm/solution/solve_brute.py index 0b86b722a..3fe3bfd59 100644 --- a/src/lcm/solution/solve_brute.py +++ b/src/lcm/solution/solve_brute.py @@ -11,8 +11,8 @@ import jax.numpy as jnp from lcm.ages import AgeGrid -from lcm.interfaces import InternalRegime -from lcm.typing import FloatND, InternalParams, RegimeName +from lcm.interfaces import InternalRegime, _build_regime_sharding +from lcm.typing import FloatND, InternalParams, RegimeName, StateName from lcm.utils.error_handling import validate_V from lcm.utils.logging import ( format_duration, @@ -46,27 +46,19 @@ def solve( Immutable mapping of periods to regime value function arrays. """ - # Compute V array shapes and build a consistent next_regime_to_V_arr - # template. Using the same pytree structure (keys and shapes) across - # all periods avoids JIT re-compilation from pytree mismatches. - regime_V_shapes = _get_regime_V_shapes( - internal_regimes=internal_regimes, - internal_params=internal_params, - ) - - regime_V_shardings = _get_regime_V_shardings( + # Compute V array shapes (and their device shardings, if any) and build + # a consistent next_regime_to_V_arr template. Using the same pytree + # structure (keys and shapes) across all periods avoids JIT re- + # compilation from pytree mismatches. + regime_V_topology = _get_regime_V_shapes_and_shardings( internal_regimes=internal_regimes, internal_params=internal_params, ) next_regime_to_V_arr = MappingProxyType( { - regime_name: jax.device_put( - jnp.zeros(shape), regime_V_shardings[regime_name] - ) - if regime_V_shardings - else jnp.zeros(shape) - for regime_name, shape in regime_V_shapes.items() + regime_name: _build_zero_v_array(topology=topology) + for regime_name, topology in regime_V_topology.items() } ) @@ -395,80 +387,61 @@ def _func_dedup_key(*, func: Callable) -> Hashable: return id(func) -def _get_regime_V_shapes( +@dataclass(frozen=True) +class _RegimeVTopology: + """Shape and (optional) sharding of a single regime's V-array.""" + + shape: tuple[int, ...] + """V-array shape, with one entry per state.""" + + sharding: jax.NamedSharding | None + """Device sharding for the V-array, or `None` when no state is distributed.""" + + +def _get_regime_V_shapes_and_shardings( *, internal_regimes: MappingProxyType[RegimeName, InternalRegime], internal_params: InternalParams, -) -> dict[RegimeName, tuple[int, ...]]: - """Compute value function array shapes for all regimes. +) -> dict[RegimeName, _RegimeVTopology]: + """Compute V-array shapes and shardings for every regime. - The V array has one dimension per state variable, with size equal to - the number of grid points for that state. + The V-array has one dimension per state variable, sized by that state's + grid. When at least one state grid in a regime is distributed, the + V-array is sharded across devices along those axes; otherwise the + sharding is `None`. Args: - internal_regimes: The internal regimes. + internal_regimes: Immutable mapping of regime names to internal regimes. internal_params: Regime parameters (needed for runtime grid shapes). Returns: - Dict of regime names to V array shapes. + Dict of regime names to `_RegimeVTopology` (shape and sharding). """ - shapes: dict[RegimeName, tuple[int, ...]] = {} + n_devices = len(jax.devices()) + topology: dict[RegimeName, _RegimeVTopology] = {} for regime_name, regime in internal_regimes.items(): state_action_space = regime.state_action_space( regime_params=internal_params[regime_name], ) - shapes[regime_name] = tuple(len(v) for v in state_action_space.states.values()) - return shapes - - -def _get_regime_V_shardings( - *, - internal_regimes: MappingProxyType[RegimeName, InternalRegime], - internal_params: InternalParams, -) -> dict[RegimeName, jax.NamedSharding]: - """Compute value function array shardings for all regimes. - - The V-Array is distributed over all axes that belong to a distributed state. - - Args: - internal_regimes: The internal regimes. - internal_params: Regime parameters (needed for runtime grid shapes). + state_order: tuple[StateName, ...] = tuple(state_action_space.states.keys()) + shape = tuple(len(v) for v in state_action_space.states.values()) + sharding_plan = _build_regime_sharding(grids=regime.grids, n_devices=n_devices) + sharding = ( + sharding_plan.v_array_sharding(state_order) + if sharding_plan is not None + else None + ) + topology[regime_name] = _RegimeVTopology(shape=shape, sharding=sharding) + return topology - Returns: - Dict of regime names to V array shardings. Empty if no state is distributed. - """ - shardings: dict[RegimeName, jax.NamedSharding] = {} - avail_devices = jax.devices() - for regime_name, regime in internal_regimes.items(): - curr_regime_grids = regime.grids - if any(grid.distributed for grid in curr_regime_grids.values()): - state_action_space = regime.state_action_space( - regime_params=internal_params[regime_name], - ) - spec = [] - for name in state_action_space.states: - if curr_regime_grids[name].distributed: - spec.append(name) - else: - spec.append(None) - dist_shape = tuple( - len(v) - for name, v in state_action_space.states.items() - if curr_regime_grids[name].distributed - ) - mesh = jax.make_mesh( - dist_shape, - tuple(name for name in spec if name is not None), - axis_types=tuple( - jax.sharding.AxisType.Auto for i in range(len(dist_shape)) - ), - devices=avail_devices, - ) - sharding = jax.sharding.NamedSharding(mesh=mesh, spec=jax.P(*spec)) - shardings[regime_name] = sharding - return shardings +def _build_zero_v_array(*, topology: _RegimeVTopology) -> jax.Array: + """Build the zero V-array template for a regime, sharded where requested.""" + zeros = jnp.zeros(topology.shape) + if topology.sharding is None: + return zeros + return jax.device_put(zeros, topology.sharding) @dataclass(frozen=True) @@ -615,23 +588,26 @@ def _reconstruct_next_regime_to_V_arr( holds its V from the smallest later period where it was active, falling back to a zeros template otherwise. - We rebuild the same mapping post-hoc from `solution`. The shapes come from - the regime's state-action space at the supplied params — identical to what - `_get_regime_V_shapes_and_shardings` saw during solve setup. + Rebuild the same mapping post-hoc from `solution`. Shape and device + sharding both come from `_get_regime_V_shapes_and_shardings` so the + reconstructed templates have the same pytree structure and placement as + the live ones in `solve()`. """ - regime_V_shapes_and_shardings = _get_regime_V_shapes( + regime_V_topology = _get_regime_V_shapes_and_shardings( internal_regimes=internal_regimes, internal_params=internal_params, ) later_periods = sorted(p for p in solution if p > period) result: dict[RegimeName, FloatND] = {} - for regime_name, shape in regime_V_shapes_and_shardings.items(): + for regime_name, topology in regime_V_topology.items(): rolled: FloatND | None = None for q in later_periods: if regime_name in solution[q]: rolled = solution[q][regime_name] break - result[regime_name] = rolled if rolled is not None else jnp.zeros(shape) + result[regime_name] = ( + rolled if rolled is not None else _build_zero_v_array(topology=topology) + ) return MappingProxyType(result) diff --git a/tests/test_distributed.py b/tests/test_distributed.py index b7de4919e..891a89f24 100644 --- a/tests/test_distributed.py +++ b/tests/test_distributed.py @@ -3,7 +3,7 @@ from jax import numpy as jnp from lcm.ages import AgeGrid -from lcm.exceptions import PyLCMError +from lcm.exceptions import PyLCMError, RegimeInitializationError from lcm.grids import categorical from lcm.grids.continuous import LinSpacedGrid from lcm.grids.discrete import DiscreteGrid @@ -180,10 +180,10 @@ def test_simulation_running_on_multiple_cpus(correct_distributed_model): @_skip_pytest_parallel -def test_solution_error_if_not_multiple(wrong_distributed_model): - """Test that solution throws error if too many states for num cpus.""" +def test_solution_error_if_grid_product_exceeds_devices(wrong_distributed_model): + """Solve raises when the product of distributed grid sizes exceeds devices.""" - with pytest.raises(PyLCMError, match="smaller than the number"): + with pytest.raises(PyLCMError, match="must equal the number"): wrong_distributed_model.solve( params={"discount_factor": 0.95}, ) @@ -206,3 +206,93 @@ def test_simulation_error_if_not_multiple(correct_distributed_model): period_to_regime_to_V_arr=None, seed=12345, ) + + +@pytest.fixture +def partially_distributed_model(): + """Model where one regime has distributed grids and the other does not.""" + + @categorical(ordered=False) + class RegimeId: + working_life: ScalarInt + retirement: ScalarInt + + @categorical(ordered=True) + class Type: + low: ScalarInt + high: ScalarInt + + working_life = Regime( + functions={ + "utility": lambda wealth, consumption, type1, type2: ( + (jnp.log(consumption) + wealth * 0.001) * type1 * type2 + ), + }, + states={ + "wealth": LinSpacedGrid(start=1, stop=100, n_points=10), + "type1": DiscreteGrid(Type, distributed=True), + "type2": DiscreteGrid(Type, distributed=True), + }, + state_transitions={ + "wealth": lambda wealth, consumption: wealth - consumption, + "type1": None, + "type2": None, + }, + actions={"consumption": LinSpacedGrid(start=1, stop=50, n_points=10)}, + transition=lambda age: jnp.where( + age >= 4, RegimeId.retirement, RegimeId.working_life + ), + active=lambda age: age < 5, + ) + + retirement = Regime( + transition=None, + functions={"utility": lambda wealth: wealth * 0.5}, + states={"wealth": LinSpacedGrid(start=1, stop=100, n_points=10)}, + active=lambda age: age >= 5, + ) + + return Model( + regimes={"working_life": working_life, "retirement": retirement}, + ages=AgeGrid(start=0, stop=5, step="Y"), + regime_id_class=RegimeId, + ) + + +@_skip_pytest_parallel +def test_solve_with_partial_distribution_returns_correct_shardings( + partially_distributed_model, +): + """Mixed-regime models solve cleanly: distributed regimes get sharded V-arrays. + + The distributed regime's V-array is sharded across all devices; the + undistributed regime's V-array carries no per-axis sharding (single device). + """ + period_to_regime_to_V_arr = partially_distributed_model.solve( + params={"discount_factor": 0.95}, + ) + assert period_to_regime_to_V_arr[0]["working_life"].sharding.num_devices == 4 + assert period_to_regime_to_V_arr[5]["retirement"].sharding.num_devices == 1 + + +def test_distributed_action_grid_raises_at_regime_init(): + """Action grids cannot be distributed; constructing a `Regime` with one raises. + + Distribution is a property of state axes (which form the V-array shape). + Marking an action grid as distributed has no consistent meaning under the + current sharding model, so it is rejected at construction time. + """ + with pytest.raises(RegimeInitializationError, match="distributed=True"): + Regime( + functions={"utility": jnp.log}, + states={"wealth": LinSpacedGrid(start=1, stop=100, n_points=10)}, + state_transitions={ + "wealth": lambda wealth, consumption: wealth - consumption, + }, + actions={ + "consumption": LinSpacedGrid( + start=1, stop=50, n_points=10, distributed=True + ), + }, + transition=lambda age: age, + ) From 0e46529fb3742c95db828b2b4cd401c6e010b967 Mon Sep 17 00:00:00 2001 From: Hans-Martin von Gaudecker Date: Wed, 13 May 2026 18:58:37 +0200 Subject: [PATCH 26/77] Hoist non-parameter field set on shock grids `_NON_PARAM_FIELDS` on `_ShockGrid` lists which dataclass fields are not distribution parameters and so must be excluded from `params`. Subclasses extend it via `cls._NON_PARAM_FIELDS | {...}`. This keeps `distributed` (and any future `ContinuousGrid` field) from leaking into `params` when new fields are added at the base. Co-Authored-By: Claude Opus 4.7 --- src/lcm/shocks/_base.py | 13 +++++++++++-- src/lcm/shocks/ar1.py | 4 ++-- src/lcm/shocks/iid.py | 8 ++++---- tests/test_shock_grids.py | 23 +++++++++++++++++++++++ 4 files changed, 40 insertions(+), 8 deletions(-) diff --git a/src/lcm/shocks/_base.py b/src/lcm/shocks/_base.py index 55d4ac79b..24e33fcbd 100644 --- a/src/lcm/shocks/_base.py +++ b/src/lcm/shocks/_base.py @@ -1,7 +1,7 @@ from abc import abstractmethod from dataclasses import dataclass, fields from types import MappingProxyType -from typing import overload +from typing import ClassVar, overload import jax.numpy as jnp import numpy as np @@ -42,11 +42,20 @@ class _ShockGrid(ContinuousGrid): n_points: int """The number of points for the discretization of the shock.""" + _NON_PARAM_FIELDS: ClassVar[frozenset[str]] = frozenset( + {"n_points", "batch_size", "distributed"} + ) + """Dataclass field names that are not distribution parameters. + + Subclasses extend this via `cls._NON_PARAM_FIELDS | {...}` when they + introduce further non-parameter fields (e.g. `gauss_hermite`). + """ + @property def _param_field_names(self) -> tuple[str, ...]: """Names of distribution-specific parameters.""" return tuple( - f.name for f in fields(self) if f.name not in {"n_points", "batch_size"} + f.name for f in fields(self) if f.name not in self._NON_PARAM_FIELDS ) @property diff --git a/src/lcm/shocks/ar1.py b/src/lcm/shocks/ar1.py index a42b39455..b307a7b78 100644 --- a/src/lcm/shocks/ar1.py +++ b/src/lcm/shocks/ar1.py @@ -70,9 +70,9 @@ def __post_init__(self) -> None: @property def _param_field_names(self) -> tuple[str, ...]: - exclude = {"n_points", "batch_size", "gauss_hermite"} + exclude = self._NON_PARAM_FIELDS | {"gauss_hermite"} if self.gauss_hermite: - exclude.add("n_std") + exclude = exclude | {"n_std"} return tuple(f.name for f in fields(self) if f.name not in exclude) def compute_gridpoints(self, **kwargs: float) -> Float1D: diff --git a/src/lcm/shocks/iid.py b/src/lcm/shocks/iid.py index 506057389..60096024c 100644 --- a/src/lcm/shocks/iid.py +++ b/src/lcm/shocks/iid.py @@ -91,9 +91,9 @@ def __post_init__(self) -> None: @property def _param_field_names(self) -> tuple[str, ...]: - exclude = {"n_points", "batch_size", "gauss_hermite"} + exclude = self._NON_PARAM_FIELDS | {"gauss_hermite"} if self.gauss_hermite: - exclude.add("n_std") + exclude = exclude | {"n_std"} return tuple(f.name for f in fields(self) if f.name not in exclude) def compute_gridpoints(self, **kwargs: float) -> Float1D: @@ -158,9 +158,9 @@ def __post_init__(self) -> None: @property def _param_field_names(self) -> tuple[str, ...]: - exclude = {"n_points", "batch_size", "gauss_hermite"} + exclude = self._NON_PARAM_FIELDS | {"gauss_hermite"} if self.gauss_hermite: - exclude.add("n_std") + exclude = exclude | {"n_std"} return tuple(f.name for f in fields(self) if f.name not in exclude) def compute_gridpoints(self, **kwargs: float) -> Float1D: diff --git a/tests/test_shock_grids.py b/tests/test_shock_grids.py index e22d2571a..256247f03 100644 --- a/tests/test_shock_grids.py +++ b/tests/test_shock_grids.py @@ -290,6 +290,29 @@ def test_normal_gauss_hermite_n_std_not_in_params(): assert "n_std" not in grid.params_to_pass_at_runtime +@pytest.mark.parametrize( + "grid", + [ + lcm.shocks.iid.Normal(n_points=5, gauss_hermite=True, mu=0.0, sigma=1.0), + lcm.shocks.iid.LogNormal(n_points=5, gauss_hermite=True, mu=0.0, sigma=1.0), + lcm.shocks.iid.Uniform(n_points=5, start=0.0, stop=1.0), + lcm.shocks.ar1.Tauchen( + n_points=5, gauss_hermite=True, rho=0.9, sigma=0.5, mu=0.0 + ), + ], + ids=["normal", "lognormal", "uniform", "tauchen"], +) +def test_shock_grid_params_excludes_distributed(grid): + """`distributed` is a placement flag, not a distribution parameter. + + `ContinuousGrid` exposes `distributed` as a field, but it must not leak + into `params` (the mapping of distribution-specific parameter names to + their values used by `compute_gridpoints`). + """ + assert "distributed" not in grid.params + assert "distributed" not in grid.params_to_pass_at_runtime + + def test_tauchen_gauss_hermite_transition_probs_rows_sum_to_one(): """Each row of the GH Tauchen transition matrix sums to 1.""" grid = lcm.shocks.ar1.Tauchen( From 984fac01a1b7f71871a8edb29d5c827672d8efc0 Mon Sep 17 00:00:00 2001 From: Hans-Martin von Gaudecker Date: Wed, 13 May 2026 18:58:43 +0200 Subject: [PATCH 27/77] Fix DiscreteGrid.distributed and abstract docstring `DiscreteGrid.distributed` returned `self.__distributed` (a bool) but carried a copy-pasted docstring describing `batch_size`. Replace with the correct one and tighten the abstract base's docstring while we are here. Co-Authored-By: Claude Opus 4.7 --- src/lcm/grids/base.py | 6 +++--- src/lcm/grids/discrete.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lcm/grids/base.py b/src/lcm/grids/base.py index 4a0a071e9..2c7dde502 100644 --- a/src/lcm/grids/base.py +++ b/src/lcm/grids/base.py @@ -19,10 +19,10 @@ def batch_size(self) -> int: @property @abstractmethod def distributed(self) -> bool: - """Whether to distribute the grid over the available devices. + """Whether to shard the grid's state axis across available devices. - `ContinuousGrid` overrides this via its dataclass field. - `DiscreteGrid` overrides this via its own property. + `ContinuousGrid` exposes this as a dataclass field; `DiscreteGrid` + exposes it as a property over a private field. """ diff --git a/src/lcm/grids/discrete.py b/src/lcm/grids/discrete.py index 32f007fae..f6fa98043 100644 --- a/src/lcm/grids/discrete.py +++ b/src/lcm/grids/discrete.py @@ -56,7 +56,7 @@ def batch_size(self) -> int: @property def distributed(self) -> bool: - """Return batch size during solution.""" + """Return whether the grid is sharded across available devices.""" return self.__distributed def to_jax(self) -> Int1D: From e107250a3b4733492f57564d41b60559150c958a Mon Sep 17 00:00:00 2001 From: Hans-Martin von Gaudecker Date: Wed, 13 May 2026 19:04:05 +0200 Subject: [PATCH 28/77] Add KeyArray alias for shock-sampler PRNGKey parameters `KeyArray = Array` makes the `key:` parameter on every `draw_shock` explicit about expecting a PRNG key rather than a float array. PRNGKey arrays carry dtype `key` and don't match `FloatND`, so using `KeyArray` is the natural type slot for those signatures. Co-Authored-By: Claude Opus 4.7 --- src/lcm/shocks/ar1.py | 11 +++++------ src/lcm/shocks/iid.py | 13 ++++++------- src/lcm/typing.py | 5 +++++ 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/src/lcm/shocks/ar1.py b/src/lcm/shocks/ar1.py index eb100e18e..8829bbbe9 100644 --- a/src/lcm/shocks/ar1.py +++ b/src/lcm/shocks/ar1.py @@ -5,7 +5,6 @@ import jax import jax.numpy as jnp -from jax import Array from jax.scipy.stats.norm import cdf from lcm._beartype_conf import GRID_CONF, beartype_init @@ -15,7 +14,7 @@ _ShockGrid, _validate_gauss_hermite_grid, ) -from lcm.typing import Float1D, FloatND, ScalarFloat +from lcm.typing import Float1D, FloatND, KeyArray, ScalarFloat @dataclass(frozen=True, kw_only=True) @@ -26,7 +25,7 @@ class _ShockGridAR1(_ShockGrid): def draw_shock( self, params: MappingProxyType[str, float | FloatND], - key: Array, + key: KeyArray, current_value: ScalarFloat, ) -> ScalarFloat: ... @@ -121,7 +120,7 @@ def compute_transition_probs(self, **kwargs: float | FloatND) -> FloatND: def draw_shock( self, params: MappingProxyType[str, float | FloatND], - key: Array, + key: KeyArray, current_value: ScalarFloat, ) -> ScalarFloat: return ( @@ -190,7 +189,7 @@ def compute_transition_probs(self, **kwargs: float | FloatND) -> FloatND: def draw_shock( self, params: MappingProxyType[str, float | FloatND], - key: Array, + key: KeyArray, current_value: ScalarFloat, ) -> ScalarFloat: return ( @@ -304,7 +303,7 @@ def compute_transition_probs(self, **kwargs: float | FloatND) -> FloatND: def draw_shock( self, params: MappingProxyType[str, float | FloatND], - key: Array, + key: KeyArray, current_value: ScalarFloat, ) -> ScalarFloat: key1, key2 = jax.random.split(key) diff --git a/src/lcm/shocks/iid.py b/src/lcm/shocks/iid.py index c5e90b563..d9dcb4c7c 100644 --- a/src/lcm/shocks/iid.py +++ b/src/lcm/shocks/iid.py @@ -4,7 +4,6 @@ import jax import jax.numpy as jnp -from jax import Array from jax.scipy.stats.norm import cdf from lcm._beartype_conf import GRID_CONF, beartype_init @@ -14,7 +13,7 @@ _ShockGrid, _validate_gauss_hermite_grid, ) -from lcm.typing import Float1D, FloatND, ScalarFloat +from lcm.typing import Float1D, FloatND, KeyArray, ScalarFloat @dataclass(frozen=True, kw_only=True) @@ -25,7 +24,7 @@ class _ShockGridIID(_ShockGrid): def draw_shock( self, params: MappingProxyType[str, float | FloatND], - key: Array, + key: KeyArray, ) -> ScalarFloat: ... @@ -57,7 +56,7 @@ def compute_transition_probs(self, **kwargs: float | FloatND) -> FloatND: # noq def draw_shock( self, params: MappingProxyType[str, float | FloatND], - key: Array, + key: KeyArray, ) -> ScalarFloat: return jax.random.uniform( key=key, minval=params["start"], maxval=params["stop"] @@ -134,7 +133,7 @@ def compute_transition_probs(self, **kwargs: float | FloatND) -> FloatND: def draw_shock( self, params: MappingProxyType[str, float | FloatND], - key: Array, + key: KeyArray, ) -> ScalarFloat: return params["mu"] + params["sigma"] * jax.random.normal(key=key) @@ -200,7 +199,7 @@ def compute_transition_probs(self, **kwargs: float | FloatND) -> FloatND: def draw_shock( self, params: MappingProxyType[str, float | FloatND], - key: Array, + key: KeyArray, ) -> ScalarFloat: return jnp.exp(params["mu"] + params["sigma"] * jax.random.normal(key=key)) @@ -285,7 +284,7 @@ def compute_transition_probs(self, **kwargs: float | FloatND) -> FloatND: def draw_shock( self, params: MappingProxyType[str, float | FloatND], - key: Array, + key: KeyArray, ) -> ScalarFloat: key1, key2 = jax.random.split(key) component = jax.random.bernoulli(key1, params["p1"]) diff --git a/src/lcm/typing.py b/src/lcm/typing.py index 6a340097f..8685241fb 100644 --- a/src/lcm/typing.py +++ b/src/lcm/typing.py @@ -27,6 +27,11 @@ type ScalarFloat = Float[Scalar, ""] type ScalarBool = Bool[Scalar, ""] +# JAX PRNG keys (jax.random) have dtype `key` and are not float-typed, +# so they don't match `FloatND`. `KeyArray` keeps the perimeter check on shock +# samplers explicit about what kind of array they expect. +type KeyArray = Array + type Period = ScalarInt type Age = ScalarInt | ScalarFloat type RegimeName = str From 6e34efb7caa4d3cc1db52d26d3af9737d4ddd541 Mon Sep 17 00:00:00 2001 From: Hans-Martin von Gaudecker Date: Wed, 13 May 2026 21:10:30 +0200 Subject: [PATCH 29/77] =?UTF-8?q?Rename=20v=5Farray=20=E2=86=92=20V=5Farr?= =?UTF-8?q?=20for=20consistency=20across=20the=20solve=20path?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The rest of the file already uses `V_arr` / `next_regime_to_V_arr` everywhere; `_build_zero_v_array` and `_RegimeSharding.v_array_sharding` were the only lowercase holdouts. Co-Authored-By: Claude Opus 4.7 --- src/lcm/interfaces.py | 4 ++-- src/lcm/solution/solve_brute.py | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/lcm/interfaces.py b/src/lcm/interfaces.py index b4faeea1c..b4c76ff30 100644 --- a/src/lcm/interfaces.py +++ b/src/lcm/interfaces.py @@ -322,7 +322,7 @@ class _RegimeSharding: The mesh has one axis per distributed state, named after the state. `state_sharding` produces the 1-D sharding for a single state grid (or - array of subjects); `v_array_sharding` produces the multi-axis sharding + array of subjects); `V_arr_sharding` produces the multi-axis sharding for the V-array given the order of states in the state-action space. """ @@ -336,7 +336,7 @@ def state_sharding(self, state_name: StateName) -> jax.NamedSharding: """Return the sharding for a single state's 1-D grid array.""" return jax.NamedSharding(mesh=self.mesh, spec=jax.P(state_name)) - def v_array_sharding(self, state_order: tuple[StateName, ...]) -> jax.NamedSharding: + def V_arr_sharding(self, state_order: tuple[StateName, ...]) -> jax.NamedSharding: """Return the sharding for a V-array whose axes are `state_order`.""" spec = jax.P( *( diff --git a/src/lcm/solution/solve_brute.py b/src/lcm/solution/solve_brute.py index 3fe3bfd59..3a9a8b9ed 100644 --- a/src/lcm/solution/solve_brute.py +++ b/src/lcm/solution/solve_brute.py @@ -57,7 +57,7 @@ def solve( next_regime_to_V_arr = MappingProxyType( { - regime_name: _build_zero_v_array(topology=topology) + regime_name: _build_zero_V_arr(topology=topology) for regime_name, topology in regime_V_topology.items() } ) @@ -428,7 +428,7 @@ def _get_regime_V_shapes_and_shardings( shape = tuple(len(v) for v in state_action_space.states.values()) sharding_plan = _build_regime_sharding(grids=regime.grids, n_devices=n_devices) sharding = ( - sharding_plan.v_array_sharding(state_order) + sharding_plan.V_arr_sharding(state_order) if sharding_plan is not None else None ) @@ -436,7 +436,7 @@ def _get_regime_V_shapes_and_shardings( return topology -def _build_zero_v_array(*, topology: _RegimeVTopology) -> jax.Array: +def _build_zero_V_arr(*, topology: _RegimeVTopology) -> jax.Array: """Build the zero V-array template for a regime, sharded where requested.""" zeros = jnp.zeros(topology.shape) if topology.sharding is None: @@ -606,7 +606,7 @@ def _reconstruct_next_regime_to_V_arr( rolled = solution[q][regime_name] break result[regime_name] = ( - rolled if rolled is not None else _build_zero_v_array(topology=topology) + rolled if rolled is not None else _build_zero_V_arr(topology=topology) ) return MappingProxyType(result) From 59aca3f5f349115006c9672373a886a3f9a276a2 Mon Sep 17 00:00:00 2001 From: Hans-Martin von Gaudecker Date: Wed, 13 May 2026 21:57:25 +0200 Subject: [PATCH 30/77] Activate beartype claw at lcm.__init__ + fix surfaced drift MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Moves the `beartype_package(...)` registrations from `tests/conftest.py` into `lcm/__init__.py` so the claw actually instruments `lcm.grids`, `lcm.shocks`, and `lcm.params` at first import. The conftest version ran AFTER `from lcm._beartype_conf import ...` triggered `lcm/__init__.py`, which eagerly imports every targeted submodule — the claw was a no-op on every prior run. Live-claw drift fixed: - `Piece` `n_points` cumulative sum: explicit `dtype=jnp.int32` on the `_piece_n_points` array + `sum(dtype=jnp.int32)` so the property honours its `ScalarInt` annotation under `jax_enable_x64=True`. - `_ShockGrid.compute_gridpoints` / `compute_transition_probs` / `Tauchen._innovation_variance`: kwargs accept `float | FloatND | IntND`; runtime params (e.g. `n_std=2`) flow through as 0-d int arrays after pylcm's canonical-dtype cast. - `UniformContinuousGrid` / `LinSpacedGrid` / `LogSpacedGrid` / `_ShockGrid` `get_coordinate`: accept Python `float` / `int` alongside `ScalarFloat | FloatND`. Same widening on `get_irreg_coordinate`. - `process_params(params_template=...)`: widen to `Mapping[RegimeName, Mapping[str, object]]` since real callers and test mocks both use plain dicts at the inner levels (the strict `MappingProxyType[...]`-only alias rejected them). - `_ParamsLeaf`: add `np.ndarray` so numpy-array params (used in dtype-invariant tests) pass the perimeter check before pylcm casts them. - `create_regime_params_template(regime: ...)` and helpers, `create_params_template(internal_regimes: ...)`: typed as `Any` so duck-typed test mocks satisfy the signature (the functions themselves only touch a small structural interface — `states`, `actions`, `functions`, `regime_params_template`). - `test_as_leaf_rejects_int` now expects `InvalidParamsError` (the configured `PARAMS_CONF` exception), matching the claw-routed violation; the in-function `TypeError` raise no longer fires because beartype rejects the argument first. Drops the now-redundant claw setup from `tests/conftest.py` and strips the `# ty: ignore[invalid-argument-type]` / `[no-matching- overload]` markers the widenings make unnecessary. Co-Authored-By: Claude Opus 4.7 --- src/lcm/__init__.py | 38 +++++++++++++------ src/lcm/grids/continuous.py | 30 ++++++++++----- src/lcm/grids/coordinates.py | 4 +- src/lcm/grids/piecewise.py | 10 +++-- src/lcm/params/processing.py | 9 +++-- src/lcm/params/regime_template.py | 15 +++++--- src/lcm/shocks/_base.py | 12 +++--- src/lcm/shocks/ar1.py | 14 +++---- src/lcm/shocks/iid.py | 18 ++++----- src/lcm/typing.py | 5 ++- tests/conftest.py | 26 +++---------- .../test_create_regime_params_template.py | 14 +++---- tests/regime_building/test_process_params.py | 14 +++---- tests/test_float_dtype_invariants.py | 14 +++---- tests/test_int_dtype_invariants.py | 12 +++--- tests/test_mapping_leaf.py | 3 +- tests/test_validate_param_types.py | 2 +- 17 files changed, 132 insertions(+), 108 deletions(-) diff --git a/src/lcm/__init__.py b/src/lcm/__init__.py index d6d267568..bb00b6127 100644 --- a/src/lcm/__init__.py +++ b/src/lcm/__init__.py @@ -28,10 +28,26 @@ with contextlib.suppress(ImportError): import pdbp # noqa: F401 -from lcm import shocks -from lcm._version import __version__ -from lcm.ages import AgeGrid -from lcm.grids import ( +# Install beartype's AST-rewriting claw on `lcm.grids`, `lcm.shocks`, +# and `lcm.params` before any submodule of those packages is imported. +# The claw transforms each matching module's AST at first import to +# insert runtime type checks; if it isn't registered before the import +# happens, the affected module loads uninstrumented and `sys.modules` +# caches the unchecked version for the rest of the process. The +# per-package `BeartypeConf` maps type violations to the project +# exception most natural to that subpackage (see `lcm._beartype_conf`). +from beartype.claw import beartype_package + +from lcm._beartype_conf import GRID_CONF, PARAMS_CONF + +beartype_package("lcm.grids", conf=GRID_CONF) +beartype_package("lcm.shocks", conf=GRID_CONF) +beartype_package("lcm.params", conf=PARAMS_CONF) + +from lcm import shocks # noqa: E402 +from lcm._version import __version__ # noqa: E402 +from lcm.ages import AgeGrid # noqa: E402 +from lcm.grids import ( # noqa: E402 DiscreteGrid, IrregSpacedGrid, LinSpacedGrid, @@ -41,19 +57,19 @@ PiecewiseLogSpacedGrid, categorical, ) -from lcm.interfaces import SolveSimulateFunctionPair -from lcm.model import Model -from lcm.persistence import ( +from lcm.interfaces import SolveSimulateFunctionPair # noqa: E402 +from lcm.model import Model # noqa: E402 +from lcm.persistence import ( # noqa: E402 SimulateSnapshot, SolveSnapshot, load_snapshot, load_solution, save_solution, ) -from lcm.regime import MarkovTransition, Regime -from lcm.simulation.result import SimulationResult -from lcm.utils.containers import invert_regime_ids -from lcm.utils.error_handling import validate_transition_probs +from lcm.regime import MarkovTransition, Regime # noqa: E402 +from lcm.simulation.result import SimulationResult # noqa: E402 +from lcm.utils.containers import invert_regime_ids # noqa: E402 +from lcm.utils.error_handling import validate_transition_probs # noqa: E402 # Register MappingProxyType as a JAX pytree so it can be used in JIT-traced functions. # This allows regime transition probabilities to use immutable mappings. diff --git a/src/lcm/grids/continuous.py b/src/lcm/grids/continuous.py index 4dad41793..8bf7c9766 100644 --- a/src/lcm/grids/continuous.py +++ b/src/lcm/grids/continuous.py @@ -34,11 +34,13 @@ class ContinuousGrid(Grid): """Whether to distribute the grid over the available devices.""" @overload - def get_coordinate(self, value: ScalarFloat) -> ScalarFloat: ... + def get_coordinate(self, value: float | ScalarFloat) -> ScalarFloat: ... @overload def get_coordinate(self, value: FloatND) -> FloatND: ... @abstractmethod - def get_coordinate(self, value: ScalarFloat | FloatND) -> ScalarFloat | FloatND: + def get_coordinate( + self, value: float | ScalarFloat | FloatND + ) -> ScalarFloat | FloatND: """Return the generalized coordinate of a value in the grid.""" @@ -84,11 +86,13 @@ def to_jax(self) -> Float1D: """Convert the grid to a Jax array.""" @overload - def get_coordinate(self, value: ScalarFloat) -> ScalarFloat: ... + def get_coordinate(self, value: float | ScalarFloat) -> ScalarFloat: ... @overload def get_coordinate(self, value: FloatND) -> FloatND: ... @abstractmethod - def get_coordinate(self, value: ScalarFloat | FloatND) -> ScalarFloat | FloatND: + def get_coordinate( + self, value: float | ScalarFloat | FloatND + ) -> ScalarFloat | FloatND: """Return the generalized coordinate of a value in the grid.""" def replace(self, **kwargs: float) -> UniformContinuousGrid: @@ -126,10 +130,12 @@ def to_jax(self) -> Float1D: ) @overload - def get_coordinate(self, value: ScalarFloat) -> ScalarFloat: ... + def get_coordinate(self, value: float | ScalarFloat) -> ScalarFloat: ... @overload def get_coordinate(self, value: FloatND) -> FloatND: ... - def get_coordinate(self, value: ScalarFloat | FloatND) -> ScalarFloat | FloatND: + def get_coordinate( + self, value: float | ScalarFloat | FloatND + ) -> ScalarFloat | FloatND: """Return the generalized coordinate of a value in the grid.""" return grid_coordinates.get_linspace_coordinate( value=value, @@ -177,10 +183,12 @@ def to_jax(self) -> Float1D: ) @overload - def get_coordinate(self, value: ScalarFloat) -> ScalarFloat: ... + def get_coordinate(self, value: float | ScalarFloat) -> ScalarFloat: ... @overload def get_coordinate(self, value: FloatND) -> FloatND: ... - def get_coordinate(self, value: ScalarFloat | FloatND) -> ScalarFloat | FloatND: + def get_coordinate( + self, value: float | ScalarFloat | FloatND + ) -> ScalarFloat | FloatND: """Return the generalized coordinate of a value in the grid.""" return grid_coordinates.get_logspace_coordinate( value=value, @@ -313,10 +321,12 @@ def to_jax(self) -> Float1D: return self.points @overload - def get_coordinate(self, value: ScalarFloat) -> ScalarFloat: ... + def get_coordinate(self, value: float | ScalarFloat) -> ScalarFloat: ... @overload def get_coordinate(self, value: FloatND) -> FloatND: ... - def get_coordinate(self, value: ScalarFloat | FloatND) -> ScalarFloat | FloatND: + def get_coordinate( + self, value: float | ScalarFloat | FloatND + ) -> ScalarFloat | FloatND: """Return the generalized coordinate of a value in the grid.""" if self.points is None: raise GridInitializationError( diff --git a/src/lcm/grids/coordinates.py b/src/lcm/grids/coordinates.py index 1541484c1..527b94754 100644 --- a/src/lcm/grids/coordinates.py +++ b/src/lcm/grids/coordinates.py @@ -145,7 +145,7 @@ def get_logspace_coordinate( @overload def get_irreg_coordinate( *, - value: ScalarFloat, + value: float | ScalarFloat, points: Float1D, ) -> ScalarFloat: ... @overload @@ -156,7 +156,7 @@ def get_irreg_coordinate( ) -> FloatND: ... def get_irreg_coordinate( *, - value: ScalarFloat | FloatND, + value: float | ScalarFloat | FloatND, points: Float1D, ) -> ScalarFloat | FloatND: """Return the generalized coordinate of a value in an irregularly spaced grid. diff --git a/src/lcm/grids/piecewise.py b/src/lcm/grids/piecewise.py index c7a1ac791..4055a0453 100644 --- a/src/lcm/grids/piecewise.py +++ b/src/lcm/grids/piecewise.py @@ -87,7 +87,7 @@ def __post_init__(self) -> None: @property def n_points(self) -> ScalarInt: """Return the total number of points in the grid.""" - return self._piece_n_points.sum() + return self._piece_n_points.sum(dtype=jnp.int32) def to_jax(self) -> Float1D: """Convert the grid to a Jax array.""" @@ -157,7 +157,7 @@ def __post_init__(self) -> None: @property def n_points(self) -> ScalarInt: """Return the total number of points in the grid.""" - return self._piece_n_points.sum() + return self._piece_n_points.sum(dtype=jnp.int32) def to_jax(self) -> Float1D: """Convert the grid to a Jax array.""" @@ -243,8 +243,10 @@ def _init_piecewise_grid_cache( # Breakpoints are the effective starts of pieces 1..k-1 breakpoints = starts[1:] if len(starts) > 1 else jnp.array([]) - n_points = jnp.array([p.n_points for p in grid.pieces]) - cumulative = jnp.concatenate([jnp.array([0]), jnp.cumsum(n_points[:-1])]) + n_points = jnp.array([p.n_points for p in grid.pieces], dtype=jnp.int32) + cumulative = jnp.concatenate( + [jnp.array([0], dtype=jnp.int32), jnp.cumsum(n_points[:-1])] + ) object.__setattr__(grid, "_breakpoints", breakpoints) object.__setattr__(grid, "_piece_starts", starts) diff --git a/src/lcm/params/processing.py b/src/lcm/params/processing.py index a0d3d5ecf..65fc2ad51 100644 --- a/src/lcm/params/processing.py +++ b/src/lcm/params/processing.py @@ -33,7 +33,6 @@ from lcm.dtypes import safe_to_float_dtype, safe_to_int_dtype from lcm.exceptions import InvalidNameError, InvalidParamsError -from lcm.interfaces import InternalRegime from lcm.params.mapping_leaf import MappingLeaf from lcm.params.sequence_leaf import SequenceLeaf from lcm.typing import ( @@ -52,7 +51,7 @@ def process_params( *, params: UserParams, - params_template: Mapping[RegimeName, RegimeParamsTemplate], + params_template: Mapping[RegimeName, Mapping[str, object]], ) -> InternalParams: """Process user-provided params into internal params. @@ -298,7 +297,7 @@ def _find_candidates( def create_params_template( # noqa: C901 - internal_regimes: Mapping[RegimeName, InternalRegime], + internal_regimes: Mapping[RegimeName, Any], ) -> ParamsTemplate: """Create params_template from internal regimes and validate name uniqueness. @@ -306,7 +305,9 @@ def create_params_template( # noqa: C901 are disjoint sets to enable unambiguous parameter propagation. Args: - internal_regimes: Mapping of regime names to InternalRegime instances. + internal_regimes: Mapping of regime names to `InternalRegime` instances + (or any object exposing a `regime_params_template` attribute — + internal helpers and test mocks both qualify). Returns: The parameter template. diff --git a/src/lcm/params/regime_template.py b/src/lcm/params/regime_template.py index 67ddfe75b..42a488842 100644 --- a/src/lcm/params/regime_template.py +++ b/src/lcm/params/regime_template.py @@ -1,4 +1,5 @@ from types import MappingProxyType +from typing import Any import dags.tree as dt from dags.tree import tree_path_from_qname @@ -6,7 +7,6 @@ from lcm.exceptions import InvalidNameError from lcm.grids import IrregSpacedGrid from lcm.interfaces import SolveSimulateFunctionPair -from lcm.regime import Regime from lcm.regime_building.validation import collect_state_transitions from lcm.shocks import _ShockGrid from lcm.typing import ( @@ -17,7 +17,7 @@ ) -def create_regime_params_template(regime: Regime) -> RegimeParamsTemplate: +def create_regime_params_template(regime: Any) -> RegimeParamsTemplate: # noqa: ANN401 """Create parameter template from a regime specification. Discover parameters from function signatures via `dags.tree`. Parameters @@ -33,7 +33,10 @@ def create_regime_params_template(regime: Regime) -> RegimeParamsTemplate: pseudo-function keys matching the state or action name. Args: - regime: The regime as provided by the user. + regime: A `Regime` instance, or any object with the same + `states` / `actions` / `functions` / `constraints` / + `transition` / `state_transitions` interface (e.g., test mocks + that bypass `Regime`'s constructor validation). Returns: The regime parameter template with type annotations as values. @@ -84,7 +87,7 @@ def create_regime_params_template(regime: Regime) -> RegimeParamsTemplate: def _add_runtime_grid_params( function_params: dict[FunctionName, dict[str, str]], - regime: Regime, + regime: Any, # noqa: ANN401 ) -> None: """Add runtime-supplied state/action grid params to the template in place.""" for state_name, grid in regime.states.items(): @@ -142,7 +145,7 @@ def _fail_if_runtime_grid_shadows_function( def _collect_all_functions_for_template( - regime: Regime, + regime: Any, # noqa: ANN401 ) -> dict[ FunctionName | TransitionFunctionName, UserFunction | SolveSimulateFunctionPair ]: @@ -165,7 +168,7 @@ def _collect_all_functions_for_template( def _validate_no_shadowing( function_params: dict[FunctionName, dict[str, str]], - regime: Regime, + regime: Any, # noqa: ANN401 ) -> None: """Raise if any discovered parameter shadows a state or action name.""" state_action_names = set(regime.states) | set(regime.actions) diff --git a/src/lcm/shocks/_base.py b/src/lcm/shocks/_base.py index b0f885523..54a15bae0 100644 --- a/src/lcm/shocks/_base.py +++ b/src/lcm/shocks/_base.py @@ -10,7 +10,7 @@ from lcm.exceptions import GridInitializationError from lcm.grids import ContinuousGrid from lcm.grids import coordinates as grid_coordinates -from lcm.typing import Float1D, FloatND, ScalarFloat +from lcm.typing import Float1D, FloatND, IntND, ScalarFloat def _gauss_hermite_normal( @@ -84,11 +84,11 @@ def is_fully_specified(self) -> bool: return not self.params_to_pass_at_runtime @abstractmethod - def compute_gridpoints(self, **kwargs: float | FloatND) -> Float1D: + def compute_gridpoints(self, **kwargs: float | FloatND | IntND) -> Float1D: """Compute discretized gridpoints for the shock distribution.""" @abstractmethod - def compute_transition_probs(self, **kwargs: float | FloatND) -> FloatND: + def compute_transition_probs(self, **kwargs: float | FloatND | IntND) -> FloatND: """Compute transition probability matrix for the shock distribution.""" def get_gridpoints(self) -> Float1D: @@ -118,10 +118,12 @@ def to_jax(self) -> Float1D: return self.get_gridpoints() @overload - def get_coordinate(self, value: ScalarFloat) -> ScalarFloat: ... + def get_coordinate(self, value: float | ScalarFloat) -> ScalarFloat: ... @overload def get_coordinate(self, value: FloatND) -> FloatND: ... - def get_coordinate(self, value: ScalarFloat | FloatND) -> ScalarFloat | FloatND: + def get_coordinate( + self, value: float | ScalarFloat | FloatND + ) -> ScalarFloat | FloatND: """Return the generalized coordinate of a value in the grid.""" if not self.is_fully_specified: raise GridInitializationError( diff --git a/src/lcm/shocks/ar1.py b/src/lcm/shocks/ar1.py index 4717db242..d358d79c4 100644 --- a/src/lcm/shocks/ar1.py +++ b/src/lcm/shocks/ar1.py @@ -14,7 +14,7 @@ _ShockGrid, _validate_gauss_hermite_grid, ) -from lcm.typing import Float1D, FloatND, KeyArray, ScalarFloat +from lcm.typing import Float1D, FloatND, IntND, KeyArray, ScalarFloat @dataclass(frozen=True, kw_only=True) @@ -77,7 +77,7 @@ def _param_field_names(self) -> tuple[str, ...]: exclude = exclude | {"n_std"} return tuple(f.name for f in fields(self) if f.name not in exclude) - def compute_gridpoints(self, **kwargs: float | FloatND) -> Float1D: + def compute_gridpoints(self, **kwargs: float | FloatND | IntND) -> Float1D: n_points = self.n_points rho, sigma, mu = kwargs["rho"], kwargs["sigma"], kwargs["mu"] std_y = jnp.sqrt(sigma**2 / (1 - rho**2)) @@ -90,7 +90,7 @@ def compute_gridpoints(self, **kwargs: float | FloatND) -> Float1D: x = jnp.linspace(-x_max, x_max, n_points) return x + mu / (1 - rho) - def compute_transition_probs(self, **kwargs: float | FloatND) -> FloatND: + def compute_transition_probs(self, **kwargs: float | FloatND | IntND) -> FloatND: n_points = self.n_points rho, sigma = kwargs["rho"], kwargs["sigma"] std_y = jnp.sqrt(sigma**2 / (1 - rho**2)) @@ -152,14 +152,14 @@ class Rouwenhorst(_ShockGridAR1): mu: float | int | None = None """Intercept (drift) of the AR(1) process.""" - def compute_gridpoints(self, **kwargs: float | FloatND) -> Float1D: + def compute_gridpoints(self, **kwargs: float | FloatND | IntND) -> Float1D: n_points = self.n_points rho, sigma, mu = kwargs["rho"], kwargs["sigma"], kwargs["mu"] nu = jnp.sqrt((n_points - 1) / (1 - rho**2)) * sigma long_run_mean = mu / (1.0 - rho) return jnp.linspace(long_run_mean - nu, long_run_mean + nu, n_points) - def compute_transition_probs(self, **kwargs: float | FloatND) -> FloatND: + def compute_transition_probs(self, **kwargs: float | FloatND | IntND) -> FloatND: n_points = self.n_points rho = kwargs["rho"] q = (rho + 1) / 2 @@ -252,7 +252,7 @@ def _innovation_variance( mean_eps = p1 * mu1 + (1 - p1) * mu2 return p1 * (sigma1**2 + mu1**2) + (1 - p1) * (sigma2**2 + mu2**2) - mean_eps**2 - def compute_gridpoints(self, **kwargs: float | FloatND) -> Float1D: + def compute_gridpoints(self, **kwargs: float | FloatND | IntND) -> Float1D: n_points = self.n_points rho, mu = kwargs["rho"], kwargs["mu"] n_std = kwargs["n_std"] @@ -268,7 +268,7 @@ def compute_gridpoints(self, **kwargs: float | FloatND) -> Float1D: x_max = n_std * std_y return jnp.linspace(long_run_mean - x_max, long_run_mean + x_max, n_points) - def compute_transition_probs(self, **kwargs: float | FloatND) -> FloatND: + def compute_transition_probs(self, **kwargs: float | FloatND | IntND) -> FloatND: n_points = self.n_points rho, mu = kwargs["rho"], kwargs["mu"] n_std = kwargs["n_std"] diff --git a/src/lcm/shocks/iid.py b/src/lcm/shocks/iid.py index 1f36f63cc..2079347fd 100644 --- a/src/lcm/shocks/iid.py +++ b/src/lcm/shocks/iid.py @@ -13,7 +13,7 @@ _ShockGrid, _validate_gauss_hermite_grid, ) -from lcm.typing import Float1D, FloatND, KeyArray, ScalarFloat +from lcm.typing import Float1D, FloatND, IntND, KeyArray, ScalarFloat @dataclass(frozen=True, kw_only=True) @@ -44,12 +44,12 @@ class Uniform(_ShockGridIID): stop: float | int | None = None """Upper bound of the uniform distribution.""" - def compute_gridpoints(self, **kwargs: float | FloatND) -> Float1D: + def compute_gridpoints(self, **kwargs: float | FloatND | IntND) -> Float1D: return jnp.linspace( start=kwargs["start"], stop=kwargs["stop"], num=self.n_points ) - def compute_transition_probs(self, **kwargs: float | FloatND) -> FloatND: # noqa: ARG002 + def compute_transition_probs(self, **kwargs: float | FloatND | IntND) -> FloatND: # noqa: ARG002 n_points = self.n_points return jnp.full((n_points, n_points), fill_value=1 / n_points) @@ -99,7 +99,7 @@ def _param_field_names(self) -> tuple[str, ...]: exclude = exclude | {"n_std"} return tuple(f.name for f in fields(self) if f.name not in exclude) - def compute_gridpoints(self, **kwargs: float | FloatND) -> Float1D: + def compute_gridpoints(self, **kwargs: float | FloatND | IntND) -> Float1D: n_points = self.n_points mu, sigma = kwargs["mu"], kwargs["sigma"] if self.gauss_hermite: @@ -112,7 +112,7 @@ def compute_gridpoints(self, **kwargs: float | FloatND) -> Float1D: x_max = mu + n_std * sigma return jnp.linspace(start=x_min, stop=x_max, num=n_points) - def compute_transition_probs(self, **kwargs: float | FloatND) -> FloatND: + def compute_transition_probs(self, **kwargs: float | FloatND | IntND) -> FloatND: n_points = self.n_points mu, sigma = kwargs["mu"], kwargs["sigma"] if self.gauss_hermite: @@ -167,7 +167,7 @@ def _param_field_names(self) -> tuple[str, ...]: exclude = exclude | {"n_std"} return tuple(f.name for f in fields(self) if f.name not in exclude) - def compute_gridpoints(self, **kwargs: float | FloatND) -> Float1D: + def compute_gridpoints(self, **kwargs: float | FloatND | IntND) -> Float1D: n_points = self.n_points mu, sigma = kwargs["mu"], kwargs["sigma"] if self.gauss_hermite: @@ -178,7 +178,7 @@ def compute_gridpoints(self, **kwargs: float | FloatND) -> Float1D: n_std = kwargs["n_std"] return jnp.exp(jnp.linspace(mu - n_std * sigma, mu + n_std * sigma, n_points)) - def compute_transition_probs(self, **kwargs: float | FloatND) -> FloatND: + def compute_transition_probs(self, **kwargs: float | FloatND | IntND) -> FloatND: n_points = self.n_points mu, sigma = kwargs["mu"], kwargs["sigma"] if self.gauss_hermite: @@ -238,7 +238,7 @@ class NormalMixture(_ShockGridIID): sigma2: float | int | None = None """Standard deviation of the second mixture component.""" - def compute_gridpoints(self, **kwargs: float | FloatND) -> Float1D: + def compute_gridpoints(self, **kwargs: float | FloatND | IntND) -> Float1D: n_points = self.n_points n_std = kwargs["n_std"] p1, mu1, sigma1 = kwargs["p1"], kwargs["mu1"], kwargs["sigma1"] @@ -253,7 +253,7 @@ def compute_gridpoints(self, **kwargs: float | FloatND) -> Float1D: mean_eps - n_std * std_eps, mean_eps + n_std * std_eps, n_points ) - def compute_transition_probs(self, **kwargs: float | FloatND) -> FloatND: + def compute_transition_probs(self, **kwargs: float | FloatND | IntND) -> FloatND: n_points = self.n_points n_std = kwargs["n_std"] p1, mu1, sigma1 = kwargs["p1"], kwargs["mu1"], kwargs["sigma1"] diff --git a/src/lcm/typing.py b/src/lcm/typing.py index 8685241fb..3d3496884 100644 --- a/src/lcm/typing.py +++ b/src/lcm/typing.py @@ -2,6 +2,7 @@ from types import MappingProxyType from typing import Any, Protocol, runtime_checkable +import numpy as np import pandas as pd from jax import Array from jaxtyping import Bool, Float, Int32, Scalar @@ -54,7 +55,9 @@ type StatesPerRegime = MappingProxyType[RegimeName, RegimeStates] -type _ParamsLeaf = bool | int | float | Array | pd.Series | MappingLeaf | SequenceLeaf +type _ParamsLeaf = ( + bool | int | float | Array | np.ndarray | pd.Series | MappingLeaf | SequenceLeaf +) type UserParams = Mapping[ str, _ParamsLeaf | Mapping[str, _ParamsLeaf | Mapping[str, _ParamsLeaf]], diff --git a/tests/conftest.py b/tests/conftest.py index c95bd21a9..2bc510b69 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,25 +1,11 @@ -# Beartype claw must instrument `lcm.grids`, `lcm.shocks`, and `lcm.params` -# before any submodule of those packages is imported. The registrations below -# install an import hook on `sys.meta_path` that transforms each matching -# module's AST at first import. Subsequent imports in this file must come -# after the registration, hence the `# noqa: E402` markers on them. +from collections.abc import Iterator +from dataclasses import make_dataclass -from beartype.claw import beartype_package +import jax.numpy as jnp +import pytest +from jax import config as jax_config -from lcm._beartype_conf import GRID_CONF, PARAMS_CONF - -beartype_package("lcm.grids", conf=GRID_CONF) -beartype_package("lcm.shocks", conf=GRID_CONF) -beartype_package("lcm.params", conf=PARAMS_CONF) - -from collections.abc import Iterator # noqa: E402 -from dataclasses import make_dataclass # noqa: E402 - -import jax.numpy as jnp # noqa: E402 -import pytest # noqa: E402 -from jax import config as jax_config # noqa: E402 - -from lcm.typing import ScalarInt # noqa: E402 +from lcm.typing import ScalarInt # Module-level precision settings (updated by pytest_configure based on --precision) X64_ENABLED: bool = True diff --git a/tests/regime_building/test_create_regime_params_template.py b/tests/regime_building/test_create_regime_params_template.py index 62d5a5cb4..5ae13cdd8 100644 --- a/tests/regime_building/test_create_regime_params_template.py +++ b/tests/regime_building/test_create_regime_params_template.py @@ -19,7 +19,7 @@ def test_create_params_without_shocks(binary_category_class): transition=lambda: 0, functions={"utility": lambda a, b, c: None}, # noqa: ARG005 ) - got = create_regime_params_template(regime) # ty: ignore[invalid-argument-type] + got = create_regime_params_template(regime) assert got == ensure_containers_are_immutable( { "H": {"discount_factor": "float"}, @@ -45,7 +45,7 @@ def custom_H(utility: float, E_next_V: float) -> float: }, functions={"utility": lambda a, b, c: None, "H": custom_H}, # noqa: ARG005 ) - got = create_regime_params_template(regime) # ty: ignore[invalid-argument-type] + got = create_regime_params_template(regime) assert got == ensure_containers_are_immutable( {"H": {}, "utility": {"c": "no_annotation_found"}} ) @@ -67,7 +67,7 @@ def test_default_H_with_state_named_discount_factor_is_allowed(): functions={"utility": lambda a, discount_factor: None}, # noqa: ARG005 transition=lambda discount_factor: discount_factor, ) - got = create_regime_params_template(regime) # ty: ignore[invalid-argument-type] + got = create_regime_params_template(regime) assert got == ensure_containers_are_immutable( { "H": {}, @@ -95,7 +95,7 @@ def custom_H(utility: float, E_next_V: float, wealth: float) -> float: states={"wealth": None}, functions={"utility": lambda a, wealth: None, "H": custom_H}, # noqa: ARG005 ) - got = create_regime_params_template(regime) # ty: ignore[invalid-argument-type] + got = create_regime_params_template(regime) assert got == ensure_containers_are_immutable({"H": {}, "utility": {}}) @@ -124,7 +124,7 @@ def beta_delta_h( "H": SolveSimulateFunctionPair(solve=exponential_h, simulate=beta_delta_h), }, ) - got = create_regime_params_template(regime) # ty: ignore[invalid-argument-type] + got = create_regime_params_template(regime) assert set(got["H"]) == {"discount_factor", "beta", "delta"} @@ -141,7 +141,7 @@ def test_regular_function_taking_state_as_argument_no_error(binary_category_clas transition=lambda: 0, functions={"utility": lambda a, wealth, risk_aversion: None}, # noqa: ARG005 ) - got = create_regime_params_template(regime) # ty: ignore[invalid-argument-type] + got = create_regime_params_template(regime) assert got == ensure_containers_are_immutable( { "H": {"discount_factor": "float"}, @@ -180,7 +180,7 @@ def next_wealth(wealth: float, next_aime: float) -> float: transition=lambda: 0, functions={"utility": lambda a, wealth, aime: None}, # noqa: ARG005 ) - got = create_regime_params_template(regime) # ty: ignore[invalid-argument-type] + got = create_regime_params_template(regime) assert got == ensure_containers_are_immutable( { "H": {"discount_factor": "float"}, diff --git a/tests/regime_building/test_process_params.py b/tests/regime_building/test_process_params.py index 093df0a9d..6fee263d2 100644 --- a/tests/regime_building/test_process_params.py +++ b/tests/regime_building/test_process_params.py @@ -184,7 +184,7 @@ def test_function_params_no_qname_separator(): ), } with pytest.raises(InvalidNameError): - create_params_template(internal_regimes) # ty: ignore[invalid-argument-type] + create_params_template(internal_regimes) def test_regime_name_no_qname_separator(): @@ -195,7 +195,7 @@ def test_regime_name_no_qname_separator(): ), } with pytest.raises(InvalidNameError): - create_params_template(internal_regimes) # ty: ignore[invalid-argument-type] + create_params_template(internal_regimes) def test_function_name_no_qname_separator(): @@ -206,7 +206,7 @@ def test_function_name_no_qname_separator(): ), } with pytest.raises(InvalidNameError): - create_params_template(internal_regimes) # ty: ignore[invalid-argument-type] + create_params_template(internal_regimes) def test_regime_function_names_disjoint(): @@ -218,7 +218,7 @@ def test_regime_function_names_disjoint(): ), } with pytest.raises(InvalidNameError): - create_params_template(internal_regimes) # ty: ignore[invalid-argument-type] + create_params_template(internal_regimes) def test_regime_argument_names_disjoint(): @@ -230,7 +230,7 @@ def test_regime_argument_names_disjoint(): ), } with pytest.raises(InvalidNameError): - create_params_template(internal_regimes) # ty: ignore[invalid-argument-type] + create_params_template(internal_regimes) def test_missing_parameter_raises_error(params_template): @@ -303,7 +303,7 @@ def test_passing_same_params_to_regimes_with_different_templates(): # InvalidParamsError is raised because dead__cons_util__*, dead__utility__*, # etc. are not in the template with pytest.raises(InvalidParamsError, match="Unknown keys"): - process_params(params=params, params_template=params_template) # ty: ignore[invalid-argument-type] + process_params(params=params, params_template=params_template) def test_shock_params_via_regular_params(): @@ -332,6 +332,6 @@ def test_shock_params_via_regular_params(): }, } - result = process_params(params=params, params_template=params_template) # ty: ignore[invalid-argument-type] + result = process_params(params=params, params_template=params_template) assert result["working_life"]["adjustment_cost__start"] == 0 assert result["working_life"]["adjustment_cost__stop"] == 1 diff --git a/tests/test_float_dtype_invariants.py b/tests/test_float_dtype_invariants.py index 7e4f14fca..0f9a3984f 100644 --- a/tests/test_float_dtype_invariants.py +++ b/tests/test_float_dtype_invariants.py @@ -100,8 +100,8 @@ def test_process_params_casts_float64_array_to_canonical_under_no_x64( } out = process_params( - params=user_params, # ty: ignore[invalid-argument-type] - params_template=template, # ty: ignore[invalid-argument-type] + params=user_params, + params_template=template, ) schedule = out["regime_a"]["schedule"] @@ -117,7 +117,7 @@ def test_process_params_casts_python_float_to_canonical(x64_disabled: None): out = process_params( params=user_params, - params_template=template, # ty: ignore[invalid-argument-type] + params_template=template, ) discount_factor = out["regime_a"]["discount_factor"] @@ -134,8 +134,8 @@ def test_process_params_float_array_overflow_raises_with_qualified_name( with pytest.raises(OverflowError, match="schedule"): process_params( - params=user_params, # ty: ignore[invalid-argument-type] - params_template=template, # ty: ignore[invalid-argument-type] + params=user_params, + params_template=template, ) @@ -250,7 +250,7 @@ def test_process_params_casts_float_array_inside_mapping_leaf_to_canonical( out = process_params( params=user_params, - params_template=template, # ty: ignore[invalid-argument-type] + params_template=template, ) assert ( @@ -280,7 +280,7 @@ def test_process_params_casts_float_array_inside_sequence_leaf_to_canonical( out = process_params( params=user_params, - params_template=template, # ty: ignore[invalid-argument-type] + params_template=template, ) assert ( diff --git a/tests/test_int_dtype_invariants.py b/tests/test_int_dtype_invariants.py index 4c31f4864..f0a23de21 100644 --- a/tests/test_int_dtype_invariants.py +++ b/tests/test_int_dtype_invariants.py @@ -110,7 +110,7 @@ def test_process_params_casts_python_int_to_int32() -> None: out = process_params( params=user_params, - params_template=template, # ty: ignore[invalid-argument-type] + params_template=template, ) final_age = out["regime_a"]["final_age"] @@ -125,7 +125,7 @@ def test_process_params_casts_int64_array_to_int32() -> None: out = process_params( params=user_params, - params_template=template, # ty: ignore[invalid-argument-type] + params_template=template, ) schedule = out["regime_a"]["schedule"] @@ -141,8 +141,8 @@ def test_process_params_int_array_overflow_raises_with_qualified_name() -> None: with pytest.raises(ValueError, match="big_param"): process_params( - params=user_params, # ty: ignore[invalid-argument-type] - params_template=template, # ty: ignore[invalid-argument-type] + params=user_params, + params_template=template, ) @@ -165,7 +165,7 @@ def test_process_params_casts_int_array_inside_mapping_leaf_to_int32(key: str) - out = process_params( params=user_params, - params_template=template, # ty: ignore[invalid-argument-type] + params_template=template, ) assert ( @@ -195,7 +195,7 @@ def test_process_params_casts_int_array_inside_sequence_leaf_to_int32( out = process_params( params=user_params, - params_template=template, # ty: ignore[invalid-argument-type] + params_template=template, ) assert ( diff --git a/tests/test_mapping_leaf.py b/tests/test_mapping_leaf.py index 2e439ca61..a00ac4609 100644 --- a/tests/test_mapping_leaf.py +++ b/tests/test_mapping_leaf.py @@ -5,6 +5,7 @@ import jax.numpy as jnp import pytest +from lcm.exceptions import InvalidParamsError from lcm.params import MappingLeaf, SequenceLeaf, as_leaf from lcm.utils.containers import ( ensure_containers_are_immutable, @@ -175,5 +176,5 @@ def test_as_leaf_tuple(): def test_as_leaf_rejects_int(): - with pytest.raises(TypeError, match="as_leaf"): + with pytest.raises(InvalidParamsError, match="as_leaf"): as_leaf(42) # ty: ignore[no-matching-overload] diff --git a/tests/test_validate_param_types.py b/tests/test_validate_param_types.py index 06a95d597..0aadbfaca 100644 --- a/tests/test_validate_param_types.py +++ b/tests/test_validate_param_types.py @@ -60,7 +60,7 @@ def test_numpy_array_param_normalised_to_canonical_jax_array() -> None: """A numpy array param is cast to a JAX array at `canonical_float_dtype()`.""" model = _make_model() internal = model._process_params( - params={"bonus": np.asarray(1.0, dtype=np.float64), "discount_factor": 0.95} # ty: ignore[invalid-argument-type] + params={"bonus": np.asarray(1.0, dtype=np.float64), "discount_factor": 0.95} ) bonus = internal["working"]["utility__bonus"] assert isinstance(bonus, Array) From fda74bf75b6dde1f9d25222d1e5e2615bb73241b Mon Sep 17 00:00:00 2001 From: Hans-Martin von Gaudecker Date: Wed, 13 May 2026 22:58:43 +0200 Subject: [PATCH 31/77] Tighten pylcm-internal types: drop float / Mapping widenings; cast at boundary MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reverts the widenings introduced when activating the claw on `lcm.grids` / `lcm.shocks` / `lcm.params`. The principle: user-facing constructors keep `float | ScalarFloat` etc. as a convenience, but pylcm-internal helpers take only the canonical JAX-side types and callers cast at the boundary. Internal-API signatures restored to JAX-only: - `get_coordinate` on every continuous + shock grid (and the abstract base in `_ShockGrid` / `ContinuousGrid`) drops Python `float` from the `value:` parameter; `get_linspace_coordinate` / `get_logspace_coordinate` / `get_irreg_coordinate` in `lcm.grids.coordinates` do the same. - `_gauss_hermite_normal(mu, sigma)` drops `float`; the one Python literal call site in `Tauchen.compute_transition_probs` now wraps with `jnp.asarray(0.0)`. - `_mixture_cdf(p1, mu1, sigma1, mu2, sigma2)` drops `float` and uses `ScalarFloat | FloatND`. - `Tauchen._innovation_variance(...)` drops `float`. - `_ShockGrid.compute_gridpoints` / `compute_transition_probs` kwargs drop `float`; the per-grid concrete overrides match. - `draw_shock(params: MappingProxyType[str, float | FloatND])` → `MappingProxyType[str, FloatND | IntND]`. - `lcm.params.processing.create_params_template(internal_regimes: ...)` restored to `Mapping[RegimeName, InternalRegime]` (was `Any` for duck-typed mocks); `solve_brute.solve` and the other internal helpers in `solve_brute` use `MappingProxyType[RegimeName, InternalRegime]` (was `Mapping[...]`). - `create_regime_params_template(regime: Regime)` and its helpers restored to the strict `Regime` type. Boundary casts via a new private `_params_to_jax` helper in `lcm.shocks._base`: - `_ShockGrid.get_gridpoints` / `get_transition_probs` cast `self.params` (mix of Python literals + runtime arrays). - `interfaces.StateActionSpace.state_action_space` casts `shock_kw` before passing to `spec.compute_gridpoints`. - `regime_building.processing.weights_func_runtime` casts before invoking the shock grid's compute helpers. - `regime_building.next_state._create_*_next_func` casts before calling `_draw_shock`. `Piece._piece_n_points` cumulative `.sum()` explicit `dtype=jnp.int32` so the property returns `ScalarInt` (int32) under `jax_enable_x64=True` instead of the int64 JAX produces by default. Test mocks inherit the production class so `isinstance(x, Regime)` / `isinstance(x, InternalRegime)` holds at the beartype-checked perimeter: - `tests/regime_mock.py:RegimeMock` inherits `Regime`, overrides `__init__` to bypass the parent's `@beartype`-decorated constructor validation via `object.__setattr__`. Mirrors `Regime.__post_init__`'s default-H injection. - `tests/regime_building/test_process_params.py:MockRegime` inherits `InternalRegime`, sets only `regime_params_template` directly via `object.__setattr__`. Tests updated to pass JAX scalars at the perimeter: - `tests/test_grids.py`, `tests/test_function_representation.py`, `tests/test_fgp_discretization.py` wrap Python literals with `jnp.asarray(...)` at sites that flow into `get_coordinate` / `_mixture_cdf`. - `test_function_evaluator_performs_linear_extrapolation`: the int `10` in `wealth_outside_of_grid` becomes `10.0`. Drops the `# ty: ignore[invalid-argument-type]` markers in test files where the inheritance fix makes them unnecessary. Co-Authored-By: Claude Opus 4.7 --- src/lcm/grids/continuous.py | 30 +++------ src/lcm/grids/coordinates.py | 12 ++-- src/lcm/grids/piecewise.py | 12 ++-- src/lcm/interfaces.py | 5 +- src/lcm/params/processing.py | 10 +-- src/lcm/params/regime_template.py | 15 ++--- src/lcm/regime_building/next_state.py | 25 +++++--- src/lcm/regime_building/processing.py | 6 +- src/lcm/shocks/_base.py | 55 +++++++++++----- src/lcm/shocks/ar1.py | 34 +++++----- src/lcm/shocks/iid.py | 26 ++++---- src/lcm/solution/solve_brute.py | 16 ++--- tests/regime_building/test_process_params.py | 23 ++++--- .../regime_building/test_regime_processing.py | 6 +- tests/regime_mock.py | 64 +++++++++++++------ tests/test_fgp_discretization.py | 7 +- tests/test_function_representation.py | 22 +++---- tests/test_grids.py | 6 +- tests/test_variables.py | 4 +- 19 files changed, 216 insertions(+), 162 deletions(-) diff --git a/src/lcm/grids/continuous.py b/src/lcm/grids/continuous.py index 8bf7c9766..4dad41793 100644 --- a/src/lcm/grids/continuous.py +++ b/src/lcm/grids/continuous.py @@ -34,13 +34,11 @@ class ContinuousGrid(Grid): """Whether to distribute the grid over the available devices.""" @overload - def get_coordinate(self, value: float | ScalarFloat) -> ScalarFloat: ... + def get_coordinate(self, value: ScalarFloat) -> ScalarFloat: ... @overload def get_coordinate(self, value: FloatND) -> FloatND: ... @abstractmethod - def get_coordinate( - self, value: float | ScalarFloat | FloatND - ) -> ScalarFloat | FloatND: + def get_coordinate(self, value: ScalarFloat | FloatND) -> ScalarFloat | FloatND: """Return the generalized coordinate of a value in the grid.""" @@ -86,13 +84,11 @@ def to_jax(self) -> Float1D: """Convert the grid to a Jax array.""" @overload - def get_coordinate(self, value: float | ScalarFloat) -> ScalarFloat: ... + def get_coordinate(self, value: ScalarFloat) -> ScalarFloat: ... @overload def get_coordinate(self, value: FloatND) -> FloatND: ... @abstractmethod - def get_coordinate( - self, value: float | ScalarFloat | FloatND - ) -> ScalarFloat | FloatND: + def get_coordinate(self, value: ScalarFloat | FloatND) -> ScalarFloat | FloatND: """Return the generalized coordinate of a value in the grid.""" def replace(self, **kwargs: float) -> UniformContinuousGrid: @@ -130,12 +126,10 @@ def to_jax(self) -> Float1D: ) @overload - def get_coordinate(self, value: float | ScalarFloat) -> ScalarFloat: ... + def get_coordinate(self, value: ScalarFloat) -> ScalarFloat: ... @overload def get_coordinate(self, value: FloatND) -> FloatND: ... - def get_coordinate( - self, value: float | ScalarFloat | FloatND - ) -> ScalarFloat | FloatND: + def get_coordinate(self, value: ScalarFloat | FloatND) -> ScalarFloat | FloatND: """Return the generalized coordinate of a value in the grid.""" return grid_coordinates.get_linspace_coordinate( value=value, @@ -183,12 +177,10 @@ def to_jax(self) -> Float1D: ) @overload - def get_coordinate(self, value: float | ScalarFloat) -> ScalarFloat: ... + def get_coordinate(self, value: ScalarFloat) -> ScalarFloat: ... @overload def get_coordinate(self, value: FloatND) -> FloatND: ... - def get_coordinate( - self, value: float | ScalarFloat | FloatND - ) -> ScalarFloat | FloatND: + def get_coordinate(self, value: ScalarFloat | FloatND) -> ScalarFloat | FloatND: """Return the generalized coordinate of a value in the grid.""" return grid_coordinates.get_logspace_coordinate( value=value, @@ -321,12 +313,10 @@ def to_jax(self) -> Float1D: return self.points @overload - def get_coordinate(self, value: float | ScalarFloat) -> ScalarFloat: ... + def get_coordinate(self, value: ScalarFloat) -> ScalarFloat: ... @overload def get_coordinate(self, value: FloatND) -> FloatND: ... - def get_coordinate( - self, value: float | ScalarFloat | FloatND - ) -> ScalarFloat | FloatND: + def get_coordinate(self, value: ScalarFloat | FloatND) -> ScalarFloat | FloatND: """Return the generalized coordinate of a value in the grid.""" if self.points is None: raise GridInitializationError( diff --git a/src/lcm/grids/coordinates.py b/src/lcm/grids/coordinates.py index 527b94754..ea5a2aa3c 100644 --- a/src/lcm/grids/coordinates.py +++ b/src/lcm/grids/coordinates.py @@ -31,7 +31,7 @@ def linspace( @overload def get_linspace_coordinate( *, - value: float | ScalarFloat, + value: ScalarFloat, start: ScalarFloat | FloatND, stop: ScalarFloat | FloatND, n_points: ScalarInt | IntND, @@ -46,7 +46,7 @@ def get_linspace_coordinate( ) -> FloatND: ... def get_linspace_coordinate( *, - value: float | ScalarFloat | FloatND, + value: ScalarFloat | FloatND, start: ScalarFloat | FloatND, stop: ScalarFloat | FloatND, n_points: ScalarInt | IntND, @@ -85,7 +85,7 @@ def logspace( @overload def get_logspace_coordinate( *, - value: float | ScalarFloat, + value: ScalarFloat, start: ScalarFloat | FloatND, stop: ScalarFloat | FloatND, n_points: ScalarInt | IntND, @@ -100,7 +100,7 @@ def get_logspace_coordinate( ) -> FloatND: ... def get_logspace_coordinate( *, - value: float | ScalarFloat | FloatND, + value: ScalarFloat | FloatND, start: ScalarFloat | FloatND, stop: ScalarFloat | FloatND, n_points: ScalarInt | IntND, @@ -145,7 +145,7 @@ def get_logspace_coordinate( @overload def get_irreg_coordinate( *, - value: float | ScalarFloat, + value: ScalarFloat, points: Float1D, ) -> ScalarFloat: ... @overload @@ -156,7 +156,7 @@ def get_irreg_coordinate( ) -> FloatND: ... def get_irreg_coordinate( *, - value: float | ScalarFloat | FloatND, + value: ScalarFloat | FloatND, points: Float1D, ) -> ScalarFloat | FloatND: """Return the generalized coordinate of a value in an irregularly spaced grid. diff --git a/src/lcm/grids/piecewise.py b/src/lcm/grids/piecewise.py index 4055a0453..d4855fb08 100644 --- a/src/lcm/grids/piecewise.py +++ b/src/lcm/grids/piecewise.py @@ -98,12 +98,10 @@ def to_jax(self) -> Float1D: return jnp.concatenate(piece_arrays) @overload - def get_coordinate(self, value: float | ScalarFloat) -> ScalarFloat: ... + def get_coordinate(self, value: ScalarFloat) -> ScalarFloat: ... @overload def get_coordinate(self, value: FloatND) -> FloatND: ... - def get_coordinate( - self, value: float | ScalarFloat | FloatND - ) -> ScalarFloat | FloatND: + def get_coordinate(self, value: ScalarFloat | FloatND) -> ScalarFloat | FloatND: """Return the generalized coordinate of a value in the grid.""" piece_idx = jnp.searchsorted(self._breakpoints, value, side="right") local_coord = grid_coordinates.get_linspace_coordinate( @@ -172,12 +170,10 @@ def to_jax(self) -> Float1D: return jnp.concatenate(piece_arrays) @overload - def get_coordinate(self, value: float | ScalarFloat) -> ScalarFloat: ... + def get_coordinate(self, value: ScalarFloat) -> ScalarFloat: ... @overload def get_coordinate(self, value: FloatND) -> FloatND: ... - def get_coordinate( - self, value: float | ScalarFloat | FloatND - ) -> ScalarFloat | FloatND: + def get_coordinate(self, value: ScalarFloat | FloatND) -> ScalarFloat | FloatND: """Return the generalized coordinate of a value in the grid.""" piece_idx = jnp.searchsorted(self._breakpoints, value, side="right") local_coord = grid_coordinates.get_logspace_coordinate( diff --git a/src/lcm/interfaces.py b/src/lcm/interfaces.py index d4288f4f7..3060b304e 100644 --- a/src/lcm/interfaces.py +++ b/src/lcm/interfaces.py @@ -10,6 +10,7 @@ from lcm.exceptions import PyLCMError from lcm.grids import Grid, IrregSpacedGrid from lcm.shocks import _ShockGrid +from lcm.shocks._base import _params_to_jax from lcm.typing import ( ActionName, ArgmaxQOverAFunction, @@ -295,7 +296,9 @@ def state_action_space(self, regime_params: FlatRegimeParams) -> StateActionSpac shock_kw: dict[str, float] = dict(spec.params) for p in spec.params_to_pass_at_runtime: shock_kw[p] = cast("float", all_params[f"{name}__{p}"]) - state_replacements[name] = spec.compute_gridpoints(**shock_kw) + state_replacements[name] = spec.compute_gridpoints( + **_params_to_jax(MappingProxyType(shock_kw)) + ) new_states = ( dict(self._base_state_action_space.states) | state_replacements diff --git a/src/lcm/params/processing.py b/src/lcm/params/processing.py index 65fc2ad51..c75fb6294 100644 --- a/src/lcm/params/processing.py +++ b/src/lcm/params/processing.py @@ -33,9 +33,11 @@ from lcm.dtypes import safe_to_float_dtype, safe_to_int_dtype from lcm.exceptions import InvalidNameError, InvalidParamsError +from lcm.interfaces import InternalRegime from lcm.params.mapping_leaf import MappingLeaf from lcm.params.sequence_leaf import SequenceLeaf from lcm.typing import ( + FunctionName, InternalParams, ParamsTemplate, RegimeName, @@ -51,7 +53,7 @@ def process_params( *, params: UserParams, - params_template: Mapping[RegimeName, Mapping[str, object]], + params_template: Mapping[RegimeName, Mapping[FunctionName, object]], ) -> InternalParams: """Process user-provided params into internal params. @@ -297,7 +299,7 @@ def _find_candidates( def create_params_template( # noqa: C901 - internal_regimes: Mapping[RegimeName, Any], + internal_regimes: Mapping[RegimeName, InternalRegime], ) -> ParamsTemplate: """Create params_template from internal regimes and validate name uniqueness. @@ -305,9 +307,7 @@ def create_params_template( # noqa: C901 are disjoint sets to enable unambiguous parameter propagation. Args: - internal_regimes: Mapping of regime names to `InternalRegime` instances - (or any object exposing a `regime_params_template` attribute — - internal helpers and test mocks both qualify). + internal_regimes: Mapping of regime names to InternalRegime instances. Returns: The parameter template. diff --git a/src/lcm/params/regime_template.py b/src/lcm/params/regime_template.py index 42a488842..67ddfe75b 100644 --- a/src/lcm/params/regime_template.py +++ b/src/lcm/params/regime_template.py @@ -1,5 +1,4 @@ from types import MappingProxyType -from typing import Any import dags.tree as dt from dags.tree import tree_path_from_qname @@ -7,6 +6,7 @@ from lcm.exceptions import InvalidNameError from lcm.grids import IrregSpacedGrid from lcm.interfaces import SolveSimulateFunctionPair +from lcm.regime import Regime from lcm.regime_building.validation import collect_state_transitions from lcm.shocks import _ShockGrid from lcm.typing import ( @@ -17,7 +17,7 @@ ) -def create_regime_params_template(regime: Any) -> RegimeParamsTemplate: # noqa: ANN401 +def create_regime_params_template(regime: Regime) -> RegimeParamsTemplate: """Create parameter template from a regime specification. Discover parameters from function signatures via `dags.tree`. Parameters @@ -33,10 +33,7 @@ def create_regime_params_template(regime: Any) -> RegimeParamsTemplate: # noqa: pseudo-function keys matching the state or action name. Args: - regime: A `Regime` instance, or any object with the same - `states` / `actions` / `functions` / `constraints` / - `transition` / `state_transitions` interface (e.g., test mocks - that bypass `Regime`'s constructor validation). + regime: The regime as provided by the user. Returns: The regime parameter template with type annotations as values. @@ -87,7 +84,7 @@ def create_regime_params_template(regime: Any) -> RegimeParamsTemplate: # noqa: def _add_runtime_grid_params( function_params: dict[FunctionName, dict[str, str]], - regime: Any, # noqa: ANN401 + regime: Regime, ) -> None: """Add runtime-supplied state/action grid params to the template in place.""" for state_name, grid in regime.states.items(): @@ -145,7 +142,7 @@ def _fail_if_runtime_grid_shadows_function( def _collect_all_functions_for_template( - regime: Any, # noqa: ANN401 + regime: Regime, ) -> dict[ FunctionName | TransitionFunctionName, UserFunction | SolveSimulateFunctionPair ]: @@ -168,7 +165,7 @@ def _collect_all_functions_for_template( def _validate_no_shadowing( function_params: dict[FunctionName, dict[str, str]], - regime: Any, # noqa: ANN401 + regime: Regime, ) -> None: """Raise if any discovered parameter shadows a state or action name.""" state_action_names = set(regime.states) | set(regime.actions) diff --git a/src/lcm/regime_building/next_state.py b/src/lcm/regime_building/next_state.py index 1e81f0f23..5b260ec96 100644 --- a/src/lcm/regime_building/next_state.py +++ b/src/lcm/regime_building/next_state.py @@ -10,6 +10,7 @@ from lcm.grids import Grid from lcm.shocks import _ShockGrid +from lcm.shocks._base import _params_to_jax from lcm.shocks.ar1 import _ShockGridAR1 from lcm.shocks.iid import _ShockGridIID from lcm.typing import ( @@ -300,11 +301,13 @@ def _create_ar1_next_func( @with_signature(args=args, return_annotation="ContinuousState") def next_stochastic_state(**kwargs: FloatND) -> ContinuousState: - params = MappingProxyType( - { - **fixed_params, - **{raw: kwargs[qn] for qn, raw in runtime_param_names.items()}, - } + params = _params_to_jax( + MappingProxyType( + { + **fixed_params, + **{raw: kwargs[qn] for qn, raw in runtime_param_names.items()}, + } + ) ) return _draw_shock( params=params, @@ -330,11 +333,13 @@ def _create_iid_next_func( @with_signature(args=args, return_annotation="ContinuousState") def next_stochastic_state(**kwargs: FloatND) -> ContinuousState: - params = MappingProxyType( - { - **fixed_params, - **{raw: kwargs[qn] for qn, raw in runtime_param_names.items()}, - } + params = _params_to_jax( + MappingProxyType( + { + **fixed_params, + **{raw: kwargs[qn] for qn, raw in runtime_param_names.items()}, + } + ) ) return _draw_shock( params=params, diff --git a/src/lcm/regime_building/processing.py b/src/lcm/regime_building/processing.py index 0b0c78945..85fbb7fe0 100644 --- a/src/lcm/regime_building/processing.py +++ b/src/lcm/regime_building/processing.py @@ -41,6 +41,7 @@ from lcm.regime_building.V import VInterpolationInfo, create_v_interpolation_info from lcm.regime_building.validation import collect_state_transitions from lcm.shocks import _ShockGrid +from lcm.shocks._base import _params_to_jax from lcm.state_action_space import create_state_action_space from lcm.typing import ( ArgmaxQOverAFunction, @@ -892,8 +893,9 @@ def weights_func_runtime(*a: Array, **kwargs: Array) -> Float1D: # noqa: ARG001 **fixed_params, **{raw: kwargs[qn] for qn, raw in runtime_param_names.items()}, } - gridpoints = grid.compute_gridpoints(**shock_kw) - transition_probs = grid.compute_transition_probs(**shock_kw) + shock_kw_jax = _params_to_jax(MappingProxyType(shock_kw)) + gridpoints = grid.compute_gridpoints(**shock_kw_jax) + transition_probs = grid.compute_transition_probs(**shock_kw_jax) coord = get_irreg_coordinate(value=kwargs[name], points=gridpoints) return map_coordinates( input=transition_probs, diff --git a/src/lcm/shocks/_base.py b/src/lcm/shocks/_base.py index 54a15bae0..4bb768f2e 100644 --- a/src/lcm/shocks/_base.py +++ b/src/lcm/shocks/_base.py @@ -1,7 +1,8 @@ from abc import abstractmethod +from collections.abc import Mapping from dataclasses import dataclass, fields from types import MappingProxyType -from typing import ClassVar, overload +from typing import Any, ClassVar, overload import jax.numpy as jnp import numpy as np @@ -13,11 +14,35 @@ from lcm.typing import Float1D, FloatND, IntND, ScalarFloat +def _params_to_jax( + params: Mapping[str, Any], +) -> MappingProxyType[str, FloatND | IntND]: + """Cast Python `int` / `float` shock params to JAX scalars. + + `self.params` on a shock grid mixes dataclass-default literals (Python + `int` / `float`) with runtime-substituted values (already JAX scalars). + The downstream `compute_gridpoints` / `compute_transition_probs` and + `draw_shock` slots take `FloatND | IntND`-valued mappings, so the + boundary cast happens here rather than widening every kwarg signature + to admit Python scalars. + + """ + out: dict[str, FloatND | IntND] = {} + for name, value in params.items(): + if isinstance(value, bool | int): + out[name] = jnp.int32(value) + elif isinstance(value, float): + out[name] = jnp.asarray(value) + else: + out[name] = value + return MappingProxyType(out) + + def _gauss_hermite_normal( *, n_points: int, - mu: float | ScalarFloat | Float1D, - sigma: float | ScalarFloat | Float1D, + mu: ScalarFloat | Float1D, + sigma: ScalarFloat | Float1D, ) -> tuple[Float1D, Float1D]: """Compute Gauss-Hermite quadrature nodes and weights for $N(\\mu, \\sigma^2)$. @@ -84,11 +109,11 @@ def is_fully_specified(self) -> bool: return not self.params_to_pass_at_runtime @abstractmethod - def compute_gridpoints(self, **kwargs: float | FloatND | IntND) -> Float1D: + def compute_gridpoints(self, **kwargs: FloatND | IntND) -> Float1D: """Compute discretized gridpoints for the shock distribution.""" @abstractmethod - def compute_transition_probs(self, **kwargs: float | FloatND | IntND) -> FloatND: + def compute_transition_probs(self, **kwargs: FloatND | IntND) -> FloatND: """Compute transition probability matrix for the shock distribution.""" def get_gridpoints(self) -> Float1D: @@ -100,7 +125,7 @@ def get_gridpoints(self) -> Float1D: """ if not self.is_fully_specified: return jnp.full(self.n_points, jnp.nan) - return self.compute_gridpoints(**self.params) + return self.compute_gridpoints(**_params_to_jax(self.params)) def get_transition_probs(self) -> FloatND: """Get the transition probabilities at the gridpoints. @@ -111,19 +136,17 @@ def get_transition_probs(self) -> FloatND: """ if not self.is_fully_specified: return jnp.full((self.n_points, self.n_points), jnp.nan) - return self.compute_transition_probs(**self.params) + return self.compute_transition_probs(**_params_to_jax(self.params)) def to_jax(self) -> Float1D: """Convert the grid to a Jax array.""" return self.get_gridpoints() @overload - def get_coordinate(self, value: float | ScalarFloat) -> ScalarFloat: ... + def get_coordinate(self, value: ScalarFloat) -> ScalarFloat: ... @overload def get_coordinate(self, value: FloatND) -> FloatND: ... - def get_coordinate( - self, value: float | ScalarFloat | FloatND - ) -> ScalarFloat | FloatND: + def get_coordinate(self, value: ScalarFloat | FloatND) -> ScalarFloat | FloatND: """Return the generalized coordinate of a value in the grid.""" if not self.is_fully_specified: raise GridInitializationError( @@ -154,11 +177,11 @@ def _validate_gauss_hermite_grid( def _mixture_cdf( *, x: FloatND, - p1: float | FloatND, - mu1: float | FloatND, - sigma1: float | FloatND, - mu2: float | FloatND, - sigma2: float | FloatND, + p1: ScalarFloat | FloatND, + mu1: ScalarFloat | FloatND, + sigma1: ScalarFloat | FloatND, + mu2: ScalarFloat | FloatND, + sigma2: ScalarFloat | FloatND, ) -> FloatND: """Evaluate the CDF of a two-component normal mixture. diff --git a/src/lcm/shocks/ar1.py b/src/lcm/shocks/ar1.py index d358d79c4..014701762 100644 --- a/src/lcm/shocks/ar1.py +++ b/src/lcm/shocks/ar1.py @@ -24,7 +24,7 @@ class _ShockGridAR1(_ShockGrid): @abstractmethod def draw_shock( self, - params: MappingProxyType[str, float | FloatND], + params: MappingProxyType[str, FloatND | IntND], key: KeyArray, current_value: ScalarFloat, ) -> ScalarFloat: ... @@ -77,7 +77,7 @@ def _param_field_names(self) -> tuple[str, ...]: exclude = exclude | {"n_std"} return tuple(f.name for f in fields(self) if f.name not in exclude) - def compute_gridpoints(self, **kwargs: float | FloatND | IntND) -> Float1D: + def compute_gridpoints(self, **kwargs: FloatND | IntND) -> Float1D: n_points = self.n_points rho, sigma, mu = kwargs["rho"], kwargs["sigma"], kwargs["mu"] std_y = jnp.sqrt(sigma**2 / (1 - rho**2)) @@ -90,14 +90,14 @@ def compute_gridpoints(self, **kwargs: float | FloatND | IntND) -> Float1D: x = jnp.linspace(-x_max, x_max, n_points) return x + mu / (1 - rho) - def compute_transition_probs(self, **kwargs: float | FloatND | IntND) -> FloatND: + def compute_transition_probs(self, **kwargs: FloatND | IntND) -> FloatND: n_points = self.n_points rho, sigma = kwargs["rho"], kwargs["sigma"] std_y = jnp.sqrt(sigma**2 / (1 - rho**2)) if self.gauss_hermite: nodes, _weights = _gauss_hermite_normal( - n_points=n_points, mu=0.0, sigma=std_y + n_points=n_points, mu=jnp.asarray(0.0), sigma=std_y ) else: n_std = kwargs["n_std"] @@ -119,7 +119,7 @@ def compute_transition_probs(self, **kwargs: float | FloatND | IntND) -> FloatND def draw_shock( self, - params: MappingProxyType[str, float | FloatND], + params: MappingProxyType[str, FloatND | IntND], key: KeyArray, current_value: ScalarFloat, ) -> ScalarFloat: @@ -152,14 +152,14 @@ class Rouwenhorst(_ShockGridAR1): mu: float | int | None = None """Intercept (drift) of the AR(1) process.""" - def compute_gridpoints(self, **kwargs: float | FloatND | IntND) -> Float1D: + def compute_gridpoints(self, **kwargs: FloatND | IntND) -> Float1D: n_points = self.n_points rho, sigma, mu = kwargs["rho"], kwargs["sigma"], kwargs["mu"] nu = jnp.sqrt((n_points - 1) / (1 - rho**2)) * sigma long_run_mean = mu / (1.0 - rho) return jnp.linspace(long_run_mean - nu, long_run_mean + nu, n_points) - def compute_transition_probs(self, **kwargs: float | FloatND | IntND) -> FloatND: + def compute_transition_probs(self, **kwargs: FloatND | IntND) -> FloatND: n_points = self.n_points rho = kwargs["rho"] q = (rho + 1) / 2 @@ -188,7 +188,7 @@ def compute_transition_probs(self, **kwargs: float | FloatND | IntND) -> FloatND def draw_shock( self, - params: MappingProxyType[str, float | FloatND], + params: MappingProxyType[str, FloatND | IntND], key: KeyArray, current_value: ScalarFloat, ) -> ScalarFloat: @@ -242,17 +242,17 @@ class TauchenNormalMixture(_ShockGridAR1): @staticmethod def _innovation_variance( *, - p1: float | FloatND, - mu1: float | FloatND, - sigma1: float | FloatND, - mu2: float | FloatND, - sigma2: float | FloatND, - ) -> float | FloatND: + p1: ScalarFloat | FloatND, + mu1: ScalarFloat | FloatND, + sigma1: ScalarFloat | FloatND, + mu2: ScalarFloat | FloatND, + sigma2: ScalarFloat | FloatND, + ) -> ScalarFloat | FloatND: """Compute the variance of the mixture innovation.""" mean_eps = p1 * mu1 + (1 - p1) * mu2 return p1 * (sigma1**2 + mu1**2) + (1 - p1) * (sigma2**2 + mu2**2) - mean_eps**2 - def compute_gridpoints(self, **kwargs: float | FloatND | IntND) -> Float1D: + def compute_gridpoints(self, **kwargs: FloatND | IntND) -> Float1D: n_points = self.n_points rho, mu = kwargs["rho"], kwargs["mu"] n_std = kwargs["n_std"] @@ -268,7 +268,7 @@ def compute_gridpoints(self, **kwargs: float | FloatND | IntND) -> Float1D: x_max = n_std * std_y return jnp.linspace(long_run_mean - x_max, long_run_mean + x_max, n_points) - def compute_transition_probs(self, **kwargs: float | FloatND | IntND) -> FloatND: + def compute_transition_probs(self, **kwargs: FloatND | IntND) -> FloatND: n_points = self.n_points rho, mu = kwargs["rho"], kwargs["mu"] n_std = kwargs["n_std"] @@ -302,7 +302,7 @@ def compute_transition_probs(self, **kwargs: float | FloatND | IntND) -> FloatND def draw_shock( self, - params: MappingProxyType[str, float | FloatND], + params: MappingProxyType[str, FloatND | IntND], key: KeyArray, current_value: ScalarFloat, ) -> ScalarFloat: diff --git a/src/lcm/shocks/iid.py b/src/lcm/shocks/iid.py index 2079347fd..472fec0d5 100644 --- a/src/lcm/shocks/iid.py +++ b/src/lcm/shocks/iid.py @@ -23,7 +23,7 @@ class _ShockGridIID(_ShockGrid): @abstractmethod def draw_shock( self, - params: MappingProxyType[str, float | FloatND], + params: MappingProxyType[str, FloatND | IntND], key: KeyArray, ) -> ScalarFloat: ... @@ -44,18 +44,18 @@ class Uniform(_ShockGridIID): stop: float | int | None = None """Upper bound of the uniform distribution.""" - def compute_gridpoints(self, **kwargs: float | FloatND | IntND) -> Float1D: + def compute_gridpoints(self, **kwargs: FloatND | IntND) -> Float1D: return jnp.linspace( start=kwargs["start"], stop=kwargs["stop"], num=self.n_points ) - def compute_transition_probs(self, **kwargs: float | FloatND | IntND) -> FloatND: # noqa: ARG002 + def compute_transition_probs(self, **kwargs: FloatND | IntND) -> FloatND: # noqa: ARG002 n_points = self.n_points return jnp.full((n_points, n_points), fill_value=1 / n_points) def draw_shock( self, - params: MappingProxyType[str, float | FloatND], + params: MappingProxyType[str, FloatND | IntND], key: KeyArray, ) -> ScalarFloat: return jax.random.uniform( @@ -99,7 +99,7 @@ def _param_field_names(self) -> tuple[str, ...]: exclude = exclude | {"n_std"} return tuple(f.name for f in fields(self) if f.name not in exclude) - def compute_gridpoints(self, **kwargs: float | FloatND | IntND) -> Float1D: + def compute_gridpoints(self, **kwargs: FloatND | IntND) -> Float1D: n_points = self.n_points mu, sigma = kwargs["mu"], kwargs["sigma"] if self.gauss_hermite: @@ -112,7 +112,7 @@ def compute_gridpoints(self, **kwargs: float | FloatND | IntND) -> Float1D: x_max = mu + n_std * sigma return jnp.linspace(start=x_min, stop=x_max, num=n_points) - def compute_transition_probs(self, **kwargs: float | FloatND | IntND) -> FloatND: + def compute_transition_probs(self, **kwargs: FloatND | IntND) -> FloatND: n_points = self.n_points mu, sigma = kwargs["mu"], kwargs["sigma"] if self.gauss_hermite: @@ -132,7 +132,7 @@ def compute_transition_probs(self, **kwargs: float | FloatND | IntND) -> FloatND def draw_shock( self, - params: MappingProxyType[str, float | FloatND], + params: MappingProxyType[str, FloatND | IntND], key: KeyArray, ) -> ScalarFloat: return params["mu"] + params["sigma"] * jax.random.normal(key=key) @@ -167,7 +167,7 @@ def _param_field_names(self) -> tuple[str, ...]: exclude = exclude | {"n_std"} return tuple(f.name for f in fields(self) if f.name not in exclude) - def compute_gridpoints(self, **kwargs: float | FloatND | IntND) -> Float1D: + def compute_gridpoints(self, **kwargs: FloatND | IntND) -> Float1D: n_points = self.n_points mu, sigma = kwargs["mu"], kwargs["sigma"] if self.gauss_hermite: @@ -178,7 +178,7 @@ def compute_gridpoints(self, **kwargs: float | FloatND | IntND) -> Float1D: n_std = kwargs["n_std"] return jnp.exp(jnp.linspace(mu - n_std * sigma, mu + n_std * sigma, n_points)) - def compute_transition_probs(self, **kwargs: float | FloatND | IntND) -> FloatND: + def compute_transition_probs(self, **kwargs: FloatND | IntND) -> FloatND: n_points = self.n_points mu, sigma = kwargs["mu"], kwargs["sigma"] if self.gauss_hermite: @@ -198,7 +198,7 @@ def compute_transition_probs(self, **kwargs: float | FloatND | IntND) -> FloatND def draw_shock( self, - params: MappingProxyType[str, float | FloatND], + params: MappingProxyType[str, FloatND | IntND], key: KeyArray, ) -> ScalarFloat: return jnp.exp(params["mu"] + params["sigma"] * jax.random.normal(key=key)) @@ -238,7 +238,7 @@ class NormalMixture(_ShockGridIID): sigma2: float | int | None = None """Standard deviation of the second mixture component.""" - def compute_gridpoints(self, **kwargs: float | FloatND | IntND) -> Float1D: + def compute_gridpoints(self, **kwargs: FloatND | IntND) -> Float1D: n_points = self.n_points n_std = kwargs["n_std"] p1, mu1, sigma1 = kwargs["p1"], kwargs["mu1"], kwargs["sigma1"] @@ -253,7 +253,7 @@ def compute_gridpoints(self, **kwargs: float | FloatND | IntND) -> Float1D: mean_eps - n_std * std_eps, mean_eps + n_std * std_eps, n_points ) - def compute_transition_probs(self, **kwargs: float | FloatND | IntND) -> FloatND: + def compute_transition_probs(self, **kwargs: FloatND | IntND) -> FloatND: n_points = self.n_points n_std = kwargs["n_std"] p1, mu1, sigma1 = kwargs["p1"], kwargs["mu1"], kwargs["sigma1"] @@ -283,7 +283,7 @@ def compute_transition_probs(self, **kwargs: float | FloatND | IntND) -> FloatND def draw_shock( self, - params: MappingProxyType[str, float | FloatND], + params: MappingProxyType[str, FloatND | IntND], key: KeyArray, ) -> ScalarFloat: key1, key2 = jax.random.split(key) diff --git a/src/lcm/solution/solve_brute.py b/src/lcm/solution/solve_brute.py index 57b0c9306..3a9a8b9ed 100644 --- a/src/lcm/solution/solve_brute.py +++ b/src/lcm/solution/solve_brute.py @@ -2,7 +2,7 @@ import logging import os import time -from collections.abc import Callable, Hashable, Mapping +from collections.abc import Callable, Hashable from concurrent.futures import ThreadPoolExecutor, as_completed from dataclasses import dataclass from types import MappingProxyType @@ -25,7 +25,7 @@ def solve( *, internal_params: InternalParams, ages: AgeGrid, - internal_regimes: Mapping[RegimeName, InternalRegime], + internal_regimes: MappingProxyType[RegimeName, InternalRegime], logger: logging.Logger, enable_jit: bool, max_compilation_workers: int | None = None, @@ -235,7 +235,7 @@ def solve( def _compile_all_functions( *, - internal_regimes: Mapping[RegimeName, InternalRegime], + internal_regimes: MappingProxyType[RegimeName, InternalRegime], internal_params: InternalParams, ages: AgeGrid, next_regime_to_V_arr: MappingProxyType[RegimeName, FloatND], @@ -400,7 +400,7 @@ class _RegimeVTopology: def _get_regime_V_shapes_and_shardings( *, - internal_regimes: Mapping[RegimeName, InternalRegime], + internal_regimes: MappingProxyType[RegimeName, InternalRegime], internal_params: InternalParams, ) -> dict[RegimeName, _RegimeVTopology]: """Compute V-array shapes and shardings for every regime. @@ -469,7 +469,7 @@ def _emit_post_loop_diagnostics( logger: logging.Logger, diagnostic_rows: list[_DiagnosticRow], solution: MappingProxyType[int, MappingProxyType[RegimeName, FloatND]], - internal_regimes: Mapping[RegimeName, InternalRegime], + internal_regimes: MappingProxyType[RegimeName, InternalRegime], internal_params: InternalParams, running_any_nan: FloatND, running_any_inf: FloatND, @@ -511,7 +511,7 @@ def _raise_first_nan_row( *, diagnostic_rows: list[_DiagnosticRow], solution: MappingProxyType[int, MappingProxyType[RegimeName, FloatND]], - internal_regimes: Mapping[RegimeName, InternalRegime], + internal_regimes: MappingProxyType[RegimeName, InternalRegime], internal_params: InternalParams, ) -> None: """Find the first NaN-bearing (regime, period) and raise. @@ -535,7 +535,7 @@ def _raise_at( *, row: _DiagnosticRow, solution: MappingProxyType[int, MappingProxyType[RegimeName, FloatND]], - internal_regimes: Mapping[RegimeName, InternalRegime], + internal_regimes: MappingProxyType[RegimeName, InternalRegime], internal_params: InternalParams, ) -> None: """Run the enriched NaN diagnostic on a single offending row and raise.""" @@ -577,7 +577,7 @@ def _raise_at( def _reconstruct_next_regime_to_V_arr( *, period: int, - internal_regimes: Mapping[RegimeName, InternalRegime], + internal_regimes: MappingProxyType[RegimeName, InternalRegime], internal_params: InternalParams, solution: MappingProxyType[int, MappingProxyType[RegimeName, FloatND]], ) -> MappingProxyType[RegimeName, FloatND]: diff --git a/tests/regime_building/test_process_params.py b/tests/regime_building/test_process_params.py index 6fee263d2..4413d1b4b 100644 --- a/tests/regime_building/test_process_params.py +++ b/tests/regime_building/test_process_params.py @@ -5,6 +5,7 @@ import pytest from lcm.exceptions import InvalidNameError, InvalidParamsError +from lcm.interfaces import InternalRegime from lcm.params.processing import ( create_params_template, process_params, @@ -164,16 +165,22 @@ def test_ambiguous_model_regime_level(params_template): process_params(params=params, params_template=params_template) -class MockRegime: - """Mock regime with regime_params_template for testing create_params_template.""" +class MockRegime(InternalRegime): + """Mock InternalRegime exposing only `regime_params_template`. - def __init__(self, regime_params_template: dict) -> None: - self._regime_params_template = regime_params_template + Inherits from `InternalRegime` so `isinstance(x, InternalRegime)` + holds at `create_params_template`'s beartype-checked perimeter, but + bypasses `InternalRegime.__init__` to keep test fixtures minimal — + `create_params_template` only reads `regime_params_template`. + + """ - @property - def regime_params_template(self) -> MappingProxyType: - """Return regime_params_template as MappingProxyType.""" - return MappingProxyType(self._regime_params_template) + def __init__(self, regime_params_template: dict) -> None: + object.__setattr__( + self, + "regime_params_template", + MappingProxyType(regime_params_template), + ) def test_function_params_no_qname_separator(): diff --git a/tests/regime_building/test_regime_processing.py b/tests/regime_building/test_regime_processing.py index 7c60a9a51..aa3357fef 100644 --- a/tests/regime_building/test_regime_processing.py +++ b/tests/regime_building/test_regime_processing.py @@ -37,7 +37,7 @@ def next_c(a, b): functions={"utility": utility}, ) - got = Variables.from_regime(regime_mock) # ty: ignore[invalid-argument-type] + got = Variables.from_regime(regime_mock) assert isinstance(got, Variables) assert set(got) == {"a", "c"} @@ -60,7 +60,7 @@ def next_c(a, b): functions={"utility": lambda _c: None}, ) - got = get_grids(regime_mock) # ty: ignore[invalid-argument-type] + got = get_grids(regime_mock) assert isinstance(got["a"], DiscreteGrid) assert got["a"].categories == ("cat0", "cat1") assert got["a"].codes == (0, 1) @@ -95,7 +95,7 @@ def next_state(a, b): functions={"utility": lambda _c: None}, ) - got = get_grids(regime_mock) # ty: ignore[invalid-argument-type] + got = get_grids(regime_mock) assert list(got.keys()) == ["c", "b", "e", "d", "f", "a"] diff --git a/tests/regime_mock.py b/tests/regime_mock.py index 84157d35d..5da6c9138 100644 --- a/tests/regime_mock.py +++ b/tests/regime_mock.py @@ -1,41 +1,67 @@ -from dataclasses import dataclass, field +from types import MappingProxyType from typing import Literal, cast from lcm.grids import Grid from lcm.interfaces import SolveSimulateFunctionPair -from lcm.regime import _default_H +from lcm.regime import Regime, _default_H from lcm.regime_building.validation import collect_state_transitions from lcm.typing import UserFunction -@dataclass -class RegimeMock: +class RegimeMock(Regime): """A regime mock for testing the params_template creation functions. - This dataclass has the same attributes as the Regime dataclass, but does not perform - any checks, which helps us test the params_template creation functions in isolation. + Inherits from `Regime` so `isinstance(x, Regime)` holds at the + beartype-checked perimeter of `create_regime_params_template` and + friends, but bypasses `Regime.__init__`'s validation by writing + fields directly via `object.__setattr__`. Tests use this to supply + partial / loosely-typed configurations that the real `Regime` + constructor would reject. """ - n_periods: int | None = None - actions: dict[str, Grid | None] | None = None - states: dict[str, Grid | None] | None = None - state_transitions: dict[str, UserFunction | None] = field(default_factory=dict) - constraints: dict[str, UserFunction] = field(default_factory=dict) - transition: UserFunction | None = None - functions: dict[str, UserFunction] = field(default_factory=dict) + def __init__( + self, + *, + n_periods: int | None = None, + actions: dict[str, Grid | None] | None = None, + states: dict[str, Grid | None] | None = None, + state_transitions: dict[str, UserFunction | None] | None = None, + constraints: dict[str, UserFunction] | None = None, + transition: UserFunction | None = None, + functions: dict[str, UserFunction] | None = None, + ) -> None: + object.__setattr__(self, "n_periods", n_periods) + object.__setattr__(self, "actions", actions if actions is not None else {}) + object.__setattr__(self, "states", states if states is not None else {}) + object.__setattr__( + self, + "state_transitions", + state_transitions if state_transitions is not None else {}, + ) + object.__setattr__( + self, "constraints", constraints if constraints is not None else {} + ) + object.__setattr__(self, "transition", transition) + object.__setattr__( + self, "functions", functions if functions is not None else {} + ) + # Match Regime's defaults for fields RegimeMock callers don't touch + object.__setattr__(self, "active", lambda _age: True) + object.__setattr__(self, "derived_categoricals", MappingProxyType({})) + object.__setattr__(self, "description", "") + # `Regime.__post_init__` injects the default `H` for non-terminal + # regimes; mirror that here. + if not self.terminal and "H" not in self.functions: + object.__setattr__(self, "functions", {**self.functions, "H": _default_H}) @property def terminal(self) -> bool: return self.transition is None - def __post_init__(self) -> None: - if not self.terminal and "H" not in self.functions: - self.functions = {**self.functions, "H": _default_H} - def get_all_functions( self, phase: Literal["solve", "simulate"] = "solve" - ) -> dict[str, UserFunction]: + ) -> MappingProxyType[str, UserFunction]: """Get all regime functions including utility, constraints, and transitions.""" result: dict[str, UserFunction] = {} for name, func in self.functions.items(): @@ -54,4 +80,4 @@ def get_all_functions( ) if self.transition: result["next_regime"] = self.transition - return result + return MappingProxyType(result) diff --git a/tests/test_fgp_discretization.py b/tests/test_fgp_discretization.py index 103d7fb58..4b2bc3eaa 100644 --- a/tests/test_fgp_discretization.py +++ b/tests/test_fgp_discretization.py @@ -287,7 +287,12 @@ def test_mixture_cdf_spot_check(): """Spot-check mixture CDF against manual scipy-style computation.""" x = jnp.array([0.0, 0.1, -0.1]) got = _mixture_cdf( - x=x, p1=FGP_P1, mu1=FGP_MU1, sigma1=FGP_SIGMA1, mu2=FGP_MU2, sigma2=FGP_SIGMA2 + x=x, + p1=jnp.asarray(FGP_P1), + mu1=jnp.asarray(FGP_MU1), + sigma1=jnp.asarray(FGP_SIGMA1), + mu2=jnp.asarray(FGP_MU2), + sigma2=jnp.asarray(FGP_SIGMA2), ) expected = FGP_P1 * jax_cdf((x - FGP_MU1) / FGP_SIGMA1) + (1 - FGP_P1) * jax_cdf( diff --git a/tests/test_function_representation.py b/tests/test_function_representation.py index 4c6827a89..9d00c791f 100644 --- a/tests/test_function_representation.py +++ b/tests/test_function_representation.py @@ -58,7 +58,7 @@ def test_function_evaluator_with_one_continuous_variable(): func = partial(evaluator, next_V_arr=next_V_arr) # test the evaluator - got = func(next_wealth=0.5) + got = func(next_wealth=jnp.asarray(0.5)) expected = 0.5 * jnp.pi + 2 assert jnp.allclose(got, expected) @@ -146,8 +146,8 @@ def test_function_evaluator(binary_discrete_grid): out = evaluator( next_retired=1, next_insured=0, - next_wealth=600, - next_human_capital=1.5, + next_wealth=jnp.asarray(600.0), + next_human_capital=jnp.asarray(1.5), next_V_arr=next_V_arr, ) @@ -169,7 +169,7 @@ def test_get_coordinate_finder(): grid=LinSpacedGrid(start=0, stop=10, n_points=21), ) find_coordinate = partial(find_coordinate) - calculated = find_coordinate(wealth=5.75) + calculated = find_coordinate(wealth=jnp.asarray(5.75)) assert calculated == 11.5 @@ -224,7 +224,7 @@ def test_get_function_evaluator_illustrative(): # partial the function values into the evaluator f = partial(evaluator, values_name=values) - got = f(prefix_a=0.25) + got = f(prefix_a=jnp.asarray(0.25)) expected = jnp.pi * 0.25 + 2 assert jnp.allclose(got, expected) @@ -245,10 +245,10 @@ def test_get_coordinate_finder_illustrative(): in_name="a", grid=LinSpacedGrid(start=0, stop=1, n_points=3), ) - assert find_coordinate(a=0) == 0 - assert find_coordinate(a=0.5) == 1 - assert find_coordinate(a=1) == 2 - assert find_coordinate(a=0.25) == 0.5 + assert find_coordinate(a=jnp.asarray(0.0)) == 0 + assert find_coordinate(a=jnp.asarray(0.5)) == 1 + assert find_coordinate(a=jnp.asarray(1.0)) == 2 + assert find_coordinate(a=jnp.asarray(0.25)) == 0.5 @pytest.mark.illustrative @@ -349,9 +349,9 @@ def test_function_evaluator_performs_linear_extrapolation(): func = partial(evaluator, next_V_arr=next_V_arr) # test the evaluator on values outside the grid - wealth_outside_of_grid = [-0.5, 3.5, 10] + wealth_outside_of_grid = [-0.5, 3.5, 10.0] # We expect linear extrapolation expected = jnp.pi * jnp.array(wealth_outside_of_grid) + 2 - got = jnp.array([func(next_wealth=w) for w in wealth_outside_of_grid]) + got = jnp.array([func(next_wealth=jnp.asarray(w)) for w in wealth_outside_of_grid]) assert jnp.allclose(got, expected) diff --git a/tests/test_grids.py b/tests/test_grids.py index 560b3e87e..2db7c6e61 100644 --- a/tests/test_grids.py +++ b/tests/test_grids.py @@ -753,15 +753,15 @@ def test_piecewise_boundary_conditions(grid_cls, boundary_style: str): expected_coord_at = 5.0 if is_lin else 2.0 # Test value just below boundary -> piece 0 - coord_below = float(grid.get_coordinate(below_boundary)) + coord_below = float(grid.get_coordinate(jnp.asarray(below_boundary))) assert coord_below < expected_coord_at # Test value exactly at boundary - coord_at = float(grid.get_coordinate(boundary)) + coord_at = float(grid.get_coordinate(jnp.asarray(boundary))) assert coord_at == pytest.approx(expected_coord_at) # Test value just above boundary -> piece 1 - coord_above = float(grid.get_coordinate(above_boundary)) + coord_above = float(grid.get_coordinate(jnp.asarray(above_boundary))) assert coord_above > expected_coord_at diff --git a/tests/test_variables.py b/tests/test_variables.py index 02998948f..2de2a0974 100644 --- a/tests/test_variables.py +++ b/tests/test_variables.py @@ -162,7 +162,7 @@ def next_state(x): }, functions={"utility": lambda c_action: 0}, # noqa: ARG005 ) - variables = Variables.from_regime(regime) # ty: ignore[invalid-argument-type] + variables = Variables.from_regime(regime) assert list(variables) == ["a_discrete", "b_continuous", "c_action"] @@ -188,5 +188,5 @@ def next_state(x): actions={"a": DiscreteGrid(binary_category_class)}, functions={"utility": lambda a: 0}, # noqa: ARG005 ) - variables = Variables.from_regime(regime) # ty: ignore[invalid-argument-type] + variables = Variables.from_regime(regime) assert variables.discrete_state_names == ("first", "second", "third") From f210a3c16fc96d7b1c21fd2cbada30373564631d Mon Sep 17 00:00:00 2001 From: Hans-Martin von Gaudecker Date: Wed, 13 May 2026 23:07:10 +0200 Subject: [PATCH 32/77] Activate beartype claw on lcm.regime_building MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds `lcm.regime_building` to the import-claw registrations in `lcm/__init__.py`, mapping type violations to `ModelInitializationError` (regime compilation is part of model construction). Wraps `dags.with_signature` / `dags.rename_arguments` via a thin helper in `lcm.utils._dags_forwarders` that defaults `forwarder=True`. Each direct caller of those dags helpers inside pylcm produces a generic `*args, **kwargs` forwarder whose annotations describe the inner function's contract, not the wrapper's own call protocol. `forwarder=True` advertises the wrapper as a permissive forwarder on its `__annotations__`, so beartype's claw treats it as universally permissive and skips per-parameter enforcement — matching the wrapper's actual runtime behaviour. dags' own `get_annotations` recovers the user-described view via its existing args/kwargs-mismatch fallback. Pins `dags` to the `feat/no-type-check-flag` branch (PR OpenSourceEconomics/dags#82) which adds the `forwarder` flag. Will be replaced with a released version once that PR lands. Annotation drift fixed alongside activation: - `collect_state_transitions(states: ...)` widened to `Mapping[StateName, Grid | None]`; test mocks pass `None` for placeholder states. - `map_coordinates(coordinates: ...)` widened to `Sequence[Array] | Array`; callers pass a 2D `jnp.array` (a single Array, not a sequence) and JAX produces a single tracer under vmap. - `_get_weights_func_for_shock.weights_func_runtime.shock_kw` typed as `dict[str, float | FloatND]`; under JIT the runtime shock params arrive as tracers. - `solve_brute.solve.running_any_nan` / `running_any_inf` typed as `BoolND` to match the underlying `jnp.zeros((), dtype=bool)`. - `diagnostics._wrap_with_reduction.reduced.**kwargs` typed as `Array | Mapping[str, Array]` since `next_regime_to_V_arr` flows through it as a mapping alongside Array-valued state/action inputs. - One test fixture (`tests/test_next_state.py::test_create_stochastic_next_func`) updated to pass an `int32`-typed `labels` array. Out of scope: extending the claw to `lcm.solution` / `lcm.simulation`, which surfaces further annotation drift the claw correctly catches but that needs its own pass. Co-Authored-By: Claude Opus 4.7 --- pixi.lock | 47 ++++++------ pyproject.toml | 5 ++ src/lcm/__init__.py | 3 +- src/lcm/_beartype_conf.py | 4 + src/lcm/regime_building/Q_and_F.py | 3 +- src/lcm/regime_building/V.py | 3 +- src/lcm/regime_building/diagnostics.py | 7 +- src/lcm/regime_building/max_Q_over_a.py | 2 +- src/lcm/regime_building/ndimage.py | 2 +- src/lcm/regime_building/next_state.py | 3 +- src/lcm/regime_building/processing.py | 11 ++- src/lcm/regime_building/validation.py | 2 +- src/lcm/solution/solve_brute.py | 10 +-- src/lcm/utils/_dags_forwarders.py | 98 +++++++++++++++++++++++++ tests/test_next_state.py | 2 +- 15 files changed, 160 insertions(+), 42 deletions(-) create mode 100644 src/lcm/utils/_dags_forwarders.py diff --git a/pixi.lock b/pixi.lock index 05c13a7af..3b745fb11 100644 --- a/pixi.lock +++ b/pixi.lock @@ -275,7 +275,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ae/44/c1221527f6a71a01ec6fbad7fa78f1d50dfa02217385cf0fa3eec7087d59/click-8.3.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2c/1a/aff8bb287a4b1400f69e09a53bd65de96aa5cee5691925b38731c67fc695/click_default_group-1.2.4-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/2c/c1/a662f0a8f6e024fca239d493f278d9adf5de1c8408af46a53a76beb13534/dags-0.5.1-py3-none-any.whl + - pypi: git+https://github.com/OpenSourceEconomics/dags.git?rev=d415d10#d415d10ee9704b448db90630719f1410ca321132 - pypi: https://files.pythonhosted.org/packages/8d/2d/f61c918d9edc2127068f0d5ad4604fedd9bfd393f464219090f3279c73f7/estimagic-0.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/f5/ee39c6e92acc742c052f137b47c210cd0a1b72dcd3f98495528bb4d27761/flatten_dict-0.4.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d5/1f/5f4a3cd9e4440e9d9bc78ad0a91a1c8d46b4d429d5239ebe6793c9fe5c41/fsspec-2026.3.0-py3-none-any.whl @@ -588,7 +588,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-1.3.2-h25fd6f3_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda - pypi: https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/2c/c1/a662f0a8f6e024fca239d493f278d9adf5de1c8408af46a53a76beb13534/dags-0.5.1-py3-none-any.whl + - pypi: git+https://github.com/OpenSourceEconomics/dags.git?rev=d415d10#d415d10ee9704b448db90630719f1410ca321132 - pypi: https://files.pythonhosted.org/packages/43/f5/ee39c6e92acc742c052f137b47c210cd0a1b72dcd3f98495528bb4d27761/flatten_dict-0.4.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/70/aa/dfac6d72cc35bc07e7587115b6946e333ef4ccb2e6cd26ecf639438c5d26/jax-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5f/78/a3d9ceda0793f4fb43daa292af7b801932611a1aed442636ddfc93d58c7a/jax_cuda12_pjrt-0.10.0-py3-none-manylinux_2_27_x86_64.whl @@ -888,7 +888,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-1.3.2-h25fd6f3_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda - pypi: https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/2c/c1/a662f0a8f6e024fca239d493f278d9adf5de1c8408af46a53a76beb13534/dags-0.5.1-py3-none-any.whl + - pypi: git+https://github.com/OpenSourceEconomics/dags.git?rev=d415d10#d415d10ee9704b448db90630719f1410ca321132 - pypi: https://files.pythonhosted.org/packages/43/f5/ee39c6e92acc742c052f137b47c210cd0a1b72dcd3f98495528bb4d27761/flatten_dict-0.4.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/70/aa/dfac6d72cc35bc07e7587115b6946e333ef4ccb2e6cd26ecf639438c5d26/jax-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/21/98/77f15d81fd0637da454e453c8456d4a2b5c8b2e66823b4237ee8689152cf/jax_cuda13_pjrt-0.10.0-py3-none-manylinux_2_27_x86_64.whl @@ -1158,7 +1158,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-1.3.2-h25fd6f3_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda - pypi: https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/2c/c1/a662f0a8f6e024fca239d493f278d9adf5de1c8408af46a53a76beb13534/dags-0.5.1-py3-none-any.whl + - pypi: git+https://github.com/OpenSourceEconomics/dags.git?rev=d415d10#d415d10ee9704b448db90630719f1410ca321132 - pypi: https://files.pythonhosted.org/packages/43/f5/ee39c6e92acc742c052f137b47c210cd0a1b72dcd3f98495528bb4d27761/flatten_dict-0.4.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/70/aa/dfac6d72cc35bc07e7587115b6946e333ef4ccb2e6cd26ecf639438c5d26/jax-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a1/8e/b2a08ffc51c93842de71f7f988865cebfa7f43d6721957812dc8cc8b9d40/jaxlib-0.10.0-cp314-cp314-manylinux_2_27_x86_64.whl @@ -1399,7 +1399,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zlib-1.3.2-h8088a28_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda - pypi: https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/2c/c1/a662f0a8f6e024fca239d493f278d9adf5de1c8408af46a53a76beb13534/dags-0.5.1-py3-none-any.whl + - pypi: git+https://github.com/OpenSourceEconomics/dags.git?rev=d415d10#d415d10ee9704b448db90630719f1410ca321132 - pypi: https://files.pythonhosted.org/packages/43/f5/ee39c6e92acc742c052f137b47c210cd0a1b72dcd3f98495528bb4d27761/flatten_dict-0.4.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/70/aa/dfac6d72cc35bc07e7587115b6946e333ef4ccb2e6cd26ecf639438c5d26/jax-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a7/25/e1e52a21786b321fb6a2edf9ef9971aa70f06bb2738aef9afd6d8f46a441/jaxlib-0.10.0-cp314-cp314-macosx_11_0_arm64.whl @@ -1637,7 +1637,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/zlib-1.3.2-hfd05255_2.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_6.conda - pypi: https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/2c/c1/a662f0a8f6e024fca239d493f278d9adf5de1c8408af46a53a76beb13534/dags-0.5.1-py3-none-any.whl + - pypi: git+https://github.com/OpenSourceEconomics/dags.git?rev=d415d10#d415d10ee9704b448db90630719f1410ca321132 - pypi: https://files.pythonhosted.org/packages/43/f5/ee39c6e92acc742c052f137b47c210cd0a1b72dcd3f98495528bb4d27761/flatten_dict-0.4.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/70/aa/dfac6d72cc35bc07e7587115b6946e333ef4ccb2e6cd26ecf639438c5d26/jax-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/24/08/26e6a3ecf0a95f1ec0dcd7a668d5c9a72e581c40fe4ae51e102ca63174c5/jaxlib-0.10.0-cp314-cp314-win_amd64.whl @@ -1895,7 +1895,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-1.3.2-h25fd6f3_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda - pypi: https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/2c/c1/a662f0a8f6e024fca239d493f278d9adf5de1c8408af46a53a76beb13534/dags-0.5.1-py3-none-any.whl + - pypi: git+https://github.com/OpenSourceEconomics/dags.git?rev=d415d10#d415d10ee9704b448db90630719f1410ca321132 - pypi: https://files.pythonhosted.org/packages/43/f5/ee39c6e92acc742c052f137b47c210cd0a1b72dcd3f98495528bb4d27761/flatten_dict-0.4.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/70/aa/dfac6d72cc35bc07e7587115b6946e333ef4ccb2e6cd26ecf639438c5d26/jax-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a1/8e/b2a08ffc51c93842de71f7f988865cebfa7f43d6721957812dc8cc8b9d40/jaxlib-0.10.0-cp314-cp314-manylinux_2_27_x86_64.whl @@ -2146,7 +2146,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zlib-1.3.2-h8088a28_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda - pypi: https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/2c/c1/a662f0a8f6e024fca239d493f278d9adf5de1c8408af46a53a76beb13534/dags-0.5.1-py3-none-any.whl + - pypi: git+https://github.com/OpenSourceEconomics/dags.git?rev=d415d10#d415d10ee9704b448db90630719f1410ca321132 - pypi: https://files.pythonhosted.org/packages/43/f5/ee39c6e92acc742c052f137b47c210cd0a1b72dcd3f98495528bb4d27761/flatten_dict-0.4.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/70/aa/dfac6d72cc35bc07e7587115b6946e333ef4ccb2e6cd26ecf639438c5d26/jax-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a7/25/e1e52a21786b321fb6a2edf9ef9971aa70f06bb2738aef9afd6d8f46a441/jaxlib-0.10.0-cp314-cp314-macosx_11_0_arm64.whl @@ -2393,7 +2393,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/zlib-1.3.2-hfd05255_2.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_6.conda - pypi: https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/2c/c1/a662f0a8f6e024fca239d493f278d9adf5de1c8408af46a53a76beb13534/dags-0.5.1-py3-none-any.whl + - pypi: git+https://github.com/OpenSourceEconomics/dags.git?rev=d415d10#d415d10ee9704b448db90630719f1410ca321132 - pypi: https://files.pythonhosted.org/packages/43/f5/ee39c6e92acc742c052f137b47c210cd0a1b72dcd3f98495528bb4d27761/flatten_dict-0.4.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/70/aa/dfac6d72cc35bc07e7587115b6946e333ef4ccb2e6cd26ecf639438c5d26/jax-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/24/08/26e6a3ecf0a95f1ec0dcd7a668d5c9a72e581c40fe4ae51e102ca63174c5/jaxlib-0.10.0-cp314-cp314-win_amd64.whl @@ -2648,7 +2648,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zlib-1.3.2-h8088a28_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda - pypi: https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/2c/c1/a662f0a8f6e024fca239d493f278d9adf5de1c8408af46a53a76beb13534/dags-0.5.1-py3-none-any.whl + - pypi: git+https://github.com/OpenSourceEconomics/dags.git?rev=d415d10#d415d10ee9704b448db90630719f1410ca321132 - pypi: https://files.pythonhosted.org/packages/43/f5/ee39c6e92acc742c052f137b47c210cd0a1b72dcd3f98495528bb4d27761/flatten_dict-0.4.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/70/aa/dfac6d72cc35bc07e7587115b6946e333ef4ccb2e6cd26ecf639438c5d26/jax-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/09/dc/6d8fbfc29d902251cf333414cf7dcfaf4b252a9920c881354584ed36270d/jax_metal-0.1.1-py3-none-macosx_13_0_arm64.whl @@ -2911,7 +2911,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-1.3.2-h25fd6f3_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda - pypi: https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/2c/c1/a662f0a8f6e024fca239d493f278d9adf5de1c8408af46a53a76beb13534/dags-0.5.1-py3-none-any.whl + - pypi: git+https://github.com/OpenSourceEconomics/dags.git?rev=d415d10#d415d10ee9704b448db90630719f1410ca321132 - pypi: https://files.pythonhosted.org/packages/43/f5/ee39c6e92acc742c052f137b47c210cd0a1b72dcd3f98495528bb4d27761/flatten_dict-0.4.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/70/aa/dfac6d72cc35bc07e7587115b6946e333ef4ccb2e6cd26ecf639438c5d26/jax-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a1/8e/b2a08ffc51c93842de71f7f988865cebfa7f43d6721957812dc8cc8b9d40/jaxlib-0.10.0-cp314-cp314-manylinux_2_27_x86_64.whl @@ -3166,7 +3166,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zlib-1.3.2-h8088a28_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda - pypi: https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/2c/c1/a662f0a8f6e024fca239d493f278d9adf5de1c8408af46a53a76beb13534/dags-0.5.1-py3-none-any.whl + - pypi: git+https://github.com/OpenSourceEconomics/dags.git?rev=d415d10#d415d10ee9704b448db90630719f1410ca321132 - pypi: https://files.pythonhosted.org/packages/43/f5/ee39c6e92acc742c052f137b47c210cd0a1b72dcd3f98495528bb4d27761/flatten_dict-0.4.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/70/aa/dfac6d72cc35bc07e7587115b6946e333ef4ccb2e6cd26ecf639438c5d26/jax-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a7/25/e1e52a21786b321fb6a2edf9ef9971aa70f06bb2738aef9afd6d8f46a441/jaxlib-0.10.0-cp314-cp314-macosx_11_0_arm64.whl @@ -3417,7 +3417,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/zlib-1.3.2-hfd05255_2.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_6.conda - pypi: https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/2c/c1/a662f0a8f6e024fca239d493f278d9adf5de1c8408af46a53a76beb13534/dags-0.5.1-py3-none-any.whl + - pypi: git+https://github.com/OpenSourceEconomics/dags.git?rev=d415d10#d415d10ee9704b448db90630719f1410ca321132 - pypi: https://files.pythonhosted.org/packages/43/f5/ee39c6e92acc742c052f137b47c210cd0a1b72dcd3f98495528bb4d27761/flatten_dict-0.4.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/70/aa/dfac6d72cc35bc07e7587115b6946e333ef4ccb2e6cd26ecf639438c5d26/jax-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/24/08/26e6a3ecf0a95f1ec0dcd7a668d5c9a72e581c40fe4ae51e102ca63174c5/jaxlib-0.10.0-cp314-cp314-win_amd64.whl @@ -3716,7 +3716,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-1.3.2-h25fd6f3_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda - pypi: https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/2c/c1/a662f0a8f6e024fca239d493f278d9adf5de1c8408af46a53a76beb13534/dags-0.5.1-py3-none-any.whl + - pypi: git+https://github.com/OpenSourceEconomics/dags.git?rev=d415d10#d415d10ee9704b448db90630719f1410ca321132 - pypi: https://files.pythonhosted.org/packages/43/f5/ee39c6e92acc742c052f137b47c210cd0a1b72dcd3f98495528bb4d27761/flatten_dict-0.4.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/70/aa/dfac6d72cc35bc07e7587115b6946e333ef4ccb2e6cd26ecf639438c5d26/jax-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5f/78/a3d9ceda0793f4fb43daa292af7b801932611a1aed442636ddfc93d58c7a/jax_cuda12_pjrt-0.10.0-py3-none-manylinux_2_27_x86_64.whl @@ -4029,7 +4029,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-1.3.2-h25fd6f3_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda - pypi: https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/2c/c1/a662f0a8f6e024fca239d493f278d9adf5de1c8408af46a53a76beb13534/dags-0.5.1-py3-none-any.whl + - pypi: git+https://github.com/OpenSourceEconomics/dags.git?rev=d415d10#d415d10ee9704b448db90630719f1410ca321132 - pypi: https://files.pythonhosted.org/packages/43/f5/ee39c6e92acc742c052f137b47c210cd0a1b72dcd3f98495528bb4d27761/flatten_dict-0.4.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/70/aa/dfac6d72cc35bc07e7587115b6946e333ef4ccb2e6cd26ecf639438c5d26/jax-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/21/98/77f15d81fd0637da454e453c8456d4a2b5c8b2e66823b4237ee8689152cf/jax_cuda13_pjrt-0.10.0-py3-none-manylinux_2_27_x86_64.whl @@ -4308,7 +4308,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zlib-1.3.2-h8088a28_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda - pypi: https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/2c/c1/a662f0a8f6e024fca239d493f278d9adf5de1c8408af46a53a76beb13534/dags-0.5.1-py3-none-any.whl + - pypi: git+https://github.com/OpenSourceEconomics/dags.git?rev=d415d10#d415d10ee9704b448db90630719f1410ca321132 - pypi: https://files.pythonhosted.org/packages/43/f5/ee39c6e92acc742c052f137b47c210cd0a1b72dcd3f98495528bb4d27761/flatten_dict-0.4.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/70/aa/dfac6d72cc35bc07e7587115b6946e333ef4ccb2e6cd26ecf639438c5d26/jax-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/09/dc/6d8fbfc29d902251cf333414cf7dcfaf4b252a9920c881354584ed36270d/jax_metal-0.1.1-py3-none-macosx_13_0_arm64.whl @@ -4673,7 +4673,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-ng-2.3.3-hceb46e0_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda - pypi: https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/2c/c1/a662f0a8f6e024fca239d493f278d9adf5de1c8408af46a53a76beb13534/dags-0.5.1-py3-none-any.whl + - pypi: git+https://github.com/OpenSourceEconomics/dags.git?rev=d415d10#d415d10ee9704b448db90630719f1410ca321132 - pypi: https://files.pythonhosted.org/packages/43/f5/ee39c6e92acc742c052f137b47c210cd0a1b72dcd3f98495528bb4d27761/flatten_dict-0.4.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/70/aa/dfac6d72cc35bc07e7587115b6946e333ef4ccb2e6cd26ecf639438c5d26/jax-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a1/8e/b2a08ffc51c93842de71f7f988865cebfa7f43d6721957812dc8cc8b9d40/jaxlib-0.10.0-cp314-cp314-manylinux_2_27_x86_64.whl @@ -4964,7 +4964,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zlib-ng-2.3.3-hed4e4f5_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda - pypi: https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/2c/c1/a662f0a8f6e024fca239d493f278d9adf5de1c8408af46a53a76beb13534/dags-0.5.1-py3-none-any.whl + - pypi: git+https://github.com/OpenSourceEconomics/dags.git?rev=d415d10#d415d10ee9704b448db90630719f1410ca321132 - pypi: https://files.pythonhosted.org/packages/43/f5/ee39c6e92acc742c052f137b47c210cd0a1b72dcd3f98495528bb4d27761/flatten_dict-0.4.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/70/aa/dfac6d72cc35bc07e7587115b6946e333ef4ccb2e6cd26ecf639438c5d26/jax-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a7/25/e1e52a21786b321fb6a2edf9ef9971aa70f06bb2738aef9afd6d8f46a441/jaxlib-0.10.0-cp314-cp314-macosx_11_0_arm64.whl @@ -5274,7 +5274,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/zlib-ng-2.3.3-h0261ad2_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_6.conda - pypi: https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/2c/c1/a662f0a8f6e024fca239d493f278d9adf5de1c8408af46a53a76beb13534/dags-0.5.1-py3-none-any.whl + - pypi: git+https://github.com/OpenSourceEconomics/dags.git?rev=d415d10#d415d10ee9704b448db90630719f1410ca321132 - pypi: https://files.pythonhosted.org/packages/43/f5/ee39c6e92acc742c052f137b47c210cd0a1b72dcd3f98495528bb4d27761/flatten_dict-0.4.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/70/aa/dfac6d72cc35bc07e7587115b6946e333ef4ccb2e6cd26ecf639438c5d26/jax-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/24/08/26e6a3ecf0a95f1ec0dcd7a668d5c9a72e581c40fe4ae51e102ca63174c5/jaxlib-0.10.0-cp314-cp314-win_amd64.whl @@ -7542,10 +7542,9 @@ packages: purls: [] size: 210103 timestamp: 1771943128249 -- pypi: https://files.pythonhosted.org/packages/2c/c1/a662f0a8f6e024fca239d493f278d9adf5de1c8408af46a53a76beb13534/dags-0.5.1-py3-none-any.whl +- pypi: git+https://github.com/OpenSourceEconomics/dags.git?rev=d415d10#d415d10ee9704b448db90630719f1410ca321132 name: dags - version: 0.5.1 - sha256: e9fd9fbe0536784fe8b8ce58ea194801b1de39d7364941d4a1f2d8240c14123d + version: 0.5.2.dev1+gd415d10ee requires_dist: - flatten-dict - networkx>=3.6 @@ -14086,8 +14085,8 @@ packages: timestamp: 1774796815820 - pypi: ./ name: pylcm - version: 0.0.2.dev136+ga5d932a48.d20260513 - sha256: fc3e3cff622b9db6e1f5a01c6cfad41879d5d1efdb982743f18b8ca3edf585a7 + version: 0.0.2.dev179+g59aca3f5f.d20260513 + sha256: 0c5af9810a5be3f1e5861656eaf8e0c6fa09dcbf505d6e746feffeff83e0cce6 requires_dist: - beartype>=0.21 - cloudpickle>=3.1.2 diff --git a/pyproject.toml b/pyproject.toml index 3651a96a5..e23e07d64 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -149,6 +149,11 @@ ty = "ty check" jax = ">=0.9" pdbp = "*" pylcm = { path = ".", editable = true } +# Pin dags to the feat/no-type-check-flag branch (PR +# OpenSourceEconomics/dags#82): adds the `forwarder=True` flag we need +# to make `with_signature` cooperate with beartype's import claw. +# Will be replaced with a released version once that PR lands. +dags = { git = "https://github.com/OpenSourceEconomics/dags.git", rev = "d415d10" } [tool.pixi.tasks] asv-compare = "asv compare" asv-preview = "asv preview" diff --git a/src/lcm/__init__.py b/src/lcm/__init__.py index bb00b6127..565acfce4 100644 --- a/src/lcm/__init__.py +++ b/src/lcm/__init__.py @@ -38,11 +38,12 @@ # exception most natural to that subpackage (see `lcm._beartype_conf`). from beartype.claw import beartype_package -from lcm._beartype_conf import GRID_CONF, PARAMS_CONF +from lcm._beartype_conf import GRID_CONF, PARAMS_CONF, REGIME_BUILDING_CONF beartype_package("lcm.grids", conf=GRID_CONF) beartype_package("lcm.shocks", conf=GRID_CONF) beartype_package("lcm.params", conf=PARAMS_CONF) +beartype_package("lcm.regime_building", conf=REGIME_BUILDING_CONF) from lcm import shocks # noqa: E402 from lcm._version import __version__ # noqa: E402 diff --git a/src/lcm/_beartype_conf.py b/src/lcm/_beartype_conf.py index daf276120..6a7bee7cf 100644 --- a/src/lcm/_beartype_conf.py +++ b/src/lcm/_beartype_conf.py @@ -66,3 +66,7 @@ def deco(cls: type[C]) -> type[C]: # Used on `Model.solve` and `Model.simulate`. PARAMS_CONF = _conf(InvalidParamsError) + +# Used by the claw on `lcm.regime_building` (regime compilation pipeline, +# part of model construction). +REGIME_BUILDING_CONF = _conf(ModelInitializationError) diff --git a/src/lcm/regime_building/Q_and_F.py b/src/lcm/regime_building/Q_and_F.py index 814619ddf..9bff01a85 100644 --- a/src/lcm/regime_building/Q_and_F.py +++ b/src/lcm/regime_building/Q_and_F.py @@ -3,7 +3,7 @@ from typing import Any, cast import jax.numpy as jnp -from dags import concatenate_functions, with_signature +from dags import concatenate_functions from jax import Array from lcm.regime_building.h_dag import _get_build_H_kwargs @@ -24,6 +24,7 @@ TransitionFunctionName, TransitionFunctionsMapping, ) +from lcm.utils._dags_forwarders import with_signature from lcm.utils.dispatchers import productmap from lcm.utils.functools import get_union_of_args diff --git a/src/lcm/regime_building/V.py b/src/lcm/regime_building/V.py index 31521a5c8..ad8c7d21a 100644 --- a/src/lcm/regime_building/V.py +++ b/src/lcm/regime_building/V.py @@ -3,7 +3,7 @@ from types import MappingProxyType import jax.numpy as jnp -from dags import concatenate_functions, with_signature +from dags import concatenate_functions from dags.tree import qname_from_tree_path from jax import Array @@ -13,6 +13,7 @@ from lcm.regime_building.ndimage import map_coordinates from lcm.shocks import _ShockGrid from lcm.typing import FloatND, ScalarFloat, StateName +from lcm.utils._dags_forwarders import with_signature from lcm.utils.functools import all_as_kwargs from lcm.variables import Variables, get_grids diff --git a/src/lcm/regime_building/diagnostics.py b/src/lcm/regime_building/diagnostics.py index fb0ac9f17..ad88e6cce 100644 --- a/src/lcm/regime_building/diagnostics.py +++ b/src/lcm/regime_building/diagnostics.py @@ -9,7 +9,7 @@ `lcm.utils.error_handling`. """ -from collections.abc import Callable +from collections.abc import Callable, Mapping from types import MappingProxyType from typing import Any @@ -164,7 +164,10 @@ def _wrap_with_reduction( """ - def reduced(**kwargs: Array) -> dict[str, Any]: + # `kwargs` carries the wrapped function's full input map, which may + # include the `next_regime_to_V_arr` mapping alongside the Array-valued + # state/action inputs — so the kwarg-value type is `Array | Mapping`. + def reduced(**kwargs: Array | Mapping[str, Array]) -> dict[str, Any]: U_arr, F_arr, E_next_V, Q_arr, regime_probs = func(**kwargs) F_float = F_arr.astype(float) # NaN-count arrays are masked by feasibility: only feasible cells diff --git a/src/lcm/regime_building/max_Q_over_a.py b/src/lcm/regime_building/max_Q_over_a.py index f1e5d3913..d5fa18b93 100644 --- a/src/lcm/regime_building/max_Q_over_a.py +++ b/src/lcm/regime_building/max_Q_over_a.py @@ -3,7 +3,6 @@ from types import MappingProxyType import jax.numpy as jnp -from dags import with_signature from jax import Array from lcm.regime_building.argmax import argmax_and_max @@ -17,6 +16,7 @@ RegimeName, StateName, ) +from lcm.utils._dags_forwarders import with_signature from lcm.utils.dispatchers import productmap diff --git a/src/lcm/regime_building/ndimage.py b/src/lcm/regime_building/ndimage.py index 8d7d80d97..69a812b02 100644 --- a/src/lcm/regime_building/ndimage.py +++ b/src/lcm/regime_building/ndimage.py @@ -25,7 +25,7 @@ @jit def map_coordinates( input: Array, # noqa: A002 - coordinates: Sequence[Array], + coordinates: Sequence[Array] | Array, ) -> Array: """Map the input array to new coordinates using linear interpolation. diff --git a/src/lcm/regime_building/next_state.py b/src/lcm/regime_building/next_state.py index 1e81f0f23..42fffc1b4 100644 --- a/src/lcm/regime_building/next_state.py +++ b/src/lcm/regime_building/next_state.py @@ -4,7 +4,7 @@ from types import MappingProxyType import jax -from dags import concatenate_functions, with_signature +from dags import concatenate_functions from dags.tree import qname_from_tree_path from jax import Array @@ -26,6 +26,7 @@ TransitionFunctionName, TransitionFunctionsMapping, ) +from lcm.utils._dags_forwarders import with_signature from lcm.variables import Variables diff --git a/src/lcm/regime_building/processing.py b/src/lcm/regime_building/processing.py index 0b0c78945..8f19ca1c8 100644 --- a/src/lcm/regime_building/processing.py +++ b/src/lcm/regime_building/processing.py @@ -6,8 +6,7 @@ from typing import Any, Literal, cast import jax -from dags import concatenate_functions, get_annotations, with_signature -from dags.signature import rename_arguments +from dags import concatenate_functions, get_annotations from dags.tree import QNAME_DELIMITER, qname_from_tree_path, tree_path_from_qname from jax import Array from jax import numpy as jnp @@ -45,6 +44,7 @@ from lcm.typing import ( ArgmaxQOverAFunction, Float1D, + FloatND, FunctionsMapping, Int1D, InternalUserFunction, @@ -63,6 +63,7 @@ UserFunction, VmappedRegimeTransitionFunction, ) +from lcm.utils._dags_forwarders import rename_arguments, with_signature from lcm.utils.containers import ensure_containers_are_immutable from lcm.utils.dispatchers import simulation_spacemap, vmap_1d from lcm.utils.namespace import flatten_regime_namespace, unflatten_regime_namespace @@ -888,7 +889,11 @@ def _get_weights_func_for_shock(*, name: str, grid: _ShockGrid) -> UserFunction: @with_signature(args=args, return_annotation="FloatND", enforce=False) def weights_func_runtime(*a: Array, **kwargs: Array) -> Float1D: # noqa: ARG001 - shock_kw: dict[str, float] = { # ty: ignore[invalid-assignment] + # `float` here covers Python floats from fixed_params; under + # JIT tracing, the runtime values forwarded through `kwargs` + # arrive as JAX tracers (`FloatND`), which are accepted by the + # shock grid's `compute_gridpoints` / `compute_transition_probs`. + shock_kw: dict[str, float | FloatND] = { **fixed_params, **{raw: kwargs[qn] for qn, raw in runtime_param_names.items()}, } diff --git a/src/lcm/regime_building/validation.py b/src/lcm/regime_building/validation.py index 5457e03f5..471d16548 100644 --- a/src/lcm/regime_building/validation.py +++ b/src/lcm/regime_building/validation.py @@ -241,7 +241,7 @@ def _find_function_output_grid_indexing( def collect_state_transitions( - states: Mapping[StateName, Grid], + states: Mapping[StateName, Grid | None], state_transitions: Mapping[ StateName, UserFunction | Callable | None | Mapping[RegimeName, UserFunction | Callable], diff --git a/src/lcm/solution/solve_brute.py b/src/lcm/solution/solve_brute.py index 57b0c9306..a87eb9ffc 100644 --- a/src/lcm/solution/solve_brute.py +++ b/src/lcm/solution/solve_brute.py @@ -12,7 +12,7 @@ from lcm.ages import AgeGrid from lcm.interfaces import InternalRegime, _build_regime_sharding -from lcm.typing import FloatND, InternalParams, RegimeName, StateName +from lcm.typing import BoolND, FloatND, InternalParams, RegimeName, StateName from lcm.utils.error_handling import validate_V from lcm.utils.logging import ( format_duration, @@ -107,8 +107,8 @@ def solve( diagnostic_min: list[FloatND] = [] diagnostic_max: list[FloatND] = [] diagnostic_mean: list[FloatND] = [] - running_any_nan: FloatND = jnp.zeros((), dtype=bool) - running_any_inf: FloatND = jnp.zeros((), dtype=bool) + running_any_nan: BoolND = jnp.zeros((), dtype=bool) + running_any_inf: BoolND = jnp.zeros((), dtype=bool) logger.info("Starting solution") total_start = time.monotonic() @@ -471,8 +471,8 @@ def _emit_post_loop_diagnostics( solution: MappingProxyType[int, MappingProxyType[RegimeName, FloatND]], internal_regimes: Mapping[RegimeName, InternalRegime], internal_params: InternalParams, - running_any_nan: FloatND, - running_any_inf: FloatND, + running_any_nan: BoolND, + running_any_inf: BoolND, diagnostic_min: list[FloatND] | None, diagnostic_max: list[FloatND] | None, diagnostic_mean: list[FloatND] | None, diff --git a/src/lcm/utils/_dags_forwarders.py b/src/lcm/utils/_dags_forwarders.py new file mode 100644 index 000000000..7ba1ab0cb --- /dev/null +++ b/src/lcm/utils/_dags_forwarders.py @@ -0,0 +1,98 @@ +"""Thin wrappers around `dags.signature` that default to `forwarder=True`. + +Every direct caller of `dags.with_signature` / `dags.rename_arguments` +inside pylcm produces a generic `*args, **kwargs` forwarder whose +annotations describe the inner function's contract, not the wrapper's +own runtime call protocol. Setting `forwarder=True` on each call tells +beartype's import claw to treat the wrapper as permissive — matching +the wrapper's actual behaviour — instead of enforcing user annotations +against JAX tracers and other forwarded objects. + +Importing these wrappers (rather than the dags ones) keeps that intent +visible at each call site and concentrates the configuration in one +place if pylcm ever needs to flip the default. + +""" + +import inspect +from collections.abc import Callable, Mapping, Sequence +from typing import Any, overload + +from dags import rename_arguments as _rename_arguments +from dags import with_signature as _with_signature +from dags.typing import P, R + + +@overload +def with_signature( + func: Callable[P, R], + *, + args: Mapping[str, str] | Sequence[str] | None = None, + kwargs: Mapping[str, str] | Sequence[str] | None = None, + enforce: bool = True, + return_annotation: Any = inspect.Parameter.empty, # noqa: ANN401 +) -> Callable[P, R]: ... + + +@overload +def with_signature( + *, + args: Mapping[str, str] | Sequence[str] | None = None, + kwargs: Mapping[str, str] | Sequence[str] | None = None, + enforce: bool = True, + return_annotation: Any = inspect.Parameter.empty, # noqa: ANN401 +) -> Callable[[Callable[P, R]], Callable[P, R]]: ... + + +def with_signature( + func: Callable[P, R] | None = None, + *, + args: Mapping[str, str] | Sequence[str] | None = None, + kwargs: Mapping[str, str] | Sequence[str] | None = None, + enforce: bool = True, + return_annotation: Any = inspect.Parameter.empty, +) -> Callable[P, R] | Callable[[Callable[P, R]], Callable[P, R]]: + """`dags.with_signature` with `forwarder=True` baked in. + + See `dags.signature.with_signature` for argument semantics. The + `forwarder=True` setting advertises the resulting wrapper as a + permissive `*args, **kwargs` forwarder on `__annotations__`, + keeping beartype's claw from enforcing per-parameter annotations + against the forwarder's actual call arguments. + """ + return _with_signature( + func, # ty: ignore[invalid-argument-type] + args=args, + kwargs=kwargs, + enforce=enforce, + return_annotation=return_annotation, + forwarder=True, + ) + + +@overload +def rename_arguments( + func: Callable[P, R], *, mapper: Mapping[str, str] +) -> Callable[..., R]: ... + + +@overload +def rename_arguments( + *, mapper: Mapping[str, str] +) -> Callable[[Callable[P, R]], Callable[..., R]]: ... + + +def rename_arguments( + func: Callable[P, R] | None = None, + *, + mapper: Mapping[str, str] | None = None, +) -> Callable[..., R] | Callable[[Callable[P, R]], Callable[..., R]]: + """`dags.rename_arguments` with `forwarder=True` baked in. + + See `dags.signature.rename_arguments` for argument semantics. + """ + return _rename_arguments( + func, # ty: ignore[invalid-argument-type] + mapper=mapper, # ty: ignore[invalid-argument-type] + forwarder=True, + ) diff --git a/tests/test_next_state.py b/tests/test_next_state.py index f89bad620..08f51649b 100644 --- a/tests/test_next_state.py +++ b/tests/test_next_state.py @@ -102,7 +102,7 @@ class MockCategory: def test_create_stochastic_next_func(): - labels = jnp.arange(2) + labels = jnp.arange(2, dtype=jnp.int32) got_func = _create_discrete_stochastic_next_func( target="t", next_state_name="next_a", labels=labels ) From 37eb6e6798249605980c8866310807d7821ec778 Mon Sep 17 00:00:00 2001 From: Hans-Martin von Gaudecker Date: Thu, 14 May 2026 09:24:43 +0200 Subject: [PATCH 33/77] Drop _dags_forwarders shim; import dags wrappers directly MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit dags#82 made the `*args, **kwargs` forwarder shape the only behaviour for `with_signature` / `rename_arguments` — there is no `forwarder` flag left to default, so the shim has nothing to do. regime_building imports the dags wrappers directly again; the dags pin moves to the current branch head. Co-Authored-By: Claude Opus 4.7 --- pixi.lock | 46 ++++++------ pyproject.toml | 9 ++- src/lcm/regime_building/Q_and_F.py | 3 +- src/lcm/regime_building/V.py | 3 +- src/lcm/regime_building/max_Q_over_a.py | 2 +- src/lcm/regime_building/next_state.py | 3 +- src/lcm/regime_building/processing.py | 4 +- src/lcm/utils/_dags_forwarders.py | 98 ------------------------- 8 files changed, 34 insertions(+), 134 deletions(-) delete mode 100644 src/lcm/utils/_dags_forwarders.py diff --git a/pixi.lock b/pixi.lock index 3b745fb11..f46905259 100644 --- a/pixi.lock +++ b/pixi.lock @@ -275,7 +275,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ae/44/c1221527f6a71a01ec6fbad7fa78f1d50dfa02217385cf0fa3eec7087d59/click-8.3.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2c/1a/aff8bb287a4b1400f69e09a53bd65de96aa5cee5691925b38731c67fc695/click_default_group-1.2.4-py2.py3-none-any.whl - - pypi: git+https://github.com/OpenSourceEconomics/dags.git?rev=d415d10#d415d10ee9704b448db90630719f1410ca321132 + - pypi: git+https://github.com/OpenSourceEconomics/dags.git?rev=cf59c04#cf59c04c6ba07b7c54ca763dc155deea3341a480 - pypi: https://files.pythonhosted.org/packages/8d/2d/f61c918d9edc2127068f0d5ad4604fedd9bfd393f464219090f3279c73f7/estimagic-0.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/f5/ee39c6e92acc742c052f137b47c210cd0a1b72dcd3f98495528bb4d27761/flatten_dict-0.4.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d5/1f/5f4a3cd9e4440e9d9bc78ad0a91a1c8d46b4d429d5239ebe6793c9fe5c41/fsspec-2026.3.0-py3-none-any.whl @@ -588,7 +588,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-1.3.2-h25fd6f3_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda - pypi: https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl - - pypi: git+https://github.com/OpenSourceEconomics/dags.git?rev=d415d10#d415d10ee9704b448db90630719f1410ca321132 + - pypi: git+https://github.com/OpenSourceEconomics/dags.git?rev=cf59c04#cf59c04c6ba07b7c54ca763dc155deea3341a480 - pypi: https://files.pythonhosted.org/packages/43/f5/ee39c6e92acc742c052f137b47c210cd0a1b72dcd3f98495528bb4d27761/flatten_dict-0.4.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/70/aa/dfac6d72cc35bc07e7587115b6946e333ef4ccb2e6cd26ecf639438c5d26/jax-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5f/78/a3d9ceda0793f4fb43daa292af7b801932611a1aed442636ddfc93d58c7a/jax_cuda12_pjrt-0.10.0-py3-none-manylinux_2_27_x86_64.whl @@ -888,7 +888,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-1.3.2-h25fd6f3_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda - pypi: https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl - - pypi: git+https://github.com/OpenSourceEconomics/dags.git?rev=d415d10#d415d10ee9704b448db90630719f1410ca321132 + - pypi: git+https://github.com/OpenSourceEconomics/dags.git?rev=cf59c04#cf59c04c6ba07b7c54ca763dc155deea3341a480 - pypi: https://files.pythonhosted.org/packages/43/f5/ee39c6e92acc742c052f137b47c210cd0a1b72dcd3f98495528bb4d27761/flatten_dict-0.4.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/70/aa/dfac6d72cc35bc07e7587115b6946e333ef4ccb2e6cd26ecf639438c5d26/jax-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/21/98/77f15d81fd0637da454e453c8456d4a2b5c8b2e66823b4237ee8689152cf/jax_cuda13_pjrt-0.10.0-py3-none-manylinux_2_27_x86_64.whl @@ -1158,7 +1158,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-1.3.2-h25fd6f3_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda - pypi: https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl - - pypi: git+https://github.com/OpenSourceEconomics/dags.git?rev=d415d10#d415d10ee9704b448db90630719f1410ca321132 + - pypi: git+https://github.com/OpenSourceEconomics/dags.git?rev=cf59c04#cf59c04c6ba07b7c54ca763dc155deea3341a480 - pypi: https://files.pythonhosted.org/packages/43/f5/ee39c6e92acc742c052f137b47c210cd0a1b72dcd3f98495528bb4d27761/flatten_dict-0.4.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/70/aa/dfac6d72cc35bc07e7587115b6946e333ef4ccb2e6cd26ecf639438c5d26/jax-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a1/8e/b2a08ffc51c93842de71f7f988865cebfa7f43d6721957812dc8cc8b9d40/jaxlib-0.10.0-cp314-cp314-manylinux_2_27_x86_64.whl @@ -1399,7 +1399,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zlib-1.3.2-h8088a28_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda - pypi: https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl - - pypi: git+https://github.com/OpenSourceEconomics/dags.git?rev=d415d10#d415d10ee9704b448db90630719f1410ca321132 + - pypi: git+https://github.com/OpenSourceEconomics/dags.git?rev=cf59c04#cf59c04c6ba07b7c54ca763dc155deea3341a480 - pypi: https://files.pythonhosted.org/packages/43/f5/ee39c6e92acc742c052f137b47c210cd0a1b72dcd3f98495528bb4d27761/flatten_dict-0.4.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/70/aa/dfac6d72cc35bc07e7587115b6946e333ef4ccb2e6cd26ecf639438c5d26/jax-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a7/25/e1e52a21786b321fb6a2edf9ef9971aa70f06bb2738aef9afd6d8f46a441/jaxlib-0.10.0-cp314-cp314-macosx_11_0_arm64.whl @@ -1637,7 +1637,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/zlib-1.3.2-hfd05255_2.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_6.conda - pypi: https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl - - pypi: git+https://github.com/OpenSourceEconomics/dags.git?rev=d415d10#d415d10ee9704b448db90630719f1410ca321132 + - pypi: git+https://github.com/OpenSourceEconomics/dags.git?rev=cf59c04#cf59c04c6ba07b7c54ca763dc155deea3341a480 - pypi: https://files.pythonhosted.org/packages/43/f5/ee39c6e92acc742c052f137b47c210cd0a1b72dcd3f98495528bb4d27761/flatten_dict-0.4.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/70/aa/dfac6d72cc35bc07e7587115b6946e333ef4ccb2e6cd26ecf639438c5d26/jax-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/24/08/26e6a3ecf0a95f1ec0dcd7a668d5c9a72e581c40fe4ae51e102ca63174c5/jaxlib-0.10.0-cp314-cp314-win_amd64.whl @@ -1895,7 +1895,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-1.3.2-h25fd6f3_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda - pypi: https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl - - pypi: git+https://github.com/OpenSourceEconomics/dags.git?rev=d415d10#d415d10ee9704b448db90630719f1410ca321132 + - pypi: git+https://github.com/OpenSourceEconomics/dags.git?rev=cf59c04#cf59c04c6ba07b7c54ca763dc155deea3341a480 - pypi: https://files.pythonhosted.org/packages/43/f5/ee39c6e92acc742c052f137b47c210cd0a1b72dcd3f98495528bb4d27761/flatten_dict-0.4.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/70/aa/dfac6d72cc35bc07e7587115b6946e333ef4ccb2e6cd26ecf639438c5d26/jax-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a1/8e/b2a08ffc51c93842de71f7f988865cebfa7f43d6721957812dc8cc8b9d40/jaxlib-0.10.0-cp314-cp314-manylinux_2_27_x86_64.whl @@ -2146,7 +2146,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zlib-1.3.2-h8088a28_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda - pypi: https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl - - pypi: git+https://github.com/OpenSourceEconomics/dags.git?rev=d415d10#d415d10ee9704b448db90630719f1410ca321132 + - pypi: git+https://github.com/OpenSourceEconomics/dags.git?rev=cf59c04#cf59c04c6ba07b7c54ca763dc155deea3341a480 - pypi: https://files.pythonhosted.org/packages/43/f5/ee39c6e92acc742c052f137b47c210cd0a1b72dcd3f98495528bb4d27761/flatten_dict-0.4.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/70/aa/dfac6d72cc35bc07e7587115b6946e333ef4ccb2e6cd26ecf639438c5d26/jax-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a7/25/e1e52a21786b321fb6a2edf9ef9971aa70f06bb2738aef9afd6d8f46a441/jaxlib-0.10.0-cp314-cp314-macosx_11_0_arm64.whl @@ -2393,7 +2393,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/zlib-1.3.2-hfd05255_2.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_6.conda - pypi: https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl - - pypi: git+https://github.com/OpenSourceEconomics/dags.git?rev=d415d10#d415d10ee9704b448db90630719f1410ca321132 + - pypi: git+https://github.com/OpenSourceEconomics/dags.git?rev=cf59c04#cf59c04c6ba07b7c54ca763dc155deea3341a480 - pypi: https://files.pythonhosted.org/packages/43/f5/ee39c6e92acc742c052f137b47c210cd0a1b72dcd3f98495528bb4d27761/flatten_dict-0.4.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/70/aa/dfac6d72cc35bc07e7587115b6946e333ef4ccb2e6cd26ecf639438c5d26/jax-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/24/08/26e6a3ecf0a95f1ec0dcd7a668d5c9a72e581c40fe4ae51e102ca63174c5/jaxlib-0.10.0-cp314-cp314-win_amd64.whl @@ -2648,7 +2648,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zlib-1.3.2-h8088a28_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda - pypi: https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl - - pypi: git+https://github.com/OpenSourceEconomics/dags.git?rev=d415d10#d415d10ee9704b448db90630719f1410ca321132 + - pypi: git+https://github.com/OpenSourceEconomics/dags.git?rev=cf59c04#cf59c04c6ba07b7c54ca763dc155deea3341a480 - pypi: https://files.pythonhosted.org/packages/43/f5/ee39c6e92acc742c052f137b47c210cd0a1b72dcd3f98495528bb4d27761/flatten_dict-0.4.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/70/aa/dfac6d72cc35bc07e7587115b6946e333ef4ccb2e6cd26ecf639438c5d26/jax-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/09/dc/6d8fbfc29d902251cf333414cf7dcfaf4b252a9920c881354584ed36270d/jax_metal-0.1.1-py3-none-macosx_13_0_arm64.whl @@ -2911,7 +2911,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-1.3.2-h25fd6f3_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda - pypi: https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl - - pypi: git+https://github.com/OpenSourceEconomics/dags.git?rev=d415d10#d415d10ee9704b448db90630719f1410ca321132 + - pypi: git+https://github.com/OpenSourceEconomics/dags.git?rev=cf59c04#cf59c04c6ba07b7c54ca763dc155deea3341a480 - pypi: https://files.pythonhosted.org/packages/43/f5/ee39c6e92acc742c052f137b47c210cd0a1b72dcd3f98495528bb4d27761/flatten_dict-0.4.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/70/aa/dfac6d72cc35bc07e7587115b6946e333ef4ccb2e6cd26ecf639438c5d26/jax-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a1/8e/b2a08ffc51c93842de71f7f988865cebfa7f43d6721957812dc8cc8b9d40/jaxlib-0.10.0-cp314-cp314-manylinux_2_27_x86_64.whl @@ -3166,7 +3166,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zlib-1.3.2-h8088a28_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda - pypi: https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl - - pypi: git+https://github.com/OpenSourceEconomics/dags.git?rev=d415d10#d415d10ee9704b448db90630719f1410ca321132 + - pypi: git+https://github.com/OpenSourceEconomics/dags.git?rev=cf59c04#cf59c04c6ba07b7c54ca763dc155deea3341a480 - pypi: https://files.pythonhosted.org/packages/43/f5/ee39c6e92acc742c052f137b47c210cd0a1b72dcd3f98495528bb4d27761/flatten_dict-0.4.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/70/aa/dfac6d72cc35bc07e7587115b6946e333ef4ccb2e6cd26ecf639438c5d26/jax-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a7/25/e1e52a21786b321fb6a2edf9ef9971aa70f06bb2738aef9afd6d8f46a441/jaxlib-0.10.0-cp314-cp314-macosx_11_0_arm64.whl @@ -3417,7 +3417,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/zlib-1.3.2-hfd05255_2.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_6.conda - pypi: https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl - - pypi: git+https://github.com/OpenSourceEconomics/dags.git?rev=d415d10#d415d10ee9704b448db90630719f1410ca321132 + - pypi: git+https://github.com/OpenSourceEconomics/dags.git?rev=cf59c04#cf59c04c6ba07b7c54ca763dc155deea3341a480 - pypi: https://files.pythonhosted.org/packages/43/f5/ee39c6e92acc742c052f137b47c210cd0a1b72dcd3f98495528bb4d27761/flatten_dict-0.4.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/70/aa/dfac6d72cc35bc07e7587115b6946e333ef4ccb2e6cd26ecf639438c5d26/jax-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/24/08/26e6a3ecf0a95f1ec0dcd7a668d5c9a72e581c40fe4ae51e102ca63174c5/jaxlib-0.10.0-cp314-cp314-win_amd64.whl @@ -3716,7 +3716,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-1.3.2-h25fd6f3_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda - pypi: https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl - - pypi: git+https://github.com/OpenSourceEconomics/dags.git?rev=d415d10#d415d10ee9704b448db90630719f1410ca321132 + - pypi: git+https://github.com/OpenSourceEconomics/dags.git?rev=cf59c04#cf59c04c6ba07b7c54ca763dc155deea3341a480 - pypi: https://files.pythonhosted.org/packages/43/f5/ee39c6e92acc742c052f137b47c210cd0a1b72dcd3f98495528bb4d27761/flatten_dict-0.4.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/70/aa/dfac6d72cc35bc07e7587115b6946e333ef4ccb2e6cd26ecf639438c5d26/jax-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5f/78/a3d9ceda0793f4fb43daa292af7b801932611a1aed442636ddfc93d58c7a/jax_cuda12_pjrt-0.10.0-py3-none-manylinux_2_27_x86_64.whl @@ -4029,7 +4029,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-1.3.2-h25fd6f3_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda - pypi: https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl - - pypi: git+https://github.com/OpenSourceEconomics/dags.git?rev=d415d10#d415d10ee9704b448db90630719f1410ca321132 + - pypi: git+https://github.com/OpenSourceEconomics/dags.git?rev=cf59c04#cf59c04c6ba07b7c54ca763dc155deea3341a480 - pypi: https://files.pythonhosted.org/packages/43/f5/ee39c6e92acc742c052f137b47c210cd0a1b72dcd3f98495528bb4d27761/flatten_dict-0.4.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/70/aa/dfac6d72cc35bc07e7587115b6946e333ef4ccb2e6cd26ecf639438c5d26/jax-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/21/98/77f15d81fd0637da454e453c8456d4a2b5c8b2e66823b4237ee8689152cf/jax_cuda13_pjrt-0.10.0-py3-none-manylinux_2_27_x86_64.whl @@ -4308,7 +4308,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zlib-1.3.2-h8088a28_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda - pypi: https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl - - pypi: git+https://github.com/OpenSourceEconomics/dags.git?rev=d415d10#d415d10ee9704b448db90630719f1410ca321132 + - pypi: git+https://github.com/OpenSourceEconomics/dags.git?rev=cf59c04#cf59c04c6ba07b7c54ca763dc155deea3341a480 - pypi: https://files.pythonhosted.org/packages/43/f5/ee39c6e92acc742c052f137b47c210cd0a1b72dcd3f98495528bb4d27761/flatten_dict-0.4.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/70/aa/dfac6d72cc35bc07e7587115b6946e333ef4ccb2e6cd26ecf639438c5d26/jax-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/09/dc/6d8fbfc29d902251cf333414cf7dcfaf4b252a9920c881354584ed36270d/jax_metal-0.1.1-py3-none-macosx_13_0_arm64.whl @@ -4673,7 +4673,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-ng-2.3.3-hceb46e0_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda - pypi: https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl - - pypi: git+https://github.com/OpenSourceEconomics/dags.git?rev=d415d10#d415d10ee9704b448db90630719f1410ca321132 + - pypi: git+https://github.com/OpenSourceEconomics/dags.git?rev=cf59c04#cf59c04c6ba07b7c54ca763dc155deea3341a480 - pypi: https://files.pythonhosted.org/packages/43/f5/ee39c6e92acc742c052f137b47c210cd0a1b72dcd3f98495528bb4d27761/flatten_dict-0.4.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/70/aa/dfac6d72cc35bc07e7587115b6946e333ef4ccb2e6cd26ecf639438c5d26/jax-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a1/8e/b2a08ffc51c93842de71f7f988865cebfa7f43d6721957812dc8cc8b9d40/jaxlib-0.10.0-cp314-cp314-manylinux_2_27_x86_64.whl @@ -4964,7 +4964,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zlib-ng-2.3.3-hed4e4f5_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda - pypi: https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl - - pypi: git+https://github.com/OpenSourceEconomics/dags.git?rev=d415d10#d415d10ee9704b448db90630719f1410ca321132 + - pypi: git+https://github.com/OpenSourceEconomics/dags.git?rev=cf59c04#cf59c04c6ba07b7c54ca763dc155deea3341a480 - pypi: https://files.pythonhosted.org/packages/43/f5/ee39c6e92acc742c052f137b47c210cd0a1b72dcd3f98495528bb4d27761/flatten_dict-0.4.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/70/aa/dfac6d72cc35bc07e7587115b6946e333ef4ccb2e6cd26ecf639438c5d26/jax-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a7/25/e1e52a21786b321fb6a2edf9ef9971aa70f06bb2738aef9afd6d8f46a441/jaxlib-0.10.0-cp314-cp314-macosx_11_0_arm64.whl @@ -5274,7 +5274,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/zlib-ng-2.3.3-h0261ad2_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_6.conda - pypi: https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl - - pypi: git+https://github.com/OpenSourceEconomics/dags.git?rev=d415d10#d415d10ee9704b448db90630719f1410ca321132 + - pypi: git+https://github.com/OpenSourceEconomics/dags.git?rev=cf59c04#cf59c04c6ba07b7c54ca763dc155deea3341a480 - pypi: https://files.pythonhosted.org/packages/43/f5/ee39c6e92acc742c052f137b47c210cd0a1b72dcd3f98495528bb4d27761/flatten_dict-0.4.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/70/aa/dfac6d72cc35bc07e7587115b6946e333ef4ccb2e6cd26ecf639438c5d26/jax-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/24/08/26e6a3ecf0a95f1ec0dcd7a668d5c9a72e581c40fe4ae51e102ca63174c5/jaxlib-0.10.0-cp314-cp314-win_amd64.whl @@ -7542,9 +7542,9 @@ packages: purls: [] size: 210103 timestamp: 1771943128249 -- pypi: git+https://github.com/OpenSourceEconomics/dags.git?rev=d415d10#d415d10ee9704b448db90630719f1410ca321132 +- pypi: git+https://github.com/OpenSourceEconomics/dags.git?rev=cf59c04#cf59c04c6ba07b7c54ca763dc155deea3341a480 name: dags - version: 0.5.2.dev1+gd415d10ee + version: 0.5.2.dev6+gcf59c04c6 requires_dist: - flatten-dict - networkx>=3.6 @@ -14085,8 +14085,8 @@ packages: timestamp: 1774796815820 - pypi: ./ name: pylcm - version: 0.0.2.dev179+g59aca3f5f.d20260513 - sha256: 0c5af9810a5be3f1e5861656eaf8e0c6fa09dcbf505d6e746feffeff83e0cce6 + version: 0.0.2.dev182+g1a92dffec.d20260514 + sha256: 2ac3c0e6987658df12a93886cc7ea80b12456826f61aa1720a0c56e8fd828128 requires_dist: - beartype>=0.21 - cloudpickle>=3.1.2 diff --git a/pyproject.toml b/pyproject.toml index e23e07d64..6e8f346d5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -150,10 +150,11 @@ jax = ">=0.9" pdbp = "*" pylcm = { path = ".", editable = true } # Pin dags to the feat/no-type-check-flag branch (PR -# OpenSourceEconomics/dags#82): adds the `forwarder=True` flag we need -# to make `with_signature` cooperate with beartype's import claw. -# Will be replaced with a released version once that PR lands. -dags = { git = "https://github.com/OpenSourceEconomics/dags.git", rev = "d415d10" } +# OpenSourceEconomics/dags#82): its wrappers advertise the `*args, +# **kwargs` forwarder shape on `__annotations__`, so beartype's import +# claw treats them as permissive forwarders. Replace with `dags>=0.6` +# once that PR is released. +dags = { git = "https://github.com/OpenSourceEconomics/dags.git", rev = "cf59c04" } [tool.pixi.tasks] asv-compare = "asv compare" asv-preview = "asv preview" diff --git a/src/lcm/regime_building/Q_and_F.py b/src/lcm/regime_building/Q_and_F.py index 9bff01a85..814619ddf 100644 --- a/src/lcm/regime_building/Q_and_F.py +++ b/src/lcm/regime_building/Q_and_F.py @@ -3,7 +3,7 @@ from typing import Any, cast import jax.numpy as jnp -from dags import concatenate_functions +from dags import concatenate_functions, with_signature from jax import Array from lcm.regime_building.h_dag import _get_build_H_kwargs @@ -24,7 +24,6 @@ TransitionFunctionName, TransitionFunctionsMapping, ) -from lcm.utils._dags_forwarders import with_signature from lcm.utils.dispatchers import productmap from lcm.utils.functools import get_union_of_args diff --git a/src/lcm/regime_building/V.py b/src/lcm/regime_building/V.py index ad8c7d21a..31521a5c8 100644 --- a/src/lcm/regime_building/V.py +++ b/src/lcm/regime_building/V.py @@ -3,7 +3,7 @@ from types import MappingProxyType import jax.numpy as jnp -from dags import concatenate_functions +from dags import concatenate_functions, with_signature from dags.tree import qname_from_tree_path from jax import Array @@ -13,7 +13,6 @@ from lcm.regime_building.ndimage import map_coordinates from lcm.shocks import _ShockGrid from lcm.typing import FloatND, ScalarFloat, StateName -from lcm.utils._dags_forwarders import with_signature from lcm.utils.functools import all_as_kwargs from lcm.variables import Variables, get_grids diff --git a/src/lcm/regime_building/max_Q_over_a.py b/src/lcm/regime_building/max_Q_over_a.py index d5fa18b93..f1e5d3913 100644 --- a/src/lcm/regime_building/max_Q_over_a.py +++ b/src/lcm/regime_building/max_Q_over_a.py @@ -3,6 +3,7 @@ from types import MappingProxyType import jax.numpy as jnp +from dags import with_signature from jax import Array from lcm.regime_building.argmax import argmax_and_max @@ -16,7 +17,6 @@ RegimeName, StateName, ) -from lcm.utils._dags_forwarders import with_signature from lcm.utils.dispatchers import productmap diff --git a/src/lcm/regime_building/next_state.py b/src/lcm/regime_building/next_state.py index 6f554c557..5b260ec96 100644 --- a/src/lcm/regime_building/next_state.py +++ b/src/lcm/regime_building/next_state.py @@ -4,7 +4,7 @@ from types import MappingProxyType import jax -from dags import concatenate_functions +from dags import concatenate_functions, with_signature from dags.tree import qname_from_tree_path from jax import Array @@ -27,7 +27,6 @@ TransitionFunctionName, TransitionFunctionsMapping, ) -from lcm.utils._dags_forwarders import with_signature from lcm.variables import Variables diff --git a/src/lcm/regime_building/processing.py b/src/lcm/regime_building/processing.py index 33bb47fd5..7e07e8212 100644 --- a/src/lcm/regime_building/processing.py +++ b/src/lcm/regime_building/processing.py @@ -6,7 +6,8 @@ from typing import Any, Literal, cast import jax -from dags import concatenate_functions, get_annotations +from dags import concatenate_functions, get_annotations, with_signature +from dags.signature import rename_arguments from dags.tree import QNAME_DELIMITER, qname_from_tree_path, tree_path_from_qname from jax import Array from jax import numpy as jnp @@ -64,7 +65,6 @@ UserFunction, VmappedRegimeTransitionFunction, ) -from lcm.utils._dags_forwarders import rename_arguments, with_signature from lcm.utils.containers import ensure_containers_are_immutable from lcm.utils.dispatchers import simulation_spacemap, vmap_1d from lcm.utils.namespace import flatten_regime_namespace, unflatten_regime_namespace diff --git a/src/lcm/utils/_dags_forwarders.py b/src/lcm/utils/_dags_forwarders.py deleted file mode 100644 index 7ba1ab0cb..000000000 --- a/src/lcm/utils/_dags_forwarders.py +++ /dev/null @@ -1,98 +0,0 @@ -"""Thin wrappers around `dags.signature` that default to `forwarder=True`. - -Every direct caller of `dags.with_signature` / `dags.rename_arguments` -inside pylcm produces a generic `*args, **kwargs` forwarder whose -annotations describe the inner function's contract, not the wrapper's -own runtime call protocol. Setting `forwarder=True` on each call tells -beartype's import claw to treat the wrapper as permissive — matching -the wrapper's actual behaviour — instead of enforcing user annotations -against JAX tracers and other forwarded objects. - -Importing these wrappers (rather than the dags ones) keeps that intent -visible at each call site and concentrates the configuration in one -place if pylcm ever needs to flip the default. - -""" - -import inspect -from collections.abc import Callable, Mapping, Sequence -from typing import Any, overload - -from dags import rename_arguments as _rename_arguments -from dags import with_signature as _with_signature -from dags.typing import P, R - - -@overload -def with_signature( - func: Callable[P, R], - *, - args: Mapping[str, str] | Sequence[str] | None = None, - kwargs: Mapping[str, str] | Sequence[str] | None = None, - enforce: bool = True, - return_annotation: Any = inspect.Parameter.empty, # noqa: ANN401 -) -> Callable[P, R]: ... - - -@overload -def with_signature( - *, - args: Mapping[str, str] | Sequence[str] | None = None, - kwargs: Mapping[str, str] | Sequence[str] | None = None, - enforce: bool = True, - return_annotation: Any = inspect.Parameter.empty, # noqa: ANN401 -) -> Callable[[Callable[P, R]], Callable[P, R]]: ... - - -def with_signature( - func: Callable[P, R] | None = None, - *, - args: Mapping[str, str] | Sequence[str] | None = None, - kwargs: Mapping[str, str] | Sequence[str] | None = None, - enforce: bool = True, - return_annotation: Any = inspect.Parameter.empty, -) -> Callable[P, R] | Callable[[Callable[P, R]], Callable[P, R]]: - """`dags.with_signature` with `forwarder=True` baked in. - - See `dags.signature.with_signature` for argument semantics. The - `forwarder=True` setting advertises the resulting wrapper as a - permissive `*args, **kwargs` forwarder on `__annotations__`, - keeping beartype's claw from enforcing per-parameter annotations - against the forwarder's actual call arguments. - """ - return _with_signature( - func, # ty: ignore[invalid-argument-type] - args=args, - kwargs=kwargs, - enforce=enforce, - return_annotation=return_annotation, - forwarder=True, - ) - - -@overload -def rename_arguments( - func: Callable[P, R], *, mapper: Mapping[str, str] -) -> Callable[..., R]: ... - - -@overload -def rename_arguments( - *, mapper: Mapping[str, str] -) -> Callable[[Callable[P, R]], Callable[..., R]]: ... - - -def rename_arguments( - func: Callable[P, R] | None = None, - *, - mapper: Mapping[str, str] | None = None, -) -> Callable[..., R] | Callable[[Callable[P, R]], Callable[..., R]]: - """`dags.rename_arguments` with `forwarder=True` baked in. - - See `dags.signature.rename_arguments` for argument semantics. - """ - return _rename_arguments( - func, # ty: ignore[invalid-argument-type] - mapper=mapper, # ty: ignore[invalid-argument-type] - forwarder=True, - ) From 17890f51b38dbfe3e46bca5eaa1fe16838d50cfc Mon Sep 17 00:00:00 2001 From: Hans-Martin von Gaudecker Date: Thu, 14 May 2026 09:45:09 +0200 Subject: [PATCH 34/77] Narrow type hints repo-wide: drop redundant unions, scalar/array overloads, bare Array ty erases jaxtyping shapes, so `ScalarX | XND` unions and scalar/array `@overload` pairs add zero static precision; beartype treats a 0-d array as satisfying both, so the union is redundant at runtime too. Collapse both patterns, eliminate bare `Array` annotations in favour of the narrowest `lcm.typing` alias, and tighten `process_params` / `_params_to_jax` boundaries to their strict canonical types. Rewrite the AGENTS.md guidance that mandated the overload pattern. Co-Authored-By: Claude Opus 4.7 --- AGENTS.md | 15 ++-- src/lcm/dtypes.py | 7 +- src/lcm/grids/continuous.py | 31 ++------ src/lcm/grids/coordinates.py | 73 ++++--------------- src/lcm/grids/piecewise.py | 13 +--- src/lcm/interfaces.py | 17 ++++- src/lcm/pandas_utils.py | 5 +- src/lcm/params/processing.py | 3 +- src/lcm/regime_building/Q_and_F.py | 10 +-- src/lcm/regime_building/V.py | 16 ++--- src/lcm/regime_building/argmax.py | 4 +- src/lcm/regime_building/diagnostics.py | 3 +- src/lcm/regime_building/processing.py | 5 +- src/lcm/shocks/_base.py | 31 ++++---- src/lcm/shocks/ar1.py | 34 ++++----- src/lcm/shocks/iid.py | 28 ++++---- src/lcm/simulation/initial_conditions.py | 30 ++++---- src/lcm/simulation/random.py | 7 +- src/lcm/simulation/simulate.py | 8 ++- src/lcm/simulation/transitions.py | 17 +++-- src/lcm/typing.py | 25 +++++-- src/lcm/utils/error_handling.py | 14 ++-- tests/regime_building/test_process_params.py | 76 ++++++++++++-------- tests/test_float_dtype_invariants.py | 70 ++++++++++-------- tests/test_int_dtype_invariants.py | 69 ++++++++++-------- 25 files changed, 304 insertions(+), 307 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index b311ee198..805f2b137 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -527,11 +527,16 @@ Code structure should be self-evident from function names and ordering. - **Helper function names follow `{verb}_{qualifier}_noun` patterns.** E.g., `get_irreg_coordinate`, `find_irreg_coordinate`, `get_linspace_coordinate` — not `get_coordinate_irreg`. -- **Use `@overload` when a function accepts both scalar and array inputs.** When a - function works with both `ScalarFloat` and `Array`, add overload declarations so the - type checker can track `(ScalarFloat) -> ScalarFloat` and `(Array) -> Array` - separately. Concrete subclass methods need their own overloads too (not just the - abstract base). +- **Pick the single narrowest jaxtyping alias — never scalar/array `@overload` pairs, + never `ScalarX | XND` unions.** ty erases jaxtyping shape annotations: `ScalarFloat`, + `Float1D`, and `FloatND` all reveal as `Array`, so scalar/array `@overload` pairs and + `ScalarFloat | FloatND`-style unions add zero static precision — they are pure noise. + At runtime, beartype treats a 0-d float array as satisfying both `ScalarFloat` and + `FloatND`, so `ScalarFloat ⊆ FloatND` and the union is redundant. Annotate each slot + with the one alias that matches its genuine rank: `ScalarFloat`/`ScalarInt` for + fixed-0-d, `Float1D`/`Int1D` for fixed-1-d, `FloatND`/`IntND` for genuinely rank- + polymorphic. Never use a bare `Array` annotation — always reach for the narrowest + `lcm.typing` alias. - **`func` for callable abbreviations** — use `func`, `func_name`, `func_params` (never `fn`). Full word `function(s)` in dataclass field names and public method names. - **Singular `state_names` / `action_names`** — not `states_names` / `actions_names`. diff --git a/src/lcm/dtypes.py b/src/lcm/dtypes.py index bdc1b504e..45e7f46a7 100644 --- a/src/lcm/dtypes.py +++ b/src/lcm/dtypes.py @@ -11,7 +11,8 @@ import jax import jax.numpy as jnp import numpy as np -from jax import Array + +from lcm.typing import FloatND, IntND _INT32_MIN = int(np.iinfo(np.int32).min) _INT32_MAX = int(np.iinfo(np.int32).max) @@ -30,7 +31,7 @@ def canonical_float_dtype() -> type: return jnp.float64 if jax.config.read("jax_enable_x64") else jnp.float32 -def safe_to_int_dtype(value: object, *, name: str) -> Array: +def safe_to_int_dtype(value: object, *, name: str) -> IntND: """Cast a scalar, sequence, or array to `jnp.int32`, checking int32 range. Args: @@ -60,7 +61,7 @@ def safe_to_int_dtype(value: object, *, name: str) -> Array: return jnp.asarray(np_value, dtype=jnp.int32) -def safe_to_float_dtype(value: object, *, name: str) -> Array: +def safe_to_float_dtype(value: object, *, name: str) -> FloatND: """Cast a scalar, sequence, or array to the canonical float dtype. Range check fires only on a down-cast: diff --git a/src/lcm/grids/continuous.py b/src/lcm/grids/continuous.py index 4dad41793..74a763ccc 100644 --- a/src/lcm/grids/continuous.py +++ b/src/lcm/grids/continuous.py @@ -2,7 +2,6 @@ from abc import ABC, abstractmethod from collections.abc import Sequence from dataclasses import dataclass -from typing import overload import jax.numpy as jnp @@ -33,12 +32,8 @@ class ContinuousGrid(Grid): distributed: bool = False """Whether to distribute the grid over the available devices.""" - @overload - def get_coordinate(self, value: ScalarFloat) -> ScalarFloat: ... - @overload - def get_coordinate(self, value: FloatND) -> FloatND: ... @abstractmethod - def get_coordinate(self, value: ScalarFloat | FloatND) -> ScalarFloat | FloatND: + def get_coordinate(self, value: FloatND) -> FloatND: """Return the generalized coordinate of a value in the grid.""" @@ -83,12 +78,8 @@ def __init__( def to_jax(self) -> Float1D: """Convert the grid to a Jax array.""" - @overload - def get_coordinate(self, value: ScalarFloat) -> ScalarFloat: ... - @overload - def get_coordinate(self, value: FloatND) -> FloatND: ... @abstractmethod - def get_coordinate(self, value: ScalarFloat | FloatND) -> ScalarFloat | FloatND: + def get_coordinate(self, value: FloatND) -> FloatND: """Return the generalized coordinate of a value in the grid.""" def replace(self, **kwargs: float) -> UniformContinuousGrid: @@ -125,11 +116,7 @@ def to_jax(self) -> Float1D: start=self.start, stop=self.stop, n_points=self.n_points ) - @overload - def get_coordinate(self, value: ScalarFloat) -> ScalarFloat: ... - @overload - def get_coordinate(self, value: FloatND) -> FloatND: ... - def get_coordinate(self, value: ScalarFloat | FloatND) -> ScalarFloat | FloatND: + def get_coordinate(self, value: FloatND) -> FloatND: """Return the generalized coordinate of a value in the grid.""" return grid_coordinates.get_linspace_coordinate( value=value, @@ -176,11 +163,7 @@ def to_jax(self) -> Float1D: start=self.start, stop=self.stop, n_points=self.n_points ) - @overload - def get_coordinate(self, value: ScalarFloat) -> ScalarFloat: ... - @overload - def get_coordinate(self, value: FloatND) -> FloatND: ... - def get_coordinate(self, value: ScalarFloat | FloatND) -> ScalarFloat | FloatND: + def get_coordinate(self, value: FloatND) -> FloatND: """Return the generalized coordinate of a value in the grid.""" return grid_coordinates.get_logspace_coordinate( value=value, @@ -312,11 +295,7 @@ def to_jax(self) -> Float1D: ) return self.points - @overload - def get_coordinate(self, value: ScalarFloat) -> ScalarFloat: ... - @overload - def get_coordinate(self, value: FloatND) -> FloatND: ... - def get_coordinate(self, value: ScalarFloat | FloatND) -> ScalarFloat | FloatND: + def get_coordinate(self, value: FloatND) -> FloatND: """Return the generalized coordinate of a value in the grid.""" if self.points is None: raise GridInitializationError( diff --git a/src/lcm/grids/coordinates.py b/src/lcm/grids/coordinates.py index ea5a2aa3c..20e83afbf 100644 --- a/src/lcm/grids/coordinates.py +++ b/src/lcm/grids/coordinates.py @@ -1,13 +1,12 @@ """Functions to generate and work with different kinds of grids. -These helpers operate on JAX scalars (`ScalarFloat` for endpoints/values, -`ScalarInt` or Python `int` for `n_points`) — every production caller is -either a `Grid` method that has already converted user input to a JAX -scalar, or a piecewise dispatch that selects pieces via `searchsorted`. +The `get_*_coordinate` helpers take JAX arrays of any rank: a `Grid` +method passes a 0-d value, while a piecewise dispatch selects pieces via +`searchsorted` and passes `start` / `stop` / `n_points` indexed by an +N-d `piece_idx`. `linspace` / `logspace` build a grid and take JAX +scalars. """ -from typing import overload - import jax.numpy as jnp from lcm.typing import Float1D, FloatND, IntND, ScalarFloat, ScalarInt @@ -28,29 +27,13 @@ def linspace( return jnp.linspace(start, stop, n_points) # ty: ignore[no-matching-overload] -@overload -def get_linspace_coordinate( - *, - value: ScalarFloat, - start: ScalarFloat | FloatND, - stop: ScalarFloat | FloatND, - n_points: ScalarInt | IntND, -) -> ScalarFloat: ... -@overload def get_linspace_coordinate( *, value: FloatND, - start: ScalarFloat | FloatND, - stop: ScalarFloat | FloatND, - n_points: ScalarInt | IntND, -) -> FloatND: ... -def get_linspace_coordinate( - *, - value: ScalarFloat | FloatND, - start: ScalarFloat | FloatND, - stop: ScalarFloat | FloatND, - n_points: ScalarInt | IntND, -) -> ScalarFloat | FloatND: + start: FloatND, + stop: FloatND, + n_points: IntND, +) -> FloatND: """Map a value into the input needed for jax.scipy.ndimage.map_coordinates.""" step_length = (stop - start) / (n_points - 1) return (value - start) / step_length @@ -82,29 +65,13 @@ def logspace( return grid.at[0].set(start).at[-1].set(stop) -@overload -def get_logspace_coordinate( - *, - value: ScalarFloat, - start: ScalarFloat | FloatND, - stop: ScalarFloat | FloatND, - n_points: ScalarInt | IntND, -) -> ScalarFloat: ... -@overload def get_logspace_coordinate( *, value: FloatND, - start: ScalarFloat | FloatND, - stop: ScalarFloat | FloatND, - n_points: ScalarInt | IntND, -) -> FloatND: ... -def get_logspace_coordinate( - *, - value: ScalarFloat | FloatND, - start: ScalarFloat | FloatND, - stop: ScalarFloat | FloatND, - n_points: ScalarInt | IntND, -) -> ScalarFloat | FloatND: + start: FloatND, + stop: FloatND, + n_points: IntND, +) -> FloatND: """Map a value into the input needed for jax.scipy.ndimage.map_coordinates.""" # Transform start, stop, and value to linear scale start_linear = jnp.log(start) @@ -142,23 +109,11 @@ def get_logspace_coordinate( return rank_lower_gridpoint + decimal_part -@overload -def get_irreg_coordinate( - *, - value: ScalarFloat, - points: Float1D, -) -> ScalarFloat: ... -@overload def get_irreg_coordinate( *, value: FloatND, points: Float1D, -) -> FloatND: ... -def get_irreg_coordinate( - *, - value: ScalarFloat | FloatND, - points: Float1D, -) -> ScalarFloat | FloatND: +) -> FloatND: """Return the generalized coordinate of a value in an irregularly spaced grid. Uses binary search (jnp.searchsorted) to find the position of the value among diff --git a/src/lcm/grids/piecewise.py b/src/lcm/grids/piecewise.py index d4855fb08..c0e23a03f 100644 --- a/src/lcm/grids/piecewise.py +++ b/src/lcm/grids/piecewise.py @@ -1,6 +1,5 @@ import dataclasses from dataclasses import dataclass -from typing import overload import jax.numpy as jnp import portion @@ -97,11 +96,7 @@ def to_jax(self) -> Float1D: ] return jnp.concatenate(piece_arrays) - @overload - def get_coordinate(self, value: ScalarFloat) -> ScalarFloat: ... - @overload - def get_coordinate(self, value: FloatND) -> FloatND: ... - def get_coordinate(self, value: ScalarFloat | FloatND) -> ScalarFloat | FloatND: + def get_coordinate(self, value: FloatND) -> FloatND: """Return the generalized coordinate of a value in the grid.""" piece_idx = jnp.searchsorted(self._breakpoints, value, side="right") local_coord = grid_coordinates.get_linspace_coordinate( @@ -169,11 +164,7 @@ def to_jax(self) -> Float1D: ] return jnp.concatenate(piece_arrays) - @overload - def get_coordinate(self, value: ScalarFloat) -> ScalarFloat: ... - @overload - def get_coordinate(self, value: FloatND) -> FloatND: ... - def get_coordinate(self, value: ScalarFloat | FloatND) -> ScalarFloat | FloatND: + def get_coordinate(self, value: FloatND) -> FloatND: """Return the generalized coordinate of a value in the grid.""" piece_idx = jnp.searchsorted(self._breakpoints, value, side="right") local_coord = grid_coordinates.get_logspace_coordinate( diff --git a/src/lcm/interfaces.py b/src/lcm/interfaces.py index 3060b304e..bf96f1fb3 100644 --- a/src/lcm/interfaces.py +++ b/src/lcm/interfaces.py @@ -20,6 +20,7 @@ DiscreteAction, DiscreteState, FlatRegimeParams, + Float1D, FunctionsMapping, MaxQOverAFunction, NextStateSimulationFunction, @@ -454,14 +455,24 @@ def _distribute_states_to_devices( class PeriodRegimeSimulationData: """Raw simulation data for one period in one regime.""" - V_arr: Array + V_arr: Float1D """Value function array for all subjects at this period.""" actions: MappingProxyType[ActionName, Array] - """Immutable mapping of action names to optimal action arrays for all subjects.""" + """Immutable mapping of action names to optimal action arrays for all subjects. + + Action arrays carry the dtype of their grid — discrete actions are int, + continuous actions are float — so the value type stays the dtype-agnostic + `Array`. + """ states: MappingProxyType[StateName, Array] - """Immutable mapping of state names to state value arrays for all subjects.""" + """Immutable mapping of state names to state value arrays for all subjects. + + State arrays carry the dtype of their grid — discrete states are int, + continuous states are float — so the value type stays the dtype-agnostic + `Array`. + """ in_regime: Bool1D """Boolean mask indicating which subjects are in this regime at this period.""" diff --git a/src/lcm/pandas_utils.py b/src/lcm/pandas_utils.py index 68f8ad25e..f3797d85c 100644 --- a/src/lcm/pandas_utils.py +++ b/src/lcm/pandas_utils.py @@ -20,6 +20,7 @@ from lcm.shocks import _ShockGrid from lcm.simulation.initial_conditions import MISSING_CAT_CODE from lcm.typing import ( + FloatND, FunctionName, InternalParams, RegimeName, @@ -336,7 +337,7 @@ def array_from_series( regimes: Mapping[RegimeName, Regime], regime_names_to_ids: RegimeNamesToIds, regime_name: RegimeName | None = None, -) -> Array: +) -> FloatND: """Convert a pandas Series to a JAX array. Inspect `func` to determine indexing dimensions (states, actions, @@ -683,7 +684,7 @@ def _scatter_series( series: pd.Series, level_mappings: tuple[_LevelMapping, ...], fill_value: float = np.nan, -) -> Array: +) -> FloatND: """Scatter a MultiIndex Series into an N-dimensional JAX array. Each `_LevelMapping` defines one axis: its size, and how to map labels from diff --git a/src/lcm/params/processing.py b/src/lcm/params/processing.py index c75fb6294..43c83e4d8 100644 --- a/src/lcm/params/processing.py +++ b/src/lcm/params/processing.py @@ -37,7 +37,6 @@ from lcm.params.mapping_leaf import MappingLeaf from lcm.params.sequence_leaf import SequenceLeaf from lcm.typing import ( - FunctionName, InternalParams, ParamsTemplate, RegimeName, @@ -53,7 +52,7 @@ def process_params( *, params: UserParams, - params_template: Mapping[RegimeName, Mapping[FunctionName, object]], + params_template: ParamsTemplate, ) -> InternalParams: """Process user-provided params into internal params. diff --git a/src/lcm/regime_building/Q_and_F.py b/src/lcm/regime_building/Q_and_F.py index 814619ddf..5defdb9a9 100644 --- a/src/lcm/regime_building/Q_and_F.py +++ b/src/lcm/regime_building/Q_and_F.py @@ -149,7 +149,7 @@ def Q_and_F( A tuple containing the arrays with state-action values and feasibilities. """ - regime_transition_probs: MappingProxyType[str, Array] = ( # ty: ignore[invalid-assignment] + regime_transition_probs: MappingProxyType[str, FloatND] = ( # ty: ignore[invalid-assignment] compute_regime_transition_probs(**states_actions_params) ) U_arr, F_arr = U_and_F(**states_actions_params) @@ -309,15 +309,17 @@ def get_compute_intermediates( args=arg_names_of_compute_intermediates, return_annotation=( "tuple[FloatND, FloatND, FloatND, FloatND, " - "MappingProxyType[RegimeName, Array]]" + "MappingProxyType[RegimeName, FloatND]]" ), ) def compute_intermediates( next_regime_to_V_arr: FloatND, **states_actions_params: Array, - ) -> tuple[FloatND, FloatND, FloatND, FloatND, MappingProxyType[RegimeName, Array]]: + ) -> tuple[ + FloatND, FloatND, FloatND, FloatND, MappingProxyType[RegimeName, FloatND] + ]: """Compute all Q_and_F intermediates.""" - regime_transition_probs: MappingProxyType[str, Array] = ( # ty: ignore[invalid-assignment] + regime_transition_probs: MappingProxyType[str, FloatND] = ( # ty: ignore[invalid-assignment] compute_regime_transition_probs(**states_actions_params) ) U_arr, F_arr = U_and_F(**states_actions_params) diff --git a/src/lcm/regime_building/V.py b/src/lcm/regime_building/V.py index 31521a5c8..0a04fe6e7 100644 --- a/src/lcm/regime_building/V.py +++ b/src/lcm/regime_building/V.py @@ -159,7 +159,7 @@ def _get_lookup_function( *, array_name: str, axis_names: list[str], -) -> Callable[..., Array]: +) -> Callable[..., FloatND]: """Create a function that emulates indexing into an array via named axes. Args: @@ -174,7 +174,7 @@ def _get_lookup_function( arg_names = [*axis_names, array_name] @with_signature(args=dict.fromkeys(arg_names, "Array"), return_annotation="Array") - def lookup_wrapper(*args: Array, **kwargs: Array) -> Array: + def lookup_wrapper(*args: Array, **kwargs: Array) -> FloatND: kwargs = all_as_kwargs(args=args, kwargs=kwargs, arg_names=arg_names) positions = tuple(kwargs[var] for var in axis_names) return kwargs[array_name][positions] @@ -186,7 +186,7 @@ def _get_coordinate_finder( *, in_name: str, grid: ContinuousGrid, -) -> Callable[..., Array]: +) -> Callable[..., FloatND]: """Create a function that translates a value into coordinates on a grid. The resulting coordinates can be used to do linear interpolation via @@ -212,7 +212,7 @@ def _get_coordinate_finder( @with_signature( args=dict.fromkeys(arg_names, "Array"), return_annotation="Array" ) - def find_irreg_coordinate(*args: Array, **kwargs: Array) -> Array: + def find_irreg_coordinate(*args: Array, **kwargs: Array) -> FloatND: kwargs = all_as_kwargs(args=args, kwargs=kwargs, arg_names=arg_names) return get_irreg_coordinate( value=kwargs[in_name], points=kwargs[points_param] @@ -226,7 +226,7 @@ def find_irreg_coordinate(*args: Array, **kwargs: Array) -> Array: @with_signature( args=dict.fromkeys([in_name], "Array"), return_annotation="Array" ) - def find_irreg_coordinate(*args: Array, **kwargs: Array) -> Array: + def find_irreg_coordinate(*args: Array, **kwargs: Array) -> FloatND: kwargs = all_as_kwargs(args=args, kwargs=kwargs, arg_names=[in_name]) return get_irreg_coordinate(value=kwargs[in_name], points=points_jax) @@ -234,7 +234,7 @@ def find_irreg_coordinate(*args: Array, **kwargs: Array) -> Array: # All other grid types (LinSpaced, LogSpaced, Piecewise*, ShockGrid) @with_signature(args=dict.fromkeys([in_name], "Array"), return_annotation="Array") - def find_coordinate(*args: Array, **kwargs: Array) -> Array: + def find_coordinate(*args: Array, **kwargs: Array) -> FloatND: kwargs = all_as_kwargs(args=args, kwargs=kwargs, arg_names=[in_name]) return grid.get_coordinate(kwargs[in_name]) @@ -245,7 +245,7 @@ def _get_interpolator( *, name_of_values_on_grid: str, axis_names: list[str], -) -> Callable[..., Array]: +) -> Callable[..., FloatND]: """Create a function interpolator via named axes. Args: @@ -261,7 +261,7 @@ def _get_interpolator( arg_names = [name_of_values_on_grid, *axis_names] @with_signature(args=dict.fromkeys(arg_names, "Array"), return_annotation="Array") - def interpolate(*args: Array, **kwargs: Array) -> Array: + def interpolate(*args: Array, **kwargs: Array) -> FloatND: kwargs = all_as_kwargs(args=args, kwargs=kwargs, arg_names=arg_names) coordinates = jnp.array([kwargs[var] for var in axis_names]) return map_coordinates( diff --git a/src/lcm/regime_building/argmax.py b/src/lcm/regime_building/argmax.py index a4271e2f9..721acb644 100644 --- a/src/lcm/regime_building/argmax.py +++ b/src/lcm/regime_building/argmax.py @@ -1,14 +1,14 @@ import jax.numpy as jnp from jax import Array -from lcm.typing import IntND +from lcm.typing import BoolND, IntND def argmax_and_max( a: Array, axis: int | tuple[int, ...] | None = None, initial: float | None = None, - where: Array | None = None, + where: BoolND | None = None, ) -> tuple[IntND, Array]: """Compute the argmax of an n-dim array along axis. diff --git a/src/lcm/regime_building/diagnostics.py b/src/lcm/regime_building/diagnostics.py index fb0ac9f17..b182d929f 100644 --- a/src/lcm/regime_building/diagnostics.py +++ b/src/lcm/regime_building/diagnostics.py @@ -24,6 +24,7 @@ from lcm.regime_building.V import VInterpolationInfo from lcm.typing import ( ActionName, + FloatND, FunctionsMapping, RegimeName, RegimeTransitionFunction, @@ -171,7 +172,7 @@ def reduced(**kwargs: Array) -> dict[str, Any]: # contribute to numerators. Infeasible cells are zeroed out because # the solver masks them before the max, so a NaN there never # propagates to V_arr — reporting it would conflate causes. - nan_arrays: dict[str, Array] = { + nan_arrays: dict[str, FloatND] = { "U_nan": jnp.isnan(U_arr).astype(float) * F_float, "E_nan": jnp.isnan(E_next_V).astype(float) * F_float, "Q_nan": jnp.isnan(Q_arr).astype(float) * F_float, diff --git a/src/lcm/regime_building/processing.py b/src/lcm/regime_building/processing.py index 85fbb7fe0..df159a146 100644 --- a/src/lcm/regime_building/processing.py +++ b/src/lcm/regime_building/processing.py @@ -46,6 +46,7 @@ from lcm.typing import ( ArgmaxQOverAFunction, Float1D, + FloatND, FunctionsMapping, Int1D, InternalUserFunction, @@ -1314,12 +1315,12 @@ def _wrap_deterministic_regime_transition( # Preserve original annotations but update return type annotations = {k: v for k, v in get_annotations(func).items() if k != "return"} - @with_signature(args=annotations, return_annotation="Array") + @with_signature(args=annotations, return_annotation="FloatND") @functools.wraps(func) def wrapped( *args: Array | int, **kwargs: Array | int, - ) -> Array: + ) -> FloatND: regime_idx = func(*args, **kwargs) return jax.nn.one_hot(regime_idx, n_regimes) diff --git a/src/lcm/shocks/_base.py b/src/lcm/shocks/_base.py index 4bb768f2e..6dd859478 100644 --- a/src/lcm/shocks/_base.py +++ b/src/lcm/shocks/_base.py @@ -1,8 +1,7 @@ from abc import abstractmethod -from collections.abc import Mapping from dataclasses import dataclass, fields from types import MappingProxyType -from typing import Any, ClassVar, overload +from typing import Any, ClassVar import jax.numpy as jnp import numpy as np @@ -11,11 +10,11 @@ from lcm.exceptions import GridInitializationError from lcm.grids import ContinuousGrid from lcm.grids import coordinates as grid_coordinates -from lcm.typing import Float1D, FloatND, IntND, ScalarFloat +from lcm.typing import Float1D, FloatND, IntND, ScalarFloat, ScalarInt def _params_to_jax( - params: Mapping[str, Any], + params: MappingProxyType[str, Any], ) -> MappingProxyType[str, FloatND | IntND]: """Cast Python `int` / `float` shock params to JAX scalars. @@ -41,8 +40,8 @@ def _params_to_jax( def _gauss_hermite_normal( *, n_points: int, - mu: ScalarFloat | Float1D, - sigma: ScalarFloat | Float1D, + mu: ScalarFloat, + sigma: ScalarFloat, ) -> tuple[Float1D, Float1D]: """Compute Gauss-Hermite quadrature nodes and weights for $N(\\mu, \\sigma^2)$. @@ -109,11 +108,11 @@ def is_fully_specified(self) -> bool: return not self.params_to_pass_at_runtime @abstractmethod - def compute_gridpoints(self, **kwargs: FloatND | IntND) -> Float1D: + def compute_gridpoints(self, **kwargs: ScalarFloat | ScalarInt) -> Float1D: """Compute discretized gridpoints for the shock distribution.""" @abstractmethod - def compute_transition_probs(self, **kwargs: FloatND | IntND) -> FloatND: + def compute_transition_probs(self, **kwargs: ScalarFloat | ScalarInt) -> FloatND: """Compute transition probability matrix for the shock distribution.""" def get_gridpoints(self) -> Float1D: @@ -142,11 +141,7 @@ def to_jax(self) -> Float1D: """Convert the grid to a Jax array.""" return self.get_gridpoints() - @overload - def get_coordinate(self, value: ScalarFloat) -> ScalarFloat: ... - @overload - def get_coordinate(self, value: FloatND) -> FloatND: ... - def get_coordinate(self, value: ScalarFloat | FloatND) -> ScalarFloat | FloatND: + def get_coordinate(self, value: FloatND) -> FloatND: """Return the generalized coordinate of a value in the grid.""" if not self.is_fully_specified: raise GridInitializationError( @@ -177,11 +172,11 @@ def _validate_gauss_hermite_grid( def _mixture_cdf( *, x: FloatND, - p1: ScalarFloat | FloatND, - mu1: ScalarFloat | FloatND, - sigma1: ScalarFloat | FloatND, - mu2: ScalarFloat | FloatND, - sigma2: ScalarFloat | FloatND, + p1: ScalarFloat, + mu1: ScalarFloat, + sigma1: ScalarFloat, + mu2: ScalarFloat, + sigma2: ScalarFloat, ) -> FloatND: """Evaluate the CDF of a two-component normal mixture. diff --git a/src/lcm/shocks/ar1.py b/src/lcm/shocks/ar1.py index 014701762..017cb5c63 100644 --- a/src/lcm/shocks/ar1.py +++ b/src/lcm/shocks/ar1.py @@ -14,7 +14,7 @@ _ShockGrid, _validate_gauss_hermite_grid, ) -from lcm.typing import Float1D, FloatND, IntND, KeyArray, ScalarFloat +from lcm.typing import Float1D, FloatND, KeyArray, ScalarFloat, ScalarInt @dataclass(frozen=True, kw_only=True) @@ -24,7 +24,7 @@ class _ShockGridAR1(_ShockGrid): @abstractmethod def draw_shock( self, - params: MappingProxyType[str, FloatND | IntND], + params: MappingProxyType[str, ScalarFloat | ScalarInt], key: KeyArray, current_value: ScalarFloat, ) -> ScalarFloat: ... @@ -77,7 +77,7 @@ def _param_field_names(self) -> tuple[str, ...]: exclude = exclude | {"n_std"} return tuple(f.name for f in fields(self) if f.name not in exclude) - def compute_gridpoints(self, **kwargs: FloatND | IntND) -> Float1D: + def compute_gridpoints(self, **kwargs: ScalarFloat | ScalarInt) -> Float1D: n_points = self.n_points rho, sigma, mu = kwargs["rho"], kwargs["sigma"], kwargs["mu"] std_y = jnp.sqrt(sigma**2 / (1 - rho**2)) @@ -90,7 +90,7 @@ def compute_gridpoints(self, **kwargs: FloatND | IntND) -> Float1D: x = jnp.linspace(-x_max, x_max, n_points) return x + mu / (1 - rho) - def compute_transition_probs(self, **kwargs: FloatND | IntND) -> FloatND: + def compute_transition_probs(self, **kwargs: ScalarFloat | ScalarInt) -> FloatND: n_points = self.n_points rho, sigma = kwargs["rho"], kwargs["sigma"] std_y = jnp.sqrt(sigma**2 / (1 - rho**2)) @@ -119,7 +119,7 @@ def compute_transition_probs(self, **kwargs: FloatND | IntND) -> FloatND: def draw_shock( self, - params: MappingProxyType[str, FloatND | IntND], + params: MappingProxyType[str, ScalarFloat | ScalarInt], key: KeyArray, current_value: ScalarFloat, ) -> ScalarFloat: @@ -152,14 +152,14 @@ class Rouwenhorst(_ShockGridAR1): mu: float | int | None = None """Intercept (drift) of the AR(1) process.""" - def compute_gridpoints(self, **kwargs: FloatND | IntND) -> Float1D: + def compute_gridpoints(self, **kwargs: ScalarFloat | ScalarInt) -> Float1D: n_points = self.n_points rho, sigma, mu = kwargs["rho"], kwargs["sigma"], kwargs["mu"] nu = jnp.sqrt((n_points - 1) / (1 - rho**2)) * sigma long_run_mean = mu / (1.0 - rho) return jnp.linspace(long_run_mean - nu, long_run_mean + nu, n_points) - def compute_transition_probs(self, **kwargs: FloatND | IntND) -> FloatND: + def compute_transition_probs(self, **kwargs: ScalarFloat | ScalarInt) -> FloatND: n_points = self.n_points rho = kwargs["rho"] q = (rho + 1) / 2 @@ -188,7 +188,7 @@ def compute_transition_probs(self, **kwargs: FloatND | IntND) -> FloatND: def draw_shock( self, - params: MappingProxyType[str, FloatND | IntND], + params: MappingProxyType[str, ScalarFloat | ScalarInt], key: KeyArray, current_value: ScalarFloat, ) -> ScalarFloat: @@ -242,17 +242,17 @@ class TauchenNormalMixture(_ShockGridAR1): @staticmethod def _innovation_variance( *, - p1: ScalarFloat | FloatND, - mu1: ScalarFloat | FloatND, - sigma1: ScalarFloat | FloatND, - mu2: ScalarFloat | FloatND, - sigma2: ScalarFloat | FloatND, - ) -> ScalarFloat | FloatND: + p1: ScalarFloat, + mu1: ScalarFloat, + sigma1: ScalarFloat, + mu2: ScalarFloat, + sigma2: ScalarFloat, + ) -> ScalarFloat: """Compute the variance of the mixture innovation.""" mean_eps = p1 * mu1 + (1 - p1) * mu2 return p1 * (sigma1**2 + mu1**2) + (1 - p1) * (sigma2**2 + mu2**2) - mean_eps**2 - def compute_gridpoints(self, **kwargs: FloatND | IntND) -> Float1D: + def compute_gridpoints(self, **kwargs: ScalarFloat | ScalarInt) -> Float1D: n_points = self.n_points rho, mu = kwargs["rho"], kwargs["mu"] n_std = kwargs["n_std"] @@ -268,7 +268,7 @@ def compute_gridpoints(self, **kwargs: FloatND | IntND) -> Float1D: x_max = n_std * std_y return jnp.linspace(long_run_mean - x_max, long_run_mean + x_max, n_points) - def compute_transition_probs(self, **kwargs: FloatND | IntND) -> FloatND: + def compute_transition_probs(self, **kwargs: ScalarFloat | ScalarInt) -> FloatND: n_points = self.n_points rho, mu = kwargs["rho"], kwargs["mu"] n_std = kwargs["n_std"] @@ -302,7 +302,7 @@ def compute_transition_probs(self, **kwargs: FloatND | IntND) -> FloatND: def draw_shock( self, - params: MappingProxyType[str, FloatND | IntND], + params: MappingProxyType[str, ScalarFloat | ScalarInt], key: KeyArray, current_value: ScalarFloat, ) -> ScalarFloat: diff --git a/src/lcm/shocks/iid.py b/src/lcm/shocks/iid.py index 472fec0d5..fb2beac72 100644 --- a/src/lcm/shocks/iid.py +++ b/src/lcm/shocks/iid.py @@ -13,7 +13,7 @@ _ShockGrid, _validate_gauss_hermite_grid, ) -from lcm.typing import Float1D, FloatND, IntND, KeyArray, ScalarFloat +from lcm.typing import Float1D, FloatND, KeyArray, ScalarFloat, ScalarInt @dataclass(frozen=True, kw_only=True) @@ -23,7 +23,7 @@ class _ShockGridIID(_ShockGrid): @abstractmethod def draw_shock( self, - params: MappingProxyType[str, FloatND | IntND], + params: MappingProxyType[str, ScalarFloat | ScalarInt], key: KeyArray, ) -> ScalarFloat: ... @@ -44,18 +44,18 @@ class Uniform(_ShockGridIID): stop: float | int | None = None """Upper bound of the uniform distribution.""" - def compute_gridpoints(self, **kwargs: FloatND | IntND) -> Float1D: + def compute_gridpoints(self, **kwargs: ScalarFloat | ScalarInt) -> Float1D: return jnp.linspace( start=kwargs["start"], stop=kwargs["stop"], num=self.n_points ) - def compute_transition_probs(self, **kwargs: FloatND | IntND) -> FloatND: # noqa: ARG002 + def compute_transition_probs(self, **kwargs: ScalarFloat | ScalarInt) -> FloatND: # noqa: ARG002 n_points = self.n_points return jnp.full((n_points, n_points), fill_value=1 / n_points) def draw_shock( self, - params: MappingProxyType[str, FloatND | IntND], + params: MappingProxyType[str, ScalarFloat | ScalarInt], key: KeyArray, ) -> ScalarFloat: return jax.random.uniform( @@ -99,7 +99,7 @@ def _param_field_names(self) -> tuple[str, ...]: exclude = exclude | {"n_std"} return tuple(f.name for f in fields(self) if f.name not in exclude) - def compute_gridpoints(self, **kwargs: FloatND | IntND) -> Float1D: + def compute_gridpoints(self, **kwargs: ScalarFloat | ScalarInt) -> Float1D: n_points = self.n_points mu, sigma = kwargs["mu"], kwargs["sigma"] if self.gauss_hermite: @@ -112,7 +112,7 @@ def compute_gridpoints(self, **kwargs: FloatND | IntND) -> Float1D: x_max = mu + n_std * sigma return jnp.linspace(start=x_min, stop=x_max, num=n_points) - def compute_transition_probs(self, **kwargs: FloatND | IntND) -> FloatND: + def compute_transition_probs(self, **kwargs: ScalarFloat | ScalarInt) -> FloatND: n_points = self.n_points mu, sigma = kwargs["mu"], kwargs["sigma"] if self.gauss_hermite: @@ -132,7 +132,7 @@ def compute_transition_probs(self, **kwargs: FloatND | IntND) -> FloatND: def draw_shock( self, - params: MappingProxyType[str, FloatND | IntND], + params: MappingProxyType[str, ScalarFloat | ScalarInt], key: KeyArray, ) -> ScalarFloat: return params["mu"] + params["sigma"] * jax.random.normal(key=key) @@ -167,7 +167,7 @@ def _param_field_names(self) -> tuple[str, ...]: exclude = exclude | {"n_std"} return tuple(f.name for f in fields(self) if f.name not in exclude) - def compute_gridpoints(self, **kwargs: FloatND | IntND) -> Float1D: + def compute_gridpoints(self, **kwargs: ScalarFloat | ScalarInt) -> Float1D: n_points = self.n_points mu, sigma = kwargs["mu"], kwargs["sigma"] if self.gauss_hermite: @@ -178,7 +178,7 @@ def compute_gridpoints(self, **kwargs: FloatND | IntND) -> Float1D: n_std = kwargs["n_std"] return jnp.exp(jnp.linspace(mu - n_std * sigma, mu + n_std * sigma, n_points)) - def compute_transition_probs(self, **kwargs: FloatND | IntND) -> FloatND: + def compute_transition_probs(self, **kwargs: ScalarFloat | ScalarInt) -> FloatND: n_points = self.n_points mu, sigma = kwargs["mu"], kwargs["sigma"] if self.gauss_hermite: @@ -198,7 +198,7 @@ def compute_transition_probs(self, **kwargs: FloatND | IntND) -> FloatND: def draw_shock( self, - params: MappingProxyType[str, FloatND | IntND], + params: MappingProxyType[str, ScalarFloat | ScalarInt], key: KeyArray, ) -> ScalarFloat: return jnp.exp(params["mu"] + params["sigma"] * jax.random.normal(key=key)) @@ -238,7 +238,7 @@ class NormalMixture(_ShockGridIID): sigma2: float | int | None = None """Standard deviation of the second mixture component.""" - def compute_gridpoints(self, **kwargs: FloatND | IntND) -> Float1D: + def compute_gridpoints(self, **kwargs: ScalarFloat | ScalarInt) -> Float1D: n_points = self.n_points n_std = kwargs["n_std"] p1, mu1, sigma1 = kwargs["p1"], kwargs["mu1"], kwargs["sigma1"] @@ -253,7 +253,7 @@ def compute_gridpoints(self, **kwargs: FloatND | IntND) -> Float1D: mean_eps - n_std * std_eps, mean_eps + n_std * std_eps, n_points ) - def compute_transition_probs(self, **kwargs: FloatND | IntND) -> FloatND: + def compute_transition_probs(self, **kwargs: ScalarFloat | ScalarInt) -> FloatND: n_points = self.n_points n_std = kwargs["n_std"] p1, mu1, sigma1 = kwargs["p1"], kwargs["mu1"], kwargs["sigma1"] @@ -283,7 +283,7 @@ def compute_transition_probs(self, **kwargs: FloatND | IntND) -> FloatND: def draw_shock( self, - params: MappingProxyType[str, FloatND | IntND], + params: MappingProxyType[str, ScalarFloat | ScalarInt], key: KeyArray, ) -> ScalarFloat: key1, key2 = jax.random.split(key) diff --git a/src/lcm/simulation/initial_conditions.py b/src/lcm/simulation/initial_conditions.py index dfdb74fb0..dd5d23595 100644 --- a/src/lcm/simulation/initial_conditions.py +++ b/src/lcm/simulation/initial_conditions.py @@ -27,7 +27,9 @@ from lcm.regime_building.Q_and_F import _get_feasibility from lcm.typing import ( ActionName, + BoolND, FlatRegimeParams, + Int1D, InternalParams, RegimeIdsToNames, RegimeName, @@ -240,7 +242,7 @@ def _format_missing_states_message(missing: set[str], required: set[str]) -> str def _collect_state_name_errors( *, initial_states: Mapping[StateName, Array], - regime_id_arr: Array, + regime_id_arr: Int1D, regime_ids_to_names: RegimeIdsToNames, internal_regimes: MappingProxyType[RegimeName, InternalRegime], valid_regime_names: set[RegimeName], @@ -298,7 +300,7 @@ def _collect_state_name_errors( def _collect_structural_errors( *, initial_states: Mapping[StateName, Array], - regime_id_arr: Array, + regime_id_arr: Int1D, regime_ids_to_names: RegimeIdsToNames, regime_names_to_ids: RegimeNamesToIds, internal_regimes: MappingProxyType[RegimeName, InternalRegime], @@ -398,7 +400,7 @@ def _collect_structural_errors( def _collect_feasibility_errors( *, initial_states: Mapping[StateName, Array], - regime_id_arr: Array, + regime_id_arr: Int1D, regime_names_to_ids: RegimeNamesToIds, internal_regimes: MappingProxyType[RegimeName, InternalRegime], internal_params: InternalParams, @@ -450,7 +452,7 @@ def _validate_discrete_state_values( *, initial_states: Mapping[StateName, Array], internal_regimes: MappingProxyType[RegimeName, InternalRegime], - regime_id_arr: Array, + regime_id_arr: Int1D, regime_names_to_ids: RegimeNamesToIds, ) -> None: """Validate that discrete state values are valid codes. @@ -512,12 +514,12 @@ def _validate_discrete_state_values( def _batched_feasibility_check( *, - feasibility_func: Callable[..., Array], + feasibility_func: Callable[..., BoolND], subject_states: Mapping[StateName, Array], action_kwargs: Mapping[str, Array], filtered_params: Mapping[str, object], flat_actions: Mapping[ActionName, Array], -) -> Array: +) -> BoolND: """Check feasibility for all subjects, batching to avoid OOM. Vmaps over action combos individually (like solve/simulate do) so each @@ -542,10 +544,10 @@ def _batched_feasibility_check( def _is_combo_feasible( action_kw: dict[str, Array], subject_kw: dict[str, Array], - ) -> Array: + ) -> BoolND: return feasibility_func(**action_kw, **subject_kw, **filtered_params) - def _is_any_action_feasible(per_subject_kwargs: dict[str, Array]) -> Array: + def _is_any_action_feasible(per_subject_kwargs: dict[str, Array]) -> BoolND: per_combo = jax.vmap(_is_combo_feasible, in_axes=(0, None))( action_kwargs, per_subject_kwargs, @@ -554,7 +556,7 @@ def _is_any_action_feasible(per_subject_kwargs: dict[str, Array]) -> Array: else: - def _is_any_action_feasible(per_subject_kwargs: dict[str, Array]) -> Array: + def _is_any_action_feasible(per_subject_kwargs: dict[str, Array]) -> BoolND: return jnp.any(feasibility_func(**per_subject_kwargs, **filtered_params)) vmapped_check = jax.vmap(_is_any_action_feasible) @@ -569,7 +571,7 @@ def _is_any_action_feasible(per_subject_kwargs: dict[str, Array]) -> Array: if n_subjects <= batch_size: return vmapped_check(subject_states) - results: list[Array] = [] + results: list[BoolND] = [] for start in range(0, n_subjects, batch_size): end = min(start + batch_size, n_subjects) batch = {k: v[start:end] for k, v in subject_states.items()} @@ -672,7 +674,7 @@ def _check_regime_feasibility( # noqa: C901 # No per-subject varying states: feasibility is identical for all subjects. if action_kwargs: - def _check_combo(action_kw: dict[str, Array]) -> Array: + def _check_combo(action_kw: dict[str, Array]) -> BoolND: return feasibility_func(**action_kw, **filtered_params) # ty: ignore[invalid-argument-type] result = jax.vmap(_check_combo)(action_kwargs) @@ -704,14 +706,14 @@ def _check_combo(action_kw: dict[str, Array]) -> Array: def _admits_any_action( *, - feasibility_func: Callable[..., Array], + feasibility_func: Callable[..., BoolND], action_kwargs: Mapping[str, Array], params: Mapping[str, object], ) -> bool: """Return True iff the feasibility function admits ≥ 1 action under params.""" if action_kwargs: - def _check_combo(action_kw: dict[str, Array]) -> Array: + def _check_combo(action_kw: dict[str, Array]) -> BoolND: return feasibility_func(**action_kw, **params) per_combo = jax.vmap(_check_combo)(action_kwargs) @@ -725,7 +727,7 @@ def _per_constraint_feasibility( subject_states: Mapping[StateName, Array], regime_params: Mapping[str, object], flat_actions: Mapping[ActionName, Array], - idx_arr: Array, + idx_arr: Int1D, infeasible_indices: Sequence[int], ) -> dict[str, np.ndarray]: """Per-constraint feasibility for the infeasible subjects. diff --git a/src/lcm/simulation/random.py b/src/lcm/simulation/random.py index b89c0b0eb..c099e2900 100644 --- a/src/lcm/simulation/random.py +++ b/src/lcm/simulation/random.py @@ -1,12 +1,13 @@ import os import jax -from jax import Array + +from lcm.typing import KeyArray def generate_simulation_keys( - *, key: Array, names: list[str], n_initial_states: int -) -> tuple[Array, dict[str, Array]]: + *, key: KeyArray, names: list[str], n_initial_states: int +) -> tuple[KeyArray, dict[str, KeyArray]]: """Generate pseudo-random number generator keys (PRNG keys) for simulation. PRNG keys in JAX are immutable objects used to control random number generation. diff --git a/src/lcm/simulation/simulate.py b/src/lcm/simulation/simulate.py index 70c6f0d6c..4bb7e174f 100644 --- a/src/lcm/simulation/simulate.py +++ b/src/lcm/simulation/simulate.py @@ -25,10 +25,12 @@ create_regime_state_action_space, ) from lcm.typing import ( + Float1D, FloatND, Int1D, InternalParams, IntND, + KeyArray, RegimeName, RegimeNamesToIds, ScalarFloat, @@ -215,8 +217,8 @@ def _simulate_regime_in_period( internal_params: InternalParams, regime_names_to_ids: RegimeNamesToIds, active_regimes_next_period: tuple[RegimeName, ...], - key: Array, -) -> tuple[PeriodRegimeSimulationData, StatesPerRegime, Int1D, Array]: + key: KeyArray, +) -> tuple[PeriodRegimeSimulationData, StatesPerRegime, Int1D, KeyArray]: """Simulate one regime for one period. This function processes all subjects in a given regime for a single period, @@ -369,7 +371,7 @@ def _lookup_values_from_indices( def _compute_starting_periods( *, - initial_ages: Array, + initial_ages: Float1D, ages: AgeGrid, ) -> Int1D: """Convert per-subject initial ages to starting period indices. diff --git a/src/lcm/simulation/transitions.py b/src/lcm/simulation/transitions.py index 484fe9f12..21c3b5a63 100644 --- a/src/lcm/simulation/transitions.py +++ b/src/lcm/simulation/transitions.py @@ -20,7 +20,10 @@ ActionName, Bool1D, FlatRegimeParams, + Float1D, + FloatND, Int1D, + KeyArray, RegimeName, RegimeNamesToIds, RegimeStates, @@ -75,7 +78,7 @@ def calculate_next_states( regime_params: FlatRegimeParams, states_per_regime: StatesPerRegime, state_action_space: StateActionSpace, - key: Array, + key: KeyArray, subjects_in_regime: Bool1D, ) -> StatesPerRegime: """Calculate next period states for subjects in a regime. @@ -166,7 +169,7 @@ def calculate_next_regime_membership( regime_names_to_ids: RegimeNamesToIds, new_subject_regime_ids: Int1D, active_regimes_next_period: tuple[RegimeName, ...], - key: Array, + key: KeyArray, subjects_in_regime: Bool1D, ) -> Int1D: """Calculate next period regime membership for subjects in a regime. @@ -196,7 +199,7 @@ def calculate_next_regime_membership( """ # Compute regime transition probabilities # --------------------------------------------------------------------------------- - regime_transition_probs: MappingProxyType[str, Array] = ( # ty: ignore[invalid-assignment] + regime_transition_probs: MappingProxyType[str, FloatND] = ( # ty: ignore[invalid-assignment] internal_regime.simulate_functions.compute_regime_transition_probs( # ty: ignore[call-non-callable] **state_action_space.states, **optimal_actions, @@ -230,9 +233,9 @@ def calculate_next_regime_membership( def draw_key_from_dict( *, - d: MappingProxyType[RegimeName, Array], + d: MappingProxyType[RegimeName, Float1D], regime_names_to_ids: RegimeNamesToIds, - keys: Array, + keys: KeyArray, ) -> Int1D: """Draw a random key from a dictionary of arrays. @@ -256,8 +259,8 @@ def draw_key_from_dict( ) def random_id( - key: Array, - p: Array, + key: KeyArray, + p: Float1D, ) -> Int1D: return jax.random.choice( key, diff --git a/src/lcm/typing.py b/src/lcm/typing.py index 3d3496884..45e4ebbe8 100644 --- a/src/lcm/typing.py +++ b/src/lcm/typing.py @@ -5,7 +5,7 @@ import numpy as np import pandas as pd from jax import Array -from jaxtyping import Bool, Float, Int32, Scalar +from jaxtyping import Bool, Float, Int, Int32, Scalar from lcm.params import MappingLeaf from lcm.params.sequence_leaf import SequenceLeaf @@ -55,8 +55,21 @@ type StatesPerRegime = MappingProxyType[RegimeName, RegimeStates] +# User-supplied param leaf, checked by beartype at the `process_params` +# boundary. The int slot is dtype-generic `Int[Array, "..."]` rather than +# `IntND` (int32-only): users legitimately pass int64 arrays, which the +# boundary downcasts. `FloatND`/`BoolND` are already dtype-generic. type _ParamsLeaf = ( - bool | int | float | Array | np.ndarray | pd.Series | MappingLeaf | SequenceLeaf + bool + | int + | float + | FloatND + | Int[Array, "..."] + | BoolND + | np.ndarray + | pd.Series + | MappingLeaf + | SequenceLeaf ) type UserParams = Mapping[ str, @@ -172,9 +185,9 @@ class MaxQOverAFunction(Protocol): def __call__( self, - next_regime_to_V_arr: MappingProxyType[RegimeName, Array], + next_regime_to_V_arr: MappingProxyType[RegimeName, FloatND], **kwargs: Any, # noqa: ANN401 - ) -> Array: ... + ) -> FloatND: ... @runtime_checkable @@ -190,9 +203,9 @@ class ArgmaxQOverAFunction(Protocol): def __call__( self, - next_regime_to_V_arr: MappingProxyType[RegimeName, Array], + next_regime_to_V_arr: MappingProxyType[RegimeName, FloatND], **kwargs: Any, # noqa: ANN401 - ) -> tuple[Array, Array]: ... + ) -> tuple[IntND, FloatND]: ... @runtime_checkable diff --git a/src/lcm/utils/error_handling.py b/src/lcm/utils/error_handling.py index b9c7e9b25..2e2785122 100644 --- a/src/lcm/utils/error_handling.py +++ b/src/lcm/utils/error_handling.py @@ -38,7 +38,7 @@ def validate_V( *, - V_arr: Array, + V_arr: FloatND, age: float | ScalarInt | ScalarFloat, regime_name: RegimeName | None = None, partial_solution: object = None, @@ -284,7 +284,7 @@ def _format_diagnostic_summary(summary: dict[str, Any]) -> str: def validate_regime_transition_probs( *, - regime_transition_probs: MappingProxyType[str, Array], + regime_transition_probs: MappingProxyType[str, FloatND], active_regimes_next_period: tuple[RegimeName, ...], regime_name: RegimeName, age: float | ScalarInt | ScalarFloat, @@ -352,7 +352,7 @@ def validate_regime_transition_probs( def _format_sum_violation( *, - sum_all: Array, + sum_all: FloatND, state_action_values: MappingProxyType[str, Array] | None = None, ) -> str: """Format a human-readable description of probability sum violations. @@ -499,18 +499,18 @@ def _call( _func: object = regime_transition_func, _period: int = period, _age: ScalarInt | ScalarFloat = ages.values[period], # noqa: PD011 - ) -> MappingProxyType[str, Array]: + ) -> MappingProxyType[str, FloatND]: kwargs = dict(zip(_names, args, strict=True)) return _func( # ty: ignore[call-non-callable] **kwargs, **_params, period=_period, age=_age ) - regime_transition_probs: MappingProxyType[str, Array] = jax.vmap(_call)( + regime_transition_probs: MappingProxyType[str, FloatND] = jax.vmap(_call)( *flat_arrays ) point = dict(zip(grid_var_names, flat_arrays, strict=True)) else: - regime_transition_probs: MappingProxyType[str, Array] = ( # ty: ignore[invalid-assignment] + regime_transition_probs: MappingProxyType[str, FloatND] = ( # ty: ignore[invalid-assignment] regime_transition_func( # ty: ignore[call-non-callable] **filtered_params, period=period, @@ -540,7 +540,7 @@ def _call( def _validate_no_reachable_incomplete_targets( *, internal_regimes: MappingProxyType[RegimeName, InternalRegime], - regime_transition_probs: MappingProxyType[str, Array], + regime_transition_probs: MappingProxyType[str, FloatND], active_regimes_next_period: tuple[RegimeName, ...], regime_name: RegimeName, age: float | ScalarInt | ScalarFloat, diff --git a/tests/regime_building/test_process_params.py b/tests/regime_building/test_process_params.py index 4413d1b4b..14afc2e2b 100644 --- a/tests/regime_building/test_process_params.py +++ b/tests/regime_building/test_process_params.py @@ -1,6 +1,7 @@ """Tests for process_params function.""" from types import MappingProxyType +from typing import cast import pytest @@ -10,6 +11,13 @@ create_params_template, process_params, ) +from lcm.typing import ParamsTemplate +from lcm.utils.containers import ensure_containers_are_immutable + + +def _as_template(plain: dict) -> ParamsTemplate: + """Deep-freeze a plain nested dict into a `ParamsTemplate` for tests.""" + return cast("ParamsTemplate", ensure_containers_are_immutable(plain)) def _expected_flat_keys(params_template, regime): @@ -23,16 +31,18 @@ def _expected_flat_keys(params_template, regime): @pytest.fixture def params_template(): """Fixture providing a params_template with two regimes and two functions each.""" - return { - "regime_0": { - "fun_0": {"arg_0": float, "arg_1": float}, - "fun_1": {"arg_0": float, "arg_1": 1.0}, - }, - "regime_1": { - "fun_0": {"arg_0": float, "arg_1": float}, - "fun_1": {"arg_0": float, "arg_1": 1.0}, - }, - } + return _as_template( + { + "regime_0": { + "fun_0": {"arg_0": "float", "arg_1": "float"}, + "fun_1": {"arg_0": "float", "arg_1": "float"}, + }, + "regime_1": { + "fun_0": {"arg_0": "float", "arg_1": "float"}, + "fun_1": {"arg_0": "float", "arg_1": "float"}, + }, + } + ) def test_params_at_function_level(params_template): @@ -191,7 +201,7 @@ def test_function_params_no_qname_separator(): ), } with pytest.raises(InvalidNameError): - create_params_template(internal_regimes) + create_params_template(MappingProxyType(internal_regimes)) def test_regime_name_no_qname_separator(): @@ -202,7 +212,7 @@ def test_regime_name_no_qname_separator(): ), } with pytest.raises(InvalidNameError): - create_params_template(internal_regimes) + create_params_template(MappingProxyType(internal_regimes)) def test_function_name_no_qname_separator(): @@ -213,7 +223,7 @@ def test_function_name_no_qname_separator(): ), } with pytest.raises(InvalidNameError): - create_params_template(internal_regimes) + create_params_template(MappingProxyType(internal_regimes)) def test_regime_function_names_disjoint(): @@ -225,7 +235,7 @@ def test_regime_function_names_disjoint(): ), } with pytest.raises(InvalidNameError): - create_params_template(internal_regimes) + create_params_template(MappingProxyType(internal_regimes)) def test_regime_argument_names_disjoint(): @@ -237,7 +247,7 @@ def test_regime_argument_names_disjoint(): ), } with pytest.raises(InvalidNameError): - create_params_template(internal_regimes) + create_params_template(MappingProxyType(internal_regimes)) def test_missing_parameter_raises_error(params_template): @@ -278,19 +288,21 @@ def test_passing_same_params_to_regimes_with_different_templates(): """ # Template for a non-terminal regime with functions that have parameters alive_template = { - "discount_factor": float, - "utility": {"beta_mean": float, "beta_std": float}, - "cons_util": {"sigma": float, "bb": float, "kappa": float}, - "next_health": {"probs_array": float}, + "H": {"discount_factor": "float"}, + "utility": {"beta_mean": "float", "beta_std": "float"}, + "cons_util": {"sigma": "float", "bb": "float", "kappa": "float"}, + "next_health": {"probs_array": "float"}, } # Template for a terminal regime - empty (no H, no transitions) - dead_template: dict[str, type] = {} + dead_template: dict[str, dict[str, str]] = {} - params_template = { - "alive": alive_template, - "dead": dead_template, - } + params_template = _as_template( + { + "alive": alive_template, + "dead": dead_template, + } + ) # User's shared params dict - has all parameters for the alive regime shared_params = { @@ -320,13 +332,15 @@ def test_shock_params_via_regular_params(): state name (e.g., "adjustment_cost"), so users can pass them via regular params. """ # Template includes ShockGrid params under the state name - params_template = { - "working_life": { - "discount_factor": float, - "utility": {"param": float}, - "adjustment_cost": {"start": float, "stop": float}, - }, - } + params_template = _as_template( + { + "working_life": { + "H": {"discount_factor": "float"}, + "utility": {"param": "float"}, + "adjustment_cost": {"start": "float", "stop": "float"}, + }, + } + ) params = { "working_life": { diff --git a/tests/test_float_dtype_invariants.py b/tests/test_float_dtype_invariants.py index 0f9a3984f..940fe25cf 100644 --- a/tests/test_float_dtype_invariants.py +++ b/tests/test_float_dtype_invariants.py @@ -1,7 +1,7 @@ """Float dtypes follow `canonical_float_dtype()` across pylcm boundaries.""" from collections.abc import Callable -from types import MappingProxyType +from typing import cast import jax.numpy as jnp import numpy as np @@ -13,12 +13,20 @@ from lcm.params.processing import process_params from lcm.params.sequence_leaf import SequenceLeaf from lcm.simulation.initial_conditions import build_initial_states +from lcm.typing import ParamsTemplate +from lcm.utils.containers import ensure_containers_are_immutable from tests.test_models.deterministic.regression import ( RegimeId, get_model, get_params, ) + +def _as_template(plain: dict) -> ParamsTemplate: + """Deep-freeze a plain nested dict into a `ParamsTemplate` for tests.""" + return cast("ParamsTemplate", ensure_containers_are_immutable(plain)) + + # These tests deliberately pass `float64` inputs to verify the cast at # the barrier. Re-allow the JAX truncation warning that the # project-wide filter (see `pyproject.toml`) promotes to an error — @@ -94,9 +102,9 @@ def test_process_params_casts_float64_array_to_canonical_under_no_x64( silently truncates to `float32` under no-x64 at construction time, so a JAX-built input would never reach the helper as `float64`. """ - template = MappingProxyType({"regime_a": MappingProxyType({"schedule": "Array"})}) + template = _as_template({"regime_a": {"fun": {"schedule": "Array"}}}) user_params = { - "regime_a": {"schedule": np.asarray([0.1, 0.2, 0.3], dtype=np.float64)} + "regime_a": {"fun": {"schedule": np.asarray([0.1, 0.2, 0.3], dtype=np.float64)}} } out = process_params( @@ -104,23 +112,21 @@ def test_process_params_casts_float64_array_to_canonical_under_no_x64( params_template=template, ) - schedule = out["regime_a"]["schedule"] + schedule = out["regime_a"]["fun__schedule"] assert schedule.dtype == jnp.float32 def test_process_params_casts_python_float_to_canonical(x64_disabled: None): """A Python `float` param leaf is cast to `canonical_float_dtype()`.""" - template = MappingProxyType( - {"regime_a": MappingProxyType({"discount_factor": "float"})} - ) - user_params = {"regime_a": {"discount_factor": 0.95}} + template = _as_template({"regime_a": {"fun": {"discount_factor": "float"}}}) + user_params = {"regime_a": {"fun": {"discount_factor": 0.95}}} out = process_params( params=user_params, params_template=template, ) - discount_factor = out["regime_a"]["discount_factor"] + discount_factor = out["regime_a"]["fun__discount_factor"] np.testing.assert_allclose(float(discount_factor), 0.95, rtol=1e-6) assert discount_factor.dtype == canonical_float_dtype() @@ -129,8 +135,10 @@ def test_process_params_float_array_overflow_raises_with_qualified_name( x64_disabled: None, ): """An out-of-float32 float64 array raises naming the qualified leaf.""" - template = MappingProxyType({"regime_a": MappingProxyType({"schedule": "Array"})}) - user_params = {"regime_a": {"schedule": np.asarray([0.0, 1e40], dtype=np.float64)}} + template = _as_template({"regime_a": {"fun": {"schedule": "Array"}}}) + user_params = { + "regime_a": {"fun": {"schedule": np.asarray([0.0, 1e40], dtype=np.float64)}} + } with pytest.raises(OverflowError, match="schedule"): process_params( @@ -234,17 +242,17 @@ def test_process_params_casts_float_array_inside_mapping_leaf_to_canonical( key: str, x64_disabled: None ): """`MappingLeaf` float arrays land at `canonical_float_dtype()`.""" - template = MappingProxyType( - {"regime_a": MappingProxyType({"sched": "MappingLeaf"})} - ) + template = _as_template({"regime_a": {"fun": {"sched": "MappingLeaf"}}}) user_params = { "regime_a": { - "sched": MappingLeaf( - { - "low": np.asarray([0.1, 0.2], dtype=np.float64), - "high": np.asarray([0.5, 0.7], dtype=np.float64), - } - ) + "fun": { + "sched": MappingLeaf( + { + "low": np.asarray([0.1, 0.2], dtype=np.float64), + "high": np.asarray([0.5, 0.7], dtype=np.float64), + } + ) + } } } @@ -254,7 +262,7 @@ def test_process_params_casts_float_array_inside_mapping_leaf_to_canonical( ) assert ( - out["regime_a"]["sched"].data[key].dtype # ty: ignore[unresolved-attribute] + out["regime_a"]["fun__sched"].data[key].dtype # ty: ignore[unresolved-attribute] == jnp.float32 ) @@ -264,17 +272,17 @@ def test_process_params_casts_float_array_inside_sequence_leaf_to_canonical( index: int, x64_disabled: None ): """`SequenceLeaf` float arrays land at `canonical_float_dtype()`.""" - template = MappingProxyType( - {"regime_a": MappingProxyType({"sched": "SequenceLeaf"})} - ) + template = _as_template({"regime_a": {"fun": {"sched": "SequenceLeaf"}}}) user_params = { "regime_a": { - "sched": SequenceLeaf( - [ - np.asarray([0.1, 0.2], dtype=np.float64), - np.asarray([0.5, 0.7], dtype=np.float64), - ] - ) + "fun": { + "sched": SequenceLeaf( + [ + np.asarray([0.1, 0.2], dtype=np.float64), + np.asarray([0.5, 0.7], dtype=np.float64), + ] + ) + } } } @@ -284,6 +292,6 @@ def test_process_params_casts_float_array_inside_sequence_leaf_to_canonical( ) assert ( - out["regime_a"]["sched"].data[index].dtype # ty: ignore[unresolved-attribute] + out["regime_a"]["fun__sched"].data[index].dtype # ty: ignore[unresolved-attribute] == jnp.float32 ) diff --git a/tests/test_int_dtype_invariants.py b/tests/test_int_dtype_invariants.py index f0a23de21..d1cbc61bb 100644 --- a/tests/test_int_dtype_invariants.py +++ b/tests/test_int_dtype_invariants.py @@ -1,6 +1,7 @@ """Integer dtypes are pinned to int32 across pylcm regardless of x64 mode.""" from types import MappingProxyType +from typing import cast import jax.numpy as jnp import numpy as np @@ -17,6 +18,8 @@ build_initial_states, ) from lcm.simulation.transitions import _advance_states_for_subjects +from lcm.typing import ParamsTemplate +from lcm.utils.containers import ensure_containers_are_immutable from tests.test_models.deterministic.regression import ( RegimeId, dead, @@ -25,6 +28,12 @@ working_life, ) + +def _as_template(plain: dict) -> ParamsTemplate: + """Deep-freeze a plain nested dict into a `ParamsTemplate` for tests.""" + return cast("ParamsTemplate", ensure_containers_are_immutable(plain)) + + # These tests deliberately pass `int64` inputs to verify the cast at # the barrier. Re-allow the JAX truncation warning that the # project-wide filter (see `pyproject.toml`) promotes to an error — @@ -105,39 +114,43 @@ def test_advance_states_for_subjects_keeps_same_dtype_round_trip() -> None: def test_process_params_casts_python_int_to_int32() -> None: """A Python `int` param leaf is cast to `jnp.int32`.""" - template = MappingProxyType({"regime_a": MappingProxyType({"final_age": "int"})}) - user_params = {"regime_a": {"final_age": 65}} + template = _as_template({"regime_a": {"fun": {"final_age": "int"}}}) + user_params = {"regime_a": {"fun": {"final_age": 65}}} out = process_params( params=user_params, params_template=template, ) - final_age = out["regime_a"]["final_age"] + final_age = out["regime_a"]["fun__final_age"] assert int(final_age) == 65 assert final_age.dtype == jnp.int32 def test_process_params_casts_int64_array_to_int32() -> None: """A `jnp.int64` array param leaf is normalised to `jnp.int32`.""" - template = MappingProxyType({"regime_a": MappingProxyType({"schedule": "Array"})}) - user_params = {"regime_a": {"schedule": jnp.asarray([0, 1, 2], dtype=jnp.int64)}} + template = _as_template({"regime_a": {"fun": {"schedule": "Array"}}}) + user_params = { + "regime_a": {"fun": {"schedule": jnp.asarray([0, 1, 2], dtype=jnp.int64)}} + } out = process_params( params=user_params, params_template=template, ) - schedule = out["regime_a"]["schedule"] + schedule = out["regime_a"]["fun__schedule"] assert schedule.dtype == jnp.int32 def test_process_params_int_array_overflow_raises_with_qualified_name() -> None: """An out-of-int32-range int array surfaces the param's qualified name.""" - template = MappingProxyType({"regime_a": MappingProxyType({"big_param": "Array"})}) + template = _as_template({"regime_a": {"fun": {"big_param": "Array"}}}) # Numpy here: under `jax_enable_x64=False`, `jnp.asarray(..., dtype=int64)` # of an out-of-int32 value raises before our helper sees it. - user_params = {"regime_a": {"big_param": np.asarray([0, 2**40], dtype=np.int64)}} + user_params = { + "regime_a": {"fun": {"big_param": np.asarray([0, 2**40], dtype=np.int64)}} + } with pytest.raises(ValueError, match="big_param"): process_params( @@ -149,17 +162,17 @@ def test_process_params_int_array_overflow_raises_with_qualified_name() -> None: @pytest.mark.parametrize("key", ["low", "high"]) def test_process_params_casts_int_array_inside_mapping_leaf_to_int32(key: str) -> None: """`MappingLeaf` int arrays land at `jnp.int32` after params processing.""" - template = MappingProxyType( - {"regime_a": MappingProxyType({"sched": "MappingLeaf"})} - ) + template = _as_template({"regime_a": {"fun": {"sched": "MappingLeaf"}}}) user_params = { "regime_a": { - "sched": MappingLeaf( - { - "low": jnp.asarray([0, 1], dtype=jnp.int64), - "high": jnp.asarray([10, 20], dtype=jnp.int64), - } - ) + "fun": { + "sched": MappingLeaf( + { + "low": jnp.asarray([0, 1], dtype=jnp.int64), + "high": jnp.asarray([10, 20], dtype=jnp.int64), + } + ) + } } } @@ -169,7 +182,7 @@ def test_process_params_casts_int_array_inside_mapping_leaf_to_int32(key: str) - ) assert ( - out["regime_a"]["sched"].data[key].dtype # ty: ignore[unresolved-attribute] + out["regime_a"]["fun__sched"].data[key].dtype # ty: ignore[unresolved-attribute] == jnp.int32 ) @@ -179,17 +192,17 @@ def test_process_params_casts_int_array_inside_sequence_leaf_to_int32( index: int, ) -> None: """`SequenceLeaf` int arrays land at `jnp.int32` after params processing.""" - template = MappingProxyType( - {"regime_a": MappingProxyType({"sched": "SequenceLeaf"})} - ) + template = _as_template({"regime_a": {"fun": {"sched": "SequenceLeaf"}}}) user_params = { "regime_a": { - "sched": SequenceLeaf( - [ - jnp.asarray([0, 1], dtype=jnp.int64), - jnp.asarray([10, 20], dtype=jnp.int64), - ] - ) + "fun": { + "sched": SequenceLeaf( + [ + jnp.asarray([0, 1], dtype=jnp.int64), + jnp.asarray([10, 20], dtype=jnp.int64), + ] + ) + } } } @@ -199,7 +212,7 @@ def test_process_params_casts_int_array_inside_sequence_leaf_to_int32( ) assert ( - out["regime_a"]["sched"].data[index].dtype # ty: ignore[unresolved-attribute] + out["regime_a"]["fun__sched"].data[index].dtype # ty: ignore[unresolved-attribute] == jnp.int32 ) From 43c0cd04c09549e087f1057bae2801a1d4f84991 Mon Sep 17 00:00:00 2001 From: Hans-Martin von Gaudecker Date: Thu, 14 May 2026 10:25:24 +0200 Subject: [PATCH 35/77] Tighten create_params_template input to MappingProxyType Its sole producer, process_regimes, returns MappingProxyType; no caller passes a plain dict, so Mapping only loses precision. Co-Authored-By: Claude Opus 4.7 --- src/lcm/params/processing.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/lcm/params/processing.py b/src/lcm/params/processing.py index 43c83e4d8..881191905 100644 --- a/src/lcm/params/processing.py +++ b/src/lcm/params/processing.py @@ -298,7 +298,7 @@ def _find_candidates( def create_params_template( # noqa: C901 - internal_regimes: Mapping[RegimeName, InternalRegime], + internal_regimes: MappingProxyType[RegimeName, InternalRegime], ) -> ParamsTemplate: """Create params_template from internal regimes and validate name uniqueness. @@ -306,7 +306,8 @@ def create_params_template( # noqa: C901 are disjoint sets to enable unambiguous parameter propagation. Args: - internal_regimes: Mapping of regime names to InternalRegime instances. + internal_regimes: Immutable mapping of regime names to InternalRegime + instances. Returns: The parameter template. From 8d672dcb1c007f5c03992eb03bab3c364aca26eb Mon Sep 17 00:00:00 2001 From: Hans-Martin von Gaudecker Date: Thu, 14 May 2026 10:46:13 +0200 Subject: [PATCH 36/77] Eliminate remaining bare Array annotations across pylcm MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace every bare `Array` annotation with the narrowest jaxtyping alias or union (`FloatND`, `IntND`, `BoolND`, `KeyArray`). Add a `UserInitialConditions` alias whose int slot is dtype-generic `Int[Array, "..."]` for the `Model.simulate` boundary, where users pass int64 arrays that `build_initial_states` downcasts — parallel to `_ParamsLeaf`. Remaining `Array` references are runtime `isinstance` checks and jaxtyping bracket parameters. Co-Authored-By: Claude Opus 4.7 --- src/lcm/interfaces.py | 17 +++--- src/lcm/model.py | 4 +- src/lcm/pandas_utils.py | 6 +-- src/lcm/persistence.py | 15 ++++-- src/lcm/regime_building/Q_and_F.py | 8 +-- src/lcm/regime_building/V.py | 30 ++++++----- src/lcm/regime_building/argmax.py | 11 ++-- src/lcm/regime_building/diagnostics.py | 7 ++- src/lcm/regime_building/max_Q_over_a.py | 5 +- src/lcm/regime_building/ndimage.py | 20 +++---- src/lcm/regime_building/next_state.py | 20 +++---- src/lcm/regime_building/processing.py | 17 +++--- src/lcm/simulation/compile.py | 9 ++-- src/lcm/simulation/initial_conditions.py | 66 +++++++++++++----------- src/lcm/simulation/result.py | 19 ++++--- src/lcm/simulation/simulate.py | 9 ++-- src/lcm/simulation/transitions.py | 9 ++-- src/lcm/solution/solve_brute.py | 2 +- src/lcm/state_action_space.py | 11 ++-- src/lcm/typing.py | 30 ++++++----- src/lcm/utils/dispatchers.py | 21 ++++---- src/lcm/utils/error_handling.py | 12 ++--- 22 files changed, 195 insertions(+), 153 deletions(-) diff --git a/src/lcm/interfaces.py b/src/lcm/interfaces.py index bf96f1fb3..ff7559afa 100644 --- a/src/lcm/interfaces.py +++ b/src/lcm/interfaces.py @@ -5,7 +5,6 @@ from typing import cast import jax -from jax import Array from lcm.exceptions import PyLCMError from lcm.grids import Grid, IrregSpacedGrid @@ -21,7 +20,9 @@ DiscreteState, FlatRegimeParams, Float1D, + FloatND, FunctionsMapping, + IntND, MaxQOverAFunction, NextStateSimulationFunction, RegimeParamsTemplate, @@ -421,9 +422,9 @@ def _build_regime_sharding( def _distribute_states_to_devices( *, - states: MappingProxyType[StateName, Array], + states: MappingProxyType[StateName, FloatND | IntND], grids: MappingProxyType[StateOrActionName, Grid], -) -> MappingProxyType[StateName, Array]: +) -> MappingProxyType[StateName, FloatND | IntND]: """Place each distributed state's array on its device mesh. States whose grid carries `distributed=True` are placed via @@ -458,20 +459,18 @@ class PeriodRegimeSimulationData: V_arr: Float1D """Value function array for all subjects at this period.""" - actions: MappingProxyType[ActionName, Array] + actions: MappingProxyType[ActionName, FloatND | IntND] """Immutable mapping of action names to optimal action arrays for all subjects. Action arrays carry the dtype of their grid — discrete actions are int, - continuous actions are float — so the value type stays the dtype-agnostic - `Array`. + continuous actions are float — hence the `FloatND | IntND` value type. """ - states: MappingProxyType[StateName, Array] + states: MappingProxyType[StateName, FloatND | IntND] """Immutable mapping of state names to state value arrays for all subjects. State arrays carry the dtype of their grid — discrete states are int, - continuous states are float — so the value type stays the dtype-agnostic - `Array`. + continuous states are float — hence the `FloatND | IntND` value type. """ in_regime: Bool1D diff --git a/src/lcm/model.py b/src/lcm/model.py index efcb9ce75..dcf52064b 100644 --- a/src/lcm/model.py +++ b/src/lcm/model.py @@ -9,7 +9,6 @@ import pandas as pd from beartype import beartype -from jax import Array from lcm._beartype_conf import MODEL_CONF, PARAMS_CONF from lcm.ages import AgeGrid @@ -48,6 +47,7 @@ RegimeName, RegimeNamesToIds, UserFacingParamsTemplate, + UserInitialConditions, UserParams, ) from lcm.utils.containers import ( @@ -377,7 +377,7 @@ def simulate( self, *, params: UserParams, - initial_conditions: Mapping[str, Array], + initial_conditions: UserInitialConditions, period_to_regime_to_V_arr: MappingProxyType[ int, MappingProxyType[RegimeName, FloatND] ] diff --git a/src/lcm/pandas_utils.py b/src/lcm/pandas_utils.py index f3797d85c..ffe29fded 100644 --- a/src/lcm/pandas_utils.py +++ b/src/lcm/pandas_utils.py @@ -9,7 +9,6 @@ import numpy as np import pandas as pd from dags.tree import qname_from_tree_path, tree_path_from_qname -from jax import Array from lcm.ages import PSEUDO_STATE_NAMES, AgeGrid from lcm.dtypes import canonical_float_dtype @@ -23,6 +22,7 @@ FloatND, FunctionName, InternalParams, + IntND, RegimeName, RegimeNamesToIds, StateName, @@ -53,7 +53,7 @@ def initial_conditions_from_dataframe( # noqa: C901 df: pd.DataFrame, regimes: Mapping[RegimeName, Regime], regime_names_to_ids: RegimeNamesToIds, -) -> dict[str, Array]: +) -> dict[str, FloatND | IntND]: """Convert a DataFrame of initial conditions to LCM initial conditions format. Args: @@ -148,7 +148,7 @@ def initial_conditions_from_dataframe( # noqa: C901 nan_mask = np.isnan(result_arrays[col]) result_arrays[col][nan_mask] = MISSING_CAT_CODE - initial_conditions: dict[str, Array] = { + initial_conditions: dict[str, FloatND | IntND] = { col: jnp.array(arr, dtype=jnp.int32) if col in discrete_state_names else jnp.array(arr, dtype=canonical_float_dtype()) diff --git a/src/lcm/persistence.py b/src/lcm/persistence.py index 99d32fc82..3981f3141 100644 --- a/src/lcm/persistence.py +++ b/src/lcm/persistence.py @@ -9,7 +9,7 @@ import shutil import tempfile import textwrap -from collections.abc import Mapping, Sequence +from collections.abc import Sequence from dataclasses import dataclass from pathlib import Path from types import MappingProxyType @@ -19,9 +19,14 @@ import h5py import jax.numpy as jnp import numpy as np -from jax import Array -from lcm.typing import FloatND, PeriodToRegimeToVArr, RegimeName, UserParams +from lcm.typing import ( + FloatND, + PeriodToRegimeToVArr, + RegimeName, + UserInitialConditions, + UserParams, +) if TYPE_CHECKING: from lcm.model import Model @@ -69,7 +74,7 @@ class SimulateSnapshot: params: UserParams | None """User parameters passed to simulate.""" - initial_conditions: Mapping[str, Array] | None + initial_conditions: UserInitialConditions | None """Mapping of state names and "regime" to arrays.""" period_to_regime_to_V_arr: PeriodToRegimeToVArr | None @@ -201,7 +206,7 @@ def save_simulate_snapshot( *, model: Model, params: UserParams, - initial_conditions: Mapping[str, Array], + initial_conditions: UserInitialConditions, period_to_regime_to_V_arr: PeriodToRegimeToVArr, result: SimulationResult, log_path: Path, diff --git a/src/lcm/regime_building/Q_and_F.py b/src/lcm/regime_building/Q_and_F.py index 5defdb9a9..8b3d3a076 100644 --- a/src/lcm/regime_building/Q_and_F.py +++ b/src/lcm/regime_building/Q_and_F.py @@ -4,7 +4,6 @@ import jax.numpy as jnp from dags import concatenate_functions, with_signature -from jax import Array from lcm.regime_building.h_dag import _get_build_H_kwargs from lcm.regime_building.next_state import ( @@ -18,6 +17,7 @@ FloatND, FunctionsMapping, InternalUserFunction, + IntND, QAndFFunction, RegimeName, RegimeTransitionFunction, @@ -136,7 +136,7 @@ def get_Q_and_F( ) def Q_and_F( next_regime_to_V_arr: FloatND, - **states_actions_params: Array, + **states_actions_params: FloatND | IntND | BoolND, ) -> tuple[FloatND, BoolND]: """Calculate the state-action value and feasibility for a non-terminal period. @@ -314,7 +314,7 @@ def get_compute_intermediates( ) def compute_intermediates( next_regime_to_V_arr: FloatND, - **states_actions_params: Array, + **states_actions_params: FloatND | IntND | BoolND, ) -> tuple[ FloatND, FloatND, FloatND, FloatND, MappingProxyType[RegimeName, FloatND] ]: @@ -395,7 +395,7 @@ def get_Q_and_F_terminal( ) def Q_and_F( next_regime_to_V_arr: FloatND, # noqa: ARG001 - **states_actions_params: Array, + **states_actions_params: FloatND | IntND | BoolND, ) -> tuple[FloatND, BoolND]: """Calculate the state-action values and feasibilities for a terminal period. diff --git a/src/lcm/regime_building/V.py b/src/lcm/regime_building/V.py index 0a04fe6e7..e49ae9888 100644 --- a/src/lcm/regime_building/V.py +++ b/src/lcm/regime_building/V.py @@ -5,14 +5,13 @@ import jax.numpy as jnp from dags import concatenate_functions, with_signature from dags.tree import qname_from_tree_path -from jax import Array from lcm.grids import ContinuousGrid, DiscreteGrid, IrregSpacedGrid from lcm.grids.coordinates import get_irreg_coordinate from lcm.regime import Regime from lcm.regime_building.ndimage import map_coordinates from lcm.shocks import _ShockGrid -from lcm.typing import FloatND, ScalarFloat, StateName +from lcm.typing import FloatND, IntND, ScalarFloat, StateName from lcm.utils.functools import all_as_kwargs from lcm.variables import Variables, get_grids @@ -173,8 +172,11 @@ def _get_lookup_function( """ arg_names = [*axis_names, array_name] - @with_signature(args=dict.fromkeys(arg_names, "Array"), return_annotation="Array") - def lookup_wrapper(*args: Array, **kwargs: Array) -> FloatND: + @with_signature( + args=dict.fromkeys(arg_names, "FloatND | IntND"), + return_annotation="FloatND", + ) + def lookup_wrapper(*args: FloatND | IntND, **kwargs: FloatND | IntND) -> FloatND: kwargs = all_as_kwargs(args=args, kwargs=kwargs, arg_names=arg_names) positions = tuple(kwargs[var] for var in axis_names) return kwargs[array_name][positions] @@ -210,9 +212,9 @@ def _get_coordinate_finder( arg_names = [in_name, points_param] @with_signature( - args=dict.fromkeys(arg_names, "Array"), return_annotation="Array" + args=dict.fromkeys(arg_names, "FloatND"), return_annotation="FloatND" ) - def find_irreg_coordinate(*args: Array, **kwargs: Array) -> FloatND: + def find_irreg_coordinate(*args: FloatND, **kwargs: FloatND) -> FloatND: kwargs = all_as_kwargs(args=args, kwargs=kwargs, arg_names=arg_names) return get_irreg_coordinate( value=kwargs[in_name], points=kwargs[points_param] @@ -224,17 +226,19 @@ def find_irreg_coordinate(*args: Array, **kwargs: Array) -> FloatND: points_jax = grid.to_jax() @with_signature( - args=dict.fromkeys([in_name], "Array"), return_annotation="Array" + args=dict.fromkeys([in_name], "FloatND"), return_annotation="FloatND" ) - def find_irreg_coordinate(*args: Array, **kwargs: Array) -> FloatND: + def find_irreg_coordinate(*args: FloatND, **kwargs: FloatND) -> FloatND: kwargs = all_as_kwargs(args=args, kwargs=kwargs, arg_names=[in_name]) return get_irreg_coordinate(value=kwargs[in_name], points=points_jax) return find_irreg_coordinate # All other grid types (LinSpaced, LogSpaced, Piecewise*, ShockGrid) - @with_signature(args=dict.fromkeys([in_name], "Array"), return_annotation="Array") - def find_coordinate(*args: Array, **kwargs: Array) -> FloatND: + @with_signature( + args=dict.fromkeys([in_name], "FloatND"), return_annotation="FloatND" + ) + def find_coordinate(*args: FloatND, **kwargs: FloatND) -> FloatND: kwargs = all_as_kwargs(args=args, kwargs=kwargs, arg_names=[in_name]) return grid.get_coordinate(kwargs[in_name]) @@ -260,8 +264,10 @@ def _get_interpolator( """ arg_names = [name_of_values_on_grid, *axis_names] - @with_signature(args=dict.fromkeys(arg_names, "Array"), return_annotation="Array") - def interpolate(*args: Array, **kwargs: Array) -> FloatND: + @with_signature( + args=dict.fromkeys(arg_names, "FloatND"), return_annotation="FloatND" + ) + def interpolate(*args: FloatND, **kwargs: FloatND) -> FloatND: kwargs = all_as_kwargs(args=args, kwargs=kwargs, arg_names=arg_names) coordinates = jnp.array([kwargs[var] for var in axis_names]) return map_coordinates( diff --git a/src/lcm/regime_building/argmax.py b/src/lcm/regime_building/argmax.py index 721acb644..9d420e868 100644 --- a/src/lcm/regime_building/argmax.py +++ b/src/lcm/regime_building/argmax.py @@ -1,15 +1,14 @@ import jax.numpy as jnp -from jax import Array -from lcm.typing import BoolND, IntND +from lcm.typing import BoolND, FloatND, IntND def argmax_and_max( - a: Array, + a: FloatND, axis: int | tuple[int, ...] | None = None, initial: float | None = None, where: BoolND | None = None, -) -> tuple[IntND, Array]: +) -> tuple[IntND, FloatND]: """Compute the argmax of an n-dim array along axis. If multiple maxima exist, the first index will be selected. @@ -70,7 +69,7 @@ def argmax_and_max( return _argmax, _max.reshape(_argmax.shape) -def _move_axes_to_back(a: Array, axes: tuple[int, ...]) -> Array: +def _move_axes_to_back(a: FloatND | BoolND, axes: tuple[int, ...]) -> FloatND | BoolND: """Move specified axes to the back of the array. Args: @@ -85,7 +84,7 @@ def _move_axes_to_back(a: Array, axes: tuple[int, ...]) -> Array: return a.transpose((*front_axes, *axes)) -def _flatten_last_n_axes(a: Array, n: int) -> Array: +def _flatten_last_n_axes(a: FloatND | BoolND, n: int) -> FloatND | BoolND: """Flatten the last n axes of a to 1 dimension. Args: diff --git a/src/lcm/regime_building/diagnostics.py b/src/lcm/regime_building/diagnostics.py index b182d929f..9d57868dc 100644 --- a/src/lcm/regime_building/diagnostics.py +++ b/src/lcm/regime_building/diagnostics.py @@ -15,7 +15,6 @@ import jax import jax.numpy as jnp -from jax import Array from lcm.ages import AgeGrid from lcm.grids import Grid @@ -24,8 +23,10 @@ from lcm.regime_building.V import VInterpolationInfo from lcm.typing import ( ActionName, + BoolND, FloatND, FunctionsMapping, + IntND, RegimeName, RegimeTransitionFunction, StateName, @@ -165,7 +166,9 @@ def _wrap_with_reduction( """ - def reduced(**kwargs: Array) -> dict[str, Any]: + def reduced( + **kwargs: MappingProxyType[RegimeName, FloatND] | FloatND | IntND | BoolND, + ) -> dict[str, Any]: U_arr, F_arr, E_next_V, Q_arr, regime_probs = func(**kwargs) F_float = F_arr.astype(float) # NaN-count arrays are masked by feasibility: only feasible cells diff --git a/src/lcm/regime_building/max_Q_over_a.py b/src/lcm/regime_building/max_Q_over_a.py index f1e5d3913..4c9b3e979 100644 --- a/src/lcm/regime_building/max_Q_over_a.py +++ b/src/lcm/regime_building/max_Q_over_a.py @@ -4,7 +4,6 @@ import jax.numpy as jnp from dags import with_signature -from jax import Array from lcm.regime_building.argmax import argmax_and_max from lcm.typing import ( @@ -80,7 +79,7 @@ def get_max_Q_over_a( ) def max_Q_over_a( next_regime_to_V_arr: MappingProxyType[RegimeName, FloatND], - **states_actions_params: Array, + **states_actions_params: FloatND | IntND | BoolND, ) -> FloatND: Q_arr, F_arr = Q_and_F( next_regime_to_V_arr=next_regime_to_V_arr, @@ -153,7 +152,7 @@ def get_argmax_and_max_Q_over_a( ) def argmax_and_max_Q_over_a( next_regime_to_V_arr: MappingProxyType[RegimeName, FloatND], - **states_actions_params: Array, + **states_actions_params: FloatND | IntND | BoolND, ) -> tuple[IntND, FloatND]: Q_arr, F_arr = Q_and_F( next_regime_to_V_arr=next_regime_to_V_arr, diff --git a/src/lcm/regime_building/ndimage.py b/src/lcm/regime_building/ndimage.py index 8d7d80d97..7a27df1bd 100644 --- a/src/lcm/regime_building/ndimage.py +++ b/src/lcm/regime_building/ndimage.py @@ -19,14 +19,16 @@ from collections.abc import Sequence import jax.numpy as jnp -from jax import Array, jit, lax +from jax import jit, lax + +from lcm.typing import FloatND, IntND @jit def map_coordinates( - input: Array, # noqa: A002 - coordinates: Sequence[Array], -) -> Array: + input: FloatND | IntND, # noqa: A002 + coordinates: Sequence[FloatND], +) -> FloatND | IntND: """Map the input array to new coordinates using linear interpolation. Modified from JAX implementation of `scipy.ndimage.map_coordinates`. @@ -71,8 +73,8 @@ def map_coordinates( def _compute_indices_and_weights( - coordinate: Array, input_size: int -) -> list[tuple[Array, Array]]: + coordinate: FloatND, input_size: int +) -> list[tuple[IntND, FloatND]]: """Compute indices and weights for linear interpolation.""" lower_index = jnp.clip(jnp.floor(coordinate), 0, input_size - 2).astype(jnp.int32) upper_weight = coordinate - lower_index @@ -80,15 +82,15 @@ def _compute_indices_and_weights( return [(lower_index, lower_weight), (lower_index + 1, upper_weight)] -def _multiply_all(arrs: Sequence[Array]) -> Array: +def _multiply_all(arrs: Sequence[FloatND]) -> FloatND: """Multiply all arrays in the sequence.""" return functools.reduce(operator.mul, arrs) -def _sum_all(arrs: Sequence[Array]) -> Array: +def _sum_all(arrs: Sequence[FloatND]) -> FloatND: """Sum all arrays in the sequence.""" return functools.reduce(operator.add, arrs) -def _round_half_away_from_zero(a: Array) -> Array: +def _round_half_away_from_zero(a: FloatND | IntND) -> FloatND | IntND: return a if jnp.issubdtype(a.dtype, jnp.integer) else lax.round(a) diff --git a/src/lcm/regime_building/next_state.py b/src/lcm/regime_building/next_state.py index 5b260ec96..c9f11552e 100644 --- a/src/lcm/regime_building/next_state.py +++ b/src/lcm/regime_building/next_state.py @@ -6,7 +6,6 @@ import jax from dags import concatenate_functions, with_signature from dags.tree import qname_from_tree_path -from jax import Array from lcm.grids import Grid from lcm.shocks import _ShockGrid @@ -18,6 +17,7 @@ DiscreteState, FloatND, FunctionsMapping, + IntND, NextStateSimulationFunction, RegimeName, ShockName, @@ -94,7 +94,7 @@ def get_next_state_function_for_simulation( Returns `{target_regime_name: {next_: array}}`. """ - per_target_funcs: dict[RegimeName, Callable[..., dict[str, Array]]] = {} + per_target_funcs: dict[RegimeName, Callable[..., dict[str, FloatND | IntND]]] = {} for target, target_transitions in transitions.items(): extended = _extend_target_transitions_for_simulation( target=target, @@ -126,7 +126,7 @@ def get_next_stochastic_weights_function( functions: FunctionsMapping, transitions: FunctionsMapping, stochastic_transition_names: frozenset[TransitionFunctionName], -) -> Callable[..., dict[str, Array]]: +) -> Callable[..., dict[str, FloatND | IntND]]: """Get function that computes the weights for the next stochastic states. Args: @@ -156,11 +156,13 @@ def get_next_stochastic_weights_function( def _extend_target_transitions_for_simulation( *, target: RegimeName, - target_transitions: MappingProxyType[TransitionFunctionName, Callable[..., Array]], + target_transitions: MappingProxyType[ + TransitionFunctionName, Callable[..., FloatND | IntND] + ], all_grids: MappingProxyType[RegimeName, MappingProxyType[StateOrActionName, Grid]], variables: Variables, stochastic_transition_names: frozenset[TransitionFunctionName], -) -> dict[TransitionFunctionName, Callable[..., Array]]: +) -> dict[TransitionFunctionName, Callable[..., FloatND | IntND]]: """Replace stochastic transitions for one target with realisation wrappers. Deterministic transitions are passed through unchanged. Stochastic transitions @@ -186,7 +188,7 @@ def _extend_target_transitions_for_simulation( """ shock_names: frozenset[ShockName] = frozenset(variables.shock_names) - extended: dict[TransitionFunctionName, Callable[..., Array]] = dict( + extended: dict[TransitionFunctionName, Callable[..., FloatND | IntND]] = dict( target_transitions ) for next_state_name in target_transitions: @@ -237,7 +239,7 @@ def _create_discrete_stochastic_next_func( qname = qname_from_tree_path((target, next_state_name)) @with_signature( - args={f"weight_{qname}": "FloatND", f"key_{qname}": "dict[str, Array]"}, + args={f"weight_{qname}": "FloatND", f"key_{qname}": "dict[str, KeyArray]"}, return_annotation="DiscreteState", ) def next_stochastic_state(**kwargs: FloatND) -> DiscreteState: @@ -293,7 +295,7 @@ def _create_ar1_next_func( qname_from_tree_path((state_name, p)): p for p in grid.params_to_pass_at_runtime } args: dict[str, str] = { - f"key_{qname}": "dict[str, Array]", + f"key_{qname}": "dict[str, KeyArray]", state_name: "ContinuousState", **dict.fromkeys(runtime_param_names, "float"), } @@ -326,7 +328,7 @@ def _create_iid_next_func( qname_from_tree_path((state_name, p)): p for p in grid.params_to_pass_at_runtime } args: dict[str, str] = { - f"key_{qname}": "dict[str, Array]", + f"key_{qname}": "dict[str, KeyArray]", **dict.fromkeys(runtime_param_names, "float"), } _draw_shock = grid.draw_shock diff --git a/src/lcm/regime_building/processing.py b/src/lcm/regime_building/processing.py index df159a146..b4008fe1a 100644 --- a/src/lcm/regime_building/processing.py +++ b/src/lcm/regime_building/processing.py @@ -9,7 +9,6 @@ from dags import concatenate_functions, get_annotations, with_signature from dags.signature import rename_arguments from dags.tree import QNAME_DELIMITER, qname_from_tree_path, tree_path_from_qname -from jax import Array from jax import numpy as jnp from lcm.ages import AgeGrid @@ -50,6 +49,7 @@ FunctionsMapping, Int1D, InternalUserFunction, + IntND, MaxQOverAFunction, NextStateSimulationFunction, QAndFFunction, @@ -889,7 +889,7 @@ def _get_weights_func_for_shock(*, name: str, grid: _ShockGrid) -> UserFunction: args = {name: "ContinuousState", **dict.fromkeys(runtime_param_names, "float")} @with_signature(args=args, return_annotation="FloatND", enforce=False) - def weights_func_runtime(*a: Array, **kwargs: Array) -> Float1D: # noqa: ARG001 + def weights_func_runtime(*a: FloatND, **kwargs: FloatND) -> Float1D: # noqa: ARG001 shock_kw: dict[str, float] = { # ty: ignore[invalid-assignment] **fixed_params, **{raw: kwargs[qn] for qn, raw in runtime_param_names.items()}, @@ -916,7 +916,7 @@ def weights_func_runtime(*a: Array, **kwargs: Array) -> Float1D: # noqa: ARG001 return_annotation="FloatND", enforce=False, ) - def weights_func(*args: Array, **kwargs: Array) -> Float1D: # noqa: ARG001 + def weights_func(*args: FloatND, **kwargs: FloatND) -> Float1D: # noqa: ARG001 coordinate = get_irreg_coordinate(value=kwargs[f"{name}"], points=gridpoints) return map_coordinates( input=transition_probs, @@ -1259,7 +1259,8 @@ def _wrap_regime_transition_probs( regime_names_to_ids: Immutable mapping of regime names to integer indices. Returns: - A wrapped function that returns MappingProxyType[str, float|Array]. + A wrapped function that returns an immutable mapping of regime + names to probability scalars. """ # Get regime names in index order from regime_names_to_ids. Coerce @@ -1279,8 +1280,8 @@ def _wrap_regime_transition_probs( ) @functools.wraps(func) def wrapped( - *args: Array | int, - **kwargs: Array | int, + *args: FloatND | IntND | int, + **kwargs: FloatND | IntND | int, ) -> MappingProxyType[str, Any]: result = func(*args, **kwargs) # Convert array to dict using ordering by regime id @@ -1318,8 +1319,8 @@ def _wrap_deterministic_regime_transition( @with_signature(args=annotations, return_annotation="FloatND") @functools.wraps(func) def wrapped( - *args: Array | int, - **kwargs: Array | int, + *args: FloatND | IntND | int, + **kwargs: FloatND | IntND | int, ) -> FloatND: regime_idx = func(*args, **kwargs) return jax.nn.one_hot(regime_idx, n_regimes) diff --git a/src/lcm/simulation/compile.py b/src/lcm/simulation/compile.py index 06a8ceff7..60e554a73 100644 --- a/src/lcm/simulation/compile.py +++ b/src/lcm/simulation/compile.py @@ -21,7 +21,6 @@ import jax import jax.numpy as jnp from dags.tree import qname_from_tree_path -from jax import Array from lcm.ages import AgeGrid from lcm.interfaces import InternalRegime @@ -32,7 +31,9 @@ ) from lcm.typing import ( FlatRegimeParams, + FloatND, InternalParams, + IntND, RegimeName, ) from lcm.utils.logging import format_duration @@ -340,7 +341,7 @@ def _build_argmax_args( ages: AgeGrid, period: int, n_subjects: int, - next_regime_to_V_arr: MappingProxyType[RegimeName, Array], + next_regime_to_V_arr: MappingProxyType[RegimeName, FloatND], ) -> dict[str, object]: base = internal_regime.state_action_space(regime_params=regime_params) subject_states = _subject_shape_arrays(base.states, n_subjects=n_subjects) @@ -419,10 +420,10 @@ def _build_crtp_args( def _subject_shape_arrays( - base_arrays: Mapping[str, Array], + base_arrays: Mapping[str, FloatND | IntND], *, n_subjects: int, -) -> dict[str, Array]: +) -> dict[str, FloatND | IntND]: """Return zeros of shape `(n_subjects,)` mirroring each base array's dtype. With `build_initial_states` casting discrete states to the grid dtype, diff --git a/src/lcm/simulation/initial_conditions.py b/src/lcm/simulation/initial_conditions.py index dd5d23595..b660e8546 100644 --- a/src/lcm/simulation/initial_conditions.py +++ b/src/lcm/simulation/initial_conditions.py @@ -12,7 +12,6 @@ import jax import numpy as np import pandas as pd -from jax import Array from jax import numpy as jnp from lcm.ages import PSEUDO_STATE_NAMES, AgeGrid @@ -29,13 +28,16 @@ ActionName, BoolND, FlatRegimeParams, + FloatND, Int1D, InternalParams, + IntND, RegimeIdsToNames, RegimeName, RegimeNamesToIds, StateName, StatesPerRegime, + UserInitialConditions, ) from lcm.utils.containers import invert_regime_ids from lcm.utils.functools import get_union_of_args @@ -48,7 +50,7 @@ def build_initial_states( *, - initial_states: Mapping[StateName, Array], + initial_states: UserInitialConditions, internal_regimes: MappingProxyType[RegimeName, InternalRegime], ) -> StatesPerRegime: """Build the regime-keyed state carrier from user-provided initial states. @@ -68,10 +70,12 @@ def build_initial_states( """ n_subjects = len(next(iter(initial_states.values()))) - states_per_regime: dict[RegimeName, MappingProxyType[StateName, Array]] = {} + states_per_regime: dict[ + RegimeName, MappingProxyType[StateName, FloatND | IntND] + ] = {} for regime_name, internal_regime in internal_regimes.items(): - regime_states: dict[StateName, Array] = {} + regime_states: dict[StateName, FloatND | IntND] = {} # Logic for distribution of subjects over devices distributed = any(grid.distributed for grid in internal_regime.grids.values()) devices = jax.devices() @@ -123,7 +127,7 @@ def build_initial_states( def validate_initial_conditions( *, - initial_conditions: Mapping[str, Array], + initial_conditions: UserInitialConditions, internal_regimes: MappingProxyType[RegimeName, InternalRegime], regime_names_to_ids: RegimeNamesToIds, internal_params: InternalParams, @@ -241,7 +245,7 @@ def _format_missing_states_message(missing: set[str], required: set[str]) -> str def _collect_state_name_errors( *, - initial_states: Mapping[StateName, Array], + initial_states: UserInitialConditions, regime_id_arr: Int1D, regime_ids_to_names: RegimeIdsToNames, internal_regimes: MappingProxyType[RegimeName, InternalRegime], @@ -299,7 +303,7 @@ def _collect_state_name_errors( def _collect_structural_errors( *, - initial_states: Mapping[StateName, Array], + initial_states: UserInitialConditions, regime_id_arr: Int1D, regime_ids_to_names: RegimeIdsToNames, regime_names_to_ids: RegimeNamesToIds, @@ -399,7 +403,7 @@ def _collect_structural_errors( def _collect_feasibility_errors( *, - initial_states: Mapping[StateName, Array], + initial_states: UserInitialConditions, regime_id_arr: Int1D, regime_names_to_ids: RegimeNamesToIds, internal_regimes: MappingProxyType[RegimeName, InternalRegime], @@ -450,7 +454,7 @@ def _collect_feasibility_errors( def _validate_discrete_state_values( *, - initial_states: Mapping[StateName, Array], + initial_states: UserInitialConditions, internal_regimes: MappingProxyType[RegimeName, InternalRegime], regime_id_arr: Int1D, regime_names_to_ids: RegimeNamesToIds, @@ -515,10 +519,10 @@ def _validate_discrete_state_values( def _batched_feasibility_check( *, feasibility_func: Callable[..., BoolND], - subject_states: Mapping[StateName, Array], - action_kwargs: Mapping[str, Array], + subject_states: UserInitialConditions, + action_kwargs: Mapping[str, FloatND | IntND], filtered_params: Mapping[str, object], - flat_actions: Mapping[ActionName, Array], + flat_actions: Mapping[ActionName, FloatND | IntND], ) -> BoolND: """Check feasibility for all subjects, batching to avoid OOM. @@ -542,12 +546,14 @@ def _batched_feasibility_check( if action_kwargs: def _is_combo_feasible( - action_kw: dict[str, Array], - subject_kw: dict[str, Array], + action_kw: dict[str, FloatND | IntND], + subject_kw: dict[str, FloatND | IntND], ) -> BoolND: return feasibility_func(**action_kw, **subject_kw, **filtered_params) - def _is_any_action_feasible(per_subject_kwargs: dict[str, Array]) -> BoolND: + def _is_any_action_feasible( + per_subject_kwargs: dict[str, FloatND | IntND], + ) -> BoolND: per_combo = jax.vmap(_is_combo_feasible, in_axes=(0, None))( action_kwargs, per_subject_kwargs, @@ -556,7 +562,9 @@ def _is_any_action_feasible(per_subject_kwargs: dict[str, Array]) -> BoolND: else: - def _is_any_action_feasible(per_subject_kwargs: dict[str, Array]) -> BoolND: + def _is_any_action_feasible( + per_subject_kwargs: dict[str, FloatND | IntND], + ) -> BoolND: return jnp.any(feasibility_func(**per_subject_kwargs, **filtered_params)) vmapped_check = jax.vmap(_is_any_action_feasible) @@ -583,7 +591,7 @@ def _check_regime_feasibility( # noqa: C901 *, internal_regime: InternalRegime, regime_name: RegimeName, - initial_states: Mapping[StateName, Array], + initial_states: UserInitialConditions, subject_indices: list[int], regime_params: Mapping[str, object], ages: AgeGrid, @@ -619,7 +627,7 @@ def _check_regime_feasibility( # noqa: C901 state_action_space = internal_regime.state_action_space( regime_params=cast("FlatRegimeParams", MappingProxyType(dict(regime_params))), ) - action_grids: dict[str, Array] = { + action_grids: dict[str, FloatND | IntND] = { **state_action_space.discrete_actions, **state_action_space.continuous_actions, } @@ -635,7 +643,7 @@ def _check_regime_feasibility( # noqa: C901 # Build per-subject state arrays idx_arr = jnp.array(subject_indices) - subject_states: dict[StateName, Array] = {} + subject_states: dict[StateName, FloatND | IntND] = {} for sn in state_names: if sn in accepted: subject_states[sn] = initial_states[sn][idx_arr] @@ -648,7 +656,7 @@ def _check_regime_feasibility( # noqa: C901 ) # Split actions and params — actions are vmapped over, params are not - action_kwargs: dict[str, Array] = { + action_kwargs: dict[str, FloatND | IntND] = { k: v for k, v in flat_actions.items() if k in accepted } @@ -674,7 +682,7 @@ def _check_regime_feasibility( # noqa: C901 # No per-subject varying states: feasibility is identical for all subjects. if action_kwargs: - def _check_combo(action_kw: dict[str, Array]) -> BoolND: + def _check_combo(action_kw: dict[str, FloatND | IntND]) -> BoolND: return feasibility_func(**action_kw, **filtered_params) # ty: ignore[invalid-argument-type] result = jax.vmap(_check_combo)(action_kwargs) @@ -707,13 +715,13 @@ def _check_combo(action_kw: dict[str, Array]) -> BoolND: def _admits_any_action( *, feasibility_func: Callable[..., BoolND], - action_kwargs: Mapping[str, Array], + action_kwargs: Mapping[str, FloatND | IntND], params: Mapping[str, object], ) -> bool: """Return True iff the feasibility function admits ≥ 1 action under params.""" if action_kwargs: - def _check_combo(action_kw: dict[str, Array]) -> BoolND: + def _check_combo(action_kw: dict[str, FloatND | IntND]) -> BoolND: return feasibility_func(**action_kw, **params) per_combo = jax.vmap(_check_combo)(action_kwargs) @@ -724,9 +732,9 @@ def _check_combo(action_kw: dict[str, Array]) -> BoolND: def _per_constraint_feasibility( *, internal_regime: InternalRegime, - subject_states: Mapping[StateName, Array], + subject_states: UserInitialConditions, regime_params: Mapping[str, object], - flat_actions: Mapping[ActionName, Array], + flat_actions: Mapping[ActionName, FloatND | IntND], idx_arr: Int1D, infeasible_indices: Sequence[int], ) -> dict[str, np.ndarray]: @@ -791,7 +799,7 @@ def _raise_feasibility_type_error( exc: TypeError, regime_name: RegimeName, internal_regime: InternalRegime, - subject_states: dict[StateName, Array], + subject_states: dict[StateName, FloatND | IntND], ) -> NoReturn: """Re-raise a TypeError from feasibility checking with diagnostic context. @@ -837,7 +845,7 @@ def _format_infeasibility_message( infeasible_indices: Sequence[int], internal_regime: InternalRegime, regime_name: RegimeName, - initial_states: Mapping[StateName, Array], + initial_states: UserInitialConditions, state_names: Sequence[str], per_constraint_admits_any: Mapping[str, np.ndarray], ) -> str: @@ -904,8 +912,8 @@ def _format_infeasibility_message( def _build_flat_action_grid( *, action_names: list[ActionName], - grids: MappingProxyType[str, Array], -) -> dict[str, Array]: + grids: MappingProxyType[str, FloatND | IntND], +) -> dict[str, FloatND | IntND]: """Build a flat array of all action combinations from action grids. Args: diff --git a/src/lcm/simulation/result.py b/src/lcm/simulation/result.py index e5f8c0cb4..d40700b63 100644 --- a/src/lcm/simulation/result.py +++ b/src/lcm/simulation/result.py @@ -12,7 +12,6 @@ import jax.numpy as jnp import pandas as pd from dags import concatenate_functions -from jax import Array from lcm.ages import AgeGrid from lcm.exceptions import InvalidAdditionalTargetsError @@ -23,9 +22,11 @@ from lcm.regime_building.processing import compute_merged_discrete_categories from lcm.typing import ( ActionName, + BoolND, FlatRegimeParams, FloatND, InternalParams, + IntND, RegimeName, RegimeNamesToIds, StateName, @@ -490,7 +491,9 @@ def _process_regime( ] # Concatenate and filter to in-regime subjects - data: dict[str, Array | Sequence[str]] = _concatenate_and_filter(period_dicts) # ty: ignore[invalid-assignment] + data: dict[str, FloatND | IntND | BoolND | Sequence[str]] = _concatenate_and_filter( + period_dicts + ) # ty: ignore[invalid-assignment] # Add age column (computed from period using ages grid) data["age"] = ages.values[data["period"]] # noqa: PD011 @@ -521,9 +524,9 @@ def _extract_period_data( period: int, regime_states: tuple[str, ...], regime_actions: tuple[str, ...], -) -> dict[str, Array]: +) -> dict[str, FloatND | IntND | BoolND]: """Extract data from a single period's simulation results.""" - data: dict[str, Array] = { + data: dict[str, FloatND | IntND | BoolND] = { "subject_id": jnp.arange(len(result.in_regime)), "period": jnp.full_like(result.in_regime, period, dtype=jnp.int32), "_in_regime": result.in_regime, @@ -541,7 +544,9 @@ def _extract_period_data( return data -def _concatenate_and_filter(period_dicts: list[dict[str, Array]]) -> dict[str, Array]: +def _concatenate_and_filter( + period_dicts: list[dict[str, FloatND | IntND | BoolND]], +) -> dict[str, FloatND | IntND | BoolND]: """Concatenate period data and filter to in-regime subjects.""" keys = [k for k in period_dicts[0] if k != "_in_regime"] @@ -745,11 +750,11 @@ def _codes_to_categorical( def _compute_targets( *, - data: dict[str, Array | Sequence[str]], + data: dict[str, FloatND | IntND | BoolND | Sequence[str]], targets: list[str], internal_regime: InternalRegime, regime_params: FlatRegimeParams, -) -> dict[str, Array]: +) -> dict[str, FloatND | IntND | BoolND]: """Compute additional targets for a regime.""" functions_pool = _build_functions_pool(internal_regime) target_func = _create_target_function( diff --git a/src/lcm/simulation/simulate.py b/src/lcm/simulation/simulate.py index 4bb7e174f..50efccc2d 100644 --- a/src/lcm/simulation/simulate.py +++ b/src/lcm/simulation/simulate.py @@ -6,7 +6,7 @@ import jax import jax.numpy as jnp import pandas as pd -from jax import Array, vmap +from jax import vmap from lcm.ages import AgeGrid from lcm.interfaces import ( @@ -36,6 +36,7 @@ ScalarFloat, ScalarInt, StatesPerRegime, + UserInitialConditions, ) from lcm.utils.containers import invert_regime_ids from lcm.utils.error_handling import validate_V @@ -51,7 +52,7 @@ def simulate( *, internal_params: InternalParams, - initial_conditions: Mapping[str, Array], + initial_conditions: UserInitialConditions, internal_regimes: MappingProxyType[RegimeName, InternalRegime], regime_names_to_ids: RegimeNamesToIds, logger: logging.Logger, @@ -337,8 +338,8 @@ def _simulate_regime_in_period( def _lookup_values_from_indices( *, flat_indices: IntND, - grids: MappingProxyType[str, Array], -) -> MappingProxyType[str, Array]: + grids: MappingProxyType[str, FloatND | IntND], +) -> MappingProxyType[str, FloatND | IntND]: """Retrieve values from indices. Args: diff --git a/src/lcm/simulation/transitions.py b/src/lcm/simulation/transitions.py index 21c3b5a63..20f42b886 100644 --- a/src/lcm/simulation/transitions.py +++ b/src/lcm/simulation/transitions.py @@ -10,8 +10,8 @@ import jax from dags.tree import qname_from_tree_path -from jax import Array, vmap from jax import numpy as jnp +from jax import vmap from lcm.interfaces import InternalRegime, StateActionSpace from lcm.simulation.random import generate_simulation_keys @@ -23,6 +23,7 @@ Float1D, FloatND, Int1D, + IntND, KeyArray, RegimeName, RegimeNamesToIds, @@ -72,7 +73,7 @@ def create_regime_state_action_space( def calculate_next_states( *, internal_regime: InternalRegime, - optimal_actions: MappingProxyType[ActionName, Array], + optimal_actions: MappingProxyType[ActionName, FloatND | IntND], period: int, age: ScalarInt | ScalarFloat, regime_params: FlatRegimeParams, @@ -162,7 +163,7 @@ def calculate_next_regime_membership( *, internal_regime: InternalRegime, state_action_space: StateActionSpace, - optimal_actions: MappingProxyType[ActionName, Array], + optimal_actions: MappingProxyType[ActionName, FloatND | IntND], period: int, age: ScalarInt | ScalarFloat, regime_params: FlatRegimeParams, @@ -301,7 +302,7 @@ def _advance_states_for_subjects( Updated carrier with next-period values written in for selected subjects. """ - updated: dict[RegimeName, dict[StateName, Array]] = { + updated: dict[RegimeName, dict[StateName, FloatND | IntND]] = { regime_name: dict(regime_states) for regime_name, regime_states in states_per_regime.items() } diff --git a/src/lcm/solution/solve_brute.py b/src/lcm/solution/solve_brute.py index 3a9a8b9ed..9e531ebb5 100644 --- a/src/lcm/solution/solve_brute.py +++ b/src/lcm/solution/solve_brute.py @@ -436,7 +436,7 @@ def _get_regime_V_shapes_and_shardings( return topology -def _build_zero_V_arr(*, topology: _RegimeVTopology) -> jax.Array: +def _build_zero_V_arr(*, topology: _RegimeVTopology) -> FloatND: """Build the zero V-array template for a regime, sharded where requested.""" zeros = jnp.zeros(topology.shape) if topology.sharding is None: diff --git a/src/lcm/state_action_space.py b/src/lcm/state_action_space.py index 32f59bb1e..ed0fe2283 100644 --- a/src/lcm/state_action_space.py +++ b/src/lcm/state_action_space.py @@ -1,11 +1,10 @@ from types import MappingProxyType import jax.numpy as jnp -from jax import Array from lcm.grids import Grid, IrregSpacedGrid from lcm.interfaces import StateActionSpace -from lcm.typing import StateName, StateOrActionName +from lcm.typing import FloatND, IntND, StateName, StateOrActionName from lcm.variables import Variables @@ -13,7 +12,7 @@ def create_state_action_space( *, variables: Variables, grids: MappingProxyType[StateOrActionName, Grid], - states: dict[StateName, Array] | None = None, + states: dict[StateName, FloatND | IntND] | None = None, ) -> StateActionSpace: """Create a state-action-space. @@ -58,7 +57,7 @@ def create_state_action_space( ) -def _grid_to_jax_or_placeholder(grid: Grid) -> Array: +def _grid_to_jax_or_placeholder(grid: Grid) -> FloatND | IntND: """Return the grid's points, or a NaN placeholder for runtime-supplied grids. `IrregSpacedGrid.to_jax()` raises when its points haven't been supplied — that @@ -74,7 +73,9 @@ def _grid_to_jax_or_placeholder(grid: Grid) -> Array: def _validate_all_states_present( - *, provided_states: dict[StateName, Array], required_state_names: set[StateName] + *, + provided_states: dict[StateName, FloatND | IntND], + required_state_names: set[StateName], ) -> None: """Check that all states are present in the provided states.""" provided_state_names = set(provided_states) diff --git a/src/lcm/typing.py b/src/lcm/typing.py index 45e4ebbe8..8545800e2 100644 --- a/src/lcm/typing.py +++ b/src/lcm/typing.py @@ -51,9 +51,15 @@ RegimeName, MappingProxyType[TransitionFunctionName, InternalUserFunction] ] -type RegimeStates = MappingProxyType[StateName, Array] +type RegimeStates = MappingProxyType[StateName, FloatND | IntND] type StatesPerRegime = MappingProxyType[RegimeName, RegimeStates] +# User-supplied initial conditions / states, checked by beartype at the +# `Model.simulate` boundary. The int slot is dtype-generic `Int[Array, "..."]` +# rather than `IntND` (int32-only): users pass int64 arrays, which +# `build_initial_states` downcasts. Parallels `_ParamsLeaf`. +type UserInitialConditions = Mapping[str, FloatND | Int[Array, "..."]] + # User-supplied param leaf, checked by beartype at the `process_params` # boundary. The int slot is dtype-generic `Int[Array, "..."]` rather than @@ -78,8 +84,8 @@ # Internal regime parameters: A flat mapping with function-qualified names. # Keys are always function-qualified (e.g., "utility__risk_aversion", -# "H__discount_factor"). Values are scalars or arrays. -type FlatRegimeParams = MappingProxyType[str, Array] +# "H__discount_factor"). Values are canonical-dtype scalars or arrays. +type FlatRegimeParams = MappingProxyType[str, FloatND | IntND | BoolND] type InternalParams = MappingProxyType[RegimeName, FlatRegimeParams] # Immutable templates, used internally @@ -115,9 +121,9 @@ class InternalUserFunction(Protocol): def __call__( self, - *args: Array | float, - **kwargs: Array | float, - ) -> Array: ... + *args: FloatND | IntND | BoolND | float, + **kwargs: FloatND | IntND | BoolND | float, + ) -> FloatND | IntND | BoolND: ... @runtime_checkable @@ -132,8 +138,8 @@ class RegimeTransitionFunction(Protocol): def __call__( self, - *args: Array | float, - **kwargs: Array | float, + *args: FloatND | IntND | BoolND | float, + **kwargs: FloatND | IntND | BoolND | float, ) -> Float1D: ... @@ -149,8 +155,8 @@ class VmappedRegimeTransitionFunction(Protocol): def __call__( self, - *args: Array | float, - **kwargs: Array | float, + *args: FloatND | IntND | BoolND | float, + **kwargs: FloatND | IntND | BoolND | float, ) -> FloatND: ... @@ -216,7 +222,7 @@ class StochasticNextFunction(Protocol): """ - def __call__(self, **kwargs: Array) -> Array: ... + def __call__(self, **kwargs: FloatND | IntND) -> FloatND | IntND: ... @runtime_checkable @@ -230,7 +236,7 @@ class NextStateSimulationFunction(Protocol): def __call__( self, - **kwargs: Array | Period | Age, + **kwargs: FloatND | IntND | Period | Age, ) -> MappingProxyType[ RegimeName, MappingProxyType[str, DiscreteState | ContinuousState] ]: ... diff --git a/src/lcm/utils/dispatchers.py b/src/lcm/utils/dispatchers.py index 91a30b1e0..253093395 100644 --- a/src/lcm/utils/dispatchers.py +++ b/src/lcm/utils/dispatchers.py @@ -6,10 +6,10 @@ import jax import jax.numpy as jnp -from jax import Array, vmap +from jax import vmap from lcm.exceptions import FunctionDispatchError -from lcm.typing import ActionName, FloatND, StateName +from lcm.typing import ActionName, BoolND, FloatND, IntND, StateName from lcm.utils.containers import find_duplicates from lcm.utils.functools import allow_args, allow_only_kwargs @@ -17,10 +17,12 @@ "FunctionWithArrayReturn", bound=Callable[ ..., - Array - | tuple[Array, Array] - | MappingProxyType[str, Array] - | MappingProxyType[str, MappingProxyType[str, Array]], + FloatND + | IntND + | BoolND + | tuple[FloatND | IntND | BoolND, FloatND | IntND | BoolND] + | MappingProxyType[str, FloatND | IntND] + | MappingProxyType[str, MappingProxyType[str, FloatND | IntND]], ], ) @@ -246,7 +248,7 @@ def _base_productmap_batched( "is POSITIONAL_ONLY." ) - def batched_vmap(**kwargs: Array) -> Array: + def batched_vmap(**kwargs: FloatND | IntND | BoolND) -> FloatND: non_array_kwargs = { key: val for key, val in kwargs.items() if key not in product_axes } @@ -259,8 +261,9 @@ def map_one_more( loop_func: FunctionWithArrayReturn, axis: str ) -> FunctionWithArrayReturn: def func_mapped_over_one_more_axis( - *already_mapped_args: Array, **already_mapped_kwargs: Array - ) -> Array: + *already_mapped_args: FloatND | IntND | BoolND, + **already_mapped_kwargs: FloatND | IntND | BoolND, + ) -> FloatND | IntND | BoolND: return jax.lax.map( lambda axis_i: loop_func( *already_mapped_args, **{axis: axis_i}, **already_mapped_kwargs diff --git a/src/lcm/utils/error_handling.py b/src/lcm/utils/error_handling.py index 2e2785122..3a37694d3 100644 --- a/src/lcm/utils/error_handling.py +++ b/src/lcm/utils/error_handling.py @@ -9,7 +9,6 @@ import jax import jax.numpy as jnp import pandas as pd -from jax import Array from lcm.ages import AgeGrid from lcm.exceptions import ( @@ -23,6 +22,7 @@ FlatRegimeParams, FloatND, InternalParams, + IntND, RegimeName, ScalarFloat, ScalarInt, @@ -289,7 +289,7 @@ def validate_regime_transition_probs( regime_name: RegimeName, age: float | ScalarInt | ScalarFloat, next_age: float | ScalarInt | ScalarFloat, - state_action_values: MappingProxyType[str, Array] | None = None, + state_action_values: MappingProxyType[str, FloatND | IntND] | None = None, ) -> None: """Validate regime transition probabilities. @@ -353,7 +353,7 @@ def validate_regime_transition_probs( def _format_sum_violation( *, sum_all: FloatND, - state_action_values: MappingProxyType[str, Array] | None = None, + state_action_values: MappingProxyType[str, FloatND | IntND] | None = None, ) -> str: """Format a human-readable description of probability sum violations. @@ -480,7 +480,7 @@ def _validate_regime_transition_single( filtered_params = {k: v for k, v in regime_params.items() if k in accepted_params} # Collect only grid variables the transition function accepts - grids: dict[str, Array] = { + grids: dict[str, FloatND | IntND] = { k: v for k, v in state_action_space.states.items() if k in accepted_params } | {k: v for k, v in state_action_space.actions.items() if k in accepted_params} @@ -493,7 +493,7 @@ def _validate_regime_transition_single( flat_arrays = [m.ravel() for m in mesh] def _call( - *args: Array, + *args: FloatND | IntND, _names: list[str] = grid_var_names, _params: dict = filtered_params, _func: object = regime_transition_func, @@ -517,7 +517,7 @@ def _call( age=ages.values[period], # noqa: PD011 ) ) - point: dict[str, Array] = {} + point: dict[str, FloatND | IntND] = {} validate_regime_transition_probs( regime_transition_probs=regime_transition_probs, From e9deac348ebb23611405501f16bf54929778aece Mon Sep 17 00:00:00 2001 From: Hans-Martin von Gaudecker Date: Thu, 14 May 2026 10:47:17 +0200 Subject: [PATCH 37/77] Make GPU-peak-mem subprocess parsing robust to stdout noise The subprocess imports lcm, whose beartype claw can emit diagnostics to stdout, so `int(result.stdout.strip())` blew up on a polluted stream. Mark the peak-bytes line and locate it instead of parsing stdout whole. Co-Authored-By: Claude Opus 4.7 --- benchmarks/_gpu_mem.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/benchmarks/_gpu_mem.py b/benchmarks/_gpu_mem.py index 45e328216..591b6ba18 100644 --- a/benchmarks/_gpu_mem.py +++ b/benchmarks/_gpu_mem.py @@ -26,6 +26,11 @@ class MahlerYumGpuPeakMem(GpuPeakMem): # Project root: the directory containing the benchmarks/ package. _PROJECT_ROOT = Path(__file__).resolve().parent.parent +# Marks the peak-memory line on the subprocess's stdout. The subprocess imports +# lcm, whose beartype claw can emit diagnostics to stdout, so the parent locates +# this line instead of parsing stdout wholesale. +_PEAK_MARKER = "__PEAK_BYTES_IN_USE__" + def measure_gpu_peak(bench_module: str, bench_class: str) -> int: """Run a benchmark in a subprocess and return peak GPU bytes. @@ -58,7 +63,15 @@ def measure_gpu_peak(bench_module: str, bench_class: str) -> int: f"stderr: {result.stderr!r}" ) raise RuntimeError(msg) - return int(result.stdout.strip()) + for line in result.stdout.splitlines(): + if line.startswith(_PEAK_MARKER): + return int(line.removeprefix(_PEAK_MARKER).strip()) + msg = ( + "GPU memory subprocess produced no peak-bytes line.\n" + f"stdout: {result.stdout!r}\n" + f"stderr: {result.stderr!r}" + ) + raise RuntimeError(msg) def _track_gpu_peak_mem(self): @@ -104,4 +117,4 @@ def setup(self): import jax stats = jax.local_devices()[0].memory_stats() - print(stats["peak_bytes_in_use"]) + print(f"{_PEAK_MARKER} {stats['peak_bytes_in_use']}") From af44b27367b6e8c9aa6ef6686fd01b204c1ba6de Mon Sep 17 00:00:00 2001 From: Hans-Martin von Gaudecker Date: Thu, 14 May 2026 11:34:43 +0200 Subject: [PATCH 38/77] Keep IntND at user boundaries; add RealND for ndimage/argmax primitives Revert the int64-accepting widening of `_ParamsLeaf` and the `UserInitialConditions` alias: pylcm pins integers to int32 everywhere, so the `process_params` / `Model.simulate` boundaries take `IntND` and reject raw int64 fail-fast. Add `RealND` (jaxtyping `Real`, any-width float or int) for the genuinely dtype-polymorphic `ndimage` / `argmax` primitives, which are exercised across dtypes below the canonical-dtype invariant. Pin internal `jnp.arange` index arrays to int32 so pylcm never produces int64 under x64. Fix tests that passed raw int64 initial conditions / params to use the correct dtype. Co-Authored-By: Claude Opus 4.7 --- src/lcm/model.py | 4 ++-- src/lcm/persistence.py | 8 ++++---- src/lcm/regime_building/argmax.py | 10 +++++----- src/lcm/regime_building/ndimage.py | 18 +++++++++--------- src/lcm/regime_building/processing.py | 6 +++--- src/lcm/simulation/initial_conditions.py | 21 ++++++++++----------- src/lcm/simulation/simulate.py | 3 +-- src/lcm/typing.py | 20 ++++++++------------ tests/simulation/test_simulate.py | 2 +- tests/test_int_dtype_invariants.py | 9 +++++++-- tests/test_shock_grids.py | 10 +++++----- tests/test_stochastic.py | 8 ++++---- 12 files changed, 59 insertions(+), 60 deletions(-) diff --git a/src/lcm/model.py b/src/lcm/model.py index dcf52064b..dd20184cb 100644 --- a/src/lcm/model.py +++ b/src/lcm/model.py @@ -43,11 +43,11 @@ FloatND, FunctionName, InternalParams, + IntND, ParamsTemplate, RegimeName, RegimeNamesToIds, UserFacingParamsTemplate, - UserInitialConditions, UserParams, ) from lcm.utils.containers import ( @@ -377,7 +377,7 @@ def simulate( self, *, params: UserParams, - initial_conditions: UserInitialConditions, + initial_conditions: Mapping[str, FloatND | IntND], period_to_regime_to_V_arr: MappingProxyType[ int, MappingProxyType[RegimeName, FloatND] ] diff --git a/src/lcm/persistence.py b/src/lcm/persistence.py index 3981f3141..c4296f124 100644 --- a/src/lcm/persistence.py +++ b/src/lcm/persistence.py @@ -9,7 +9,7 @@ import shutil import tempfile import textwrap -from collections.abc import Sequence +from collections.abc import Mapping, Sequence from dataclasses import dataclass from pathlib import Path from types import MappingProxyType @@ -22,9 +22,9 @@ from lcm.typing import ( FloatND, + IntND, PeriodToRegimeToVArr, RegimeName, - UserInitialConditions, UserParams, ) @@ -74,7 +74,7 @@ class SimulateSnapshot: params: UserParams | None """User parameters passed to simulate.""" - initial_conditions: UserInitialConditions | None + initial_conditions: Mapping[str, FloatND | IntND] | None """Mapping of state names and "regime" to arrays.""" period_to_regime_to_V_arr: PeriodToRegimeToVArr | None @@ -206,7 +206,7 @@ def save_simulate_snapshot( *, model: Model, params: UserParams, - initial_conditions: UserInitialConditions, + initial_conditions: Mapping[str, FloatND | IntND], period_to_regime_to_V_arr: PeriodToRegimeToVArr, result: SimulationResult, log_path: Path, diff --git a/src/lcm/regime_building/argmax.py b/src/lcm/regime_building/argmax.py index 9d420e868..e68f57673 100644 --- a/src/lcm/regime_building/argmax.py +++ b/src/lcm/regime_building/argmax.py @@ -1,14 +1,14 @@ import jax.numpy as jnp -from lcm.typing import BoolND, FloatND, IntND +from lcm.typing import BoolND, IntND, RealND def argmax_and_max( - a: FloatND, + a: RealND, axis: int | tuple[int, ...] | None = None, initial: float | None = None, where: BoolND | None = None, -) -> tuple[IntND, FloatND]: +) -> tuple[IntND, RealND]: """Compute the argmax of an n-dim array along axis. If multiple maxima exist, the first index will be selected. @@ -69,7 +69,7 @@ def argmax_and_max( return _argmax, _max.reshape(_argmax.shape) -def _move_axes_to_back(a: FloatND | BoolND, axes: tuple[int, ...]) -> FloatND | BoolND: +def _move_axes_to_back(a: RealND | BoolND, axes: tuple[int, ...]) -> RealND | BoolND: """Move specified axes to the back of the array. Args: @@ -84,7 +84,7 @@ def _move_axes_to_back(a: FloatND | BoolND, axes: tuple[int, ...]) -> FloatND | return a.transpose((*front_axes, *axes)) -def _flatten_last_n_axes(a: FloatND | BoolND, n: int) -> FloatND | BoolND: +def _flatten_last_n_axes(a: RealND | BoolND, n: int) -> RealND | BoolND: """Flatten the last n axes of a to 1 dimension. Args: diff --git a/src/lcm/regime_building/ndimage.py b/src/lcm/regime_building/ndimage.py index 7a27df1bd..eeb2bea5d 100644 --- a/src/lcm/regime_building/ndimage.py +++ b/src/lcm/regime_building/ndimage.py @@ -21,14 +21,14 @@ import jax.numpy as jnp from jax import jit, lax -from lcm.typing import FloatND, IntND +from lcm.typing import IntND, RealND @jit def map_coordinates( - input: FloatND | IntND, # noqa: A002 - coordinates: Sequence[FloatND], -) -> FloatND | IntND: + input: RealND, # noqa: A002 + coordinates: Sequence[RealND] | RealND, +) -> RealND: """Map the input array to new coordinates using linear interpolation. Modified from JAX implementation of `scipy.ndimage.map_coordinates`. @@ -73,8 +73,8 @@ def map_coordinates( def _compute_indices_and_weights( - coordinate: FloatND, input_size: int -) -> list[tuple[IntND, FloatND]]: + coordinate: RealND, input_size: int +) -> list[tuple[IntND, RealND]]: """Compute indices and weights for linear interpolation.""" lower_index = jnp.clip(jnp.floor(coordinate), 0, input_size - 2).astype(jnp.int32) upper_weight = coordinate - lower_index @@ -82,15 +82,15 @@ def _compute_indices_and_weights( return [(lower_index, lower_weight), (lower_index + 1, upper_weight)] -def _multiply_all(arrs: Sequence[FloatND]) -> FloatND: +def _multiply_all(arrs: Sequence[RealND]) -> RealND: """Multiply all arrays in the sequence.""" return functools.reduce(operator.mul, arrs) -def _sum_all(arrs: Sequence[FloatND]) -> FloatND: +def _sum_all(arrs: Sequence[RealND]) -> RealND: """Sum all arrays in the sequence.""" return functools.reduce(operator.add, arrs) -def _round_half_away_from_zero(a: FloatND | IntND) -> FloatND | IntND: +def _round_half_away_from_zero(a: RealND) -> RealND: return a if jnp.issubdtype(a.dtype, jnp.integer) else lax.round(a) diff --git a/src/lcm/regime_building/processing.py b/src/lcm/regime_building/processing.py index b4008fe1a..7dd1d610c 100644 --- a/src/lcm/regime_building/processing.py +++ b/src/lcm/regime_building/processing.py @@ -868,7 +868,7 @@ def _get_stochastic_next_function_for_shock( @with_signature(args={f"{name}": "ContinuousState"}, return_annotation="Int1D") def next_func(**kwargs: Any) -> Int1D: # noqa: ARG001, ANN401 - return jnp.arange(grid.shape[0]) + return jnp.arange(grid.shape[0], dtype=jnp.int32) return next_func @@ -902,7 +902,7 @@ def weights_func_runtime(*a: FloatND, **kwargs: FloatND) -> Float1D: # noqa: AR input=transition_probs, coordinates=[ jnp.full(n_points, fill_value=coord), - jnp.arange(n_points), + jnp.arange(n_points, dtype=jnp.int32), ], ) @@ -922,7 +922,7 @@ def weights_func(*args: FloatND, **kwargs: FloatND) -> Float1D: # noqa: ARG001 input=transition_probs, coordinates=[ jnp.full(grid.n_points, fill_value=coordinate), - jnp.arange(grid.n_points), + jnp.arange(grid.n_points, dtype=jnp.int32), ], ) diff --git a/src/lcm/simulation/initial_conditions.py b/src/lcm/simulation/initial_conditions.py index b660e8546..89bcf5371 100644 --- a/src/lcm/simulation/initial_conditions.py +++ b/src/lcm/simulation/initial_conditions.py @@ -37,7 +37,6 @@ RegimeNamesToIds, StateName, StatesPerRegime, - UserInitialConditions, ) from lcm.utils.containers import invert_regime_ids from lcm.utils.functools import get_union_of_args @@ -50,7 +49,7 @@ def build_initial_states( *, - initial_states: UserInitialConditions, + initial_states: Mapping[str, FloatND | IntND], internal_regimes: MappingProxyType[RegimeName, InternalRegime], ) -> StatesPerRegime: """Build the regime-keyed state carrier from user-provided initial states. @@ -127,7 +126,7 @@ def build_initial_states( def validate_initial_conditions( *, - initial_conditions: UserInitialConditions, + initial_conditions: Mapping[str, FloatND | IntND], internal_regimes: MappingProxyType[RegimeName, InternalRegime], regime_names_to_ids: RegimeNamesToIds, internal_params: InternalParams, @@ -245,7 +244,7 @@ def _format_missing_states_message(missing: set[str], required: set[str]) -> str def _collect_state_name_errors( *, - initial_states: UserInitialConditions, + initial_states: Mapping[str, FloatND | IntND], regime_id_arr: Int1D, regime_ids_to_names: RegimeIdsToNames, internal_regimes: MappingProxyType[RegimeName, InternalRegime], @@ -303,7 +302,7 @@ def _collect_state_name_errors( def _collect_structural_errors( *, - initial_states: UserInitialConditions, + initial_states: Mapping[str, FloatND | IntND], regime_id_arr: Int1D, regime_ids_to_names: RegimeIdsToNames, regime_names_to_ids: RegimeNamesToIds, @@ -403,7 +402,7 @@ def _collect_structural_errors( def _collect_feasibility_errors( *, - initial_states: UserInitialConditions, + initial_states: Mapping[str, FloatND | IntND], regime_id_arr: Int1D, regime_names_to_ids: RegimeNamesToIds, internal_regimes: MappingProxyType[RegimeName, InternalRegime], @@ -454,7 +453,7 @@ def _collect_feasibility_errors( def _validate_discrete_state_values( *, - initial_states: UserInitialConditions, + initial_states: Mapping[str, FloatND | IntND], internal_regimes: MappingProxyType[RegimeName, InternalRegime], regime_id_arr: Int1D, regime_names_to_ids: RegimeNamesToIds, @@ -519,7 +518,7 @@ def _validate_discrete_state_values( def _batched_feasibility_check( *, feasibility_func: Callable[..., BoolND], - subject_states: UserInitialConditions, + subject_states: Mapping[str, FloatND | IntND], action_kwargs: Mapping[str, FloatND | IntND], filtered_params: Mapping[str, object], flat_actions: Mapping[ActionName, FloatND | IntND], @@ -591,7 +590,7 @@ def _check_regime_feasibility( # noqa: C901 *, internal_regime: InternalRegime, regime_name: RegimeName, - initial_states: UserInitialConditions, + initial_states: Mapping[str, FloatND | IntND], subject_indices: list[int], regime_params: Mapping[str, object], ages: AgeGrid, @@ -732,7 +731,7 @@ def _check_combo(action_kw: dict[str, FloatND | IntND]) -> BoolND: def _per_constraint_feasibility( *, internal_regime: InternalRegime, - subject_states: UserInitialConditions, + subject_states: Mapping[str, FloatND | IntND], regime_params: Mapping[str, object], flat_actions: Mapping[ActionName, FloatND | IntND], idx_arr: Int1D, @@ -845,7 +844,7 @@ def _format_infeasibility_message( infeasible_indices: Sequence[int], internal_regime: InternalRegime, regime_name: RegimeName, - initial_states: UserInitialConditions, + initial_states: Mapping[str, FloatND | IntND], state_names: Sequence[str], per_constraint_admits_any: Mapping[str, np.ndarray], ) -> str: diff --git a/src/lcm/simulation/simulate.py b/src/lcm/simulation/simulate.py index 50efccc2d..7401792d7 100644 --- a/src/lcm/simulation/simulate.py +++ b/src/lcm/simulation/simulate.py @@ -36,7 +36,6 @@ ScalarFloat, ScalarInt, StatesPerRegime, - UserInitialConditions, ) from lcm.utils.containers import invert_regime_ids from lcm.utils.error_handling import validate_V @@ -52,7 +51,7 @@ def simulate( *, internal_params: InternalParams, - initial_conditions: UserInitialConditions, + initial_conditions: Mapping[str, FloatND | IntND], internal_regimes: MappingProxyType[RegimeName, InternalRegime], regime_names_to_ids: RegimeNamesToIds, logger: logging.Logger, diff --git a/src/lcm/typing.py b/src/lcm/typing.py index 8545800e2..37ad55884 100644 --- a/src/lcm/typing.py +++ b/src/lcm/typing.py @@ -5,7 +5,7 @@ import numpy as np import pandas as pd from jax import Array -from jaxtyping import Bool, Float, Int, Int32, Scalar +from jaxtyping import Bool, Float, Int32, Real, Scalar from lcm.params import MappingLeaf from lcm.params.sequence_leaf import SequenceLeaf @@ -19,6 +19,12 @@ type IntND = Int32[Array, "..."] type BoolND = Bool[Array, "..."] +# Dtype-generic real array (any-width float or int, no complex/bool). For +# low-level numeric primitives (`ndimage`, `argmax`) that operate *below* +# pylcm's canonical-dtype invariant — they are exercised across dtypes +# (e.g. int64) and so cannot pin the int side to `IntND` (int32-only). +type RealND = Real[Array, "..."] + type Float1D = Float[Array, "_"] # noqa: F821 type Int1D = Int32[Array, "_"] # noqa: F821 type Bool1D = Bool[Array, "_"] # noqa: F821 @@ -54,23 +60,13 @@ type RegimeStates = MappingProxyType[StateName, FloatND | IntND] type StatesPerRegime = MappingProxyType[RegimeName, RegimeStates] -# User-supplied initial conditions / states, checked by beartype at the -# `Model.simulate` boundary. The int slot is dtype-generic `Int[Array, "..."]` -# rather than `IntND` (int32-only): users pass int64 arrays, which -# `build_initial_states` downcasts. Parallels `_ParamsLeaf`. -type UserInitialConditions = Mapping[str, FloatND | Int[Array, "..."]] - -# User-supplied param leaf, checked by beartype at the `process_params` -# boundary. The int slot is dtype-generic `Int[Array, "..."]` rather than -# `IntND` (int32-only): users legitimately pass int64 arrays, which the -# boundary downcasts. `FloatND`/`BoolND` are already dtype-generic. type _ParamsLeaf = ( bool | int | float | FloatND - | Int[Array, "..."] + | IntND | BoolND | np.ndarray | pd.Series diff --git a/tests/simulation/test_simulate.py b/tests/simulation/test_simulate.py index 66d62f225..9e5e5b668 100644 --- a/tests/simulation/test_simulate.py +++ b/tests/simulation/test_simulate.py @@ -199,7 +199,7 @@ def test_simulate_with_only_discrete_actions(): result = model.simulate( params=params, initial_conditions={ - "wealth": jnp.array([0, 2]), + "wealth": jnp.array([0.0, 2.0]), "age": jnp.array([50.0, 50.0]), "regime": jnp.array([DiscreteRegimeId.working_life] * 2), }, diff --git a/tests/test_int_dtype_invariants.py b/tests/test_int_dtype_invariants.py index d1cbc61bb..b302c6474 100644 --- a/tests/test_int_dtype_invariants.py +++ b/tests/test_int_dtype_invariants.py @@ -128,10 +128,15 @@ def test_process_params_casts_python_int_to_int32() -> None: def test_process_params_casts_int64_array_to_int32() -> None: - """A `jnp.int64` array param leaf is normalised to `jnp.int32`.""" + """An `int64` array param leaf is normalised to `jnp.int32`. + + Built with `np.asarray`: a JAX `int64` array is rejected at the beartype + boundary (`_ParamsLeaf` pins JAX integer leaves to `int32`), so the + realistic raw-data path for a wider-dtype input is a numpy array. + """ template = _as_template({"regime_a": {"fun": {"schedule": "Array"}}}) user_params = { - "regime_a": {"fun": {"schedule": jnp.asarray([0, 1, 2], dtype=jnp.int64)}} + "regime_a": {"fun": {"schedule": np.asarray([0, 1, 2], dtype=np.int64)}} } out = process_params( diff --git a/tests/test_shock_grids.py b/tests/test_shock_grids.py index 256247f03..84d1ca794 100644 --- a/tests/test_shock_grids.py +++ b/tests/test_shock_grids.py @@ -35,9 +35,9 @@ def test_model_with_shock(distribution_type): got_simulate = model.simulate( params=params, initial_conditions={ - "health": jnp.asarray([0, 0]), - "income": jnp.asarray([0, 0]), - "wealth": jnp.asarray([1, 1]), + "health": jnp.asarray([0, 0], dtype=jnp.int32), + "income": jnp.asarray([0.0, 0.0]), + "wealth": jnp.asarray([1.0, 1.0]), "age": jnp.asarray([0.0, 0.0]), "regime": jnp.array([RegimeId.alive] * 2), }, @@ -84,11 +84,11 @@ def test_model_with_cross_regime_shocks(distribution_type: str) -> None: result = model.simulate( params=params, initial_conditions={ - "health": jnp.zeros(2, dtype=int), + "health": jnp.zeros(2, dtype=jnp.int32), "income": jnp.zeros(2), "wealth": jnp.ones(2), "age": jnp.zeros(2), - "regime": jnp.full(2, MultiRegimeId.work, dtype=int), + "regime": jnp.full(2, MultiRegimeId.work, dtype=jnp.int32), }, period_to_regime_to_V_arr=None, seed=42, diff --git a/tests/test_stochastic.py b/tests/test_stochastic.py index 2ec8ec107..6b1014016 100644 --- a/tests/test_stochastic.py +++ b/tests/test_stochastic.py @@ -41,8 +41,8 @@ def test_model_simulate_with_stochastic_model(): result = model.simulate( params=params, initial_conditions={ - "health": jnp.array([1, 1, 0, 0]), - "partner": jnp.array([0, 0, 1, 0]), + "health": jnp.array([1, 1, 0, 0], dtype=jnp.int32), + "partner": jnp.array([0, 0, 1, 0], dtype=jnp.int32), "wealth": jnp.array([10.0, 50.0, 30, 80.0]), "age": jnp.array([40.0, 40.0, 40.0, 40.0]), "regime": jnp.array([RegimeId.working_life] * 4), @@ -190,8 +190,8 @@ def test_compare_deterministic_and_stochastic_results_value_function( # Compare simulation results # ================================================================================== initial_conditions = { - "health": jnp.array([1, 1, 0, 0]), - "partner": jnp.array([0, 0, 0, 0]), + "health": jnp.array([1, 1, 0, 0], dtype=jnp.int32), + "partner": jnp.array([0, 0, 0, 0], dtype=jnp.int32), "wealth": jnp.array([10.0, 50.0, 30, 80.0]), "age": jnp.array([40.0, 40.0, 40.0, 40.0]), "regime": jnp.array([RegimeId.working_life] * 4), From 976ddd299ab4d079bc0bb594190d0f6f088cdecc Mon Sep 17 00:00:00 2001 From: Hans-Martin von Gaudecker Date: Thu, 14 May 2026 11:40:44 +0200 Subject: [PATCH 39/77] Drop RealND; ndimage/argmax use FloatND/IntND, tests pin int32 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit RealND only existed to let the vendored ndimage/argmax primitives accept int64 from `test_ndimage` / `test_argmax` parametrizations — but pylcm never produces int64, so those int64 cases tested a path that cannot occur. Narrow the primitives to `FloatND` / `IntND` / `FloatND | IntND` and switch the test dtype parametrizations to int32. Co-Authored-By: Claude Opus 4.7 --- src/lcm/regime_building/argmax.py | 14 +++++++++----- src/lcm/regime_building/ndimage.py | 18 +++++++++--------- src/lcm/typing.py | 8 +------- tests/test_argmax.py | 24 ++++++++++++------------ tests/test_ndimage.py | 2 +- 5 files changed, 32 insertions(+), 34 deletions(-) diff --git a/src/lcm/regime_building/argmax.py b/src/lcm/regime_building/argmax.py index e68f57673..302fc4d4e 100644 --- a/src/lcm/regime_building/argmax.py +++ b/src/lcm/regime_building/argmax.py @@ -1,14 +1,14 @@ import jax.numpy as jnp -from lcm.typing import BoolND, IntND, RealND +from lcm.typing import BoolND, FloatND, IntND def argmax_and_max( - a: RealND, + a: FloatND | IntND, axis: int | tuple[int, ...] | None = None, initial: float | None = None, where: BoolND | None = None, -) -> tuple[IntND, RealND]: +) -> tuple[IntND, FloatND | IntND]: """Compute the argmax of an n-dim array along axis. If multiple maxima exist, the first index will be selected. @@ -69,7 +69,9 @@ def argmax_and_max( return _argmax, _max.reshape(_argmax.shape) -def _move_axes_to_back(a: RealND | BoolND, axes: tuple[int, ...]) -> RealND | BoolND: +def _move_axes_to_back( + a: FloatND | IntND | BoolND, axes: tuple[int, ...] +) -> FloatND | IntND | BoolND: """Move specified axes to the back of the array. Args: @@ -84,7 +86,9 @@ def _move_axes_to_back(a: RealND | BoolND, axes: tuple[int, ...]) -> RealND | Bo return a.transpose((*front_axes, *axes)) -def _flatten_last_n_axes(a: RealND | BoolND, n: int) -> RealND | BoolND: +def _flatten_last_n_axes( + a: FloatND | IntND | BoolND, n: int +) -> FloatND | IntND | BoolND: """Flatten the last n axes of a to 1 dimension. Args: diff --git a/src/lcm/regime_building/ndimage.py b/src/lcm/regime_building/ndimage.py index eeb2bea5d..be0b338a4 100644 --- a/src/lcm/regime_building/ndimage.py +++ b/src/lcm/regime_building/ndimage.py @@ -21,14 +21,14 @@ import jax.numpy as jnp from jax import jit, lax -from lcm.typing import IntND, RealND +from lcm.typing import FloatND, IntND @jit def map_coordinates( - input: RealND, # noqa: A002 - coordinates: Sequence[RealND] | RealND, -) -> RealND: + input: FloatND | IntND, # noqa: A002 + coordinates: Sequence[FloatND | IntND] | FloatND | IntND, +) -> FloatND | IntND: """Map the input array to new coordinates using linear interpolation. Modified from JAX implementation of `scipy.ndimage.map_coordinates`. @@ -73,8 +73,8 @@ def map_coordinates( def _compute_indices_and_weights( - coordinate: RealND, input_size: int -) -> list[tuple[IntND, RealND]]: + coordinate: FloatND | IntND, input_size: int +) -> list[tuple[IntND, FloatND | IntND]]: """Compute indices and weights for linear interpolation.""" lower_index = jnp.clip(jnp.floor(coordinate), 0, input_size - 2).astype(jnp.int32) upper_weight = coordinate - lower_index @@ -82,15 +82,15 @@ def _compute_indices_and_weights( return [(lower_index, lower_weight), (lower_index + 1, upper_weight)] -def _multiply_all(arrs: Sequence[RealND]) -> RealND: +def _multiply_all(arrs: Sequence[FloatND | IntND]) -> FloatND | IntND: """Multiply all arrays in the sequence.""" return functools.reduce(operator.mul, arrs) -def _sum_all(arrs: Sequence[RealND]) -> RealND: +def _sum_all(arrs: Sequence[FloatND | IntND]) -> FloatND | IntND: """Sum all arrays in the sequence.""" return functools.reduce(operator.add, arrs) -def _round_half_away_from_zero(a: RealND) -> RealND: +def _round_half_away_from_zero(a: FloatND | IntND) -> FloatND | IntND: return a if jnp.issubdtype(a.dtype, jnp.integer) else lax.round(a) diff --git a/src/lcm/typing.py b/src/lcm/typing.py index 37ad55884..865394478 100644 --- a/src/lcm/typing.py +++ b/src/lcm/typing.py @@ -5,7 +5,7 @@ import numpy as np import pandas as pd from jax import Array -from jaxtyping import Bool, Float, Int32, Real, Scalar +from jaxtyping import Bool, Float, Int32, Scalar from lcm.params import MappingLeaf from lcm.params.sequence_leaf import SequenceLeaf @@ -19,12 +19,6 @@ type IntND = Int32[Array, "..."] type BoolND = Bool[Array, "..."] -# Dtype-generic real array (any-width float or int, no complex/bool). For -# low-level numeric primitives (`ndimage`, `argmax`) that operate *below* -# pylcm's canonical-dtype invariant — they are exercised across dtypes -# (e.g. int64) and so cannot pin the int side to `IntND` (int32-only). -type RealND = Real[Array, "..."] - type Float1D = Float[Array, "_"] # noqa: F821 type Int1D = Int32[Array, "_"] # noqa: F821 type Bool1D = Bool[Array, "_"] # noqa: F821 diff --git a/tests/test_argmax.py b/tests/test_argmax.py index 7ef50c6e1..5fb14d9e7 100644 --- a/tests/test_argmax.py +++ b/tests/test_argmax.py @@ -13,7 +13,7 @@ def test_argmax_1d_with_mask(): - a = jnp.arange(10) + a = jnp.arange(10, dtype=jnp.int32) mask = jnp.array([1, 0, 0, 1, 1, 0, 0, 0, 0, 0], dtype=bool) _argmax, _max = jitted_argmax(a, where=mask, initial=-1) assert _argmax == 4 @@ -21,7 +21,7 @@ def test_argmax_1d_with_mask(): def test_argmax_2d_with_mask(): - a = jnp.arange(10).reshape(2, 5) + a = jnp.arange(10, dtype=jnp.int32).reshape(2, 5) mask = jnp.array([1, 0, 0, 1, 1, 0, 0, 0, 0, 0], dtype=bool).reshape(a.shape) _argmax, _max = jitted_argmax(a, axis=None, where=mask, initial=-1) @@ -38,14 +38,14 @@ def test_argmax_2d_with_mask(): def test_argmax_1d_no_mask(): - a = jnp.arange(10) + a = jnp.arange(10, dtype=jnp.int32) _argmax, _max = jitted_argmax(a) assert _argmax == 9 assert _max == 9 def test_argmax_2d_no_mask(): - a = jnp.arange(10).reshape(2, 5) + a = jnp.arange(10, dtype=jnp.int32).reshape(2, 5) _argmax, _max = jitted_argmax(a, axis=None) assert _argmax == 9 @@ -65,7 +65,7 @@ def test_argmax_2d_no_mask(): def test_argmax_3d_no_mask(): - a = jnp.arange(24).reshape(2, 3, 4) + a = jnp.arange(24, dtype=jnp.int32).reshape(2, 3, 4) _argmax, _max = jitted_argmax(a, axis=None) assert _argmax == 23 @@ -107,37 +107,37 @@ def test_argmax_with_ties(): def test_move_axes_to_back_1d(): - a = jnp.arange(4) + a = jnp.arange(4, dtype=jnp.int32) got = _move_axes_to_back(a, axes=(0,)) assert_array_equal(got, a) def test_move_axes_to_back_2d(): - a = jnp.arange(4).reshape(2, 2) + a = jnp.arange(4, dtype=jnp.int32).reshape(2, 2) got = _move_axes_to_back(a, axes=(0,)) assert_array_equal(got, a.transpose(1, 0)) def test_move_axes_to_back_3d(): # 2 dimensions in back - a = jnp.arange(8).reshape(2, 2, 2) + a = jnp.arange(8, dtype=jnp.int32).reshape(2, 2, 2) got = _move_axes_to_back(a, axes=(0, 1)) assert_array_equal(got, a.transpose(2, 0, 1)) # 2 dimensions in front - a = jnp.arange(8).reshape(2, 2, 2) + a = jnp.arange(8, dtype=jnp.int32).reshape(2, 2, 2) got = _move_axes_to_back(a, axes=(1,)) assert_array_equal(got, a.transpose(0, 2, 1)) def test_flatten_last_n_axes_1d(): - a = jnp.arange(4) + a = jnp.arange(4, dtype=jnp.int32) got = _flatten_last_n_axes(a, n=1) assert_array_equal(got, a) def test_flatten_last_n_axes_2d(): - a = jnp.arange(4).reshape(2, 2) + a = jnp.arange(4, dtype=jnp.int32).reshape(2, 2) got = _flatten_last_n_axes(a, n=1) assert_array_equal(got, a) @@ -147,7 +147,7 @@ def test_flatten_last_n_axes_2d(): def test_flatten_last_n_axes_3d(): - a = jnp.arange(8).reshape(2, 2, 2) + a = jnp.arange(8, dtype=jnp.int32).reshape(2, 2, 2) got = _flatten_last_n_axes(a, n=1) assert_array_equal(got, a) diff --git a/tests/test_ndimage.py b/tests/test_ndimage.py index 5bb379086..c4ce07b8f 100644 --- a/tests/test_ndimage.py +++ b/tests/test_ndimage.py @@ -26,7 +26,7 @@ from tests.conftest import DECIMAL_PRECISION, X64_ENABLED # Use 64-bit dtypes when x64 is enabled, 32-bit otherwise -TEST_DTYPES = [np.int64, np.float64] if X64_ENABLED else [np.int32, np.float32] +TEST_DTYPES = [np.int32, np.float64] if X64_ENABLED else [np.int32, np.float32] jax_map_coordinates = partial(jax.scipy.ndimage.map_coordinates, order=1, cval=0) scipy_map_coordinates = partial(scipy.ndimage.map_coordinates, order=1, cval=0) From bee52c8a39296bb7fea4d826feee745e58752f57 Mon Sep 17 00:00:00 2001 From: Hans-Martin von Gaudecker Date: Thu, 14 May 2026 12:05:47 +0200 Subject: [PATCH 40/77] Make jaxtyping "..." sentinel survive pickling; fix int64 ndimage unit tests Activating the claw on `lcm.regime_building` makes jaxtyping shape checks run on cloudpickled annotation types. jaxtyping marks a `"..."` axis with a plain `object()` sentinel that loses identity across a pickle round-trip, tripping `assert type(variadic_dim) is _NamedVariadicDim`. Replace it with a `__reduce__`-backed singleton, patched in before any jaxtyping-subscripted type is created. Fix `test_ndimage_unit` cases that passed raw int64 arrays the int32-pinned primitives now reject. Co-Authored-By: Claude Opus 4.7 --- src/lcm/__init__.py | 4 ++++ src/lcm/_jaxtyping_patch.py | 36 ++++++++++++++++++++++++++++++++++++ tests/test_ndimage_unit.py | 13 ++++++++----- 3 files changed, 48 insertions(+), 5 deletions(-) create mode 100644 src/lcm/_jaxtyping_patch.py diff --git a/src/lcm/__init__.py b/src/lcm/__init__.py index 565acfce4..e18fdc350 100644 --- a/src/lcm/__init__.py +++ b/src/lcm/__init__.py @@ -25,6 +25,10 @@ import jax +# Patch jaxtyping's `"..."` sentinel to survive pickling before any +# `jaxtyping`-subscripted type is created (see the module docstring). +from lcm import _jaxtyping_patch # noqa: F401 + with contextlib.suppress(ImportError): import pdbp # noqa: F401 diff --git a/src/lcm/_jaxtyping_patch.py b/src/lcm/_jaxtyping_patch.py new file mode 100644 index 000000000..e9dced877 --- /dev/null +++ b/src/lcm/_jaxtyping_patch.py @@ -0,0 +1,36 @@ +"""Make jaxtyping's anonymous-variadic-dim sentinel survive pickling. + +jaxtyping marks a `"..."` axis with a module-level `object()` sentinel +(`_anonymous_variadic_dim`). A plain `object()` does not keep its identity +across a pickle round-trip, so cloudpickling a value whose type annotations +reference a `Foo[Array, "..."]` type — which the beartype claw makes +pervasive — yields a type whose variadic-dim marker no longer matches the +live module global. jaxtyping's shape check then trips +`assert type(variadic_dim) is _NamedVariadicDim`. + +Replacing the sentinel with a `__reduce__`-backed singleton makes it +round-trip to the same object, so unpickled annotation types stay valid. +This module must be imported before any `jaxtyping`-subscripted type is +created — `lcm/__init__.py` imports it before every other `lcm` submodule. +""" + +from typing import Self + +from jaxtyping import _array_types + + +class _AnonymousVariadicDim: + """Picklable singleton for jaxtyping's `"..."` axis marker.""" + + _instance: Self | None = None + + def __new__(cls) -> Self: + if cls._instance is None: + cls._instance = super().__new__(cls) + return cls._instance + + def __reduce__(self) -> tuple[type[_AnonymousVariadicDim], tuple[()]]: + return (_AnonymousVariadicDim, ()) + + +_array_types._anonymous_variadic_dim = _AnonymousVariadicDim() # noqa: SLF001 diff --git a/tests/test_ndimage_unit.py b/tests/test_ndimage_unit.py index 516a5aec2..f70903acc 100644 --- a/tests/test_ndimage_unit.py +++ b/tests/test_ndimage_unit.py @@ -12,8 +12,11 @@ def test_map_coordinates_wrong_input_dimensions(): - values = jnp.arange(2) # ndim = 1 - coordinates = [jnp.array([0]), jnp.array([1])] # len = 2 + values = jnp.arange(2, dtype=jnp.int32) # ndim = 1 + coordinates = [ + jnp.array([0], dtype=jnp.int32), + jnp.array([1], dtype=jnp.int32), + ] # len = 2 with pytest.raises(ValueError, match="coordinates must be a sequence of length"): map_coordinates(values, coordinates) @@ -29,7 +32,7 @@ def test_map_coordinates_extrapolation(): def test_nonempty_sum(): - a = jnp.arange(3) + a = jnp.arange(3, dtype=jnp.int32) expected = a + a + a got = _sum_all([a, a, a]) @@ -38,7 +41,7 @@ def test_nonempty_sum(): def test_nonempty_prod(): - a = jnp.arange(3) + a = jnp.arange(3, dtype=jnp.int32) expected = a * a * a got = _multiply_all([a, a, a]) @@ -75,7 +78,7 @@ def test_linear_indices_and_weights_inside_domain(): def test_linear_indices_and_weights_outside_domain(): - coordinates = jnp.array([-1, 2]) + coordinates = jnp.array([-1.0, 2.0]) (idx_low, weight_low), (idx_high, weight_high) = _compute_indices_and_weights( coordinates, input_size=2 From ce1aba16c9d0400bfc91e0c129053b5481c93649 Mon Sep 17 00:00:00 2001 From: Hans-Martin von Gaudecker Date: Thu, 14 May 2026 12:12:51 +0200 Subject: [PATCH 41/77] Rename KeyArray to PRNGKeyND; fix mis-typed key params and regime-prob keys MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `KeyArray` -> `PRNGKeyND` to fit the `*ND` alias family. The stochastic next-state wrappers annotated their `key_` parameter as `dict[str, PRNGKeyND]`, but dags hands the function a single (batched) PRNG key — `generate_simulation_keys` is the thing that returns the dict, keyed by `key_`. Correct those three sites to `PRNGKeyND`. Key `regime_transition_probs` mappings by `RegimeName` rather than bare `str`. Co-Authored-By: Claude Opus 4.7 --- src/lcm/regime_building/Q_and_F.py | 4 ++-- src/lcm/regime_building/next_state.py | 6 +++--- src/lcm/shocks/ar1.py | 10 +++++----- src/lcm/shocks/iid.py | 12 ++++++------ src/lcm/simulation/random.py | 6 +++--- src/lcm/simulation/simulate.py | 6 +++--- src/lcm/simulation/transitions.py | 12 ++++++------ src/lcm/typing.py | 4 ++-- src/lcm/utils/error_handling.py | 14 +++++++------- 9 files changed, 37 insertions(+), 37 deletions(-) diff --git a/src/lcm/regime_building/Q_and_F.py b/src/lcm/regime_building/Q_and_F.py index 8b3d3a076..e2e4c8410 100644 --- a/src/lcm/regime_building/Q_and_F.py +++ b/src/lcm/regime_building/Q_and_F.py @@ -149,7 +149,7 @@ def Q_and_F( A tuple containing the arrays with state-action values and feasibilities. """ - regime_transition_probs: MappingProxyType[str, FloatND] = ( # ty: ignore[invalid-assignment] + regime_transition_probs: MappingProxyType[RegimeName, FloatND] = ( # ty: ignore[invalid-assignment] compute_regime_transition_probs(**states_actions_params) ) U_arr, F_arr = U_and_F(**states_actions_params) @@ -319,7 +319,7 @@ def compute_intermediates( FloatND, FloatND, FloatND, FloatND, MappingProxyType[RegimeName, FloatND] ]: """Compute all Q_and_F intermediates.""" - regime_transition_probs: MappingProxyType[str, FloatND] = ( # ty: ignore[invalid-assignment] + regime_transition_probs: MappingProxyType[RegimeName, FloatND] = ( # ty: ignore[invalid-assignment] compute_regime_transition_probs(**states_actions_params) ) U_arr, F_arr = U_and_F(**states_actions_params) diff --git a/src/lcm/regime_building/next_state.py b/src/lcm/regime_building/next_state.py index c9f11552e..1eeac78bd 100644 --- a/src/lcm/regime_building/next_state.py +++ b/src/lcm/regime_building/next_state.py @@ -239,7 +239,7 @@ def _create_discrete_stochastic_next_func( qname = qname_from_tree_path((target, next_state_name)) @with_signature( - args={f"weight_{qname}": "FloatND", f"key_{qname}": "dict[str, KeyArray]"}, + args={f"weight_{qname}": "FloatND", f"key_{qname}": "PRNGKeyND"}, return_annotation="DiscreteState", ) def next_stochastic_state(**kwargs: FloatND) -> DiscreteState: @@ -295,7 +295,7 @@ def _create_ar1_next_func( qname_from_tree_path((state_name, p)): p for p in grid.params_to_pass_at_runtime } args: dict[str, str] = { - f"key_{qname}": "dict[str, KeyArray]", + f"key_{qname}": "PRNGKeyND", state_name: "ContinuousState", **dict.fromkeys(runtime_param_names, "float"), } @@ -328,7 +328,7 @@ def _create_iid_next_func( qname_from_tree_path((state_name, p)): p for p in grid.params_to_pass_at_runtime } args: dict[str, str] = { - f"key_{qname}": "dict[str, KeyArray]", + f"key_{qname}": "PRNGKeyND", **dict.fromkeys(runtime_param_names, "float"), } _draw_shock = grid.draw_shock diff --git a/src/lcm/shocks/ar1.py b/src/lcm/shocks/ar1.py index 017cb5c63..7cef736be 100644 --- a/src/lcm/shocks/ar1.py +++ b/src/lcm/shocks/ar1.py @@ -14,7 +14,7 @@ _ShockGrid, _validate_gauss_hermite_grid, ) -from lcm.typing import Float1D, FloatND, KeyArray, ScalarFloat, ScalarInt +from lcm.typing import Float1D, FloatND, PRNGKeyND, ScalarFloat, ScalarInt @dataclass(frozen=True, kw_only=True) @@ -25,7 +25,7 @@ class _ShockGridAR1(_ShockGrid): def draw_shock( self, params: MappingProxyType[str, ScalarFloat | ScalarInt], - key: KeyArray, + key: PRNGKeyND, current_value: ScalarFloat, ) -> ScalarFloat: ... @@ -120,7 +120,7 @@ def compute_transition_probs(self, **kwargs: ScalarFloat | ScalarInt) -> FloatND def draw_shock( self, params: MappingProxyType[str, ScalarFloat | ScalarInt], - key: KeyArray, + key: PRNGKeyND, current_value: ScalarFloat, ) -> ScalarFloat: return ( @@ -189,7 +189,7 @@ def compute_transition_probs(self, **kwargs: ScalarFloat | ScalarInt) -> FloatND def draw_shock( self, params: MappingProxyType[str, ScalarFloat | ScalarInt], - key: KeyArray, + key: PRNGKeyND, current_value: ScalarFloat, ) -> ScalarFloat: return ( @@ -303,7 +303,7 @@ def compute_transition_probs(self, **kwargs: ScalarFloat | ScalarInt) -> FloatND def draw_shock( self, params: MappingProxyType[str, ScalarFloat | ScalarInt], - key: KeyArray, + key: PRNGKeyND, current_value: ScalarFloat, ) -> ScalarFloat: key1, key2 = jax.random.split(key) diff --git a/src/lcm/shocks/iid.py b/src/lcm/shocks/iid.py index fb2beac72..e2e410a92 100644 --- a/src/lcm/shocks/iid.py +++ b/src/lcm/shocks/iid.py @@ -13,7 +13,7 @@ _ShockGrid, _validate_gauss_hermite_grid, ) -from lcm.typing import Float1D, FloatND, KeyArray, ScalarFloat, ScalarInt +from lcm.typing import Float1D, FloatND, PRNGKeyND, ScalarFloat, ScalarInt @dataclass(frozen=True, kw_only=True) @@ -24,7 +24,7 @@ class _ShockGridIID(_ShockGrid): def draw_shock( self, params: MappingProxyType[str, ScalarFloat | ScalarInt], - key: KeyArray, + key: PRNGKeyND, ) -> ScalarFloat: ... @@ -56,7 +56,7 @@ def compute_transition_probs(self, **kwargs: ScalarFloat | ScalarInt) -> FloatND def draw_shock( self, params: MappingProxyType[str, ScalarFloat | ScalarInt], - key: KeyArray, + key: PRNGKeyND, ) -> ScalarFloat: return jax.random.uniform( key=key, minval=params["start"], maxval=params["stop"] @@ -133,7 +133,7 @@ def compute_transition_probs(self, **kwargs: ScalarFloat | ScalarInt) -> FloatND def draw_shock( self, params: MappingProxyType[str, ScalarFloat | ScalarInt], - key: KeyArray, + key: PRNGKeyND, ) -> ScalarFloat: return params["mu"] + params["sigma"] * jax.random.normal(key=key) @@ -199,7 +199,7 @@ def compute_transition_probs(self, **kwargs: ScalarFloat | ScalarInt) -> FloatND def draw_shock( self, params: MappingProxyType[str, ScalarFloat | ScalarInt], - key: KeyArray, + key: PRNGKeyND, ) -> ScalarFloat: return jnp.exp(params["mu"] + params["sigma"] * jax.random.normal(key=key)) @@ -284,7 +284,7 @@ def compute_transition_probs(self, **kwargs: ScalarFloat | ScalarInt) -> FloatND def draw_shock( self, params: MappingProxyType[str, ScalarFloat | ScalarInt], - key: KeyArray, + key: PRNGKeyND, ) -> ScalarFloat: key1, key2 = jax.random.split(key) component = jax.random.bernoulli(key1, params["p1"]) diff --git a/src/lcm/simulation/random.py b/src/lcm/simulation/random.py index c099e2900..69189bc0c 100644 --- a/src/lcm/simulation/random.py +++ b/src/lcm/simulation/random.py @@ -2,12 +2,12 @@ import jax -from lcm.typing import KeyArray +from lcm.typing import PRNGKeyND def generate_simulation_keys( - *, key: KeyArray, names: list[str], n_initial_states: int -) -> tuple[KeyArray, dict[str, KeyArray]]: + *, key: PRNGKeyND, names: list[str], n_initial_states: int +) -> tuple[PRNGKeyND, dict[str, PRNGKeyND]]: """Generate pseudo-random number generator keys (PRNG keys) for simulation. PRNG keys in JAX are immutable objects used to control random number generation. diff --git a/src/lcm/simulation/simulate.py b/src/lcm/simulation/simulate.py index 7401792d7..2a9a7fa46 100644 --- a/src/lcm/simulation/simulate.py +++ b/src/lcm/simulation/simulate.py @@ -30,7 +30,7 @@ Int1D, InternalParams, IntND, - KeyArray, + PRNGKeyND, RegimeName, RegimeNamesToIds, ScalarFloat, @@ -217,8 +217,8 @@ def _simulate_regime_in_period( internal_params: InternalParams, regime_names_to_ids: RegimeNamesToIds, active_regimes_next_period: tuple[RegimeName, ...], - key: KeyArray, -) -> tuple[PeriodRegimeSimulationData, StatesPerRegime, Int1D, KeyArray]: + key: PRNGKeyND, +) -> tuple[PeriodRegimeSimulationData, StatesPerRegime, Int1D, PRNGKeyND]: """Simulate one regime for one period. This function processes all subjects in a given regime for a single period, diff --git a/src/lcm/simulation/transitions.py b/src/lcm/simulation/transitions.py index 20f42b886..6dcc0d989 100644 --- a/src/lcm/simulation/transitions.py +++ b/src/lcm/simulation/transitions.py @@ -24,7 +24,7 @@ FloatND, Int1D, IntND, - KeyArray, + PRNGKeyND, RegimeName, RegimeNamesToIds, RegimeStates, @@ -79,7 +79,7 @@ def calculate_next_states( regime_params: FlatRegimeParams, states_per_regime: StatesPerRegime, state_action_space: StateActionSpace, - key: KeyArray, + key: PRNGKeyND, subjects_in_regime: Bool1D, ) -> StatesPerRegime: """Calculate next period states for subjects in a regime. @@ -170,7 +170,7 @@ def calculate_next_regime_membership( regime_names_to_ids: RegimeNamesToIds, new_subject_regime_ids: Int1D, active_regimes_next_period: tuple[RegimeName, ...], - key: KeyArray, + key: PRNGKeyND, subjects_in_regime: Bool1D, ) -> Int1D: """Calculate next period regime membership for subjects in a regime. @@ -200,7 +200,7 @@ def calculate_next_regime_membership( """ # Compute regime transition probabilities # --------------------------------------------------------------------------------- - regime_transition_probs: MappingProxyType[str, FloatND] = ( # ty: ignore[invalid-assignment] + regime_transition_probs: MappingProxyType[RegimeName, FloatND] = ( # ty: ignore[invalid-assignment] internal_regime.simulate_functions.compute_regime_transition_probs( # ty: ignore[call-non-callable] **state_action_space.states, **optimal_actions, @@ -236,7 +236,7 @@ def draw_key_from_dict( *, d: MappingProxyType[RegimeName, Float1D], regime_names_to_ids: RegimeNamesToIds, - keys: KeyArray, + keys: PRNGKeyND, ) -> Int1D: """Draw a random key from a dictionary of arrays. @@ -260,7 +260,7 @@ def draw_key_from_dict( ) def random_id( - key: KeyArray, + key: PRNGKeyND, p: Float1D, ) -> Int1D: return jax.random.choice( diff --git a/src/lcm/typing.py b/src/lcm/typing.py index 865394478..dcdaec0cc 100644 --- a/src/lcm/typing.py +++ b/src/lcm/typing.py @@ -29,9 +29,9 @@ type ScalarBool = Bool[Scalar, ""] # JAX PRNG keys (jax.random) have dtype `key` and are not float-typed, -# so they don't match `FloatND`. `KeyArray` keeps the perimeter check on shock +# so they don't match `FloatND`. `PRNGKeyND` keeps the perimeter check on shock # samplers explicit about what kind of array they expect. -type KeyArray = Array +type PRNGKeyND = Array type Period = ScalarInt type Age = ScalarInt | ScalarFloat diff --git a/src/lcm/utils/error_handling.py b/src/lcm/utils/error_handling.py index 3a37694d3..8c034f130 100644 --- a/src/lcm/utils/error_handling.py +++ b/src/lcm/utils/error_handling.py @@ -284,7 +284,7 @@ def _format_diagnostic_summary(summary: dict[str, Any]) -> str: def validate_regime_transition_probs( *, - regime_transition_probs: MappingProxyType[str, FloatND], + regime_transition_probs: MappingProxyType[RegimeName, FloatND], active_regimes_next_period: tuple[RegimeName, ...], regime_name: RegimeName, age: float | ScalarInt | ScalarFloat, @@ -499,18 +499,18 @@ def _call( _func: object = regime_transition_func, _period: int = period, _age: ScalarInt | ScalarFloat = ages.values[period], # noqa: PD011 - ) -> MappingProxyType[str, FloatND]: + ) -> MappingProxyType[RegimeName, FloatND]: kwargs = dict(zip(_names, args, strict=True)) return _func( # ty: ignore[call-non-callable] **kwargs, **_params, period=_period, age=_age ) - regime_transition_probs: MappingProxyType[str, FloatND] = jax.vmap(_call)( - *flat_arrays - ) + regime_transition_probs: MappingProxyType[RegimeName, FloatND] = jax.vmap( + _call + )(*flat_arrays) point = dict(zip(grid_var_names, flat_arrays, strict=True)) else: - regime_transition_probs: MappingProxyType[str, FloatND] = ( # ty: ignore[invalid-assignment] + regime_transition_probs: MappingProxyType[RegimeName, FloatND] = ( # ty: ignore[invalid-assignment] regime_transition_func( # ty: ignore[call-non-callable] **filtered_params, period=period, @@ -540,7 +540,7 @@ def _call( def _validate_no_reachable_incomplete_targets( *, internal_regimes: MappingProxyType[RegimeName, InternalRegime], - regime_transition_probs: MappingProxyType[str, FloatND], + regime_transition_probs: MappingProxyType[RegimeName, FloatND], active_regimes_next_period: tuple[RegimeName, ...], regime_name: RegimeName, age: float | ScalarInt | ScalarFloat, From adb99e40023ce2802135bd9216d8521da4e2b66f Mon Sep 17 00:00:00 2001 From: Hans-Martin von Gaudecker Date: Thu, 14 May 2026 12:23:52 +0200 Subject: [PATCH 42/77] Type RegimeTransitionFunction by its real return; PRNGKeyND via jaxtyping Key MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `RegimeTransitionFunction` / `VmappedRegimeTransitionFunction` are only ever the processed, dict-returning wrappers — never the user's raw `next_regime`. Annotate their `__call__` as `MappingProxyType[RegimeName, FloatND]`, which drops the `# ty: ignore[invalid-assignment]` at every call site. Type `PRNGKeyND` as `Key[Array, "..."]` — jaxtyping's `Key` matches the `key` dtype, so it no longer falls back to bare `Array`. Co-Authored-By: Claude Opus 4.7 --- src/lcm/regime_building/Q_and_F.py | 4 ++-- src/lcm/simulation/transitions.py | 2 +- src/lcm/typing.py | 26 +++++++++++++++----------- src/lcm/utils/error_handling.py | 2 +- 4 files changed, 19 insertions(+), 15 deletions(-) diff --git a/src/lcm/regime_building/Q_and_F.py b/src/lcm/regime_building/Q_and_F.py index e2e4c8410..371a49018 100644 --- a/src/lcm/regime_building/Q_and_F.py +++ b/src/lcm/regime_building/Q_and_F.py @@ -149,7 +149,7 @@ def Q_and_F( A tuple containing the arrays with state-action values and feasibilities. """ - regime_transition_probs: MappingProxyType[RegimeName, FloatND] = ( # ty: ignore[invalid-assignment] + regime_transition_probs: MappingProxyType[RegimeName, FloatND] = ( compute_regime_transition_probs(**states_actions_params) ) U_arr, F_arr = U_and_F(**states_actions_params) @@ -319,7 +319,7 @@ def compute_intermediates( FloatND, FloatND, FloatND, FloatND, MappingProxyType[RegimeName, FloatND] ]: """Compute all Q_and_F intermediates.""" - regime_transition_probs: MappingProxyType[RegimeName, FloatND] = ( # ty: ignore[invalid-assignment] + regime_transition_probs: MappingProxyType[RegimeName, FloatND] = ( compute_regime_transition_probs(**states_actions_params) ) U_arr, F_arr = U_and_F(**states_actions_params) diff --git a/src/lcm/simulation/transitions.py b/src/lcm/simulation/transitions.py index 6dcc0d989..30b551088 100644 --- a/src/lcm/simulation/transitions.py +++ b/src/lcm/simulation/transitions.py @@ -200,7 +200,7 @@ def calculate_next_regime_membership( """ # Compute regime transition probabilities # --------------------------------------------------------------------------------- - regime_transition_probs: MappingProxyType[RegimeName, FloatND] = ( # ty: ignore[invalid-assignment] + regime_transition_probs: MappingProxyType[RegimeName, FloatND] = ( internal_regime.simulate_functions.compute_regime_transition_probs( # ty: ignore[call-non-callable] **state_action_space.states, **optimal_actions, diff --git a/src/lcm/typing.py b/src/lcm/typing.py index dcdaec0cc..369b2135e 100644 --- a/src/lcm/typing.py +++ b/src/lcm/typing.py @@ -5,7 +5,7 @@ import numpy as np import pandas as pd from jax import Array -from jaxtyping import Bool, Float, Int32, Scalar +from jaxtyping import Bool, Float, Int32, Key, Scalar from lcm.params import MappingLeaf from lcm.params.sequence_leaf import SequenceLeaf @@ -28,10 +28,10 @@ type ScalarFloat = Float[Scalar, ""] type ScalarBool = Bool[Scalar, ""] -# JAX PRNG keys (jax.random) have dtype `key` and are not float-typed, -# so they don't match `FloatND`. `PRNGKeyND` keeps the perimeter check on shock -# samplers explicit about what kind of array they expect. -type PRNGKeyND = Array +# JAX PRNG keys (`jax.random`) carry the dedicated `key` dtype, which +# jaxtyping matches via `Key` — distinct from `FloatND`/`IntND`. Covers both a +# single 0-d key and a batched 1-d array of keys. +type PRNGKeyND = Key[Array, "..."] type Period = ScalarInt type Age = ScalarInt | ScalarFloat @@ -118,9 +118,11 @@ def __call__( @runtime_checkable class RegimeTransitionFunction(Protocol): - """The regime transition function provided by the user. + """The processed regime transition function for the solve phase. - Returns an array of transition probabilities indexed by regime ID. + Wraps the user's `next_regime` function so its output is a mapping of + target regime name to a transition-probability array, rather than a + raw array indexed by regime id. Only used for type checking. @@ -130,14 +132,16 @@ def __call__( self, *args: FloatND | IntND | BoolND | float, **kwargs: FloatND | IntND | BoolND | float, - ) -> Float1D: ... + ) -> MappingProxyType[RegimeName, FloatND]: ... @runtime_checkable class VmappedRegimeTransitionFunction(Protocol): - """The vmapped regime transition function. + """The processed regime transition function for the simulate phase. - Returns a 2D array of transition probabilities with shape [n_regimes, n_subjects]. + The `vmap`-over-subjects counterpart of `RegimeTransitionFunction`: + same mapping output, with each probability array carrying a leading + per-subject axis. Only used for type checking. @@ -147,7 +151,7 @@ def __call__( self, *args: FloatND | IntND | BoolND | float, **kwargs: FloatND | IntND | BoolND | float, - ) -> FloatND: ... + ) -> MappingProxyType[RegimeName, FloatND]: ... @runtime_checkable diff --git a/src/lcm/utils/error_handling.py b/src/lcm/utils/error_handling.py index 8c034f130..396d62dd7 100644 --- a/src/lcm/utils/error_handling.py +++ b/src/lcm/utils/error_handling.py @@ -510,7 +510,7 @@ def _call( )(*flat_arrays) point = dict(zip(grid_var_names, flat_arrays, strict=True)) else: - regime_transition_probs: MappingProxyType[RegimeName, FloatND] = ( # ty: ignore[invalid-assignment] + regime_transition_probs: MappingProxyType[RegimeName, FloatND] = ( regime_transition_func( # ty: ignore[call-non-callable] **filtered_params, period=period, From bfc7c35a1d6c8c76b037fb1cef823e5d19ad0fae Mon Sep 17 00:00:00 2001 From: Hans-Martin von Gaudecker Date: Thu, 14 May 2026 12:33:38 +0200 Subject: [PATCH 43/77] Drop redundant @beartype_init decorators; the claw already covers grids/shocks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `beartype_package` claws on `lcm.grids` and `lcm.shocks` apply `@beartype` to every class — every method, not just `__init__` — with `GRID_CONF`. `@beartype_init` only ever wrapped `__init__` with the same conf, so all 13 decorators were a strict subset of what the claw does. Its docstring rationale ("don't police every method") is moot: the claw polices every method and the suite is green. Remove the decorators and the `beartype_init` helper. Co-Authored-By: Claude Opus 4.7 --- src/lcm/_beartype_conf.py | 22 +--------------------- src/lcm/grids/continuous.py | 4 ---- src/lcm/grids/discrete.py | 2 -- src/lcm/grids/piecewise.py | 3 --- src/lcm/shocks/ar1.py | 4 ---- src/lcm/shocks/iid.py | 5 ----- 6 files changed, 1 insertion(+), 39 deletions(-) diff --git a/src/lcm/_beartype_conf.py b/src/lcm/_beartype_conf.py index daf276120..b68ff2034 100644 --- a/src/lcm/_beartype_conf.py +++ b/src/lcm/_beartype_conf.py @@ -6,9 +6,7 @@ """ -from collections.abc import Callable - -from beartype import BeartypeConf, BeartypeStrategy, beartype +from beartype import BeartypeConf, BeartypeStrategy from lcm.exceptions import ( CategoricalDefinitionError, @@ -34,24 +32,6 @@ def _conf(exc: type[Exception]) -> BeartypeConf: ) -def beartype_init[C](conf: BeartypeConf) -> Callable[[type[C]], type[C]]: - """Class decorator that beartype-checks `__init__` only. - - Bare `@beartype` on a class wraps every method, which surfaces - annotation drift in helpers like `compute_gridpoints(**kwargs: float)` - where runtime kwargs are actually JAX arrays. Restricting decoration - to `__init__` keeps the perimeter check (parameter types at - construction) without policing every method's runtime types. - - """ - - def deco(cls: type[C]) -> type[C]: - cls.__init__ = beartype(conf=conf)(cls.__init__) # ty: ignore[invalid-assignment] - return cls - - return deco - - # Used on `Regime` and `MarkovTransition`. REGIME_CONF = _conf(RegimeInitializationError) diff --git a/src/lcm/grids/continuous.py b/src/lcm/grids/continuous.py index 74a763ccc..da1bd27df 100644 --- a/src/lcm/grids/continuous.py +++ b/src/lcm/grids/continuous.py @@ -5,7 +5,6 @@ import jax.numpy as jnp -from lcm._beartype_conf import GRID_CONF, beartype_init from lcm.dtypes import canonical_float_dtype from lcm.exceptions import GridInitializationError, format_messages from lcm.grids import coordinates as grid_coordinates @@ -100,7 +99,6 @@ def replace(self, **kwargs: float) -> UniformContinuousGrid: ) from e -@beartype_init(GRID_CONF) class LinSpacedGrid(UniformContinuousGrid): """A linearly spaced grid of continuous values. @@ -126,7 +124,6 @@ def get_coordinate(self, value: FloatND) -> FloatND: ) -@beartype_init(GRID_CONF) class LogSpacedGrid(UniformContinuousGrid): """A logarithmically spaced grid of continuous values. @@ -209,7 +206,6 @@ def _init_uniform_grid( object.__setattr__(grid, "distributed", distributed) -@beartype_init(GRID_CONF) @dataclass(frozen=True, kw_only=True, init=False) class IrregSpacedGrid(ContinuousGrid): """A grid of continuous values at irregular (user-specified) points. diff --git a/src/lcm/grids/discrete.py b/src/lcm/grids/discrete.py index 1d4409f46..f6fa98043 100644 --- a/src/lcm/grids/discrete.py +++ b/src/lcm/grids/discrete.py @@ -1,13 +1,11 @@ import jax.numpy as jnp -from lcm._beartype_conf import GRID_CONF, beartype_init from lcm.grids.base import Grid from lcm.grids.categorical import _validate_discrete_grid from lcm.typing import Int1D from lcm.utils.containers import get_field_names_and_values -@beartype_init(GRID_CONF) class DiscreteGrid(Grid): """A discrete grid defining the outcome space of a categorical variable. diff --git a/src/lcm/grids/piecewise.py b/src/lcm/grids/piecewise.py index c0e23a03f..ac0440915 100644 --- a/src/lcm/grids/piecewise.py +++ b/src/lcm/grids/piecewise.py @@ -4,7 +4,6 @@ import jax.numpy as jnp import portion -from lcm._beartype_conf import GRID_CONF, beartype_init from lcm.exceptions import GridInitializationError, format_messages from lcm.grids import coordinates as grid_coordinates from lcm.grids.continuous import ContinuousGrid @@ -44,7 +43,6 @@ def __init__( object.__setattr__(self, "n_points", jnp.int32(n_points)) -@beartype_init(GRID_CONF) @dataclass(frozen=True, kw_only=True) class PiecewiseLinSpacedGrid(ContinuousGrid): """A piecewise linearly spaced grid with multiple segments. @@ -108,7 +106,6 @@ def get_coordinate(self, value: FloatND) -> FloatND: return self._cumulative_offsets[piece_idx] + local_coord -@beartype_init(GRID_CONF) @dataclass(frozen=True, kw_only=True) class PiecewiseLogSpacedGrid(ContinuousGrid): """A piecewise logarithmically spaced grid with multiple segments. diff --git a/src/lcm/shocks/ar1.py b/src/lcm/shocks/ar1.py index 7cef736be..87785d938 100644 --- a/src/lcm/shocks/ar1.py +++ b/src/lcm/shocks/ar1.py @@ -7,7 +7,6 @@ import jax.numpy as jnp from jax.scipy.stats.norm import cdf -from lcm._beartype_conf import GRID_CONF, beartype_init from lcm.shocks._base import ( _gauss_hermite_normal, _mixture_cdf, @@ -30,7 +29,6 @@ def draw_shock( ) -> ScalarFloat: ... -@beartype_init(GRID_CONF) @dataclass(frozen=True, kw_only=True) class Tauchen(_ShockGridAR1): r"""AR(1) shock discretized via Tauchen (1986). @@ -130,7 +128,6 @@ def draw_shock( ) -@beartype_init(GRID_CONF) @dataclass(frozen=True, kw_only=True) class Rouwenhorst(_ShockGridAR1): r"""AR(1) shock discretized via Rouwenhorst (1995). @@ -199,7 +196,6 @@ def draw_shock( ) -@beartype_init(GRID_CONF) @dataclass(frozen=True, kw_only=True) class TauchenNormalMixture(_ShockGridAR1): r"""AR(1) shock with mixture-of-normals innovations, discretized via Tauchen. diff --git a/src/lcm/shocks/iid.py b/src/lcm/shocks/iid.py index e2e410a92..8fd3c59e8 100644 --- a/src/lcm/shocks/iid.py +++ b/src/lcm/shocks/iid.py @@ -6,7 +6,6 @@ import jax.numpy as jnp from jax.scipy.stats.norm import cdf -from lcm._beartype_conf import GRID_CONF, beartype_init from lcm.shocks._base import ( _gauss_hermite_normal, _mixture_cdf, @@ -28,7 +27,6 @@ def draw_shock( ) -> ScalarFloat: ... -@beartype_init(GRID_CONF) @dataclass(frozen=True, kw_only=True) class Uniform(_ShockGridIID): r"""Discretized iid uniform shock: $U(\text{start}, \text{stop})$. @@ -63,7 +61,6 @@ def draw_shock( ) -@beartype_init(GRID_CONF) @dataclass(frozen=True, kw_only=True) class Normal(_ShockGridIID): r"""Discretized iid normal shock: $N(\mu_\varepsilon, \sigma_\varepsilon^2)$. @@ -138,7 +135,6 @@ def draw_shock( return params["mu"] + params["sigma"] * jax.random.normal(key=key) -@beartype_init(GRID_CONF) @dataclass(frozen=True, kw_only=True) class LogNormal(_ShockGridIID): r"""Discretized iid log-normal shock: $\ln X \sim N(\mu, \sigma^2)$.""" @@ -204,7 +200,6 @@ def draw_shock( return jnp.exp(params["mu"] + params["sigma"] * jax.random.normal(key=key)) -@beartype_init(GRID_CONF) @dataclass(frozen=True, kw_only=True) class NormalMixture(_ShockGridIID): r"""Discretized IID normal-mixture shock. From a59b3d646e0590a07b92dfc2023bccdac1344258 Mon Sep 17 00:00:00 2001 From: Hans-Martin von Gaudecker Date: Thu, 14 May 2026 14:36:09 +0200 Subject: [PATCH 44/77] Widen Model.simulate initial_conditions hint to accept pd.DataFrame. The method body and docstring already support a DataFrame (auto-converted via initial_conditions_from_dataframe); the beartype-enforced annotation rejected it. Restore the documented contract. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/lcm/model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lcm/model.py b/src/lcm/model.py index dd20184cb..4c7b62fdc 100644 --- a/src/lcm/model.py +++ b/src/lcm/model.py @@ -377,7 +377,7 @@ def simulate( self, *, params: UserParams, - initial_conditions: Mapping[str, FloatND | IntND], + initial_conditions: Mapping[str, FloatND | IntND] | pd.DataFrame, period_to_regime_to_V_arr: MappingProxyType[ int, MappingProxyType[RegimeName, FloatND] ] From 3371206ac14c61fb9fe2ab42b696f58a19c5f964 Mon Sep 17 00:00:00 2001 From: Hans-Martin von Gaudecker Date: Thu, 14 May 2026 16:10:51 +0200 Subject: [PATCH 45/77] Activate beartype claw on lcm.solution and lcm.simulation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extends the AST-rewriting claw to the post-perimeter simulation and solution packages with a plain `INTERNAL_CONF` (violations surface as `BeartypeCallHintViolation` — an internal-bug signal, distinct from the perimeter packages' project-exception confs). Drift surfaced and fixed: - `transitions.random_id`: vmap body returns a 0-d scalar; return hint corrected `Int1D` -> `ScalarInt`. - `result._extract_period_data`: `subject_id` built without a pinned dtype; pinned to `int32`. - `initial_conditions._check_regime_feasibility`: `idx_arr` built from a Python list without a pinned dtype; pinned to `int32`. Canonicalization barrier: - New `canonicalize_initial_conditions` casts every initial-conditions array to its canonical pylcm dtype at the `Model.simulate` perimeter, before validation and the simulate stack. Downstream code keeps strict canonical annotations. Tests: - `test_beartype_claw.py`: claw-liveness checks for both packages. - Direct-call tests updated to pass canonical-dtype inputs (the claw now enforces what these internal functions always required); `InternalRegimeMock` subclasses `InternalRegime` so `isinstance` holds at solve()'s boundary. Co-Authored-By: Claude Opus 4.7 --- src/lcm/__init__.py | 25 ++++++--- src/lcm/_beartype_conf.py | 21 ++++++-- src/lcm/model.py | 9 +++- src/lcm/simulation/initial_conditions.py | 56 ++++++++++++++++++++- src/lcm/simulation/result.py | 2 +- src/lcm/simulation/transitions.py | 2 +- tests/simulation/test_initial_conditions.py | 6 ++- tests/simulation/test_simulate.py | 2 +- tests/solution/test_solve_brute.py | 37 ++++++++------ tests/test_beartype_claw.py | 52 +++++++++++++++++++ tests/test_float_dtype_invariants.py | 24 ++++++--- tests/test_random.py | 15 ++++-- 12 files changed, 207 insertions(+), 44 deletions(-) create mode 100644 tests/test_beartype_claw.py diff --git a/src/lcm/__init__.py b/src/lcm/__init__.py index e18fdc350..df76fdfee 100644 --- a/src/lcm/__init__.py +++ b/src/lcm/__init__.py @@ -32,22 +32,31 @@ with contextlib.suppress(ImportError): import pdbp # noqa: F401 -# Install beartype's AST-rewriting claw on `lcm.grids`, `lcm.shocks`, -# and `lcm.params` before any submodule of those packages is imported. -# The claw transforms each matching module's AST at first import to -# insert runtime type checks; if it isn't registered before the import +# Install beartype's AST-rewriting claw on the instrumented `lcm` +# subpackages before any of their submodules is imported. The claw +# transforms each matching module's AST at first import to insert +# runtime type checks; if it isn't registered before the import # happens, the affected module loads uninstrumented and `sys.modules` -# caches the unchecked version for the rest of the process. The -# per-package `BeartypeConf` maps type violations to the project -# exception most natural to that subpackage (see `lcm._beartype_conf`). +# caches the unchecked version for the rest of the process. Perimeter +# packages use a `BeartypeConf` mapping violations to the project +# exception most natural to that subpackage; `lcm.solution` and +# `lcm.simulation` run behind the perimeter and use `INTERNAL_CONF` +# (see `lcm._beartype_conf`). from beartype.claw import beartype_package -from lcm._beartype_conf import GRID_CONF, PARAMS_CONF, REGIME_BUILDING_CONF +from lcm._beartype_conf import ( + GRID_CONF, + INTERNAL_CONF, + PARAMS_CONF, + REGIME_BUILDING_CONF, +) beartype_package("lcm.grids", conf=GRID_CONF) beartype_package("lcm.shocks", conf=GRID_CONF) beartype_package("lcm.params", conf=PARAMS_CONF) beartype_package("lcm.regime_building", conf=REGIME_BUILDING_CONF) +beartype_package("lcm.solution", conf=INTERNAL_CONF) +beartype_package("lcm.simulation", conf=INTERNAL_CONF) from lcm import shocks # noqa: E402 from lcm._version import __version__ # noqa: E402 diff --git a/src/lcm/_beartype_conf.py b/src/lcm/_beartype_conf.py index 6d3f1175f..a2d8bf709 100644 --- a/src/lcm/_beartype_conf.py +++ b/src/lcm/_beartype_conf.py @@ -1,8 +1,10 @@ -"""Per-exception `BeartypeConf` instances used at the pylcm perimeter. +"""`BeartypeConf` instances for pylcm's perimeter and internal claws. -Decorators at user-facing entry points configure beartype to raise -the existing project exception class on parameter-type violations, -preserving the documented exception hierarchy. +Perimeter confs map type violations to the existing project exception +class, preserving the documented exception hierarchy at user-facing +entry points. `INTERNAL_CONF` covers packages that run behind the +perimeter, where a violation is an internal bug rather than user error +and so surfaces as beartype's own `BeartypeCallHintViolation`. """ @@ -50,3 +52,14 @@ def _conf(exc: type[Exception]) -> BeartypeConf: # Used by the claw on `lcm.regime_building` (regime compilation pipeline, # part of model construction). REGIME_BUILDING_CONF = _conf(ModelInitializationError) + +# Used by the claw on `lcm.solution` and `lcm.simulation`. These packages run +# behind the construction perimeter — their inputs are already validated by +# `Model.solve` / `Model.simulate` and `validate_initial_conditions` — so a +# type violation there is an internal pylcm bug, not user error. It surfaces +# as beartype's own `BeartypeCallHintViolation` rather than a project +# exception, which would mislabel it as a user-facing error. +INTERNAL_CONF = BeartypeConf( + strategy=BeartypeStrategy.On, + is_pep484_tower=True, +) diff --git a/src/lcm/model.py b/src/lcm/model.py index 4c7b62fdc..5a099f5d3 100644 --- a/src/lcm/model.py +++ b/src/lcm/model.py @@ -35,7 +35,10 @@ from lcm.regime import Regime from lcm.regime_building.processing import InternalRegime from lcm.simulation.compile import compile_all_simulate_functions -from lcm.simulation.initial_conditions import validate_initial_conditions +from lcm.simulation.initial_conditions import ( + canonicalize_initial_conditions, + validate_initial_conditions, +) from lcm.simulation.result import SimulationResult, get_simulation_output_dtypes from lcm.simulation.simulate import simulate from lcm.solution.solve_brute import solve @@ -438,6 +441,10 @@ def simulate( regimes=self.regimes, regime_names_to_ids=self.regime_names_to_ids, ) + initial_conditions = canonicalize_initial_conditions( + initial_conditions=initial_conditions, + internal_regimes=self.internal_regimes, + ) internal_params = self._process_params(params) if check_initial_conditions: validate_initial_conditions( diff --git a/src/lcm/simulation/initial_conditions.py b/src/lcm/simulation/initial_conditions.py index 89bcf5371..cc8e0c637 100644 --- a/src/lcm/simulation/initial_conditions.py +++ b/src/lcm/simulation/initial_conditions.py @@ -15,7 +15,11 @@ from jax import numpy as jnp from lcm.ages import PSEUDO_STATE_NAMES, AgeGrid -from lcm.dtypes import canonical_float_dtype, safe_to_float_dtype +from lcm.dtypes import ( + canonical_float_dtype, + safe_to_float_dtype, + safe_to_int_dtype, +) from lcm.exceptions import ( InvalidInitialConditionsError, PyLCMError, @@ -47,6 +51,54 @@ MISSING_CAT_CODE = jnp.iinfo(jnp.int32).min +def canonicalize_initial_conditions( + *, + initial_conditions: Mapping[str, object], + internal_regimes: MappingProxyType[RegimeName, InternalRegime], +) -> dict[str, FloatND | IntND]: + """Cast every initial-conditions array to its canonical pylcm dtype. + + This is pylcm's simulation input boundary: `"regime"` and discrete states + cast to `int32`; `"age"` and continuous states cast to the canonical float + dtype. Keys that match no model state are cast by their array kind (integer + arrays to `int32`, otherwise to canonical float) and left for + `validate_initial_conditions` to report. Downstream validation and the + simulate stack receive canonical-dtype arrays and do not re-cast. + + Args: + initial_conditions: Mapping of state names (plus `"regime"`) to + user-supplied arrays of any integer or floating dtype. + internal_regimes: Immutable mapping of regime names to internal regime + instances, used to classify each state as discrete or continuous. + + Returns: + Mapping of the same keys to JAX arrays at their canonical dtype. + + """ + discrete_state_names = { + state_name + for internal_regime in internal_regimes.values() + for state_name, grid in internal_regime.grids.items() + if isinstance(grid, DiscreteGrid) + } + known_state_names = { + state_name + for internal_regime in internal_regimes.values() + for state_name in internal_regime.grids + } + canonical: dict[str, FloatND | IntND] = {} + for name, value in initial_conditions.items(): + if name == "regime" or name in discrete_state_names: + canonical[name] = safe_to_int_dtype(value, name=name) + elif name == "age" or name in known_state_names: + canonical[name] = safe_to_float_dtype(value, name=name) + elif np.asarray(value).dtype.kind in "iu": + canonical[name] = safe_to_int_dtype(value, name=name) + else: + canonical[name] = safe_to_float_dtype(value, name=name) + return canonical + + def build_initial_states( *, initial_states: Mapping[str, FloatND | IntND], @@ -641,7 +693,7 @@ def _check_regime_feasibility( # noqa: C901 needs_period = "period" in accepted # Build per-subject state arrays - idx_arr = jnp.array(subject_indices) + idx_arr = jnp.array(subject_indices, dtype=jnp.int32) subject_states: dict[StateName, FloatND | IntND] = {} for sn in state_names: if sn in accepted: diff --git a/src/lcm/simulation/result.py b/src/lcm/simulation/result.py index d40700b63..da75dc59a 100644 --- a/src/lcm/simulation/result.py +++ b/src/lcm/simulation/result.py @@ -527,7 +527,7 @@ def _extract_period_data( ) -> dict[str, FloatND | IntND | BoolND]: """Extract data from a single period's simulation results.""" data: dict[str, FloatND | IntND | BoolND] = { - "subject_id": jnp.arange(len(result.in_regime)), + "subject_id": jnp.arange(len(result.in_regime), dtype=jnp.int32), "period": jnp.full_like(result.in_regime, period, dtype=jnp.int32), "_in_regime": result.in_regime, "value": result.V_arr, diff --git a/src/lcm/simulation/transitions.py b/src/lcm/simulation/transitions.py index 30b551088..cedd90a39 100644 --- a/src/lcm/simulation/transitions.py +++ b/src/lcm/simulation/transitions.py @@ -262,7 +262,7 @@ def draw_key_from_dict( def random_id( key: PRNGKeyND, p: Float1D, - ) -> Int1D: + ) -> ScalarInt: return jax.random.choice( key, regime_ids, diff --git a/tests/simulation/test_initial_conditions.py b/tests/simulation/test_initial_conditions.py index d54c56db1..882e0ffca 100644 --- a/tests/simulation/test_initial_conditions.py +++ b/tests/simulation/test_initial_conditions.py @@ -21,8 +21,10 @@ ScalarInt, ) -_ACTIVE = 0 -_TERMINAL = 1 +# Regime-id codes are canonical `int32`; `validate_initial_conditions` is an +# internal function that receives already-canonicalized initial conditions. +_ACTIVE = jnp.int32(0) +_TERMINAL = jnp.int32(1) @pytest.fixture diff --git a/tests/simulation/test_simulate.py b/tests/simulation/test_simulate.py index 9e5e5b668..a4010e5e1 100644 --- a/tests/simulation/test_simulate.py +++ b/tests/simulation/test_simulate.py @@ -468,7 +468,7 @@ def test_additional_targets_all_with_stochastic_transitions(): def test_retrieve_actions(): got = _lookup_values_from_indices( - flat_indices=jnp.array([0, 3, 7]), + flat_indices=jnp.array([0, 3, 7], dtype=jnp.int32), grids=MappingProxyType( {"a": jnp.linspace(0, 1, 5), "b": jnp.linspace(10, 20, 6)} ), diff --git a/tests/solution/test_solve_brute.py b/tests/solution/test_solve_brute.py index 5051c751c..3151e9e7a 100644 --- a/tests/solution/test_solve_brute.py +++ b/tests/solution/test_solve_brute.py @@ -7,7 +7,7 @@ from lcm.ages import AgeGrid from lcm.grids import Grid -from lcm.interfaces import StateActionSpace +from lcm.interfaces import InternalRegime, StateActionSpace from lcm.regime_building.max_Q_over_a import get_max_Q_over_a from lcm.regime_building.ndimage import map_coordinates from lcm.solution.solve_brute import solve @@ -23,21 +23,30 @@ class SolveFunctionsMock: compute_intermediates: dict = dataclasses.field(default_factory=dict) -@dataclasses.dataclass(frozen=True) -class InternalRegimeMock: +class InternalRegimeMock(InternalRegime): """Mock InternalRegime with only the attributes required by solve(). - The solve() function only accesses: - - _base_state_action_space: StateActionSpace object - - state_action_space(): method returning the state-action space - - solve_functions.max_Q_over_a: dict mapping period to max_Q_over_a function - - active_periods: list of periods the regime is active + Inherits from `InternalRegime` so `isinstance(x, InternalRegime)` holds at + the beartype-checked perimeter of `solve()`, but bypasses the dataclass + `__init__` so tests can supply only the attributes `solve()` reads: + - `_base_state_action_space`: StateActionSpace object + - `state_action_space()`: method returning the state-action space + - `solve_functions.max_Q_over_a`: dict mapping period to max_Q_over_a function + - `active_periods`: list of periods the regime is active """ - _base_state_action_space: StateActionSpace - solve_functions: SolveFunctionsMock - active_periods: list[int] - grids: MappingProxyType[StateOrActionName, Grid] + def __init__( + self, + *, + _base_state_action_space: StateActionSpace, + solve_functions: SolveFunctionsMock, + active_periods: list[int], + grids: MappingProxyType[StateOrActionName, Grid], + ) -> None: + object.__setattr__(self, "_base_state_action_space", _base_state_action_space) + object.__setattr__(self, "solve_functions", solve_functions) + object.__setattr__(self, "active_periods", active_periods) + object.__setattr__(self, "grids", grids) def state_action_space(self, regime_params): # noqa: ARG002 return self._base_state_action_space @@ -134,7 +143,7 @@ def _Q_and_F( solution = solve( internal_params=MappingProxyType({"default": internal_params}), ages=AgeGrid(start=0, stop=2, step="Y"), - internal_regimes={"default": internal_regime}, # ty: ignore[invalid-argument-type] + internal_regimes=MappingProxyType({"default": internal_regime}), logger=get_logger(log_level="off"), enable_jit=False, ) @@ -195,7 +204,7 @@ def _Q_and_F(a, c, b, d, next_regime_to_V_arr, period, age): # noqa: ARG001 got = solve( internal_params=MappingProxyType({"default": MappingProxyType({})}), ages=AgeGrid(start=0, stop=2, step="Y"), - internal_regimes={"default": internal_regime}, # ty: ignore[invalid-argument-type] + internal_regimes=MappingProxyType({"default": internal_regime}), logger=get_logger(log_level="off"), enable_jit=False, ) diff --git a/tests/test_beartype_claw.py b/tests/test_beartype_claw.py new file mode 100644 index 000000000..9f57fc777 --- /dev/null +++ b/tests/test_beartype_claw.py @@ -0,0 +1,52 @@ +"""The beartype claw is live on `lcm.solution` and `lcm.simulation`. + +These packages sit *behind* the construction perimeter: by the time their +functions run, user input has already been validated by `Model.solve` / +`Model.simulate` and `validate_initial_conditions`. A type violation here +therefore signals an internal pylcm bug, so the claw is configured to raise +beartype's own `BeartypeCallHintViolation` rather than a project exception. + +Each test calls an internal function with one argument of the wrong type, +chosen so the call would return cleanly if the function were *not* +instrumented — the violation is what proves the claw is installed. +""" + +import jax.numpy as jnp +import numpy as np +import pytest +from beartype.roar import BeartypeCallHintViolation + +from lcm import AgeGrid +from lcm.simulation.simulate import _compute_starting_periods +from lcm.solution.solve_brute import _log_per_period_stats + + +def test_claw_checks_lcm_simulation() -> None: + """An ill-typed argument to an `lcm.simulation` function is rejected. + + `_compute_starting_periods` annotates `initial_ages` as `Float1D` (a JAX + array). A NumPy array would otherwise be accepted by `jnp.searchsorted` + and the call would return cleanly; the claw turns it into a violation. + """ + with pytest.raises(BeartypeCallHintViolation): + _compute_starting_periods( + initial_ages=np.array([25.0]), # ty: ignore[invalid-argument-type] + ages=AgeGrid(start=25, stop=75, step="Y"), + ) + + +def test_claw_checks_lcm_solution() -> None: + """An ill-typed argument to an `lcm.solution` function is rejected. + + `_log_per_period_stats` annotates `logger` as `logging.Logger`. With an + empty `diagnostic_rows` the body never runs, so an un-instrumented call + would return `None`; the claw turns the bad `logger` into a violation. + """ + with pytest.raises(BeartypeCallHintViolation): + _log_per_period_stats( + logger="not a logger", # ty: ignore[invalid-argument-type] + diagnostic_rows=[], + mins=jnp.array([]), + maxs=jnp.array([]), + means=jnp.array([]), + ) diff --git a/tests/test_float_dtype_invariants.py b/tests/test_float_dtype_invariants.py index 940fe25cf..81535079f 100644 --- a/tests/test_float_dtype_invariants.py +++ b/tests/test_float_dtype_invariants.py @@ -12,7 +12,10 @@ from lcm.params import MappingLeaf from lcm.params.processing import process_params from lcm.params.sequence_leaf import SequenceLeaf -from lcm.simulation.initial_conditions import build_initial_states +from lcm.simulation.initial_conditions import ( + build_initial_states, + canonicalize_initial_conditions, +) from lcm.typing import ParamsTemplate from lcm.utils.containers import ensure_containers_are_immutable from tests.test_models.deterministic.regression import ( @@ -36,18 +39,25 @@ def _as_template(plain: dict) -> ParamsTemplate: ) -def test_build_initial_states_casts_user_float64_to_canonical(x64_disabled: None): - """A float64 continuous initial state lands at `canonical_float_dtype()`.""" +def test_canonicalize_initial_conditions_casts_user_float64_to_canonical( + x64_disabled: None, +): + """A float64 continuous initial state lands at `canonical_float_dtype()`. + + `canonicalize_initial_conditions` is pylcm's simulation input boundary: + user arrays of any dtype are cast to their canonical pylcm dtype before + validation and the simulate stack see them. + """ model = get_model(n_periods=3) - initial_states = { + initial_conditions = { "wealth": np.asarray([20.0, 50.0], dtype=np.float64), "age": np.asarray([18.0, 18.0], dtype=np.float64), } - states_per_regime = build_initial_states( - initial_states=initial_states, # ty: ignore[invalid-argument-type] + canonical = canonicalize_initial_conditions( + initial_conditions=initial_conditions, internal_regimes=model.internal_regimes, ) - assert states_per_regime["working_life"]["wealth"].dtype == canonical_float_dtype() + assert canonical["wealth"].dtype == canonical_float_dtype() def test_build_initial_states_casts_user_int_to_canonical(x64_disabled: None): diff --git a/tests/test_random.py b/tests/test_random.py index 8e4396e22..437686113 100644 --- a/tests/test_random.py +++ b/tests/test_random.py @@ -1,14 +1,23 @@ +import jax import jax.numpy as jnp from lcm.simulation.random import generate_simulation_keys def test_generate_simulation_keys(): - key = jnp.arange(2, dtype="uint32") # PRNG dtype + """Each per-name key stream split from a seed key is mutually distinct.""" + key = jax.random.key(0) stochastic_next_functions = ["a", "b"] got = generate_simulation_keys( key=key, names=stochastic_next_functions, n_initial_states=1 ) - # assert that all generated keys are different from each other - matrix = jnp.array([key, got[0], got[1]["key_a"][0], got[1]["key_b"][0]]) + # Compare the raw key data: distinct keys give a rank-2 matrix of key rows. + matrix = jnp.array( + [ + jax.random.key_data(key), + jax.random.key_data(got[0]), + jax.random.key_data(got[1]["key_a"][0]), + jax.random.key_data(got[1]["key_b"][0]), + ] + ) assert jnp.linalg.matrix_rank(matrix) == 2 From 3f8dd993aaee0db64e6da39854ca8f03dfc35d8b Mon Sep 17 00:00:00 2001 From: Hans-Martin von Gaudecker Date: Thu, 14 May 2026 16:59:10 +0200 Subject: [PATCH 46/77] Pin __annotations__ on regime-transition-prob wrappers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `concatenate_functions` reconciles the regime DAG by reading each function's `__annotations__`, not its `__signature__`. The wrappers built by `_wrap_regime_transition_probs` and `_wrap_deterministic_regime_transition` could end up with a correct `__signature__` but an empty `__annotations__` when the wrapped function carried deferred (PEP 649) annotations through `functools.wraps` — surfacing as a spurious `AnnotationMismatchError` during model construction. Both wrappers now set `__annotations__` explicitly on the returned object so the DAG composition sees honest annotations. Co-Authored-By: Claude Opus 4.7 --- src/lcm/regime_building/processing.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/lcm/regime_building/processing.py b/src/lcm/regime_building/processing.py index d5285d96c..73abc21a2 100644 --- a/src/lcm/regime_building/processing.py +++ b/src/lcm/regime_building/processing.py @@ -1293,6 +1293,11 @@ def wrapped( {name: result[idx] for idx, name in enumerate(regime_names)} ) + # Pin `__annotations__` on the final wrapper: `concatenate_functions` + # reads `__annotations__` (not `__signature__`) to reconcile the DAG, and + # the decorator stack can drop them when `func` carries deferred (PEP 649) + # annotations through `functools.wraps`. + wrapped.__annotations__ = {**annotations, "return": return_annotation} return wrapped @@ -1329,6 +1334,11 @@ def wrapped( regime_idx = func(*args, **kwargs) return jax.nn.one_hot(regime_idx, n_regimes) + # Pin `__annotations__` on the final wrapper: `concatenate_functions` + # reads `__annotations__` (not `__signature__`) to reconcile the DAG, and + # the decorator stack can drop them when `func` carries deferred (PEP 649) + # annotations through `functools.wraps`. + wrapped.__annotations__ = {**annotations, "return": "FloatND"} return wrapped From 249d0f46761c4ae04b1eba9b6a78fa6f01ec262b Mon Sep 17 00:00:00 2001 From: Hans-Martin von Gaudecker Date: Thu, 14 May 2026 17:39:48 +0200 Subject: [PATCH 47/77] Pin period arrays to int32 in initial-conditions validation `_collect_feasibility_errors` and `_check_regime_feasibility` built `period` arrays via `jnp.array([...])` over Python ints, yielding int64 under `jax_enable_x64`. Under the canonical-int32 invariant these must be pinned to int32, otherwise an int64 `period` leaks into the feasibility check and into user transition/leaf functions whose `period` parameter is typed `int32`. Co-Authored-By: Claude Opus 4.7 --- src/lcm/simulation/initial_conditions.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/lcm/simulation/initial_conditions.py b/src/lcm/simulation/initial_conditions.py index cc8e0c637..9ee1284d0 100644 --- a/src/lcm/simulation/initial_conditions.py +++ b/src/lcm/simulation/initial_conditions.py @@ -426,7 +426,9 @@ def _collect_structural_errors( else: # Validate that each subject's initial regime is active at their starting age. # Only safe to run when all ages are valid (so age_to_period lookup succeeds). - periods = jnp.array([ages.age_to_period(a.item()) for a in age_values]) + periods = jnp.array( + [ages.age_to_period(a.item()) for a in age_values], dtype=jnp.int32 + ) active_mask = jnp.ones(regime_id_arr.size, dtype=bool) for regime_name, internal_regime in internal_regimes.items(): @@ -703,7 +705,8 @@ def _check_regime_feasibility( # noqa: C901 subject_states["age"] = initial_states["age"][idx_arr] if needs_period: subject_states["period"] = jnp.array( - [ages.age_to_period(a.item()) for a in initial_states["age"][idx_arr]] + [ages.age_to_period(a.item()) for a in initial_states["age"][idx_arr]], + dtype=jnp.int32, ) # Split actions and params — actions are vmapped over, params are not From fc704af47863e43ce4a8e981e928ce2f8098c387 Mon Sep 17 00:00:00 2001 From: Hans-Martin von Gaudecker Date: Thu, 14 May 2026 18:27:02 +0200 Subject: [PATCH 48/77] Bump benchmarks aca-model pin to feat/distributed-assets-grid tip Co-Authored-By: Claude Opus 4.7 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 6e8f346d5..613efb55a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -99,7 +99,7 @@ tests-cuda13 = { features = [ "tests", "cuda13" ], solve-group = "cuda13" } tests-metal = { features = [ "tests", "metal" ], solve-group = "metal" } type-checking = { features = [ "type-checking", "tests" ], solve-group = "default" } [tool.pixi.feature.benchmarks.pypi-dependencies] -aca-model = { git = "https://github.com/OpenSourceEconomics/aca-model.git", rev = "b807b286175e00f8a721f039f4afd657bbb10e2f" } +aca-model = { git = "https://github.com/OpenSourceEconomics/aca-model.git", rev = "a212581cd7d532fb9f6fb4c34f2461e6e6417e93" } [tool.pixi.feature.cuda12] platforms = [ "linux-64" ] system-requirements = { cuda = "12" } From a38c1c9a0c51895f3eb76030a37d768e4029d8ac Mon Sep 17 00:00:00 2001 From: Hans-Martin von Gaudecker Date: Thu, 14 May 2026 21:31:09 +0200 Subject: [PATCH 49/77] Re-lock pixi.lock after aca-model pin bump and main merge Commit fc704af bumped the aca-model git pin without re-locking, which left pixi.lock stale: CI's `pixi install` dirtied the worktree (asv-run refuses) and `pixi list --frozen` failed on the locked Windows/GPU jobs. Co-Authored-By: Claude Opus 4.7 --- pixi.lock | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/pixi.lock b/pixi.lock index f46905259..4970c069b 100644 --- a/pixi.lock +++ b/pixi.lock @@ -270,7 +270,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-1.3.2-h25fd6f3_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda - - pypi: git+https://github.com/OpenSourceEconomics/aca-model.git?rev=b807b286175e00f8a721f039f4afd657bbb10e2f#b807b286175e00f8a721f039f4afd657bbb10e2f + - pypi: git+https://github.com/OpenSourceEconomics/aca-model.git?rev=a212581cd7d532fb9f6fb4c34f2461e6e6417e93#a212581cd7d532fb9f6fb4c34f2461e6e6417e93 - pypi: https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ae/44/c1221527f6a71a01ec6fbad7fa78f1d50dfa02217385cf0fa3eec7087d59/click-8.3.3-py3-none-any.whl @@ -5347,11 +5347,12 @@ packages: purls: [] size: 8191 timestamp: 1744137672556 -- pypi: git+https://github.com/OpenSourceEconomics/aca-model.git?rev=b807b286175e00f8a721f039f4afd657bbb10e2f#b807b286175e00f8a721f039f4afd657bbb10e2f +- pypi: git+https://github.com/OpenSourceEconomics/aca-model.git?rev=a212581cd7d532fb9f6fb4c34f2461e6e6417e93#a212581cd7d532fb9f6fb4c34f2461e6e6417e93 name: aca-model version: 0.0.0 requires_dist: - attrs + - beartype - cloudpickle - dags - estimagic @@ -14085,8 +14086,8 @@ packages: timestamp: 1774796815820 - pypi: ./ name: pylcm - version: 0.0.2.dev182+g1a92dffec.d20260514 - sha256: 2ac3c0e6987658df12a93886cc7ea80b12456826f61aa1720a0c56e8fd828128 + version: 0.0.2.dev207+gfc704af47 + sha256: d6e0084f7dab95ac09fd6acc2e9a5eefb8f29fbbd2f8f61d34d587ccb5b21e38 requires_dist: - beartype>=0.21 - cloudpickle>=3.1.2 From 8014d8132012399332a346ead2384d0658133d91 Mon Sep 17 00:00:00 2001 From: Hans-Martin von Gaudecker Date: Thu, 14 May 2026 22:23:19 +0200 Subject: [PATCH 50/77] Pin period to int32 in regime-transition validation `_validate_regime_transition_single` closed a Python-int `period` over the `jax.vmap` body; under x64 it traced as int64 and reached the regime transition function as int64, violating any int32 `period` contract (the aca-model beartype claw flags this on `income()`). Cast to `jnp.int32` before the closure so both the vmap and scalar branches pass it as int32. Co-Authored-By: Claude Opus 4.7 --- src/lcm/utils/error_handling.py | 8 ++- .../test_validate_regime_transition_probs.py | 50 +++++++++++++++++++ 2 files changed, 56 insertions(+), 2 deletions(-) diff --git a/src/lcm/utils/error_handling.py b/src/lcm/utils/error_handling.py index 396d62dd7..d3a2e28e1 100644 --- a/src/lcm/utils/error_handling.py +++ b/src/lcm/utils/error_handling.py @@ -488,6 +488,10 @@ def _validate_regime_transition_single( grid_var_names = list(grids.keys()) grid_arrays = list(grids.values()) + # Pin to int32: a Python-int `period` traced through `jax.vmap` becomes + # int64 under x64, breaking any int32 `period` contract downstream. + period_int32 = jnp.int32(period) + if grid_arrays: mesh = jnp.meshgrid(*grid_arrays, indexing="ij") flat_arrays = [m.ravel() for m in mesh] @@ -497,7 +501,7 @@ def _call( _names: list[str] = grid_var_names, _params: dict = filtered_params, _func: object = regime_transition_func, - _period: int = period, + _period: ScalarInt = period_int32, _age: ScalarInt | ScalarFloat = ages.values[period], # noqa: PD011 ) -> MappingProxyType[RegimeName, FloatND]: kwargs = dict(zip(_names, args, strict=True)) @@ -513,7 +517,7 @@ def _call( regime_transition_probs: MappingProxyType[RegimeName, FloatND] = ( regime_transition_func( # ty: ignore[call-non-callable] **filtered_params, - period=period, + period=period_int32, age=ages.values[period], # noqa: PD011 ) ) diff --git a/tests/test_validate_regime_transition_probs.py b/tests/test_validate_regime_transition_probs.py index 9d97f1e87..108a6450d 100644 --- a/tests/test_validate_regime_transition_probs.py +++ b/tests/test_validate_regime_transition_probs.py @@ -240,6 +240,56 @@ def test_solve_catches_transition_bug_hidden_at_first_grid_point(): model.solve(params=params) +def test_regime_transition_validation_passes_period_as_int32(): + """Regime-transition validation hands the transition function a 0-d int32 `period`. + + The x64-enabled test suite would otherwise trace a Python-int `period` as + int64 inside `jax.vmap`, breaking any consumer that dtype-checks its + `period` slot (e.g. a beartyped `Period` hint). + """ + seen_period_dtypes: list = [] + + def _transition_recording_period( + action: DiscreteAction, period: ScalarInt + ) -> FloatND: + seen_period_dtypes.append(getattr(period, "dtype", None)) + # `action` keeps a grid variable in the signature so validation takes + # the `jax.vmap` path; both outcomes route to the always-active + # terminal regime so the probabilities are valid at every transition. + return jnp.where( + action == _Action.leave, + jnp.array([0.0, 1.0]), + jnp.array([0.0, 1.0]), + ) + + active = Regime( + transition=MarkovTransition(_transition_recording_period), + active=lambda age: age < 27, + actions={ + "action": DiscreteGrid(_Action), + "consumption": LinSpacedGrid(start=1, stop=10, n_points=5), + }, + states={"wealth": LinSpacedGrid(start=1, stop=10, n_points=5)}, + state_transitions={"wealth": lambda wealth, consumption: wealth - consumption}, + constraints={"budget": lambda consumption, wealth: consumption <= wealth}, + functions={"utility": lambda consumption: jnp.log(consumption)}, # noqa: PLW0108 + ) + terminal = Regime( + transition=None, + states={"wealth": LinSpacedGrid(start=1, stop=10, n_points=5)}, + functions={"utility": lambda wealth: jnp.log(wealth)}, # noqa: PLW0108 + ) + model = Model( + regimes={"active": active, "terminal": terminal}, + ages=AgeGrid(start=25, stop=27, step="Y"), + regime_id_class=_RegimeId, + ) + model.solve(params={"discount_factor": 0.95}) + + assert seen_period_dtypes + assert all(dtype == jnp.int32 for dtype in seen_period_dtypes) + + N_PERIODS = 4 From 601cb47bd8c0998d47bb7e74cbceba49ccc06cfc Mon Sep 17 00:00:00 2001 From: Hans-Martin von Gaudecker Date: Thu, 14 May 2026 22:23:39 +0200 Subject: [PATCH 51/77] Boilerplate: bump pixi-version to v0.68.1, update .ai-instructions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - main.yml: pixi-version v0.67.2 → v0.68.1 (current latest) - .ai-instructions submodule → afbb981 (latest main) Co-Authored-By: Claude Opus 4.7 --- .ai-instructions | 2 +- .github/workflows/main.yml | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.ai-instructions b/.ai-instructions index 609ac4a8d..afbb98140 160000 --- a/.ai-instructions +++ b/.ai-instructions @@ -1 +1 @@ -Subproject commit 609ac4a8d9262f93594f36ea382d30cd94ea07a4 +Subproject commit afbb98140f3f47724eec2f689ea78953ddcd60f3 diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 80b272c16..796f8dd51 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -28,7 +28,7 @@ jobs: - uses: actions/checkout@v6 - uses: prefix-dev/setup-pixi@v0.9.5 with: - pixi-version: v0.67.2 + pixi-version: v0.68.1 cache: true cache-write: ${{ github.event_name == 'push' && github.ref_name == 'main' }} environments: tests-cpu @@ -59,7 +59,7 @@ jobs: - uses: actions/checkout@v6 - uses: prefix-dev/setup-pixi@v0.9.5 with: - pixi-version: v0.67.2 + pixi-version: v0.68.1 cache: true cache-write: ${{ github.event_name == 'push' && github.ref_name == 'main' }} environments: type-checking @@ -82,7 +82,7 @@ jobs: - uses: actions/checkout@v6 - uses: prefix-dev/setup-pixi@v0.9.5 with: - pixi-version: v0.67.2 + pixi-version: v0.68.1 cache: true cache-write: ${{ github.event_name == 'push' && github.ref_name == 'main' }} environments: tests-cuda12 @@ -101,7 +101,7 @@ jobs: - uses: actions/checkout@v6 - uses: prefix-dev/setup-pixi@v0.9.5 with: - pixi-version: v0.67.2 + pixi-version: v0.68.1 cache: true cache-write: ${{ github.event_name == 'push' && github.ref_name == 'main' }} environments: tests-cuda12 @@ -116,7 +116,7 @@ jobs: # - uses: actions/checkout@v6 # - uses: prefix-dev/setup-pixi@v0.9.5 # with: - # pixi-version: v0.67.2 + # pixi-version: v0.68.1 # cache: true # cache-write: ${{ github.event_name == 'push' && github.ref_name == 'main' }} # environments: tests-cpu From 01c84cdfca1d85408c0938816d5ba0c8f8a9e6dc Mon Sep 17 00:00:00 2001 From: Hans-Martin von Gaudecker Date: Thu, 14 May 2026 22:47:48 +0200 Subject: [PATCH 52/77] Bump benchmarks aca-model pin to the average_consumption_equiv fix aca-model 6e282a5 stores average_consumption_equiv as a float, clearing the ScalarFloat claw violation in the GPU benchmark. Co-Authored-By: Claude Opus 4.7 --- pixi.lock | 10 +++++----- pyproject.toml | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pixi.lock b/pixi.lock index 4970c069b..6312f04de 100644 --- a/pixi.lock +++ b/pixi.lock @@ -270,7 +270,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-1.3.2-h25fd6f3_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda - - pypi: git+https://github.com/OpenSourceEconomics/aca-model.git?rev=a212581cd7d532fb9f6fb4c34f2461e6e6417e93#a212581cd7d532fb9f6fb4c34f2461e6e6417e93 + - pypi: git+https://github.com/OpenSourceEconomics/aca-model.git?rev=6e282a5b7447c8198b667fe77c52d06940c64201#6e282a5b7447c8198b667fe77c52d06940c64201 - pypi: https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ae/44/c1221527f6a71a01ec6fbad7fa78f1d50dfa02217385cf0fa3eec7087d59/click-8.3.3-py3-none-any.whl @@ -5347,9 +5347,9 @@ packages: purls: [] size: 8191 timestamp: 1744137672556 -- pypi: git+https://github.com/OpenSourceEconomics/aca-model.git?rev=a212581cd7d532fb9f6fb4c34f2461e6e6417e93#a212581cd7d532fb9f6fb4c34f2461e6e6417e93 +- pypi: git+https://github.com/OpenSourceEconomics/aca-model.git?rev=6e282a5b7447c8198b667fe77c52d06940c64201#6e282a5b7447c8198b667fe77c52d06940c64201 name: aca-model - version: 0.0.0 + version: 0.1.dev83+g6e282a5b7 requires_dist: - attrs - beartype @@ -14086,8 +14086,8 @@ packages: timestamp: 1774796815820 - pypi: ./ name: pylcm - version: 0.0.2.dev207+gfc704af47 - sha256: d6e0084f7dab95ac09fd6acc2e9a5eefb8f29fbbd2f8f61d34d587ccb5b21e38 + version: 0.0.2.dev212+g601cb47bd.d20260514 + sha256: d66f80e732b584ed1cfea9766b342f41c63161643fcc39fb5442ee59031cc726 requires_dist: - beartype>=0.21 - cloudpickle>=3.1.2 diff --git a/pyproject.toml b/pyproject.toml index 613efb55a..76f0f81bc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -99,7 +99,7 @@ tests-cuda13 = { features = [ "tests", "cuda13" ], solve-group = "cuda13" } tests-metal = { features = [ "tests", "metal" ], solve-group = "metal" } type-checking = { features = [ "type-checking", "tests" ], solve-group = "default" } [tool.pixi.feature.benchmarks.pypi-dependencies] -aca-model = { git = "https://github.com/OpenSourceEconomics/aca-model.git", rev = "a212581cd7d532fb9f6fb4c34f2461e6e6417e93" } +aca-model = { git = "https://github.com/OpenSourceEconomics/aca-model.git", rev = "6e282a5b7447c8198b667fe77c52d06940c64201" } [tool.pixi.feature.cuda12] platforms = [ "linux-64" ] system-requirements = { cuda = "12" } From 326b7609e49adc7085f278e68221d84e30f6c61d Mon Sep 17 00:00:00 2001 From: Hans-Martin von Gaudecker Date: Thu, 14 May 2026 23:32:12 +0200 Subject: [PATCH 53/77] Claw lcm.utils.error_handling behind the construction perimeter Extends the beartype claw to `lcm.utils.error_handling` with `INTERNAL_CONF`. Narrows `validate_V` / `_enrich_with_diagnostics` parameters from bare `MappingProxyType` / `Mapping` to `MappingProxyType[RegimeName, FloatND]` and `FlatRegimeParams`, and switches the `Model` annotation on `validate_transition_probs` to the fully-qualified `lcm.model.Model` forward reference so the claw resolves it at first call rather than tripping the model.py import cycle at module-init time. Co-Authored-By: Claude Opus 4.7 --- src/lcm/__init__.py | 1 + src/lcm/utils/error_handling.py | 27 +++++++++++++++------------ tests/test_beartype_claw.py | 19 +++++++++++++++++++ 3 files changed, 35 insertions(+), 12 deletions(-) diff --git a/src/lcm/__init__.py b/src/lcm/__init__.py index df76fdfee..776f5c77b 100644 --- a/src/lcm/__init__.py +++ b/src/lcm/__init__.py @@ -57,6 +57,7 @@ beartype_package("lcm.regime_building", conf=REGIME_BUILDING_CONF) beartype_package("lcm.solution", conf=INTERNAL_CONF) beartype_package("lcm.simulation", conf=INTERNAL_CONF) +beartype_package("lcm.utils.error_handling", conf=INTERNAL_CONF) from lcm import shocks # noqa: E402 from lcm._version import __version__ # noqa: E402 diff --git a/src/lcm/utils/error_handling.py b/src/lcm/utils/error_handling.py index d3a2e28e1..e16ef057f 100644 --- a/src/lcm/utils/error_handling.py +++ b/src/lcm/utils/error_handling.py @@ -30,10 +30,13 @@ ) # Genuine circular import: model.py imports from this module at module level. -# Safe because Model is only used at runtime in validate_transition_probs, -# which is never called during module initialisation. +# The `model` parameter of `validate_transition_probs` is annotated with the +# fully-qualified string `"lcm.model.Model"` so the beartype claw resolves it +# by importing `lcm.model` at first call — long after the import cycle settles +# — rather than at module-init time. Importing `lcm.model` here keeps `lcm` a +# bound name for the type checker. if TYPE_CHECKING: - from lcm.model import Model + import lcm.model def validate_V( @@ -44,8 +47,8 @@ def validate_V( partial_solution: object = None, compute_intermediates: Callable | None = None, state_action_space: StateActionSpace | None = None, - next_regime_to_V_arr: MappingProxyType | None = None, - internal_params: Mapping | None = None, + next_regime_to_V_arr: MappingProxyType[RegimeName, FloatND] | None = None, + internal_params: FlatRegimeParams | None = None, period: int | None = None, ) -> None: """Validate the value function array for NaN values. @@ -128,8 +131,8 @@ def _enrich_with_diagnostics( exc: InvalidValueFunctionError, compute_intermediates: Callable, state_action_space: StateActionSpace, - next_regime_to_V_arr: MappingProxyType | None, - internal_params: Mapping | None, + next_regime_to_V_arr: MappingProxyType[RegimeName, FloatND] | None, + internal_params: FlatRegimeParams | None, regime_name: RegimeName, age: float, period: int | None, @@ -744,7 +747,7 @@ def _extract_bare_names(slice_node: ast.expr) -> list[str] | None: def validate_transition_probs( *, probs: FloatND, - model: Model, + model: lcm.model.Model, regime_name: RegimeName, state_name: StateName, ) -> None: ... @@ -754,7 +757,7 @@ def validate_transition_probs( def validate_transition_probs( *, probs: FloatND, - model: Model, + model: lcm.model.Model, regime_name: RegimeName, state_name: StateName, target_regime_name: RegimeName, @@ -765,7 +768,7 @@ def validate_transition_probs( def validate_transition_probs( *, probs: FloatND, - model: Model, + model: lcm.model.Model, regime_name: RegimeName, ) -> None: ... @@ -773,7 +776,7 @@ def validate_transition_probs( def validate_transition_probs( *, probs: FloatND, - model: Model, + model: lcm.model.Model, regime_name: RegimeName, state_name: StateName | None = None, target_regime_name: RegimeName | None = None, @@ -922,7 +925,7 @@ def _build_expected_shape( indexing_params: list[str], n_outcomes: int, grids: dict[str, DiscreteGrid], - model: Model, + model: lcm.model.Model, ) -> tuple[int, ...]: """Compute expected shape for a transition probability array.""" shape: list[int] = [] diff --git a/tests/test_beartype_claw.py b/tests/test_beartype_claw.py index 9f57fc777..f735ec44d 100644 --- a/tests/test_beartype_claw.py +++ b/tests/test_beartype_claw.py @@ -19,6 +19,7 @@ from lcm import AgeGrid from lcm.simulation.simulate import _compute_starting_periods from lcm.solution.solve_brute import _log_per_period_stats +from lcm.utils.error_handling import validate_regime_transition_probs def test_claw_checks_lcm_simulation() -> None: @@ -50,3 +51,21 @@ def test_claw_checks_lcm_solution() -> None: maxs=jnp.array([]), means=jnp.array([]), ) + + +def test_claw_checks_lcm_utils_error_handling() -> None: + """An ill-typed argument to an `lcm.utils.error_handling` function is rejected. + + `validate_regime_transition_probs` annotates `regime_transition_probs` as + `MappingProxyType[RegimeName, FloatND]`. A plain `dict` whose values are JAX + arrays would be accepted by `jnp.stack(list(...))` and the body would run to + completion; the claw turns the wrong container type into a violation. + """ + with pytest.raises(BeartypeCallHintViolation): + validate_regime_transition_probs( + regime_transition_probs={"working": jnp.array([1.0])}, # ty: ignore[invalid-argument-type] + active_regimes_next_period=("working",), + regime_name="working", + age=50.0, + next_age=51.0, + ) From 2bef7ac5a21b2889855cdd55910903824c7c4b9b Mon Sep 17 00:00:00 2001 From: Hans-Martin von Gaudecker Date: Thu, 14 May 2026 23:34:38 +0200 Subject: [PATCH 54/77] Claw lcm.state_action_space behind the construction perimeter Extends the beartype claw to `lcm.state_action_space` with `INTERNAL_CONF`. The module is already fully annotated with narrowed `lcm.typing` aliases, so no annotation changes are needed. Co-Authored-By: Claude Opus 4.7 --- src/lcm/__init__.py | 1 + tests/test_beartype_claw.py | 17 +++++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/src/lcm/__init__.py b/src/lcm/__init__.py index 776f5c77b..199470d19 100644 --- a/src/lcm/__init__.py +++ b/src/lcm/__init__.py @@ -58,6 +58,7 @@ beartype_package("lcm.solution", conf=INTERNAL_CONF) beartype_package("lcm.simulation", conf=INTERNAL_CONF) beartype_package("lcm.utils.error_handling", conf=INTERNAL_CONF) +beartype_package("lcm.state_action_space", conf=INTERNAL_CONF) from lcm import shocks # noqa: E402 from lcm._version import __version__ # noqa: E402 diff --git a/tests/test_beartype_claw.py b/tests/test_beartype_claw.py index f735ec44d..52e117377 100644 --- a/tests/test_beartype_claw.py +++ b/tests/test_beartype_claw.py @@ -19,6 +19,7 @@ from lcm import AgeGrid from lcm.simulation.simulate import _compute_starting_periods from lcm.solution.solve_brute import _log_per_period_stats +from lcm.state_action_space import _validate_all_states_present from lcm.utils.error_handling import validate_regime_transition_probs @@ -69,3 +70,19 @@ def test_claw_checks_lcm_utils_error_handling() -> None: age=50.0, next_age=51.0, ) + + +def test_claw_checks_lcm_state_action_space() -> None: + """An ill-typed argument to an `lcm.state_action_space` function is rejected. + + `_validate_all_states_present` annotates `provided_states` as a + `dict[StateName, FloatND | IntND]`. An empty `str` `provided_states` + yields an empty `set(provided_states)`, which equals an empty + `required_state_names`, so an un-instrumented call would return cleanly; + the claw turns the wrong container type into a violation. + """ + with pytest.raises(BeartypeCallHintViolation): + _validate_all_states_present( + provided_states="", # ty: ignore[invalid-argument-type] + required_state_names=set(), + ) From 7e7bd18d27f3a9cb1b687a659f4e151ed082cbc5 Mon Sep 17 00:00:00 2001 From: Hans-Martin von Gaudecker Date: Thu, 14 May 2026 23:37:20 +0200 Subject: [PATCH 55/77] Claw lcm.interfaces behind the construction perimeter Extends the beartype claw to `lcm.interfaces` with `INTERNAL_CONF`. The `SolveFunctions` / `SimulateFunctions` / `InternalRegime` dataclasses store dags-wrapped callables in Protocol-typed fields; the claw's `__init__` checks structurally accept those callables, so the regime_building / solution / simulation suites stay green. Narrows `InternalRegime.name` from bare `str` to `RegimeName`. Co-Authored-By: Claude Opus 4.7 --- src/lcm/__init__.py | 1 + src/lcm/interfaces.py | 3 ++- tests/test_beartype_claw.py | 18 ++++++++++++++++++ 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/lcm/__init__.py b/src/lcm/__init__.py index 199470d19..3bb844a1b 100644 --- a/src/lcm/__init__.py +++ b/src/lcm/__init__.py @@ -59,6 +59,7 @@ beartype_package("lcm.simulation", conf=INTERNAL_CONF) beartype_package("lcm.utils.error_handling", conf=INTERNAL_CONF) beartype_package("lcm.state_action_space", conf=INTERNAL_CONF) +beartype_package("lcm.interfaces", conf=INTERNAL_CONF) from lcm import shocks # noqa: E402 from lcm._version import __version__ # noqa: E402 diff --git a/src/lcm/interfaces.py b/src/lcm/interfaces.py index ff7559afa..4c9f29c6d 100644 --- a/src/lcm/interfaces.py +++ b/src/lcm/interfaces.py @@ -25,6 +25,7 @@ IntND, MaxQOverAFunction, NextStateSimulationFunction, + RegimeName, RegimeParamsTemplate, RegimeTransitionFunction, StateName, @@ -217,7 +218,7 @@ class SimulateFunctions: class InternalRegime: """Internal representation of a user regime.""" - name: str + name: RegimeName """Regime name (key in the regimes dict).""" terminal: bool diff --git a/tests/test_beartype_claw.py b/tests/test_beartype_claw.py index 52e117377..a38cd46d7 100644 --- a/tests/test_beartype_claw.py +++ b/tests/test_beartype_claw.py @@ -11,12 +11,15 @@ instrumented — the violation is what proves the claw is installed. """ +from types import MappingProxyType + import jax.numpy as jnp import numpy as np import pytest from beartype.roar import BeartypeCallHintViolation from lcm import AgeGrid +from lcm.interfaces import _build_regime_sharding from lcm.simulation.simulate import _compute_starting_periods from lcm.solution.solve_brute import _log_per_period_stats from lcm.state_action_space import _validate_all_states_present @@ -86,3 +89,18 @@ def test_claw_checks_lcm_state_action_space() -> None: provided_states="", # ty: ignore[invalid-argument-type] required_state_names=set(), ) + + +def test_claw_checks_lcm_interfaces() -> None: + """An ill-typed argument to an `lcm.interfaces` function is rejected. + + `_build_regime_sharding` annotates `n_devices` as `int`. With an empty + `grids` mapping the function returns `None` before `n_devices` is ever + used, so an un-instrumented call would return cleanly; the claw turns the + wrong `n_devices` type into a violation. + """ + with pytest.raises(BeartypeCallHintViolation): + _build_regime_sharding( + grids=MappingProxyType({}), + n_devices="not an int", # ty: ignore[invalid-argument-type] + ) From 7b9183b95163abfa12515e2638cba5010cd0ae01 Mon Sep 17 00:00:00 2001 From: Hans-Martin von Gaudecker Date: Thu, 14 May 2026 23:48:17 +0200 Subject: [PATCH 56/77] Claw lcm.regime behind the construction perimeter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extends the beartype claw to `lcm.regime` with `INTERNAL_CONF`. The explicit `@beartype(conf=REGIME_CONF)` decorator on the `Regime` and `MarkovTransition` constructors still wins, so construction-time type violations keep surfacing as `RegimeInitializationError`. `_default_H` receives state/value arrays whose dtype follows user input — a `discount_factor` supplied as `1` arrives as an int32 array, as `0.95` a float32 array — so its parameters are annotated `NumericND` (a new `FloatND | IntND` alias in `lcm.typing`). The named alias keeps `dags.tree`'s params-template extraction printing `NumericND` rather than a bare `Union`. The `_IdentityTransition.__call__` discrete test now passes an int32 array, matching the `DiscreteState` contract the claw enforces. Co-Authored-By: Claude Opus 4.7 --- src/lcm/__init__.py | 1 + src/lcm/regime.py | 5 ++- src/lcm/typing.py | 7 ++++ .../test_create_regime_params_template.py | 6 +-- tests/test_beartype_claw.py | 37 ++++++++++++++++++- tests/test_regime.py | 4 +- 6 files changed, 53 insertions(+), 7 deletions(-) diff --git a/src/lcm/__init__.py b/src/lcm/__init__.py index 3bb844a1b..4a4dba1f1 100644 --- a/src/lcm/__init__.py +++ b/src/lcm/__init__.py @@ -60,6 +60,7 @@ beartype_package("lcm.utils.error_handling", conf=INTERNAL_CONF) beartype_package("lcm.state_action_space", conf=INTERNAL_CONF) beartype_package("lcm.interfaces", conf=INTERNAL_CONF) +beartype_package("lcm.regime", conf=INTERNAL_CONF) from lcm import shocks # noqa: E402 from lcm._version import __version__ # noqa: E402 diff --git a/src/lcm/regime.py b/src/lcm/regime.py index 13a8734ec..20fcece81 100644 --- a/src/lcm/regime.py +++ b/src/lcm/regime.py @@ -18,6 +18,7 @@ DiscreteState, FloatND, FunctionName, + NumericND, RegimeName, StateName, UserFunction, @@ -64,7 +65,9 @@ def __call__(self, *args: Any, **kwargs: Any) -> FloatND: # noqa: ANN401 return self.func(*args, **kwargs) -def _default_H(utility: float, E_next_V: float, discount_factor: float) -> float: +def _default_H( + utility: NumericND, E_next_V: NumericND, discount_factor: NumericND +) -> NumericND: return utility + discount_factor * E_next_V diff --git a/src/lcm/typing.py b/src/lcm/typing.py index 369b2135e..d431f6de1 100644 --- a/src/lcm/typing.py +++ b/src/lcm/typing.py @@ -19,6 +19,13 @@ type IntND = Int32[Array, "..."] type BoolND = Bool[Array, "..."] +# Int-or-float JAX array of any rank. Use for slots whose dtype depends on +# user input — e.g. a `discount_factor` param the user may supply as `1` +# (int) or `0.95` (float). A named alias instead of the inline +# `FloatND | IntND` union so signature-rendering tools (e.g. `dags.tree`'s +# params-template extraction) print `NumericND` rather than a bare `Union`. +type NumericND = FloatND | IntND + type Float1D = Float[Array, "_"] # noqa: F821 type Int1D = Int32[Array, "_"] # noqa: F821 type Bool1D = Bool[Array, "_"] # noqa: F821 diff --git a/tests/regime_building/test_create_regime_params_template.py b/tests/regime_building/test_create_regime_params_template.py index 5ae13cdd8..a775b6d84 100644 --- a/tests/regime_building/test_create_regime_params_template.py +++ b/tests/regime_building/test_create_regime_params_template.py @@ -22,7 +22,7 @@ def test_create_params_without_shocks(binary_category_class): got = create_regime_params_template(regime) assert got == ensure_containers_are_immutable( { - "H": {"discount_factor": "float"}, + "H": {"discount_factor": "NumericND"}, "utility": {"c": "no_annotation_found"}, "next_b": {}, "next_regime": {}, @@ -144,7 +144,7 @@ def test_regular_function_taking_state_as_argument_no_error(binary_category_clas got = create_regime_params_template(regime) assert got == ensure_containers_are_immutable( { - "H": {"discount_factor": "float"}, + "H": {"discount_factor": "NumericND"}, "utility": {"risk_aversion": "no_annotation_found"}, "next_wealth": {}, "next_regime": {}, @@ -183,7 +183,7 @@ def next_wealth(wealth: float, next_aime: float) -> float: got = create_regime_params_template(regime) assert got == ensure_containers_are_immutable( { - "H": {"discount_factor": "float"}, + "H": {"discount_factor": "NumericND"}, "utility": {}, "next_wealth": {}, "next_aime": {}, diff --git a/tests/test_beartype_claw.py b/tests/test_beartype_claw.py index a38cd46d7..afa64a4fd 100644 --- a/tests/test_beartype_claw.py +++ b/tests/test_beartype_claw.py @@ -18,8 +18,10 @@ import pytest from beartype.roar import BeartypeCallHintViolation -from lcm import AgeGrid +from lcm import AgeGrid, LinSpacedGrid, Regime +from lcm.exceptions import RegimeInitializationError from lcm.interfaces import _build_regime_sharding +from lcm.regime import _default_H from lcm.simulation.simulate import _compute_starting_periods from lcm.solution.solve_brute import _log_per_period_stats from lcm.state_action_space import _validate_all_states_present @@ -104,3 +106,36 @@ def test_claw_checks_lcm_interfaces() -> None: grids=MappingProxyType({}), n_devices="not an int", # ty: ignore[invalid-argument-type] ) + + +def test_claw_checks_lcm_regime() -> None: + """An ill-typed argument to an `lcm.regime` function is rejected. + + `_default_H` annotates `utility` as `FloatND` (a JAX array). A NumPy array + would otherwise flow through `utility + discount_factor * E_next_V` and the + call would return a NumPy array cleanly; the claw turns the wrong array + library into a violation. + """ + with pytest.raises(BeartypeCallHintViolation): + _default_H( + utility=np.array([1.0]), # ty: ignore[invalid-argument-type] + E_next_V=jnp.array([1.0]), + discount_factor=jnp.array([0.95]), + ) + + +def test_regime_with_bad_arg_raises_project_exception() -> None: + """A bad `Regime` argument still raises `RegimeInitializationError`. + + The package claw instruments `lcm.regime`'s private helpers with + `INTERNAL_CONF`, but the explicit `@beartype(conf=REGIME_CONF)` decorator + on the `Regime` constructor still wins: a type violation at construction + surfaces as the project's `RegimeInitializationError`, not beartype's own + `BeartypeCallHintViolation`. + """ + with pytest.raises(RegimeInitializationError): + Regime( + transition=None, + states={"wealth": LinSpacedGrid(start=1.0, stop=10.0, n_points=3)}, + functions="not a mapping", # ty: ignore[invalid-argument-type] + ) diff --git a/tests/test_regime.py b/tests/test_regime.py index 723d718d9..4c322452a 100644 --- a/tests/test_regime.py +++ b/tests/test_regime.py @@ -193,8 +193,8 @@ def test_identity_transition_call(): def test_identity_transition_discrete(): """Identity transition works for discrete states.""" identity = _IdentityTransition("education", annotation=DiscreteState) - result = identity(education=jnp.array(1)) - assert result == jnp.array(1) + result = identity(education=jnp.array(1, dtype=jnp.int32)) + assert result == jnp.array(1, dtype=jnp.int32) def test_identity_transition_name(): From 67ea957ce99efecae0e9c39f059d21fd2dcbf1b6 Mon Sep 17 00:00:00 2001 From: Hans-Martin von Gaudecker Date: Thu, 14 May 2026 23:51:04 +0200 Subject: [PATCH 57/77] Claw lcm.model behind the construction perimeter Extends the beartype claw to `lcm.model` with `INTERNAL_CONF`. The explicit `@beartype` decorators on `Model.__init__` / `solve` / `simulate` still win, so construction- and call-time type violations keep surfacing as `ModelInitializationError` / `InvalidParamsError`. Swaps the inline `MappingProxyType[int, MappingProxyType[RegimeName, FloatND]]` annotations for the `PeriodToRegimeToVArr` alias. Co-Authored-By: Claude Opus 4.7 --- src/lcm/__init__.py | 1 + src/lcm/model.py | 10 ++++------ tests/test_beartype_claw.py | 37 +++++++++++++++++++++++++++++++++++-- 3 files changed, 40 insertions(+), 8 deletions(-) diff --git a/src/lcm/__init__.py b/src/lcm/__init__.py index 4a4dba1f1..a8221169d 100644 --- a/src/lcm/__init__.py +++ b/src/lcm/__init__.py @@ -61,6 +61,7 @@ beartype_package("lcm.state_action_space", conf=INTERNAL_CONF) beartype_package("lcm.interfaces", conf=INTERNAL_CONF) beartype_package("lcm.regime", conf=INTERNAL_CONF) +beartype_package("lcm.model", conf=INTERNAL_CONF) from lcm import shocks # noqa: E402 from lcm._version import __version__ # noqa: E402 diff --git a/src/lcm/model.py b/src/lcm/model.py index 5a099f5d3..f5a927b0f 100644 --- a/src/lcm/model.py +++ b/src/lcm/model.py @@ -48,6 +48,7 @@ InternalParams, IntND, ParamsTemplate, + PeriodToRegimeToVArr, RegimeName, RegimeNamesToIds, UserFacingParamsTemplate, @@ -254,7 +255,7 @@ def solve( log_level: LogLevel = "progress", log_path: str | Path | None = None, log_keep_n_latest: int = 3, - ) -> MappingProxyType[int, MappingProxyType[RegimeName, FloatND]]: + ) -> PeriodToRegimeToVArr: """Solve the model using the pre-computed functions. Args: @@ -308,7 +309,7 @@ def _solve_compiled( log_path: str | Path | None, log_keep_n_latest: int, max_compilation_workers: int | None, - ) -> MappingProxyType[int, MappingProxyType[RegimeName, FloatND]]: + ) -> PeriodToRegimeToVArr: """Run backward induction, persisting a snapshot on debug or NaN failure.""" try: period_to_regime_to_V_arr = solve( @@ -381,10 +382,7 @@ def simulate( *, params: UserParams, initial_conditions: Mapping[str, FloatND | IntND] | pd.DataFrame, - period_to_regime_to_V_arr: MappingProxyType[ - int, MappingProxyType[RegimeName, FloatND] - ] - | None, + period_to_regime_to_V_arr: PeriodToRegimeToVArr | None, check_initial_conditions: bool = True, seed: int | None = None, log_level: LogLevel = "progress", diff --git a/tests/test_beartype_claw.py b/tests/test_beartype_claw.py index afa64a4fd..cd799cd70 100644 --- a/tests/test_beartype_claw.py +++ b/tests/test_beartype_claw.py @@ -18,9 +18,10 @@ import pytest from beartype.roar import BeartypeCallHintViolation -from lcm import AgeGrid, LinSpacedGrid, Regime -from lcm.exceptions import RegimeInitializationError +from lcm import AgeGrid, LinSpacedGrid, Model, Regime +from lcm.exceptions import ModelInitializationError, RegimeInitializationError from lcm.interfaces import _build_regime_sharding +from lcm.model import _validate_log_args from lcm.regime import _default_H from lcm.simulation.simulate import _compute_starting_periods from lcm.solution.solve_brute import _log_per_period_stats @@ -139,3 +140,35 @@ def test_regime_with_bad_arg_raises_project_exception() -> None: states={"wealth": LinSpacedGrid(start=1.0, stop=10.0, n_points=3)}, functions="not a mapping", # ty: ignore[invalid-argument-type] ) + + +def test_claw_checks_lcm_model() -> None: + """An ill-typed argument to an `lcm.model` function is rejected. + + `_validate_log_args` annotates `log_path` as `str | Path | None`. With + `log_level="progress"` the function returns before `log_path` is ever + inspected, so an un-instrumented call would return cleanly; the claw turns + the wrong `log_path` type into a violation. + """ + with pytest.raises(BeartypeCallHintViolation): + _validate_log_args( + log_level="progress", + log_path=123, # ty: ignore[invalid-argument-type] + ) + + +def test_model_with_bad_arg_raises_project_exception() -> None: + """A bad `Model` argument still raises `ModelInitializationError`. + + The package claw instruments `lcm.model`'s private helpers with + `INTERNAL_CONF`, but the explicit `@beartype(conf=MODEL_CONF)` decorator on + `Model.__init__` still wins: a type violation at construction surfaces as + the project's `ModelInitializationError`, not beartype's own + `BeartypeCallHintViolation`. + """ + with pytest.raises(ModelInitializationError): + Model( + ages=AgeGrid(start=25, stop=75, step="Y"), + regimes="not a mapping", # ty: ignore[invalid-argument-type] + regime_id_class=int, + ) From 72cdfea3bf5395e61124174f3b6fc481ffe55ccc Mon Sep 17 00:00:00 2001 From: Hans-Martin von Gaudecker Date: Fri, 15 May 2026 05:44:42 +0200 Subject: [PATCH 58/77] Fix discount_factor float-typed everywhere; drop NumericND MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `_default_H`'s `discount_factor` should always receive a float, never an int — the prior commit smuggled `FloatND | IntND` via a new `NumericND` alias to accommodate test fixtures passing `1`/`0`, which was the wrong way round. Pass floats from the tests, keep `_default_H` strictly `FloatND`, drop the alias. - `_default_H` reverted to `FloatND` on all three params + return - `NumericND` removed from `lcm.typing` - `tests/test_models/shock_grids.py`: `"discount_factor": 1` → `1.0` (×2) - `tests/test_solution_on_toy_model_{deterministic,stochastic}.py`: parametrize `[0, 0.5, 0.9, 1.0]` → `[0.0, 0.5, 0.9, 1.0]` (×4) - `tests/simulation/test_simulate.py`: `discount_factor=1, interest_rate=0` → `1.0, 0.0` - `tests/regime_building/test_create_regime_params_template.py`: `"NumericND"` expectation → `"FloatND"` (×3) Co-Authored-By: Claude Opus 4.7 --- src/lcm/regime.py | 5 ++--- src/lcm/typing.py | 7 ------- .../regime_building/test_create_regime_params_template.py | 6 +++--- tests/simulation/test_simulate.py | 2 +- tests/test_models/shock_grids.py | 4 ++-- tests/test_solution_on_toy_model_deterministic.py | 4 ++-- tests/test_solution_on_toy_model_stochastic.py | 4 ++-- 7 files changed, 12 insertions(+), 20 deletions(-) diff --git a/src/lcm/regime.py b/src/lcm/regime.py index 20fcece81..894f2dc0b 100644 --- a/src/lcm/regime.py +++ b/src/lcm/regime.py @@ -18,7 +18,6 @@ DiscreteState, FloatND, FunctionName, - NumericND, RegimeName, StateName, UserFunction, @@ -66,8 +65,8 @@ def __call__(self, *args: Any, **kwargs: Any) -> FloatND: # noqa: ANN401 def _default_H( - utility: NumericND, E_next_V: NumericND, discount_factor: NumericND -) -> NumericND: + utility: FloatND, E_next_V: FloatND, discount_factor: FloatND +) -> FloatND: return utility + discount_factor * E_next_V diff --git a/src/lcm/typing.py b/src/lcm/typing.py index d431f6de1..369b2135e 100644 --- a/src/lcm/typing.py +++ b/src/lcm/typing.py @@ -19,13 +19,6 @@ type IntND = Int32[Array, "..."] type BoolND = Bool[Array, "..."] -# Int-or-float JAX array of any rank. Use for slots whose dtype depends on -# user input — e.g. a `discount_factor` param the user may supply as `1` -# (int) or `0.95` (float). A named alias instead of the inline -# `FloatND | IntND` union so signature-rendering tools (e.g. `dags.tree`'s -# params-template extraction) print `NumericND` rather than a bare `Union`. -type NumericND = FloatND | IntND - type Float1D = Float[Array, "_"] # noqa: F821 type Int1D = Int32[Array, "_"] # noqa: F821 type Bool1D = Bool[Array, "_"] # noqa: F821 diff --git a/tests/regime_building/test_create_regime_params_template.py b/tests/regime_building/test_create_regime_params_template.py index a775b6d84..1f8fa361a 100644 --- a/tests/regime_building/test_create_regime_params_template.py +++ b/tests/regime_building/test_create_regime_params_template.py @@ -22,7 +22,7 @@ def test_create_params_without_shocks(binary_category_class): got = create_regime_params_template(regime) assert got == ensure_containers_are_immutable( { - "H": {"discount_factor": "NumericND"}, + "H": {"discount_factor": "FloatND"}, "utility": {"c": "no_annotation_found"}, "next_b": {}, "next_regime": {}, @@ -144,7 +144,7 @@ def test_regular_function_taking_state_as_argument_no_error(binary_category_clas got = create_regime_params_template(regime) assert got == ensure_containers_are_immutable( { - "H": {"discount_factor": "NumericND"}, + "H": {"discount_factor": "FloatND"}, "utility": {"risk_aversion": "no_annotation_found"}, "next_wealth": {}, "next_regime": {}, @@ -183,7 +183,7 @@ def next_wealth(wealth: float, next_aime: float) -> float: got = create_regime_params_template(regime) assert got == ensure_containers_are_immutable( { - "H": {"discount_factor": "NumericND"}, + "H": {"discount_factor": "FloatND"}, "utility": {}, "next_wealth": {}, "next_aime": {}, diff --git a/tests/simulation/test_simulate.py b/tests/simulation/test_simulate.py index a4010e5e1..a90c3c2b3 100644 --- a/tests/simulation/test_simulate.py +++ b/tests/simulation/test_simulate.py @@ -194,7 +194,7 @@ def test_simulate_with_only_discrete_actions(): ) model = get_model(n_periods=3) - params = get_params(n_periods=3, wage=1.5, discount_factor=1, interest_rate=0) + params = get_params(n_periods=3, wage=1.5, discount_factor=1.0, interest_rate=0.0) result = model.simulate( params=params, diff --git a/tests/test_models/shock_grids.py b/tests/test_models/shock_grids.py index 32f005ce4..6213f8619 100644 --- a/tests/test_models/shock_grids.py +++ b/tests/test_models/shock_grids.py @@ -213,7 +213,7 @@ def get_multi_regime_params( shock_params = _SHOCK_PARAMS[distribution_type] health_probs = jnp.full((2, 2), fill_value=0.5) regime_params = { - "discount_factor": 1, + "discount_factor": 1.0, "next_health": {"probs_array": health_probs}, "income": shock_params, } @@ -240,7 +240,7 @@ def get_params( ): return { "alive": { - "discount_factor": 1, + "discount_factor": 1.0, "next_health": {"probs_array": jnp.full((2, 2), fill_value=0.5)}, "income": _SHOCK_PARAMS[distribution_type], }, diff --git a/tests/test_solution_on_toy_model_deterministic.py b/tests/test_solution_on_toy_model_deterministic.py index 98996a52b..e163caee6 100644 --- a/tests/test_solution_on_toy_model_deterministic.py +++ b/tests/test_solution_on_toy_model_deterministic.py @@ -216,7 +216,7 @@ def dict_of_vectors_to_matrix(d): return np.column_stack(list(d.values())) -@pytest.mark.parametrize("discount_factor", [0, 0.5, 0.9, 1.0]) +@pytest.mark.parametrize("discount_factor", [0.0, 0.5, 0.9, 1.0]) @pytest.mark.parametrize("n_wealth_points", [100, 1_000]) def test_deterministic_solve(discount_factor, n_wealth_points): n_periods = 3 @@ -263,7 +263,7 @@ def test_deterministic_solve(discount_factor, n_wealth_points): aaae(got[1]["alive"], expected[1], decimal=DECIMAL_PRECISION) -@pytest.mark.parametrize("discount_factor", [0, 0.5, 0.9, 1.0]) +@pytest.mark.parametrize("discount_factor", [0.0, 0.5, 0.9, 1.0]) @pytest.mark.parametrize("n_wealth_points", [100, 1_000]) def test_deterministic_simulate(discount_factor, n_wealth_points): n_periods = 3 diff --git a/tests/test_solution_on_toy_model_stochastic.py b/tests/test_solution_on_toy_model_stochastic.py index fdb175b1e..5e0ffd945 100644 --- a/tests/test_solution_on_toy_model_stochastic.py +++ b/tests/test_solution_on_toy_model_stochastic.py @@ -208,7 +208,7 @@ def analytical_simulate_stochastic(initial_wealth, initial_health, health_1, par ] -@pytest.mark.parametrize("discount_factor", [0, 0.5, 0.9, 1.0]) +@pytest.mark.parametrize("discount_factor", [0.0, 0.5, 0.9, 1.0]) @pytest.mark.parametrize("n_wealth_points", [100, 1_000]) @pytest.mark.parametrize("probs_array", HEALTH_TRANSITION) def test_stochastic_solve(discount_factor, n_wealth_points, probs_array): @@ -267,7 +267,7 @@ def test_stochastic_solve(discount_factor, n_wealth_points, probs_array): aaae(got[1]["alive"], expected[1], decimal=DECIMAL_PRECISION) -@pytest.mark.parametrize("discount_factor", [0, 0.5, 0.9, 1.0]) +@pytest.mark.parametrize("discount_factor", [0.0, 0.5, 0.9, 1.0]) @pytest.mark.parametrize("n_wealth_points", [100, 1_000]) @pytest.mark.parametrize("probs_array", HEALTH_TRANSITION) def test_stochastic_simulate(discount_factor, n_wealth_points, probs_array): From 4387640bd91218736083904918f2eb7d16493c77 Mon Sep 17 00:00:00 2001 From: Hans-Martin von Gaudecker Date: Fri, 15 May 2026 05:55:11 +0200 Subject: [PATCH 59/77] Canonicalize shock-grid params at the boundary MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `_ShockGrid.params` now returns `MappingProxyType[str, ScalarFloat | ScalarInt]` — Python `bool`/`int`/`float` dataclass fields are cast to 0-d JAX scalars on access. Every downstream consumer (`compute_gridpoints`, `compute_transition_probs`, `_create_ar1_next_func`, `_create_iid_next_func`, `StateActionSpace.replace`, the regime-building runtime closures) already required `ScalarFloat | ScalarInt`-valued mappings and was doing the cast itself via `_params_to_jax`. With the cast hoisted to the boundary, the helper is no longer needed. - `_ShockGrid.params` returns canonical 0-d scalars - `_params_to_jax` deleted (orphaned) - `weights_func_runtime` / `next_stochastic_state` closures: pass `shock_kw` through directly; annotations tightened to `dict[str, FloatND | IntND]`; `with_signature` runtime-param strings switched from `"float"` to `"FloatND"` - `StateActionSpace.replace`: `shock_kw` retyped `ScalarFloat | ScalarInt`, redundant `_params_to_jax` call dropped Co-Authored-By: Claude Opus 4.7 --- src/lcm/interfaces.py | 12 +++--- src/lcm/regime_building/next_state.py | 29 ++++++------- src/lcm/regime_building/processing.py | 19 ++++---- src/lcm/shocks/_base.py | 62 +++++++++++---------------- 4 files changed, 51 insertions(+), 71 deletions(-) diff --git a/src/lcm/interfaces.py b/src/lcm/interfaces.py index 4c9f29c6d..b036da408 100644 --- a/src/lcm/interfaces.py +++ b/src/lcm/interfaces.py @@ -2,14 +2,12 @@ from collections.abc import Callable from math import prod as math_prod from types import MappingProxyType -from typing import cast import jax from lcm.exceptions import PyLCMError from lcm.grids import Grid, IrregSpacedGrid from lcm.shocks import _ShockGrid -from lcm.shocks._base import _params_to_jax from lcm.typing import ( ActionName, ArgmaxQOverAFunction, @@ -28,6 +26,8 @@ RegimeName, RegimeParamsTemplate, RegimeTransitionFunction, + ScalarFloat, + ScalarInt, StateName, StateOrActionName, TransitionFunctionName, @@ -296,12 +296,10 @@ def state_action_space(self, regime_params: FlatRegimeParams) -> StateActionSpac ) if not all_present: continue - shock_kw: dict[str, float] = dict(spec.params) + shock_kw: dict[str, ScalarFloat | ScalarInt] = dict(spec.params) for p in spec.params_to_pass_at_runtime: - shock_kw[p] = cast("float", all_params[f"{name}__{p}"]) - state_replacements[name] = spec.compute_gridpoints( - **_params_to_jax(MappingProxyType(shock_kw)) - ) + shock_kw[p] = all_params[f"{name}__{p}"] + state_replacements[name] = spec.compute_gridpoints(**shock_kw) new_states = ( dict(self._base_state_action_space.states) | state_replacements diff --git a/src/lcm/regime_building/next_state.py b/src/lcm/regime_building/next_state.py index 1eeac78bd..e0639471e 100644 --- a/src/lcm/regime_building/next_state.py +++ b/src/lcm/regime_building/next_state.py @@ -9,7 +9,6 @@ from lcm.grids import Grid from lcm.shocks import _ShockGrid -from lcm.shocks._base import _params_to_jax from lcm.shocks.ar1 import _ShockGridAR1 from lcm.shocks.iid import _ShockGridIID from lcm.typing import ( @@ -297,19 +296,17 @@ def _create_ar1_next_func( args: dict[str, str] = { f"key_{qname}": "PRNGKeyND", state_name: "ContinuousState", - **dict.fromkeys(runtime_param_names, "float"), + **dict.fromkeys(runtime_param_names, "FloatND"), } _draw_shock = grid.draw_shock @with_signature(args=args, return_annotation="ContinuousState") def next_stochastic_state(**kwargs: FloatND) -> ContinuousState: - params = _params_to_jax( - MappingProxyType( - { - **fixed_params, - **{raw: kwargs[qn] for qn, raw in runtime_param_names.items()}, - } - ) + params = MappingProxyType( + { + **fixed_params, + **{raw: kwargs[qn] for qn, raw in runtime_param_names.items()}, + } ) return _draw_shock( params=params, @@ -329,19 +326,17 @@ def _create_iid_next_func( } args: dict[str, str] = { f"key_{qname}": "PRNGKeyND", - **dict.fromkeys(runtime_param_names, "float"), + **dict.fromkeys(runtime_param_names, "FloatND"), } _draw_shock = grid.draw_shock @with_signature(args=args, return_annotation="ContinuousState") def next_stochastic_state(**kwargs: FloatND) -> ContinuousState: - params = _params_to_jax( - MappingProxyType( - { - **fixed_params, - **{raw: kwargs[qn] for qn, raw in runtime_param_names.items()}, - } - ) + params = MappingProxyType( + { + **fixed_params, + **{raw: kwargs[qn] for qn, raw in runtime_param_names.items()}, + } ) return _draw_shock( params=params, diff --git a/src/lcm/regime_building/processing.py b/src/lcm/regime_building/processing.py index 73abc21a2..aa9c79f77 100644 --- a/src/lcm/regime_building/processing.py +++ b/src/lcm/regime_building/processing.py @@ -40,7 +40,6 @@ from lcm.regime_building.V import VInterpolationInfo, create_v_interpolation_info from lcm.regime_building.validation import collect_state_transitions from lcm.shocks import _ShockGrid -from lcm.shocks._base import _params_to_jax from lcm.state_action_space import create_state_action_space from lcm.typing import ( ArgmaxQOverAFunction, @@ -886,21 +885,21 @@ def _get_weights_func_for_shock(*, name: str, grid: _ShockGrid) -> UserFunction: runtime_param_names = { qname_from_tree_path((name, p)): p for p in grid.params_to_pass_at_runtime } - args = {name: "ContinuousState", **dict.fromkeys(runtime_param_names, "float")} + args = { + name: "ContinuousState", + **dict.fromkeys(runtime_param_names, "FloatND"), + } @with_signature(args=args, return_annotation="FloatND", enforce=False) def weights_func_runtime(*a: FloatND, **kwargs: FloatND) -> Float1D: # noqa: ARG001 - # `float` here covers Python floats from fixed_params; under - # JIT tracing, the runtime values forwarded through `kwargs` - # arrive as JAX tracers (`FloatND`), which are accepted by the - # shock grid's `compute_gridpoints` / `compute_transition_probs`. - shock_kw: dict[str, float | FloatND] = { + # `grid.params` is canonical (0-d JAX scalars) from its own + # boundary cast; `kwargs` arrive as JAX tracers from JIT. + shock_kw: dict[str, FloatND | IntND] = { **fixed_params, **{raw: kwargs[qn] for qn, raw in runtime_param_names.items()}, } - shock_kw_jax = _params_to_jax(MappingProxyType(shock_kw)) - gridpoints = grid.compute_gridpoints(**shock_kw_jax) - transition_probs = grid.compute_transition_probs(**shock_kw_jax) + gridpoints = grid.compute_gridpoints(**shock_kw) + transition_probs = grid.compute_transition_probs(**shock_kw) coord = get_irreg_coordinate(value=kwargs[name], points=gridpoints) return map_coordinates( input=transition_probs, diff --git a/src/lcm/shocks/_base.py b/src/lcm/shocks/_base.py index 6dd859478..de2b1fd80 100644 --- a/src/lcm/shocks/_base.py +++ b/src/lcm/shocks/_base.py @@ -1,7 +1,7 @@ from abc import abstractmethod from dataclasses import dataclass, fields from types import MappingProxyType -from typing import Any, ClassVar +from typing import ClassVar import jax.numpy as jnp import numpy as np @@ -10,31 +10,7 @@ from lcm.exceptions import GridInitializationError from lcm.grids import ContinuousGrid from lcm.grids import coordinates as grid_coordinates -from lcm.typing import Float1D, FloatND, IntND, ScalarFloat, ScalarInt - - -def _params_to_jax( - params: MappingProxyType[str, Any], -) -> MappingProxyType[str, FloatND | IntND]: - """Cast Python `int` / `float` shock params to JAX scalars. - - `self.params` on a shock grid mixes dataclass-default literals (Python - `int` / `float`) with runtime-substituted values (already JAX scalars). - The downstream `compute_gridpoints` / `compute_transition_probs` and - `draw_shock` slots take `FloatND | IntND`-valued mappings, so the - boundary cast happens here rather than widening every kwarg signature - to admit Python scalars. - - """ - out: dict[str, FloatND | IntND] = {} - for name, value in params.items(): - if isinstance(value, bool | int): - out[name] = jnp.int32(value) - elif isinstance(value, float): - out[name] = jnp.asarray(value) - else: - out[name] = value - return MappingProxyType(out) +from lcm.typing import Float1D, FloatND, ScalarFloat, ScalarInt def _gauss_hermite_normal( @@ -85,15 +61,27 @@ def _param_field_names(self) -> tuple[str, ...]: ) @property - def params(self) -> MappingProxyType[str, float]: - """Mapping of the distribution's parameters' names to their specified values.""" - return MappingProxyType( - { - name: getattr(self, name) - for name in self._param_field_names - if getattr(self, name) is not None - } - ) + def params(self) -> MappingProxyType[str, ScalarFloat | ScalarInt]: + """Distribution parameters as canonical 0-d JAX scalars. + + Boundary cast: dataclass fields supplied by the user as Python + `bool` / `int` / `float` are returned as `ScalarInt` / `ScalarFloat` + so every consumer downstream — `compute_gridpoints`, + `compute_transition_probs`, the regime-building runtime closures — + sees the canonical dtype. + + """ + out: dict[str, ScalarFloat | ScalarInt] = {} + for name in self._param_field_names: + value = getattr(self, name) + if value is None: + continue + # `bool` before `int` — `True` is a Python `int` subclass. + if isinstance(value, bool | int): + out[name] = jnp.int32(value) + else: + out[name] = jnp.asarray(value) + return MappingProxyType(out) @property def params_to_pass_at_runtime(self) -> tuple[str, ...]: @@ -124,7 +112,7 @@ def get_gridpoints(self) -> Float1D: """ if not self.is_fully_specified: return jnp.full(self.n_points, jnp.nan) - return self.compute_gridpoints(**_params_to_jax(self.params)) + return self.compute_gridpoints(**self.params) def get_transition_probs(self) -> FloatND: """Get the transition probabilities at the gridpoints. @@ -135,7 +123,7 @@ def get_transition_probs(self) -> FloatND: """ if not self.is_fully_specified: return jnp.full((self.n_points, self.n_points), jnp.nan) - return self.compute_transition_probs(**_params_to_jax(self.params)) + return self.compute_transition_probs(**self.params) def to_jax(self) -> Float1D: """Convert the grid to a Jax array.""" From 9b8124c6e247b798fb9af9a7643f20af92fd87a2 Mon Sep 17 00:00:00 2001 From: Hans-Martin von Gaudecker Date: Fri, 15 May 2026 06:14:33 +0200 Subject: [PATCH 60/77] Split params leaves into User (boundary) and canonical variants MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add `UserMappingLeaf` / `UserSequenceLeaf` as the boundary leaf types accepted by `Model.__init__` / `Model.solve` / `Model.simulate`, and narrow `MappingLeaf` / `SequenceLeaf` to canonical-only subclasses emitted by `cast_params_to_canonical_dtypes`. The static type system now distinguishes user input from post-canonicalization values; the runtime constructor signatures keep `Mapping[str, Any]` / `Sequence[Any]` so beartype doesn't fire on user-supplied scalars. - `_UserParamsLeaf` covers Python scalars, numpy/pandas, JAX arrays, and `UserMappingLeaf` / `UserSequenceLeaf`; `UserParams` is the boundary `Mapping` type alias. - `_ParamsLeaf` narrows to `FloatND | IntND | BoolND | MappingLeaf | SequenceLeaf`; `Params` is the post-canonicalization `Mapping` type. - Both leaf variants registered as separate JAX pytrees so `jax.tree.map` round-trips each in its own type. - `_cast_leaves_to_canonical_dtype` accepts the `User...Leaf` base classes and always emits the canonical narrow subclasses. - `convert_series_in_params` preserves the boundary variant — it runs between broadcast and canonicalization. - `_make_immutable` / `_make_mutable` / `has_series` switch their `isinstance` checks to the base classes (covering both variants). - `as_leaf` returns the boundary `User...Leaf` variant. Co-Authored-By: Claude Opus 4.7 --- src/lcm/pandas_utils.py | 31 +++++++----- src/lcm/params/__init__.py | 34 +++++++++---- src/lcm/params/mapping_leaf.py | 67 ++++++++++++++++++++----- src/lcm/params/processing.py | 25 +++++---- src/lcm/params/sequence_leaf.py | 63 +++++++++++++++++++---- src/lcm/typing.py | 21 ++++++-- src/lcm/utils/containers.py | 12 ++--- tests/test_mapping_leaf.py | 24 +++++++-- tests/test_pandas_utils.py | 34 ++++++------- tests/test_validation_scalar_actions.py | 2 +- 10 files changed, 225 insertions(+), 88 deletions(-) diff --git a/src/lcm/pandas_utils.py b/src/lcm/pandas_utils.py index ffe29fded..364da0ac4 100644 --- a/src/lcm/pandas_utils.py +++ b/src/lcm/pandas_utils.py @@ -13,8 +13,7 @@ from lcm.ages import PSEUDO_STATE_NAMES, AgeGrid from lcm.dtypes import canonical_float_dtype from lcm.grids import DiscreteGrid, IrregSpacedGrid -from lcm.params import MappingLeaf -from lcm.params.sequence_leaf import SequenceLeaf +from lcm.params import UserMappingLeaf, UserSequenceLeaf from lcm.regime import Regime from lcm.shocks import _ShockGrid from lcm.simulation.initial_conditions import MISSING_CAT_CODE @@ -39,9 +38,11 @@ def has_series(params: Mapping) -> bool: return True if isinstance(value, Mapping) and has_series(value): return True - if isinstance(value, (MappingLeaf, SequenceLeaf)): + if isinstance(value, (UserMappingLeaf, UserSequenceLeaf)): items = ( - value.data.values() if isinstance(value, MappingLeaf) else value.data + value.data.values() + if isinstance(value, UserMappingLeaf) + else value.data ) if any(isinstance(v, pd.Series) for v in items): return True @@ -197,7 +198,8 @@ def convert_series_in_params( Iterate over the template-shaped `internal_params` (produced by `process_params`) and convert any `pd.Series` leaf values via - `array_from_series`. `MappingLeaf` and `SequenceLeaf` values are + `array_from_series`. `UserMappingLeaf` and `UserSequenceLeaf` values + (and their canonical `MappingLeaf` / `SequenceLeaf` subclasses) are traversed and any Series inside are converted. Other values (scalars, existing arrays) pass through unchanged. @@ -280,7 +282,8 @@ def _convert_param_value( """Convert a single param value, dispatching on type. Args: - value: The parameter value (Series, MappingLeaf, or passthrough). + value: The parameter value (Series, `UserMappingLeaf` / + `UserSequenceLeaf`, or passthrough). func: The function that uses this parameter (`None` for runtime grid params — triggers scalar passthrough). param_name: Parameter name in the function. @@ -292,8 +295,9 @@ def _convert_param_value( regime_name: Regime name for action grid lookup. Returns: - Converted value: JAX array for Series, MappingLeaf with converted - Series entries, or the original value unchanged. + Converted value: JAX array for Series, a `UserMappingLeaf` / + `UserSequenceLeaf` with converted Series entries, or the original + value unchanged. """ @@ -320,10 +324,13 @@ def _recurse(inner_value: object) -> object: regime_names_to_ids=regime_names_to_ids, regime_name=regime_name, ) - if isinstance(value, MappingLeaf): - return MappingLeaf({k: _recurse(v) for k, v in value.data.items()}) - if isinstance(value, SequenceLeaf): - return SequenceLeaf(tuple(_recurse(v) for v in value.data)) + # `convert_series_in_params` runs between broadcast and canonicalization, + # so leaves are still in user form. Preserve that user form on output: + # canonicalization happens downstream in `cast_params_to_canonical_dtypes`. + if isinstance(value, UserMappingLeaf): + return UserMappingLeaf({k: _recurse(v) for k, v in value.data.items()}) + if isinstance(value, UserSequenceLeaf): + return UserSequenceLeaf(tuple(_recurse(v) for v in value.data)) return value diff --git a/src/lcm/params/__init__.py b/src/lcm/params/__init__.py index 9eba7e8f4..2ce2f72ea 100644 --- a/src/lcm/params/__init__.py +++ b/src/lcm/params/__init__.py @@ -1,24 +1,33 @@ from collections.abc import Mapping, Sequence from typing import Any, overload -from lcm.params.mapping_leaf import MappingLeaf -from lcm.params.sequence_leaf import SequenceLeaf +from lcm.params.mapping_leaf import MappingLeaf, UserMappingLeaf +from lcm.params.sequence_leaf import SequenceLeaf, UserSequenceLeaf @overload -def as_leaf(data: Mapping[str, Any]) -> MappingLeaf: ... +def as_leaf(data: Mapping[str, Any]) -> UserMappingLeaf: ... @overload -def as_leaf(data: Sequence[Any]) -> SequenceLeaf: ... +def as_leaf(data: Sequence[Any]) -> UserSequenceLeaf: ... -def as_leaf(data: Mapping[str, Any] | Sequence[Any]) -> MappingLeaf | SequenceLeaf: - """Wrap a Mapping or Sequence as a JAX-pytree leaf.""" +def as_leaf( + data: Mapping[str, Any] | Sequence[Any], +) -> UserMappingLeaf | UserSequenceLeaf: + """Wrap a Mapping or Sequence as a JAX-pytree leaf. + + Returns the boundary (`User...Leaf`) variant — accepts Python scalars, + numpy arrays, `pd.Series`, JAX arrays, and nested leaves. The + canonical narrowed variants (`MappingLeaf` / `SequenceLeaf`) are the + output of `cast_params_to_canonical_dtypes`. + + """ if isinstance(data, Mapping): - return MappingLeaf(dict(data)) + return UserMappingLeaf(dict(data)) if isinstance(data, Sequence): - return SequenceLeaf(data) + return UserSequenceLeaf(data) msg = f"as_leaf() expects a Mapping or Sequence, got {type(data).__name__}" raise TypeError(msg) @@ -32,4 +41,11 @@ def __getattr__(name: str) -> object: raise AttributeError(msg) -__all__ = ["MappingLeaf", "SequenceLeaf", "as_leaf", "process_params"] +__all__ = [ + "MappingLeaf", + "SequenceLeaf", + "UserMappingLeaf", + "UserSequenceLeaf", + "as_leaf", + "process_params", +] diff --git a/src/lcm/params/mapping_leaf.py b/src/lcm/params/mapping_leaf.py index aaca3e211..148b91512 100644 --- a/src/lcm/params/mapping_leaf.py +++ b/src/lcm/params/mapping_leaf.py @@ -1,22 +1,38 @@ """A Mapping wrapper that is a JAX pytree but not itself a Mapping.""" from collections.abc import Mapping, Sequence -from typing import Any +from typing import TYPE_CHECKING, Any import jax +if TYPE_CHECKING: + from lcm.typing import _ParamsLeaf, _UserParamsLeaf -class MappingLeaf: + +class UserMappingLeaf: """A Mapping wrapper that is a JAX pytree but not itself a Mapping. - Prevents flatten_regime_namespace from recursing into contents while - allowing JAX to trace through array values. + Holds the boundary leaf type — accepts the same wide value union as + `Model.solve` / `Model.simulate` parameters (Python scalars, numpy + arrays, `pd.Series`, JAX arrays, nested `UserMappingLeaf` / + `UserSequenceLeaf`). + + Prevents `flatten_regime_namespace` from recursing into contents while + allowing JAX to trace through array values. Data is frozen to + immutable containers on construction. + + The constructor accepts `Mapping[str, Any]` at runtime so beartype's + O(n) per-leaf check doesn't fire on user-supplied scalars or arrays; + the precise leaf-type contract is enforced statically through the + `data` class-attribute annotation. - Data is frozen to immutable containers on construction. """ __slots__ = ("data",) + if TYPE_CHECKING: + data: Mapping[str, _UserParamsLeaf] + def __init__(self, data: Mapping[str, Any]) -> None: from lcm.utils.containers import ( # noqa: PLC0415 ensure_containers_are_immutable, @@ -25,24 +41,51 @@ def __init__(self, data: Mapping[str, Any]) -> None: self.data = ensure_containers_are_immutable(data) def __repr__(self) -> str: - return f"MappingLeaf({dict(self.data)!r})" + return f"{type(self).__name__}({dict(self.data)!r})" __hash__ = None # MappingProxyType is not hashable def __eq__(self, other: object) -> bool: - if not isinstance(other, MappingLeaf): + if not isinstance(other, UserMappingLeaf): return NotImplemented return self.data == other.data -def _flatten(nmp: MappingLeaf) -> tuple[list[Any], tuple[str, ...]]: - keys = tuple(sorted(nmp.data.keys())) - values = [nmp.data[k] for k in keys] +class MappingLeaf(UserMappingLeaf): + """Mapping leaf carrying only canonical-dtype values. + + Output of `cast_params_to_canonical_dtypes`. Every value is either a + canonical JAX array (`FloatND` / `IntND` / `BoolND`) or another + canonical leaf (`MappingLeaf` / `SequenceLeaf`). + + Subclasses `UserMappingLeaf`, so any code that accepts the wide user + form via `isinstance(_, UserMappingLeaf)` also accepts the canonical + form. Code requiring canonical values uses the narrower `MappingLeaf` + type explicitly. + + """ + + __slots__ = () + + if TYPE_CHECKING: + data: Mapping[str, _ParamsLeaf] + + +def _user_flatten( + leaf: UserMappingLeaf, +) -> tuple[list[Any], tuple[str, ...]]: + keys = tuple(sorted(leaf.data.keys())) + values = [leaf.data[k] for k in keys] return values, keys -def _unflatten(keys: tuple[str, ...], values: Sequence[Any]) -> MappingLeaf: +def _user_unflatten(keys: tuple[str, ...], values: Sequence[Any]) -> UserMappingLeaf: + return UserMappingLeaf(dict(zip(keys, values, strict=True))) + + +def _canonical_unflatten(keys: tuple[str, ...], values: Sequence[Any]) -> MappingLeaf: return MappingLeaf(dict(zip(keys, values, strict=True))) -jax.tree_util.register_pytree_node(MappingLeaf, _flatten, _unflatten) +jax.tree_util.register_pytree_node(UserMappingLeaf, _user_flatten, _user_unflatten) +jax.tree_util.register_pytree_node(MappingLeaf, _user_flatten, _canonical_unflatten) diff --git a/src/lcm/params/processing.py b/src/lcm/params/processing.py index 881191905..5b36e091d 100644 --- a/src/lcm/params/processing.py +++ b/src/lcm/params/processing.py @@ -9,7 +9,9 @@ range values surface as `ValueError`. - Python `float` and typed float arrays cast to `canonical_float_dtype()`. Down-cast overflow surfaces as `OverflowError`. -- `MappingLeaf` / `SequenceLeaf` containers recurse. +- `UserMappingLeaf` / `UserSequenceLeaf` containers (covering both the + user-input variant and the canonical narrow variant) recurse, always + emitting a canonical `MappingLeaf` / `SequenceLeaf`. The pass runs as the *last* step over `internal_params` — `pd.Series` leaves are reshaped to JAX arrays via `convert_series_in_params` @@ -34,8 +36,8 @@ from lcm.dtypes import safe_to_float_dtype, safe_to_int_dtype from lcm.exceptions import InvalidNameError, InvalidParamsError from lcm.interfaces import InternalRegime -from lcm.params.mapping_leaf import MappingLeaf -from lcm.params.sequence_leaf import SequenceLeaf +from lcm.params.mapping_leaf import MappingLeaf, UserMappingLeaf +from lcm.params.sequence_leaf import SequenceLeaf, UserSequenceLeaf from lcm.typing import ( InternalParams, ParamsTemplate, @@ -64,8 +66,9 @@ def process_params( The output always matches the params_template skeleton. Every numeric leaf — Python `bool` / `int` / `float`, typed JAX or numpy arrays, and - numerics inside `MappingLeaf` / `SequenceLeaf` — is cast to the - canonical pylcm dtype so the AOT signature is stable across calls. + numerics inside `UserMappingLeaf` / `UserSequenceLeaf` (or their + canonical narrow subclasses) — is cast to the canonical pylcm dtype + so the AOT signature is stable across calls. Callers that pass `pd.Series` leaves should orchestrate the steps themselves: `broadcast_to_template` (resolve), `convert_series_in_params` @@ -205,7 +208,9 @@ def _cast_leaves_to_canonical_dtype(value: Any, *, name: str) -> Any: # noqa: A Casts: - - `MappingLeaf` / `SequenceLeaf`: recurse on contents. + - `UserMappingLeaf` / `UserSequenceLeaf` (covers both wide user and + canonical narrow variants): recurse on contents, always emit the + canonical `MappingLeaf` / `SequenceLeaf`. - Python `bool`: `jnp.bool_(value)` (must come before `int` — `True` is a Python `int` subclass). - Python `int`: `safe_to_int_dtype(value)` → `jnp.int32`. @@ -224,14 +229,16 @@ def _cast_leaves_to_canonical_dtype(value: Any, *, name: str) -> Any: # noqa: A - Anything else (`str`, `None`, `dict`, lists, custom objects). """ - if isinstance(value, MappingLeaf): + # `UserMappingLeaf` covers both user (wide) and canonical (`MappingLeaf`) + # variants — recursing always emits a canonical `MappingLeaf`. + if isinstance(value, UserMappingLeaf): return MappingLeaf( { k: _cast_leaves_to_canonical_dtype(v, name=f"{name}.{k}") for k, v in value.data.items() } ) - if isinstance(value, SequenceLeaf): + if isinstance(value, UserSequenceLeaf): return SequenceLeaf( [ _cast_leaves_to_canonical_dtype(v, name=f"{name}[{i}]") @@ -268,7 +275,7 @@ def _cast_leaves_to_canonical_dtype(value: Any, *, name: str) -> Any: # noqa: A msg = ( f"{name!r}: unsupported leaf type {type(value).__name__} " f"(expected bool / int / float / numpy or JAX array / " - f"MappingLeaf / SequenceLeaf)." + f"UserMappingLeaf / UserSequenceLeaf)." ) raise InvalidParamsError(msg) diff --git a/src/lcm/params/sequence_leaf.py b/src/lcm/params/sequence_leaf.py index 83398a548..8176331f3 100644 --- a/src/lcm/params/sequence_leaf.py +++ b/src/lcm/params/sequence_leaf.py @@ -1,45 +1,86 @@ """A Sequence wrapper that is a JAX pytree but not itself a Sequence.""" from collections.abc import Sequence -from typing import Any +from typing import TYPE_CHECKING, Any import jax +if TYPE_CHECKING: + from lcm.typing import _ParamsLeaf, _UserParamsLeaf -class SequenceLeaf: + +class UserSequenceLeaf: """A Sequence wrapper that is a JAX pytree but not itself a Sequence. - Prevents flatten_regime_namespace from recursing into contents while - allowing JAX to trace through array values. + Holds the boundary leaf type — accepts the same wide value union as + `Model.solve` / `Model.simulate` parameters (Python scalars, numpy + arrays, `pd.Series`, JAX arrays, nested `UserMappingLeaf` / + `UserSequenceLeaf`). + + Prevents `flatten_regime_namespace` from recursing into contents while + allowing JAX to trace through array values. Data is frozen to + immutable containers on construction. + + The constructor accepts `Sequence[Any]` at runtime so beartype's + O(n) per-leaf check doesn't fire on user-supplied scalars or arrays; + the precise leaf-type contract is enforced statically through the + `data` class-attribute annotation. - Data is frozen to immutable containers on construction. """ __slots__ = ("data",) + if TYPE_CHECKING: + data: tuple[_UserParamsLeaf, ...] + def __init__(self, data: Sequence[Any]) -> None: from lcm.utils.containers import _make_immutable # noqa: PLC0415 self.data = tuple(_make_immutable(v) for v in data) def __repr__(self) -> str: - return f"SequenceLeaf({list(self.data)!r})" + return f"{type(self).__name__}({list(self.data)!r})" def __hash__(self) -> int: return hash(self.data) def __eq__(self, other: object) -> bool: - if not isinstance(other, SequenceLeaf): + if not isinstance(other, UserSequenceLeaf): return NotImplemented return self.data == other.data -def _flatten(sl: SequenceLeaf) -> tuple[list[Any], None]: - return list(sl.data), None +class SequenceLeaf(UserSequenceLeaf): + """Sequence leaf carrying only canonical-dtype values. + + Output of `cast_params_to_canonical_dtypes`. Every value is either a + canonical JAX array (`FloatND` / `IntND` / `BoolND`) or another + canonical leaf (`MappingLeaf` / `SequenceLeaf`). + + Subclasses `UserSequenceLeaf`, so any code that accepts the wide user + form via `isinstance(_, UserSequenceLeaf)` also accepts the canonical + form. Code requiring canonical values uses the narrower `SequenceLeaf` + type explicitly. + + """ + + __slots__ = () + + if TYPE_CHECKING: + data: tuple[_ParamsLeaf, ...] + + +def _user_flatten(leaf: UserSequenceLeaf) -> tuple[list[Any], None]: + return list(leaf.data), None + + +def _user_unflatten(_aux: None, values: Sequence[Any]) -> UserSequenceLeaf: + return UserSequenceLeaf(values) -def _unflatten(_aux: None, values: Sequence[Any]) -> SequenceLeaf: +def _canonical_unflatten(_aux: None, values: Sequence[Any]) -> SequenceLeaf: return SequenceLeaf(values) -jax.tree_util.register_pytree_node(SequenceLeaf, _flatten, _unflatten) +jax.tree_util.register_pytree_node(UserSequenceLeaf, _user_flatten, _user_unflatten) +jax.tree_util.register_pytree_node(SequenceLeaf, _user_flatten, _canonical_unflatten) diff --git a/src/lcm/typing.py b/src/lcm/typing.py index 369b2135e..a4b7a676f 100644 --- a/src/lcm/typing.py +++ b/src/lcm/typing.py @@ -7,8 +7,8 @@ from jax import Array from jaxtyping import Bool, Float, Int32, Key, Scalar -from lcm.params import MappingLeaf -from lcm.params.sequence_leaf import SequenceLeaf +from lcm.params import MappingLeaf, UserMappingLeaf +from lcm.params.sequence_leaf import SequenceLeaf, UserSequenceLeaf type ContinuousState = Float[Array, "..."] type ContinuousAction = Float[Array, "..."] @@ -55,7 +55,9 @@ type StatesPerRegime = MappingProxyType[RegimeName, RegimeStates] -type _ParamsLeaf = ( +# Boundary leaf type — accepted by `Model.__init__` / `Model.solve` / +# `Model.simulate` and canonicalized by `cast_params_to_canonical_dtypes`. +type _UserParamsLeaf = ( bool | int | float @@ -64,10 +66,19 @@ | BoolND | np.ndarray | pd.Series - | MappingLeaf - | SequenceLeaf + | UserMappingLeaf + | UserSequenceLeaf ) type UserParams = Mapping[ + str, + _UserParamsLeaf | Mapping[str, _UserParamsLeaf | Mapping[str, _UserParamsLeaf]], +] + +# Post-canonicalization leaf type — output of +# `cast_params_to_canonical_dtypes`. Only canonical-dtype JAX arrays and +# canonical-narrow `MappingLeaf` / `SequenceLeaf` instances survive. +type _ParamsLeaf = FloatND | IntND | BoolND | MappingLeaf | SequenceLeaf +type Params = Mapping[ str, _ParamsLeaf | Mapping[str, _ParamsLeaf | Mapping[str, _ParamsLeaf]], ] diff --git a/src/lcm/utils/containers.py b/src/lcm/utils/containers.py index 8b8189315..fcfe5564b 100644 --- a/src/lcm/utils/containers.py +++ b/src/lcm/utils/containers.py @@ -121,10 +121,9 @@ def first_non_none(*args: T | None) -> T: def _make_immutable(value: Any) -> Any: # noqa: ANN401 """Recursively convert a value to its immutable equivalent.""" - from lcm.params import MappingLeaf # noqa: PLC0415 - from lcm.params.sequence_leaf import SequenceLeaf # noqa: PLC0415 + from lcm.params import UserMappingLeaf, UserSequenceLeaf # noqa: PLC0415 - if isinstance(value, (MappingLeaf, SequenceLeaf)): + if isinstance(value, (UserMappingLeaf, UserSequenceLeaf)): return value # already immutable by construction if isinstance(value, (MappingProxyType, tuple, frozenset)): return value @@ -139,12 +138,11 @@ def _make_immutable(value: Any) -> Any: # noqa: ANN401 def _make_mutable(value: Any) -> Any: # noqa: ANN401, PLR0911 """Recursively convert a value to its mutable equivalent.""" - from lcm.params import MappingLeaf # noqa: PLC0415 - from lcm.params.sequence_leaf import SequenceLeaf # noqa: PLC0415 + from lcm.params import UserMappingLeaf, UserSequenceLeaf # noqa: PLC0415 - if isinstance(value, MappingLeaf): + if isinstance(value, UserMappingLeaf): return {k: _make_mutable(v) for k, v in value.data.items()} - if isinstance(value, SequenceLeaf): + if isinstance(value, UserSequenceLeaf): return [_make_mutable(v) for v in value.data] if isinstance(value, (set, list)): return value diff --git a/tests/test_mapping_leaf.py b/tests/test_mapping_leaf.py index a00ac4609..21a3dde5b 100644 --- a/tests/test_mapping_leaf.py +++ b/tests/test_mapping_leaf.py @@ -6,7 +6,13 @@ import pytest from lcm.exceptions import InvalidParamsError -from lcm.params import MappingLeaf, SequenceLeaf, as_leaf +from lcm.params import ( + MappingLeaf, + SequenceLeaf, + UserMappingLeaf, + UserSequenceLeaf, + as_leaf, +) from lcm.utils.containers import ( ensure_containers_are_immutable, ensure_containers_are_mutable, @@ -153,25 +159,33 @@ def test_flatten_regime_namespace_treats_mapping_leaf_as_leaf(): def test_as_leaf_mapping(): + """`as_leaf` of a Mapping returns the boundary `UserMappingLeaf` variant.""" result = as_leaf({"a": 1}) - assert isinstance(result, MappingLeaf) + assert isinstance(result, UserMappingLeaf) + assert not isinstance(result, MappingLeaf) assert result.data == {"a": 1} def test_as_leaf_mapping_proxy(): + """`as_leaf` of a `MappingProxyType` returns the boundary `UserMappingLeaf`.""" result = as_leaf(MappingProxyType({"a": 1})) - assert isinstance(result, MappingLeaf) + assert isinstance(result, UserMappingLeaf) + assert not isinstance(result, MappingLeaf) def test_as_leaf_list(): + """`as_leaf` of a list returns the boundary `UserSequenceLeaf` variant.""" result = as_leaf([1, 2, 3]) - assert isinstance(result, SequenceLeaf) + assert isinstance(result, UserSequenceLeaf) + assert not isinstance(result, SequenceLeaf) assert result.data == (1, 2, 3) def test_as_leaf_tuple(): + """`as_leaf` of a tuple returns the boundary `UserSequenceLeaf` variant.""" result = as_leaf((1, 2)) - assert isinstance(result, SequenceLeaf) + assert isinstance(result, UserSequenceLeaf) + assert not isinstance(result, SequenceLeaf) assert result.data == (1, 2) diff --git a/tests/test_pandas_utils.py b/tests/test_pandas_utils.py index 17bd50a23..49b447743 100644 --- a/tests/test_pandas_utils.py +++ b/tests/test_pandas_utils.py @@ -1520,12 +1520,12 @@ def test_convert_series_mixed_dict() -> None: def test_convert_series_mapping_leaf() -> None: - """Series inside a MappingLeaf is converted.""" - from lcm.params import MappingLeaf # noqa: PLC0415 + """Series inside a `UserMappingLeaf` is converted in place.""" + from lcm.params import UserMappingLeaf # noqa: PLC0415 model = get_stochastic_model(3) series = _build_partner_probs_series(model) - leaf = MappingLeaf({"sub_key": series}) + leaf = UserMappingLeaf({"sub_key": series}) params = { "working_life": { "next_partner": {"probs_array": leaf}, @@ -1541,19 +1541,19 @@ def test_convert_series_mapping_leaf() -> None: regime_names_to_ids=model.regime_names_to_ids, ) converted_leaf = result["working_life"]["next_partner__probs_array"] - assert isinstance(converted_leaf, MappingLeaf) + assert isinstance(converted_leaf, UserMappingLeaf) arr = converted_leaf.data["sub_key"] - assert arr.shape == (3, 2, 2, 2) + assert arr.shape == (3, 2, 2, 2) # ty: ignore[unresolved-attribute] def test_convert_series_nested_mapping_leaf() -> None: - """Series inside nested MappingLeaf is recursively converted.""" - from lcm.params import MappingLeaf # noqa: PLC0415 + """Series inside nested `UserMappingLeaf` is recursively converted.""" + from lcm.params import UserMappingLeaf # noqa: PLC0415 model = get_stochastic_model(3) series = _build_partner_probs_series(model) - inner = MappingLeaf({"sub": series}) - outer = MappingLeaf({"inner_leaf": inner}) + inner = UserMappingLeaf({"sub": series}) + outer = UserMappingLeaf({"inner_leaf": inner}) params = { "working_life": { "next_partner": {"probs_array": outer}, @@ -1569,11 +1569,11 @@ def test_convert_series_nested_mapping_leaf() -> None: regime_names_to_ids=model.regime_names_to_ids, ) converted = result["working_life"]["next_partner__probs_array"] - assert isinstance(converted, MappingLeaf) + assert isinstance(converted, UserMappingLeaf) inner_converted = converted.data["inner_leaf"] - assert isinstance(inner_converted, MappingLeaf) + assert isinstance(inner_converted, UserMappingLeaf) assert not isinstance(inner_converted.data["sub"], pd.Series) - assert inner_converted.data["sub"].shape == (3, 2, 2, 2) + assert inner_converted.data["sub"].shape == (3, 2, 2, 2) # ty: ignore[unresolved-attribute] def test_convert_series_unknown_param_raises() -> None: @@ -1880,12 +1880,12 @@ class _RId: def test_convert_series_sequence_leaf_traversal() -> None: - """Series inside a SequenceLeaf should be converted to JAX arrays.""" - from lcm.params.sequence_leaf import SequenceLeaf # noqa: PLC0415 + """Series inside a `UserSequenceLeaf` should be converted to JAX arrays.""" + from lcm.params.sequence_leaf import UserSequenceLeaf # noqa: PLC0415 model = get_stochastic_model(3) sr = pd.Series([10.0]) - leaf = SequenceLeaf((sr, 42)) + leaf = UserSequenceLeaf((sr, 42)) params = {"working_life": {"labor_income": {"wage": leaf}}} internal = broadcast_to_template( params=params, template=model._params_template, required=False @@ -1897,9 +1897,9 @@ def test_convert_series_sequence_leaf_traversal() -> None: regime_names_to_ids=model.regime_names_to_ids, ) converted = result["working_life"]["labor_income__wage"] - assert isinstance(converted, SequenceLeaf) + assert isinstance(converted, UserSequenceLeaf) assert not isinstance(converted.data[0], pd.Series) - np.testing.assert_allclose(converted.data[0], jnp.array([10.0])) + np.testing.assert_allclose(converted.data[0], jnp.array([10.0])) # ty: ignore[no-matching-overload] def test_resolve_categoricals_conflict_raises() -> None: diff --git a/tests/test_validation_scalar_actions.py b/tests/test_validation_scalar_actions.py index e53e521b3..91040597c 100644 --- a/tests/test_validation_scalar_actions.py +++ b/tests/test_validation_scalar_actions.py @@ -38,7 +38,7 @@ def _aggregate_with_ids( """ data = jnp.array([consumption]) # shape-(1,) when scalar ids = invariant_array.data["ids"] # shape-(1,) always - result = jax.ops.segment_sum(data, ids, num_segments=1) + result = jax.ops.segment_sum(data, ids, num_segments=1) # ty: ignore[invalid-argument-type] return result.squeeze() From fd32bd6be19c6275279c61811c0a673723d12fd3 Mon Sep 17 00:00:00 2001 From: Hans-Martin von Gaudecker Date: Fri, 15 May 2026 06:23:10 +0200 Subject: [PATCH 61/77] Sweep params signatures: tighten `FlatRegimeParams`, widen call protocols MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `FlatRegimeParams` now includes `MappingLeaf` and `SequenceLeaf` in its value union, matching what `cast_params_to_canonical_dtypes` actually emits and surfacing the leaf type to every downstream consumer of `internal_params`. - `InternalUserFunction`, `RegimeTransitionFunction`, `VmappedRegimeTransitionFunction`, and `NextStateSimulationFunction` accept `MappingLeaf | SequenceLeaf` in `*args` / `**kwargs` — every call site already passes them via `**regime_params`. - `InternalRegime.state_action_space` narrows `_ParamsLeaf` to `Array` / `ScalarFloat | ScalarInt` via `cast` at the two runtime-grid / shock-grid substitution points (the only two slots where `all_params[...]` is statically wider than the value's known shape). - Tests touching `internal_params["..."]["..."].dtype` / `.shape` (or passing the leaf to `float()` / `int()` / `assert_allclose`) gain targeted `# ty: ignore` directives — runtime values are arrays at those sites, but the static type is the wider canonical-leaf union. Co-Authored-By: Claude Opus 4.7 --- src/lcm/interfaces.py | 15 ++++++++++++--- src/lcm/typing.py | 21 ++++++++++++--------- tests/test_float_dtype_invariants.py | 10 +++++----- tests/test_int_dtype_invariants.py | 10 +++++----- tests/test_pandas_utils.py | 22 +++++++++++----------- tests/test_validate_param_types.py | 6 +++--- 6 files changed, 48 insertions(+), 36 deletions(-) diff --git a/src/lcm/interfaces.py b/src/lcm/interfaces.py index b036da408..2cb178bf8 100644 --- a/src/lcm/interfaces.py +++ b/src/lcm/interfaces.py @@ -2,8 +2,10 @@ from collections.abc import Callable from math import prod as math_prod from types import MappingProxyType +from typing import cast import jax +from jax import Array from lcm.exceptions import PyLCMError from lcm.grids import Grid, IrregSpacedGrid @@ -277,10 +279,13 @@ def state_action_space(self, regime_params: FlatRegimeParams) -> StateActionSpac points_key = f"{name}__points" if points_key not in all_params: continue + # Runtime grid-point params are flat JAX arrays — never a + # `MappingLeaf` / `SequenceLeaf` — so narrow via `cast`. + points = cast("Array", all_params[points_key]) if in_states: - state_replacements[name] = all_params[points_key] + state_replacements[name] = points else: - action_replacements[name] = all_params[points_key] + action_replacements[name] = points # `_ShockGrid` is state-only by construction (intrinsic # transitions, forbidden as actions per AGENTS.md). The # `in_states` gate makes that invariant explicit — a @@ -298,7 +303,11 @@ def state_action_space(self, regime_params: FlatRegimeParams) -> StateActionSpac continue shock_kw: dict[str, ScalarFloat | ScalarInt] = dict(spec.params) for p in spec.params_to_pass_at_runtime: - shock_kw[p] = all_params[f"{name}__{p}"] + # Runtime shock-grid params are flat JAX scalars — never + # a `MappingLeaf` / `SequenceLeaf` — so narrow via `cast`. + shock_kw[p] = cast( + "ScalarFloat | ScalarInt", all_params[f"{name}__{p}"] + ) state_replacements[name] = spec.compute_gridpoints(**shock_kw) new_states = ( diff --git a/src/lcm/typing.py b/src/lcm/typing.py index a4b7a676f..16cd28e22 100644 --- a/src/lcm/typing.py +++ b/src/lcm/typing.py @@ -85,8 +85,11 @@ # Internal regime parameters: A flat mapping with function-qualified names. # Keys are always function-qualified (e.g., "utility__risk_aversion", -# "H__discount_factor"). Values are canonical-dtype scalars or arrays. -type FlatRegimeParams = MappingProxyType[str, FloatND | IntND | BoolND] +# "H__discount_factor"). Values are canonical-dtype JAX arrays or +# canonical-narrow container leaves. +type FlatRegimeParams = MappingProxyType[ + str, FloatND | IntND | BoolND | MappingLeaf | SequenceLeaf +] type InternalParams = MappingProxyType[RegimeName, FlatRegimeParams] # Immutable templates, used internally @@ -122,8 +125,8 @@ class InternalUserFunction(Protocol): def __call__( self, - *args: FloatND | IntND | BoolND | float, - **kwargs: FloatND | IntND | BoolND | float, + *args: FloatND | IntND | BoolND | float | MappingLeaf | SequenceLeaf, + **kwargs: FloatND | IntND | BoolND | float | MappingLeaf | SequenceLeaf, ) -> FloatND | IntND | BoolND: ... @@ -141,8 +144,8 @@ class RegimeTransitionFunction(Protocol): def __call__( self, - *args: FloatND | IntND | BoolND | float, - **kwargs: FloatND | IntND | BoolND | float, + *args: FloatND | IntND | BoolND | float | MappingLeaf | SequenceLeaf, + **kwargs: FloatND | IntND | BoolND | float | MappingLeaf | SequenceLeaf, ) -> MappingProxyType[RegimeName, FloatND]: ... @@ -160,8 +163,8 @@ class VmappedRegimeTransitionFunction(Protocol): def __call__( self, - *args: FloatND | IntND | BoolND | float, - **kwargs: FloatND | IntND | BoolND | float, + *args: FloatND | IntND | BoolND | float | MappingLeaf | SequenceLeaf, + **kwargs: FloatND | IntND | BoolND | float | MappingLeaf | SequenceLeaf, ) -> MappingProxyType[RegimeName, FloatND]: ... @@ -241,7 +244,7 @@ class NextStateSimulationFunction(Protocol): def __call__( self, - **kwargs: FloatND | IntND | Period | Age, + **kwargs: FloatND | IntND | Period | Age | MappingLeaf | SequenceLeaf, ) -> MappingProxyType[ RegimeName, MappingProxyType[str, DiscreteState | ContinuousState] ]: ... diff --git a/tests/test_float_dtype_invariants.py b/tests/test_float_dtype_invariants.py index 81535079f..fbc9202de 100644 --- a/tests/test_float_dtype_invariants.py +++ b/tests/test_float_dtype_invariants.py @@ -123,7 +123,7 @@ def test_process_params_casts_float64_array_to_canonical_under_no_x64( ) schedule = out["regime_a"]["fun__schedule"] - assert schedule.dtype == jnp.float32 + assert schedule.dtype == jnp.float32 # ty: ignore[unresolved-attribute] def test_process_params_casts_python_float_to_canonical(x64_disabled: None): @@ -137,8 +137,8 @@ def test_process_params_casts_python_float_to_canonical(x64_disabled: None): ) discount_factor = out["regime_a"]["fun__discount_factor"] - np.testing.assert_allclose(float(discount_factor), 0.95, rtol=1e-6) - assert discount_factor.dtype == canonical_float_dtype() + np.testing.assert_allclose(float(discount_factor), 0.95, rtol=1e-6) # ty: ignore[invalid-argument-type] + assert discount_factor.dtype == canonical_float_dtype() # ty: ignore[unresolved-attribute] def test_process_params_float_array_overflow_raises_with_qualified_name( @@ -272,7 +272,7 @@ def test_process_params_casts_float_array_inside_mapping_leaf_to_canonical( ) assert ( - out["regime_a"]["fun__sched"].data[key].dtype # ty: ignore[unresolved-attribute] + out["regime_a"]["fun__sched"].data[key].dtype # ty: ignore[unresolved-attribute, invalid-argument-type] == jnp.float32 ) @@ -302,6 +302,6 @@ def test_process_params_casts_float_array_inside_sequence_leaf_to_canonical( ) assert ( - out["regime_a"]["fun__sched"].data[index].dtype # ty: ignore[unresolved-attribute] + out["regime_a"]["fun__sched"].data[index].dtype # ty: ignore[unresolved-attribute, invalid-argument-type] == jnp.float32 ) diff --git a/tests/test_int_dtype_invariants.py b/tests/test_int_dtype_invariants.py index b302c6474..a885ea9c3 100644 --- a/tests/test_int_dtype_invariants.py +++ b/tests/test_int_dtype_invariants.py @@ -123,8 +123,8 @@ def test_process_params_casts_python_int_to_int32() -> None: ) final_age = out["regime_a"]["fun__final_age"] - assert int(final_age) == 65 - assert final_age.dtype == jnp.int32 + assert int(final_age) == 65 # ty: ignore[invalid-argument-type] + assert final_age.dtype == jnp.int32 # ty: ignore[unresolved-attribute] def test_process_params_casts_int64_array_to_int32() -> None: @@ -145,7 +145,7 @@ def test_process_params_casts_int64_array_to_int32() -> None: ) schedule = out["regime_a"]["fun__schedule"] - assert schedule.dtype == jnp.int32 + assert schedule.dtype == jnp.int32 # ty: ignore[unresolved-attribute] def test_process_params_int_array_overflow_raises_with_qualified_name() -> None: @@ -187,7 +187,7 @@ def test_process_params_casts_int_array_inside_mapping_leaf_to_int32(key: str) - ) assert ( - out["regime_a"]["fun__sched"].data[key].dtype # ty: ignore[unresolved-attribute] + out["regime_a"]["fun__sched"].data[key].dtype # ty: ignore[unresolved-attribute, invalid-argument-type] == jnp.int32 ) @@ -217,7 +217,7 @@ def test_process_params_casts_int_array_inside_sequence_leaf_to_int32( ) assert ( - out["regime_a"]["fun__sched"].data[index].dtype # ty: ignore[unresolved-attribute] + out["regime_a"]["fun__sched"].data[index].dtype # ty: ignore[unresolved-attribute, invalid-argument-type] == jnp.int32 ) diff --git a/tests/test_pandas_utils.py b/tests/test_pandas_utils.py index 49b447743..e0f2620b9 100644 --- a/tests/test_pandas_utils.py +++ b/tests/test_pandas_utils.py @@ -1444,8 +1444,8 @@ def test_convert_series_function_level_series() -> None: regime_names_to_ids=model.regime_names_to_ids, ) arr = result["working_life"]["next_partner__probs_array"] - assert arr.shape == (3, 2, 2, 2) - assert float(arr[0, 0, 0, 0]) == pytest.approx(1.0) + assert arr.shape == (3, 2, 2, 2) # ty: ignore[unresolved-attribute] + assert float(arr[0, 0, 0, 0]) == pytest.approx(1.0) # ty: ignore[not-subscriptable] def test_convert_series_model_level_scalar_passthrough() -> None: @@ -1485,7 +1485,7 @@ def test_convert_series_regime_level_series() -> None: regime_names_to_ids=model.regime_names_to_ids, ) arr = result["working_life"]["next_partner__probs_array"] - assert arr.shape == (3, 2, 2, 2) + assert arr.shape == (3, 2, 2, 2) # ty: ignore[unresolved-attribute] def test_convert_series_mixed_dict() -> None: @@ -1512,9 +1512,9 @@ def test_convert_series_mixed_dict() -> None: ) assert result["working_life"]["H__discount_factor"] == 0.95 assert result["working_life"]["utility__disutility_of_work"] == 0.5 - assert result["working_life"]["next_partner__probs_array"].shape == (3, 2, 2, 2) + assert result["working_life"]["next_partner__probs_array"].shape == (3, 2, 2, 2) # ty: ignore[unresolved-attribute] assert result["working_life"]["next_wealth__interest_rate"] == 0.05 - np.testing.assert_allclose( + np.testing.assert_allclose( # ty: ignore[no-matching-overload] result["working_life"]["labor_income__wage"], jnp.array([10.0]) ) @@ -1656,7 +1656,7 @@ def test_convert_series_with_derived_categoricals() -> None: regime_names_to_ids=model.regime_names_to_ids, ) arr = result["retirement"]["next_partner__probs_array"] - assert arr.shape == (3, 2, 2, 2) + assert arr.shape == (3, 2, 2, 2) # ty: ignore[unresolved-attribute] def test_convert_series_per_target_transition() -> None: @@ -1736,7 +1736,7 @@ def _next_wealth(wealth: float) -> float: regime_names_to_ids=model.regime_names_to_ids, ) arr = result["working"]["to_working_next_health__probs_array"] - assert arr.shape == (3, 2, 2) + assert arr.shape == (3, 2, 2) # ty: ignore[unresolved-attribute] def test_build_outcome_mapping_qualified_func_name() -> None: @@ -1834,8 +1834,8 @@ def _next_wealth_sc(wealth: float) -> float: ages=model.ages, regime_names_to_ids=model.regime_names_to_ids, ) - assert result_both["regime_a"]["utility__rates"].shape == (2,) - assert result_both["regime_b"]["utility__rates"].shape == (3,) + assert result_both["regime_a"]["utility__rates"].shape == (2,) # ty: ignore[unresolved-attribute] + assert result_both["regime_b"]["utility__rates"].shape == (3,) # ty: ignore[unresolved-attribute] def test_convert_series_runtime_grid_param() -> None: @@ -1876,7 +1876,7 @@ class _RId: ages=model.ages, regime_names_to_ids=model.regime_names_to_ids, ) - np.testing.assert_allclose(result["alive"]["wealth__points"], sr.to_numpy()) + np.testing.assert_allclose(result["alive"]["wealth__points"], sr.to_numpy()) # ty: ignore[no-matching-overload] def test_convert_series_sequence_leaf_traversal() -> None: @@ -2022,7 +2022,7 @@ def _health_probs_cross( arr = result["pre65"]["to_post65_next_health__health_trans_probs_cross"] # Shape: (n_ages=2, n_source_health=3, n_target_health=2) # n_ages=2 because AgeGrid has ages [0, 1]; missing age 1 is NaN-filled. - assert arr.shape == (2, 3, 2) + assert arr.shape == (2, 3, 2) # ty: ignore[unresolved-attribute] def test_resolve_categoricals_includes_derived_when_no_regime_name() -> None: diff --git a/tests/test_validate_param_types.py b/tests/test_validate_param_types.py index 0aadbfaca..51a5a6c29 100644 --- a/tests/test_validate_param_types.py +++ b/tests/test_validate_param_types.py @@ -74,7 +74,7 @@ def test_jax_array_param_kept_at_canonical_dtype() -> None: params={"bonus": jnp.asarray(1.0), "discount_factor": 0.95} ) bonus = internal["working"]["utility__bonus"] - assert bonus.dtype == canonical_float_dtype() + assert bonus.dtype == canonical_float_dtype() # ty: ignore[unresolved-attribute] def test_python_float_param_cast_to_canonical_dtype() -> None: @@ -82,5 +82,5 @@ def test_python_float_param_cast_to_canonical_dtype() -> None: model = _make_model() internal = model._process_params(params={"bonus": 1.0, "discount_factor": 0.95}) bonus = internal["working"]["utility__bonus"] - assert float(bonus) == 1.0 - assert bonus.dtype == canonical_float_dtype() + assert float(bonus) == 1.0 # ty: ignore[invalid-argument-type] + assert bonus.dtype == canonical_float_dtype() # ty: ignore[unresolved-attribute] From 67dec68a5fac26d900fd954481ec459441bc5b82 Mon Sep 17 00:00:00 2001 From: Hans-Martin von Gaudecker Date: Fri, 15 May 2026 06:35:08 +0200 Subject: [PATCH 62/77] Retype initial_conditions/initial_states with semantic keys MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Boundary inputs (`Model.simulate`, `canonicalize_initial_conditions`): `Mapping[StateName | Literal["regime"], Array | np.ndarray]` — honest user-facing type, accepts both JAX and numpy arrays. - Post-canonicalization signatures (`simulate`, `validate_initial_conditions`, `build_initial_states` and the other helpers, `pandas_utils`): `Mapping[StateName | Literal["regime"], FloatND | IntND]` or `Mapping[StateName, FloatND | IntND]` (no regime key) — narrow canonical dtypes flow downstream. - Persistence snapshots: `Mapping[StateName | Literal["regime"], Array]` — neutral storage form. Co-Authored-By: Claude Opus 4.7 --- src/lcm/model.py | 9 ++++++--- src/lcm/pandas_utils.py | 4 ++-- src/lcm/persistence.py | 9 +++++---- src/lcm/simulation/initial_conditions.py | 21 +++++++++++---------- src/lcm/simulation/simulate.py | 4 +++- 5 files changed, 27 insertions(+), 20 deletions(-) diff --git a/src/lcm/model.py b/src/lcm/model.py index f5a927b0f..f285154dc 100644 --- a/src/lcm/model.py +++ b/src/lcm/model.py @@ -6,9 +6,12 @@ from collections.abc import Mapping from pathlib import Path from types import MappingProxyType +from typing import Literal +import numpy as np import pandas as pd from beartype import beartype +from jax import Array from lcm._beartype_conf import MODEL_CONF, PARAMS_CONF from lcm.ages import AgeGrid @@ -43,14 +46,13 @@ from lcm.simulation.simulate import simulate from lcm.solution.solve_brute import solve from lcm.typing import ( - FloatND, FunctionName, InternalParams, - IntND, ParamsTemplate, PeriodToRegimeToVArr, RegimeName, RegimeNamesToIds, + StateName, UserFacingParamsTemplate, UserParams, ) @@ -381,7 +383,8 @@ def simulate( self, *, params: UserParams, - initial_conditions: Mapping[str, FloatND | IntND] | pd.DataFrame, + initial_conditions: Mapping[StateName | Literal["regime"], Array | np.ndarray] + | pd.DataFrame, period_to_regime_to_V_arr: PeriodToRegimeToVArr | None, check_initial_conditions: bool = True, seed: int | None = None, diff --git a/src/lcm/pandas_utils.py b/src/lcm/pandas_utils.py index 364da0ac4..0fee26ef6 100644 --- a/src/lcm/pandas_utils.py +++ b/src/lcm/pandas_utils.py @@ -3,7 +3,7 @@ from collections.abc import Callable, Mapping from dataclasses import dataclass from types import MappingProxyType -from typing import cast +from typing import Literal, cast import jax.numpy as jnp import numpy as np @@ -149,7 +149,7 @@ def initial_conditions_from_dataframe( # noqa: C901 nan_mask = np.isnan(result_arrays[col]) result_arrays[col][nan_mask] = MISSING_CAT_CODE - initial_conditions: dict[str, FloatND | IntND] = { + initial_conditions: dict[StateName | Literal["regime"], FloatND | IntND] = { col: jnp.array(arr, dtype=jnp.int32) if col in discrete_state_names else jnp.array(arr, dtype=canonical_float_dtype()) diff --git a/src/lcm/persistence.py b/src/lcm/persistence.py index c4296f124..639979a9a 100644 --- a/src/lcm/persistence.py +++ b/src/lcm/persistence.py @@ -13,18 +13,19 @@ from dataclasses import dataclass from pathlib import Path from types import MappingProxyType -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING, Any, Literal import cloudpickle import h5py import jax.numpy as jnp import numpy as np +from jax import Array from lcm.typing import ( FloatND, - IntND, PeriodToRegimeToVArr, RegimeName, + StateName, UserParams, ) @@ -74,7 +75,7 @@ class SimulateSnapshot: params: UserParams | None """User parameters passed to simulate.""" - initial_conditions: Mapping[str, FloatND | IntND] | None + initial_conditions: Mapping[StateName | Literal["regime"], Array] | None """Mapping of state names and "regime" to arrays.""" period_to_regime_to_V_arr: PeriodToRegimeToVArr | None @@ -206,7 +207,7 @@ def save_simulate_snapshot( *, model: Model, params: UserParams, - initial_conditions: Mapping[str, FloatND | IntND], + initial_conditions: Mapping[StateName | Literal["regime"], Array], period_to_regime_to_V_arr: PeriodToRegimeToVArr, result: SimulationResult, log_path: Path, diff --git a/src/lcm/simulation/initial_conditions.py b/src/lcm/simulation/initial_conditions.py index 9ee1284d0..8a3a70c7c 100644 --- a/src/lcm/simulation/initial_conditions.py +++ b/src/lcm/simulation/initial_conditions.py @@ -7,11 +7,12 @@ from collections.abc import Callable, Mapping, Sequence from types import MappingProxyType -from typing import NoReturn, cast +from typing import Literal, NoReturn, cast import jax import numpy as np import pandas as pd +from jax import Array from jax import numpy as jnp from lcm.ages import PSEUDO_STATE_NAMES, AgeGrid @@ -53,7 +54,7 @@ def canonicalize_initial_conditions( *, - initial_conditions: Mapping[str, object], + initial_conditions: Mapping[StateName | Literal["regime"], Array | np.ndarray], internal_regimes: MappingProxyType[RegimeName, InternalRegime], ) -> dict[str, FloatND | IntND]: """Cast every initial-conditions array to its canonical pylcm dtype. @@ -101,7 +102,7 @@ def canonicalize_initial_conditions( def build_initial_states( *, - initial_states: Mapping[str, FloatND | IntND], + initial_states: Mapping[StateName, FloatND | IntND], internal_regimes: MappingProxyType[RegimeName, InternalRegime], ) -> StatesPerRegime: """Build the regime-keyed state carrier from user-provided initial states. @@ -178,7 +179,7 @@ def build_initial_states( def validate_initial_conditions( *, - initial_conditions: Mapping[str, FloatND | IntND], + initial_conditions: Mapping[StateName | Literal["regime"], FloatND | IntND], internal_regimes: MappingProxyType[RegimeName, InternalRegime], regime_names_to_ids: RegimeNamesToIds, internal_params: InternalParams, @@ -296,7 +297,7 @@ def _format_missing_states_message(missing: set[str], required: set[str]) -> str def _collect_state_name_errors( *, - initial_states: Mapping[str, FloatND | IntND], + initial_states: Mapping[StateName, FloatND | IntND], regime_id_arr: Int1D, regime_ids_to_names: RegimeIdsToNames, internal_regimes: MappingProxyType[RegimeName, InternalRegime], @@ -354,7 +355,7 @@ def _collect_state_name_errors( def _collect_structural_errors( *, - initial_states: Mapping[str, FloatND | IntND], + initial_states: Mapping[StateName, FloatND | IntND], regime_id_arr: Int1D, regime_ids_to_names: RegimeIdsToNames, regime_names_to_ids: RegimeNamesToIds, @@ -456,7 +457,7 @@ def _collect_structural_errors( def _collect_feasibility_errors( *, - initial_states: Mapping[str, FloatND | IntND], + initial_states: Mapping[StateName, FloatND | IntND], regime_id_arr: Int1D, regime_names_to_ids: RegimeNamesToIds, internal_regimes: MappingProxyType[RegimeName, InternalRegime], @@ -507,7 +508,7 @@ def _collect_feasibility_errors( def _validate_discrete_state_values( *, - initial_states: Mapping[str, FloatND | IntND], + initial_states: Mapping[StateName, FloatND | IntND], internal_regimes: MappingProxyType[RegimeName, InternalRegime], regime_id_arr: Int1D, regime_names_to_ids: RegimeNamesToIds, @@ -644,7 +645,7 @@ def _check_regime_feasibility( # noqa: C901 *, internal_regime: InternalRegime, regime_name: RegimeName, - initial_states: Mapping[str, FloatND | IntND], + initial_states: Mapping[StateName, FloatND | IntND], subject_indices: list[int], regime_params: Mapping[str, object], ages: AgeGrid, @@ -899,7 +900,7 @@ def _format_infeasibility_message( infeasible_indices: Sequence[int], internal_regime: InternalRegime, regime_name: RegimeName, - initial_states: Mapping[str, FloatND | IntND], + initial_states: Mapping[StateName, FloatND | IntND], state_names: Sequence[str], per_constraint_admits_any: Mapping[str, np.ndarray], ) -> str: diff --git a/src/lcm/simulation/simulate.py b/src/lcm/simulation/simulate.py index 2a9a7fa46..f7e974acd 100644 --- a/src/lcm/simulation/simulate.py +++ b/src/lcm/simulation/simulate.py @@ -2,6 +2,7 @@ import time from collections.abc import Mapping from types import MappingProxyType +from typing import Literal import jax import jax.numpy as jnp @@ -35,6 +36,7 @@ RegimeNamesToIds, ScalarFloat, ScalarInt, + StateName, StatesPerRegime, ) from lcm.utils.containers import invert_regime_ids @@ -51,7 +53,7 @@ def simulate( *, internal_params: InternalParams, - initial_conditions: Mapping[str, FloatND | IntND], + initial_conditions: Mapping[StateName | Literal["regime"], FloatND | IntND], internal_regimes: MappingProxyType[RegimeName, InternalRegime], regime_names_to_ids: RegimeNamesToIds, logger: logging.Logger, From 2bd1664bedc14cc29b3c4dd221899aaa01c2e1cb Mon Sep 17 00:00:00 2001 From: Hans-Martin von Gaudecker Date: Fri, 15 May 2026 06:38:48 +0200 Subject: [PATCH 63/77] Use StateOrActionName / ActionName aliases in state-and-action-keyed mappings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `MappingProxyType[str, ...]` / `dict[str, ...]` at sites whose keys are state and/or action names: tighten the key type to the existing `StateOrActionName` alias (state-or-action) or `ActionName` (action-only). - `validate_regime_transition_probs.state_action_values` (×2 sigs): → `MappingProxyType[StateOrActionName, FloatND | IntND] | None` - `_validate_regime_transition_single` local `grids` / `point`: → `dict[StateOrActionName, FloatND | IntND]` - `_lookup_values_from_indices.grids` + return: → `MappingProxyType[StateOrActionName, FloatND | IntND]` - `build_initial_states` local `action_grids`: → `dict[ActionName, FloatND | IntND]` Co-Authored-By: Claude Opus 4.7 --- src/lcm/simulation/initial_conditions.py | 2 +- src/lcm/simulation/simulate.py | 5 +++-- src/lcm/utils/error_handling.py | 11 +++++++---- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/lcm/simulation/initial_conditions.py b/src/lcm/simulation/initial_conditions.py index 8a3a70c7c..f799564d0 100644 --- a/src/lcm/simulation/initial_conditions.py +++ b/src/lcm/simulation/initial_conditions.py @@ -681,7 +681,7 @@ def _check_regime_feasibility( # noqa: C901 state_action_space = internal_regime.state_action_space( regime_params=cast("FlatRegimeParams", MappingProxyType(dict(regime_params))), ) - action_grids: dict[str, FloatND | IntND] = { + action_grids: dict[ActionName, FloatND | IntND] = { **state_action_space.discrete_actions, **state_action_space.continuous_actions, } diff --git a/src/lcm/simulation/simulate.py b/src/lcm/simulation/simulate.py index f7e974acd..897537eb9 100644 --- a/src/lcm/simulation/simulate.py +++ b/src/lcm/simulation/simulate.py @@ -37,6 +37,7 @@ ScalarFloat, ScalarInt, StateName, + StateOrActionName, StatesPerRegime, ) from lcm.utils.containers import invert_regime_ids @@ -339,8 +340,8 @@ def _simulate_regime_in_period( def _lookup_values_from_indices( *, flat_indices: IntND, - grids: MappingProxyType[str, FloatND | IntND], -) -> MappingProxyType[str, FloatND | IntND]: + grids: MappingProxyType[StateOrActionName, FloatND | IntND], +) -> MappingProxyType[StateOrActionName, FloatND | IntND]: """Retrieve values from indices. Args: diff --git a/src/lcm/utils/error_handling.py b/src/lcm/utils/error_handling.py index e16ef057f..ffe0c338a 100644 --- a/src/lcm/utils/error_handling.py +++ b/src/lcm/utils/error_handling.py @@ -27,6 +27,7 @@ ScalarFloat, ScalarInt, StateName, + StateOrActionName, ) # Genuine circular import: model.py imports from this module at module level. @@ -292,7 +293,8 @@ def validate_regime_transition_probs( regime_name: RegimeName, age: float | ScalarInt | ScalarFloat, next_age: float | ScalarInt | ScalarFloat, - state_action_values: MappingProxyType[str, FloatND | IntND] | None = None, + state_action_values: MappingProxyType[StateOrActionName, FloatND | IntND] + | None = None, ) -> None: """Validate regime transition probabilities. @@ -356,7 +358,8 @@ def validate_regime_transition_probs( def _format_sum_violation( *, sum_all: FloatND, - state_action_values: MappingProxyType[str, FloatND | IntND] | None = None, + state_action_values: MappingProxyType[StateOrActionName, FloatND | IntND] + | None = None, ) -> str: """Format a human-readable description of probability sum violations. @@ -483,7 +486,7 @@ def _validate_regime_transition_single( filtered_params = {k: v for k, v in regime_params.items() if k in accepted_params} # Collect only grid variables the transition function accepts - grids: dict[str, FloatND | IntND] = { + grids: dict[StateOrActionName, FloatND | IntND] = { k: v for k, v in state_action_space.states.items() if k in accepted_params } | {k: v for k, v in state_action_space.actions.items() if k in accepted_params} @@ -524,7 +527,7 @@ def _call( age=ages.values[period], # noqa: PD011 ) ) - point: dict[str, FloatND | IntND] = {} + point: dict[StateOrActionName, FloatND | IntND] = {} validate_regime_transition_probs( regime_transition_probs=regime_transition_probs, From a1e0a09c990be25cae1c43c30d9bf562bcc02d49 Mon Sep 17 00:00:00 2001 From: Hans-Martin von Gaudecker Date: Fri, 15 May 2026 06:46:43 +0200 Subject: [PATCH 64/77] Add explicit @beartype to grid and shock public constructors Decorate every public grid and shock constructor with @beartype(conf=GRID_CONF) so a wrong-typed argument always raises GridInitializationError, independent of which beartype claw covers the module. Prepares the perimeter for collapsing the per-area claws into a single lcm-package claw without losing the project-specific exception at user-facing construction sites. Affected types: - LinSpacedGrid, LogSpacedGrid (via UniformContinuousGrid.__init__ and LogSpacedGrid.__init__) - IrregSpacedGrid - Piece, PiecewiseLinSpacedGrid, PiecewiseLogSpacedGrid - DiscreteGrid - Uniform, Normal, LogNormal, NormalMixture - Tauchen, Rouwenhorst, TauchenNormalMixture Co-Authored-By: Claude Opus 4.7 --- src/lcm/grids/continuous.py | 6 ++++++ src/lcm/grids/discrete.py | 3 +++ src/lcm/grids/piecewise.py | 5 +++++ src/lcm/shocks/ar1.py | 5 +++++ src/lcm/shocks/iid.py | 6 ++++++ 5 files changed, 25 insertions(+) diff --git a/src/lcm/grids/continuous.py b/src/lcm/grids/continuous.py index da1bd27df..0febf27f9 100644 --- a/src/lcm/grids/continuous.py +++ b/src/lcm/grids/continuous.py @@ -4,7 +4,9 @@ from dataclasses import dataclass import jax.numpy as jnp +from beartype import beartype +from lcm._beartype_conf import GRID_CONF from lcm.dtypes import canonical_float_dtype from lcm.exceptions import GridInitializationError, format_messages from lcm.grids import coordinates as grid_coordinates @@ -54,6 +56,7 @@ class UniformContinuousGrid(ContinuousGrid, ABC): n_points: ScalarInt """The number of points in the grid (`jnp.int32` JAX scalar).""" + @beartype(conf=GRID_CONF) def __init__( self, *, @@ -124,6 +127,7 @@ def get_coordinate(self, value: FloatND) -> FloatND: ) +@beartype(conf=GRID_CONF) class LogSpacedGrid(UniformContinuousGrid): """A logarithmically spaced grid of continuous values. @@ -135,6 +139,7 @@ class LogSpacedGrid(UniformContinuousGrid): """ + @beartype(conf=GRID_CONF) def __init__( self, *, @@ -231,6 +236,7 @@ class IrregSpacedGrid(ContinuousGrid): n_points: int """Number of points. Derived from `len(points)` when points are given.""" + @beartype(conf=GRID_CONF) def __init__( self, *, diff --git a/src/lcm/grids/discrete.py b/src/lcm/grids/discrete.py index f6fa98043..0852867a0 100644 --- a/src/lcm/grids/discrete.py +++ b/src/lcm/grids/discrete.py @@ -1,5 +1,7 @@ import jax.numpy as jnp +from beartype import beartype +from lcm._beartype_conf import GRID_CONF from lcm.grids.base import Grid from lcm.grids.categorical import _validate_discrete_grid from lcm.typing import Int1D @@ -19,6 +21,7 @@ class DiscreteGrid(Grid): """ + @beartype(conf=GRID_CONF) def __init__( self, category_class: type, batch_size: int = 0, *, distributed: bool = False ) -> None: diff --git a/src/lcm/grids/piecewise.py b/src/lcm/grids/piecewise.py index ac0440915..dc3abdead 100644 --- a/src/lcm/grids/piecewise.py +++ b/src/lcm/grids/piecewise.py @@ -3,7 +3,9 @@ import jax.numpy as jnp import portion +from beartype import beartype +from lcm._beartype_conf import GRID_CONF from lcm.exceptions import GridInitializationError, format_messages from lcm.grids import coordinates as grid_coordinates from lcm.grids.continuous import ContinuousGrid @@ -33,6 +35,7 @@ class Piece: n_points: ScalarInt """The number of grid points in this piece (`jnp.int32` JAX scalar).""" + @beartype(conf=GRID_CONF) def __init__( self, *, @@ -43,6 +46,7 @@ def __init__( object.__setattr__(self, "n_points", jnp.int32(n_points)) +@beartype(conf=GRID_CONF) @dataclass(frozen=True, kw_only=True) class PiecewiseLinSpacedGrid(ContinuousGrid): """A piecewise linearly spaced grid with multiple segments. @@ -106,6 +110,7 @@ def get_coordinate(self, value: FloatND) -> FloatND: return self._cumulative_offsets[piece_idx] + local_coord +@beartype(conf=GRID_CONF) @dataclass(frozen=True, kw_only=True) class PiecewiseLogSpacedGrid(ContinuousGrid): """A piecewise logarithmically spaced grid with multiple segments. diff --git a/src/lcm/shocks/ar1.py b/src/lcm/shocks/ar1.py index 87785d938..1f74f06ff 100644 --- a/src/lcm/shocks/ar1.py +++ b/src/lcm/shocks/ar1.py @@ -5,8 +5,10 @@ import jax import jax.numpy as jnp +from beartype import beartype from jax.scipy.stats.norm import cdf +from lcm._beartype_conf import GRID_CONF from lcm.shocks._base import ( _gauss_hermite_normal, _mixture_cdf, @@ -29,6 +31,7 @@ def draw_shock( ) -> ScalarFloat: ... +@beartype(conf=GRID_CONF) @dataclass(frozen=True, kw_only=True) class Tauchen(_ShockGridAR1): r"""AR(1) shock discretized via Tauchen (1986). @@ -128,6 +131,7 @@ def draw_shock( ) +@beartype(conf=GRID_CONF) @dataclass(frozen=True, kw_only=True) class Rouwenhorst(_ShockGridAR1): r"""AR(1) shock discretized via Rouwenhorst (1995). @@ -196,6 +200,7 @@ def draw_shock( ) +@beartype(conf=GRID_CONF) @dataclass(frozen=True, kw_only=True) class TauchenNormalMixture(_ShockGridAR1): r"""AR(1) shock with mixture-of-normals innovations, discretized via Tauchen. diff --git a/src/lcm/shocks/iid.py b/src/lcm/shocks/iid.py index 8fd3c59e8..a6b745ddc 100644 --- a/src/lcm/shocks/iid.py +++ b/src/lcm/shocks/iid.py @@ -4,8 +4,10 @@ import jax import jax.numpy as jnp +from beartype import beartype from jax.scipy.stats.norm import cdf +from lcm._beartype_conf import GRID_CONF from lcm.shocks._base import ( _gauss_hermite_normal, _mixture_cdf, @@ -27,6 +29,7 @@ def draw_shock( ) -> ScalarFloat: ... +@beartype(conf=GRID_CONF) @dataclass(frozen=True, kw_only=True) class Uniform(_ShockGridIID): r"""Discretized iid uniform shock: $U(\text{start}, \text{stop})$. @@ -61,6 +64,7 @@ def draw_shock( ) +@beartype(conf=GRID_CONF) @dataclass(frozen=True, kw_only=True) class Normal(_ShockGridIID): r"""Discretized iid normal shock: $N(\mu_\varepsilon, \sigma_\varepsilon^2)$. @@ -135,6 +139,7 @@ def draw_shock( return params["mu"] + params["sigma"] * jax.random.normal(key=key) +@beartype(conf=GRID_CONF) @dataclass(frozen=True, kw_only=True) class LogNormal(_ShockGridIID): r"""Discretized iid log-normal shock: $\ln X \sim N(\mu, \sigma^2)$.""" @@ -200,6 +205,7 @@ def draw_shock( return jnp.exp(params["mu"] + params["sigma"] * jax.random.normal(key=key)) +@beartype(conf=GRID_CONF) @dataclass(frozen=True, kw_only=True) class NormalMixture(_ShockGridIID): r"""Discretized IID normal-mixture shock. From 28350e9578f8345a05a7fcacff6d3f19f333e361 Mon Sep 17 00:00:00 2001 From: Hans-Martin von Gaudecker Date: Fri, 15 May 2026 07:26:15 +0200 Subject: [PATCH 65/77] Collapse the perimeter-claw fan-out into a single lcm-package claw Replace the eleven per-area `beartype_package(...)` calls with one `beartype_package("lcm", conf=INTERNAL_CONF)`. The project-specific exception mapping survives at every user-facing constructor through the explicit `@beartype(conf=...)` decorators (Model, Regime, MarkovTransition, every grid and shock, `@categorical`, `as_leaf`); internal helpers raise beartype's own `BeartypeCallHintViolation`, matching how the previous `INTERNAL_CONF` packages already behaved. Other drift surfaced by extending the claw to previously uncovered modules and resolved inline: - `lcm.variables` and `lcm.persistence` carry `TYPE_CHECKING`-only forward references (`Regime`, `Model`, `SimulationResult`) to break import cycles. Inject the resolved names into both modules in `lcm/__init__.py` after the cycle settles so beartype can walk the references at call time. - `lcm.utils.dispatchers._base_productmap_batched.batched_vmap` and its inner closure now annotate kwargs/return as `Any`. The wrapper composes arbitrary user functions whose value-pytrees include `MappingProxyType` containers; constraining to `FloatND | IntND | BoolND` was too narrow. - `lcm.utils.functools.{allow_args,allow_only_kwargs}` strip `__annotate__` from `functools.wraps` via a reduced `_WRAPPER_ASSIGNMENTS_NO_ANNOTATIONS` tuple. The `(*args: Any, **kwargs: Any)` wrappers must not inherit the wrapped function's per-parameter annotations, otherwise beartype enforces user-model types (e.g. `Int1D` on `health`) on a generic forwarding wrapper. - `lcm.params.as_leaf` carries explicit `@beartype(conf=PARAMS_CONF)` so a non-Mapping / non-Sequence argument keeps raising `InvalidParamsError` rather than `BeartypeCallHintViolation`. Add a regression test on `LinSpacedGrid` mirroring the existing `Regime` / `Model` checks: an explicitly-decorated user-facing constructor must keep raising its project exception even though the package claw covers the surrounding module. Update `tests/test_dispatchers.py` for the wider claw: the affected tests previously fed non-tuple `variables` or non-callable `func` arguments and relied on internal validation to catch them. Pass correctly-typed arguments instead, and assert `BeartypeCallHintViolation` for the genuinely-bad-literal case where internal validation is now unreachable. Co-Authored-By: Claude Opus 4.7 --- src/lcm/__init__.py | 56 +++++++++++++++++++----------------- src/lcm/params/__init__.py | 4 +++ src/lcm/utils/dispatchers.py | 17 +++++++---- src/lcm/utils/functools.py | 16 +++++++++-- tests/test_beartype_claw.py | 23 ++++++++++++++- tests/test_dispatchers.py | 27 +++++++++-------- 6 files changed, 96 insertions(+), 47 deletions(-) diff --git a/src/lcm/__init__.py b/src/lcm/__init__.py index a8221169d..85645b71f 100644 --- a/src/lcm/__init__.py +++ b/src/lcm/__init__.py @@ -32,38 +32,35 @@ with contextlib.suppress(ImportError): import pdbp # noqa: F401 -# Install beartype's AST-rewriting claw on the instrumented `lcm` -# subpackages before any of their submodules is imported. The claw -# transforms each matching module's AST at first import to insert -# runtime type checks; if it isn't registered before the import -# happens, the affected module loads uninstrumented and `sys.modules` -# caches the unchecked version for the rest of the process. Perimeter -# packages use a `BeartypeConf` mapping violations to the project -# exception most natural to that subpackage; `lcm.solution` and -# `lcm.simulation` run behind the perimeter and use `INTERNAL_CONF` -# (see `lcm._beartype_conf`). +# Install beartype's AST-rewriting claw on the entire `lcm` package +# before any of its submodules is imported. The claw transforms each +# matching module's AST at first import to insert runtime type checks; +# if it isn't registered before the import happens, the affected module +# loads uninstrumented and `sys.modules` caches the unchecked version +# for the rest of the process. The claw uses `INTERNAL_CONF`, which +# surfaces violations as beartype's own `BeartypeCallHintViolation`. +# User-facing constructors (`Model`, `Regime`, `MarkovTransition`, +# every grid and shock class, `@categorical`) carry their own explicit +# `@beartype(conf=...)` decorators that map violations to the relevant +# project exception (`ModelInitializationError`, +# `RegimeInitializationError`, `GridInitializationError`, etc.); those +# decorators stack on top of the claw and win at the user boundary. +# See `lcm._beartype_conf`. from beartype.claw import beartype_package -from lcm._beartype_conf import ( - GRID_CONF, - INTERNAL_CONF, - PARAMS_CONF, - REGIME_BUILDING_CONF, -) +from lcm._beartype_conf import INTERNAL_CONF -beartype_package("lcm.grids", conf=GRID_CONF) -beartype_package("lcm.shocks", conf=GRID_CONF) -beartype_package("lcm.params", conf=PARAMS_CONF) -beartype_package("lcm.regime_building", conf=REGIME_BUILDING_CONF) -beartype_package("lcm.solution", conf=INTERNAL_CONF) -beartype_package("lcm.simulation", conf=INTERNAL_CONF) -beartype_package("lcm.utils.error_handling", conf=INTERNAL_CONF) -beartype_package("lcm.state_action_space", conf=INTERNAL_CONF) -beartype_package("lcm.interfaces", conf=INTERNAL_CONF) -beartype_package("lcm.regime", conf=INTERNAL_CONF) -beartype_package("lcm.model", conf=INTERNAL_CONF) +beartype_package("lcm", conf=INTERNAL_CONF) +# Several modules annotate signatures with forward references that are +# `TYPE_CHECKING`-only at definition time (to break import cycles). The +# package claw rewrites those annotations into runtime forward references +# resolved against the module's globals at call time. Inject the resolved +# names here, after every involved module is loaded, so beartype can +# resolve them. +from lcm import persistence as _persistence # noqa: E402 from lcm import shocks # noqa: E402 +from lcm import variables as _variables # noqa: E402 from lcm._version import __version__ # noqa: E402 from lcm.ages import AgeGrid # noqa: E402 from lcm.grids import ( # noqa: E402 @@ -90,6 +87,11 @@ from lcm.utils.containers import invert_regime_ids # noqa: E402 from lcm.utils.error_handling import validate_transition_probs # noqa: E402 +_variables.Regime = Regime +_persistence.Model = Model +_persistence.SimulationResult = SimulationResult +del _persistence, _variables + # Register MappingProxyType as a JAX pytree so it can be used in JIT-traced functions. # This allows regime transition probabilities to use immutable mappings. jax.tree_util.register_pytree_node( diff --git a/src/lcm/params/__init__.py b/src/lcm/params/__init__.py index 2ce2f72ea..0705dde3f 100644 --- a/src/lcm/params/__init__.py +++ b/src/lcm/params/__init__.py @@ -1,6 +1,9 @@ from collections.abc import Mapping, Sequence from typing import Any, overload +from beartype import beartype + +from lcm._beartype_conf import PARAMS_CONF from lcm.params.mapping_leaf import MappingLeaf, UserMappingLeaf from lcm.params.sequence_leaf import SequenceLeaf, UserSequenceLeaf @@ -13,6 +16,7 @@ def as_leaf(data: Mapping[str, Any]) -> UserMappingLeaf: ... def as_leaf(data: Sequence[Any]) -> UserSequenceLeaf: ... +@beartype(conf=PARAMS_CONF) def as_leaf( data: Mapping[str, Any] | Sequence[Any], ) -> UserMappingLeaf | UserSequenceLeaf: diff --git a/src/lcm/utils/dispatchers.py b/src/lcm/utils/dispatchers.py index 253093395..0d55a0ac5 100644 --- a/src/lcm/utils/dispatchers.py +++ b/src/lcm/utils/dispatchers.py @@ -2,7 +2,7 @@ from collections.abc import Callable from functools import partial from types import MappingProxyType -from typing import Literal, TypeVar, cast +from typing import Any, Literal, TypeVar, cast import jax import jax.numpy as jnp @@ -248,7 +248,14 @@ def _base_productmap_batched( "is POSITIONAL_ONLY." ) - def batched_vmap(**kwargs: FloatND | IntND | BoolND) -> FloatND: + def batched_vmap(**kwargs: Any) -> Any: # noqa: ANN401 + # `batched_vmap` is a generic helper: it accepts whatever values the + # composed `func` expects (canonical JAX arrays in the production + # pipeline, but also Python scalars, non-canonical-dtype arrays, or + # `MappingProxyType` containers in callers that wrap their own pytrees) + # and returns whatever `func` returns. Beartype shouldn't constrain + # the shape here — the wrapped `func` is responsible for its own + # contract. non_array_kwargs = { key: val for key, val in kwargs.items() if key not in product_axes } @@ -261,9 +268,9 @@ def map_one_more( loop_func: FunctionWithArrayReturn, axis: str ) -> FunctionWithArrayReturn: def func_mapped_over_one_more_axis( - *already_mapped_args: FloatND | IntND | BoolND, - **already_mapped_kwargs: FloatND | IntND | BoolND, - ) -> FloatND | IntND | BoolND: + *already_mapped_args: Any, # noqa: ANN401 + **already_mapped_kwargs: Any, # noqa: ANN401 + ) -> Any: # noqa: ANN401 return jax.lax.map( lambda axis_i: loop_func( *already_mapped_args, **{axis: axis_i}, **already_mapped_kwargs diff --git a/src/lcm/utils/functools.py b/src/lcm/utils/functools.py index 1b8ffa2ef..001eb369c 100644 --- a/src/lcm/utils/functools.py +++ b/src/lcm/utils/functools.py @@ -5,6 +5,18 @@ ReturnType = TypeVar("ReturnType") +# `functools.wraps` copies `__annotate__` (PEP 649), which would push the +# wrapped function's per-parameter annotations onto the generic +# `(*args: Any, **kwargs: Any)` wrappers below. Beartype claws those +# wrappers as part of the `lcm` package, sees the inherited annotations, +# and starts enforcing user-model types on a forwarding wrapper that has +# no business policing them. Use a reduced assignment set to keep +# `__name__` / `__qualname__` / `__doc__` for debugging while leaving the +# wrappers' own `(*args: Any, **kwargs: Any)` annotations intact. +_WRAPPER_ASSIGNMENTS_NO_ANNOTATIONS: tuple[str, ...] = tuple( + name for name in functools.WRAPPER_ASSIGNMENTS if name != "__annotate__" +) + def allow_only_kwargs( func: Callable[..., ReturnType], *, enforce: bool = True @@ -34,7 +46,7 @@ def allow_only_kwargs( ] new_signature = signature.replace(parameters=new_parameters) - @functools.wraps(func) + @functools.wraps(func, assigned=_WRAPPER_ASSIGNMENTS_NO_ANNOTATIONS) def func_with_only_kwargs(*args: Any, **kwargs: Any) -> ReturnType: if args: raise ValueError( @@ -119,7 +131,7 @@ def allow_args(func: Callable[..., ReturnType]) -> Callable[..., ReturnType]: ] new_signature = signature.replace(parameters=new_parameters) - @functools.wraps(func) + @functools.wraps(func, assigned=_WRAPPER_ASSIGNMENTS_NO_ANNOTATIONS) def allow_args_wrapper(*args: Any, **kwargs: Any) -> ReturnType: # Check if the total number of arguments matches the function signature if len(args) + len(kwargs) != len(parameters): diff --git a/tests/test_beartype_claw.py b/tests/test_beartype_claw.py index cd799cd70..b1ac362ee 100644 --- a/tests/test_beartype_claw.py +++ b/tests/test_beartype_claw.py @@ -19,7 +19,11 @@ from beartype.roar import BeartypeCallHintViolation from lcm import AgeGrid, LinSpacedGrid, Model, Regime -from lcm.exceptions import ModelInitializationError, RegimeInitializationError +from lcm.exceptions import ( + GridInitializationError, + ModelInitializationError, + RegimeInitializationError, +) from lcm.interfaces import _build_regime_sharding from lcm.model import _validate_log_args from lcm.regime import _default_H @@ -172,3 +176,20 @@ def test_model_with_bad_arg_raises_project_exception() -> None: regimes="not a mapping", # ty: ignore[invalid-argument-type] regime_id_class=int, ) + + +def test_linspaced_grid_with_bad_arg_raises_project_exception() -> None: + """A bad `LinSpacedGrid` argument still raises `GridInitializationError`. + + The package claw instruments `lcm.grids`'s private helpers with + `INTERNAL_CONF`, but the explicit `@beartype(conf=GRID_CONF)` decorator on + `LinSpacedGrid.__init__` still wins: a type violation at construction + surfaces as the project's `GridInitializationError`, not beartype's own + `BeartypeCallHintViolation`. + """ + with pytest.raises(GridInitializationError): + LinSpacedGrid( + start="not a number", # ty: ignore[invalid-argument-type] + stop=10.0, + n_points=3, + ) diff --git a/tests/test_dispatchers.py b/tests/test_dispatchers.py index 08b64b266..947acd8b6 100644 --- a/tests/test_dispatchers.py +++ b/tests/test_dispatchers.py @@ -2,6 +2,7 @@ import jax.numpy as jnp import pytest +from beartype.roar import BeartypeCallHintViolation from numpy.testing import assert_array_almost_equal as aaae from lcm.exceptions import FunctionDispatchError @@ -216,13 +217,13 @@ def test_spacemap_all_arguments_mapped( [ ( "Same argument provided more than once in actions or states variables", - ["a", "b"], - ["a", "c", "d"], + ("a", "b"), + ("a", "c", "d"), ), ( "Same argument provided more than once in actions or states variables", - ["a", "a", "b"], - ["c", "d"], + ("a", "a", "b"), + ("c", "d"), ), ], ) @@ -246,15 +247,18 @@ def func(a, b, c): def test_vmap_1d_error(): + def func(a): + return a + with pytest.raises(ValueError, match=r"Same argument provided more than once."): - vmap_1d(func=None, variables=["a", "a"]) # ty: ignore[invalid-argument-type] + vmap_1d(func=func, variables=("a", "a")) def test_vmap_1d_callable_with_only_args(): def func(a): return a - vmapped = vmap_1d(func=func, variables=["a"], callable_with="only_args") # ty: ignore[invalid-argument-type] + vmapped = vmap_1d(func=func, variables=("a",), callable_with="only_args") a = jnp.array([1, 2]) # check that the function works with positional arguments aaae(vmapped(a), a) @@ -270,7 +274,7 @@ def test_vmap_1d_callable_with_only_kwargs(): def func(a): return a - vmapped = vmap_1d(func=func, variables=["a"], callable_with="only_kwargs") # ty: ignore[invalid-argument-type] + vmapped = vmap_1d(func=func, variables=("a",), callable_with="only_kwargs") a = jnp.array([1, 2]) # check that the function works with keyword arguments aaae(vmapped(a=a), a) @@ -283,11 +287,10 @@ def func(a): def test_vmap_1d_callable_with_invalid(): + """`callable_with` rejects anything outside the documented literal options.""" + def func(a): return a - with pytest.raises( - ValueError, - match=r"Invalid callable_with option: invalid. Possible options are", - ): - vmap_1d(func=func, variables=["a"], callable_with="invalid") # ty: ignore[invalid-argument-type] + with pytest.raises(BeartypeCallHintViolation): + vmap_1d(func=func, variables=("a",), callable_with="invalid") # ty: ignore[invalid-argument-type] From d6d4454cfef94bb32a8f8f12344f8b2b131408dd Mon Sep 17 00:00:00 2001 From: Hans-Martin von Gaudecker Date: Fri, 15 May 2026 07:29:26 +0200 Subject: [PATCH 66/77] Refresh beartype claw docstrings for the unified package claw Rewrite `_beartype_conf.py` and `tests/test_beartype_claw.py` headers to describe the current setup: a single package-wide claw on `lcm` with `INTERNAL_CONF`, layered with explicit `@beartype(conf=...)` decorators on user-facing constructors that map violations to project exceptions. Drop the unused `REGIME_BUILDING_CONF`. Co-Authored-By: Claude Opus 4.7 --- src/lcm/_beartype_conf.py | 36 ++++++++++++++++++++---------------- tests/test_beartype_claw.py | 28 +++++++++++++++++----------- 2 files changed, 37 insertions(+), 27 deletions(-) diff --git a/src/lcm/_beartype_conf.py b/src/lcm/_beartype_conf.py index a2d8bf709..440a13e36 100644 --- a/src/lcm/_beartype_conf.py +++ b/src/lcm/_beartype_conf.py @@ -1,10 +1,17 @@ """`BeartypeConf` instances for pylcm's perimeter and internal claws. -Perimeter confs map type violations to the existing project exception -class, preserving the documented exception hierarchy at user-facing -entry points. `INTERNAL_CONF` covers packages that run behind the -perimeter, where a violation is an internal bug rather than user error -and so surfaces as beartype's own `BeartypeCallHintViolation`. +`INTERNAL_CONF` is the default conf for the `lcm` package-wide claw +registered in `lcm/__init__.py`. Violations under that claw surface as +beartype's own `BeartypeCallHintViolation`, marking them as internal +pylcm bugs rather than user error. + +The remaining confs (`MODEL_CONF`, `REGIME_CONF`, `GRID_CONF`, +`PARAMS_CONF`, `CATEGORICAL_CONF`) are used by explicit +`@beartype(conf=...)` decorators on user-facing constructors and entry +points. They map type violations to the existing project exception +class, preserving the documented exception hierarchy at the user +boundary. The decorators stack on top of the package claw and take +precedence at the call sites they cover. """ @@ -46,19 +53,16 @@ def _conf(exc: type[Exception]) -> BeartypeConf: # Used on the `categorical` decorator factory. CATEGORICAL_CONF = _conf(CategoricalDefinitionError) -# Used on `Model.solve` and `Model.simulate`. +# Used on `Model.solve`, `Model.simulate`, and the `as_leaf` factory. PARAMS_CONF = _conf(InvalidParamsError) -# Used by the claw on `lcm.regime_building` (regime compilation pipeline, -# part of model construction). -REGIME_BUILDING_CONF = _conf(ModelInitializationError) - -# Used by the claw on `lcm.solution` and `lcm.simulation`. These packages run -# behind the construction perimeter — their inputs are already validated by -# `Model.solve` / `Model.simulate` and `validate_initial_conditions` — so a -# type violation there is an internal pylcm bug, not user error. It surfaces -# as beartype's own `BeartypeCallHintViolation` rather than a project -# exception, which would mislabel it as a user-facing error. +# Default conf for the package-wide claw on `lcm` registered in +# `lcm/__init__.py`. A type violation in any internal helper surfaces as +# beartype's own `BeartypeCallHintViolation` rather than a project +# exception. User-facing constructors layer their own +# `@beartype(conf=...)` decorators on top to map violations to project +# exceptions; those decorators take precedence at the call sites they +# cover. INTERNAL_CONF = BeartypeConf( strategy=BeartypeStrategy.On, is_pep484_tower=True, diff --git a/tests/test_beartype_claw.py b/tests/test_beartype_claw.py index b1ac362ee..fb7333f22 100644 --- a/tests/test_beartype_claw.py +++ b/tests/test_beartype_claw.py @@ -1,14 +1,20 @@ -"""The beartype claw is live on `lcm.solution` and `lcm.simulation`. - -These packages sit *behind* the construction perimeter: by the time their -functions run, user input has already been validated by `Model.solve` / -`Model.simulate` and `validate_initial_conditions`. A type violation here -therefore signals an internal pylcm bug, so the claw is configured to raise -beartype's own `BeartypeCallHintViolation` rather than a project exception. - -Each test calls an internal function with one argument of the wrong type, -chosen so the call would return cleanly if the function were *not* -instrumented — the violation is what proves the claw is installed. +"""The beartype claw is live on the entire `lcm` package. + +The claw uses `INTERNAL_CONF`, so type violations in internal helpers +surface as beartype's own `BeartypeCallHintViolation`. User-facing +constructors (`Model`, `Regime`, `MarkovTransition`, every grid and shock, +`@categorical`, `as_leaf`) carry their own explicit `@beartype(conf=...)` +decorators that map violations to the relevant project exception +(`ModelInitializationError`, `RegimeInitializationError`, +`GridInitializationError`, `InvalidParamsError`); those decorators stack +on top of the claw and win at the user boundary. + +Each `test_claw_checks_*` test calls an internal function with one argument +of the wrong type, chosen so the call would return cleanly if the function +were *not* instrumented — the violation is what proves the claw is +installed. Each `test_*_with_bad_arg_raises_project_exception` test +confirms that an ill-typed argument to a public constructor surfaces as +the project exception, not as `BeartypeCallHintViolation`. """ from types import MappingProxyType From 9bb6c3c86e0841f9078878f13a6cbb1cf8b11b56 Mon Sep 17 00:00:00 2001 From: Hans-Martin von Gaudecker Date: Fri, 15 May 2026 08:26:13 +0200 Subject: [PATCH 67/77] Rename "regime" to "regime_id" / "regime_name" by context Split the overloaded `"regime"` string into two semantically explicit names: - Input boundary (`initial_conditions` dict key, DataFrame column for `model.simulate`): renamed to `"regime_id"`. Affects every `initial_conditions["regime"]` lookup, `{"regime": ...}` dict literal in initial-conditions context, the `if name == "regime"` branch in `canonicalize_initial_conditions`, and every `Literal["regime"]` type annotation. - Output DataFrame column (produced by `SimulationResult.to_dataframe`): renamed to `"regime_name"`. Affects the column-construction site, `dtypes["regime"]`, the categorical conversion branch, the column list, the post-`to_dataframe` masks/filters in tests, and prose references to "the regime column". Regression pickles in tests/data/ have their `regime` column renamed to `regime_name` to match the new `to_dataframe()` output. --- AGENTS.md | 4 +- benchmarks/bench_mahler_yum.py | 2 +- benchmarks/bench_precautionary_savings.py | 2 +- docs/development/benchmarking.md | 2 +- docs/examples/mahler_yum_2024.md | 2 +- docs/examples/mortality.md | 2 +- docs/examples/precautionary_savings.md | 2 +- docs/examples/precautionary_savings_health.md | 2 +- docs/examples/tiny.md | 2 +- docs/user_guide/benchmarking.md | 2 +- docs/user_guide/pandas_interop.md | 6 +- docs/user_guide/solving_and_simulating.md | 12 ++-- src/lcm/model.py | 10 ++-- src/lcm/pandas_utils.py | 33 +++++------ src/lcm/persistence.py | 8 +-- src/lcm/simulation/initial_conditions.py | 26 ++++----- src/lcm/simulation/result.py | 24 ++++---- src/lcm/simulation/simulate.py | 17 +++--- .../f32/mahler_yum_simulation.pkl | Bin 10663 -> 10676 bytes .../f32/mortality_simulation.pkl | Bin 2112 -> 2125 bytes .../f32/precautionary_savings_simulation.pkl | Bin 2009 -> 2022 bytes .../f64/mahler_yum_simulation.pkl | Bin 14870 -> 14883 bytes .../f64/mortality_simulation.pkl | Bin 2378 -> 2391 bytes .../f64/precautionary_savings_simulation.pkl | Bin 2420 -> 2433 bytes .../generate_benchmark_data.py | 6 +- tests/data/regression_tests/simulation.pkl | Bin 2102 -> 2115 bytes tests/data/shocks/simulation_lognormal.pkl | Bin 2165 -> 2178 bytes tests/data/shocks/simulation_normal.pkl | Bin 2165 -> 2178 bytes tests/data/shocks/simulation_rouwenhorst.pkl | Bin 2165 -> 2178 bytes tests/data/shocks/simulation_tauchen.pkl | Bin 2165 -> 2178 bytes tests/data/shocks/simulation_uniform.pkl | Bin 2165 -> 2178 bytes tests/simulation/test_initial_conditions.py | 38 ++++++------- tests/simulation/test_simulate.py | 40 +++++++------- tests/simulation/test_simulate_aot.py | 2 +- tests/solution/test_beta_delta.py | 4 +- tests/test_ages.py | 6 +- tests/test_chained_state_transitions.py | 4 +- tests/test_distributed.py | 4 +- tests/test_dtypes.py | 4 +- tests/test_economic_validation.py | 6 +- tests/test_error_handling_invalid_vf.py | 4 +- tests/test_fgp_model.py | 8 +-- tests/test_float_dtype_invariants.py | 2 +- tests/test_heterogeneous_initial_ages.py | 2 +- tests/test_int_dtype_invariants.py | 4 +- tests/test_pandas_utils.py | 52 +++++++++--------- tests/test_persistence.py | 2 +- tests/test_regime_state_mismatch.py | 12 ++-- tests/test_regression_test.py | 10 ++-- tests/test_runtime_params.py | 2 +- tests/test_shock_grids.py | 6 +- tests/test_single_feasible_action.py | 6 +- ...est_solution_on_toy_model_deterministic.py | 8 +-- .../test_solution_on_toy_model_stochastic.py | 10 ++-- tests/test_static_params.py | 8 +-- tests/test_stochastic.py | 10 ++-- .../test_validate_regime_transition_probs.py | 4 +- tests/test_validation_scalar_actions.py | 2 +- 58 files changed, 216 insertions(+), 208 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 805f2b137..21e9fcc86 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -275,13 +275,13 @@ loaded = SimulationResult.from_pickle("path/to/file.pkl") ### Initial Conditions Format -Initial conditions use a flat dictionary with state names plus `"regime"`: +Initial conditions use a flat dictionary with state names plus `"regime_id"`: ```python initial_conditions = { "wealth": jnp.array([1.0, 2.0, 3.0]), "health": jnp.array([0.5, 0.8, 0.3]), - "regime": jnp.array([RegimeId.working, RegimeId.working, RegimeId.retired]), + "regime_id": jnp.array([RegimeId.working, RegimeId.working, RegimeId.retired]), } ``` diff --git a/benchmarks/bench_mahler_yum.py b/benchmarks/bench_mahler_yum.py index fa8e3f6ba..046757a82 100644 --- a/benchmarks/bench_mahler_yum.py +++ b/benchmarks/bench_mahler_yum.py @@ -29,7 +29,7 @@ def _build(self): self.model_params = {"alive": common_params} self.initial_conditions = { **initial_states, - "regime": jnp.full( + "regime_id": jnp.full( _N_SUBJECTS, self.model.regime_names_to_ids["alive"], dtype=jnp.int32, diff --git a/benchmarks/bench_precautionary_savings.py b/benchmarks/bench_precautionary_savings.py index 5f02671bb..822d3747d 100644 --- a/benchmarks/bench_precautionary_savings.py +++ b/benchmarks/bench_precautionary_savings.py @@ -33,7 +33,7 @@ def _make_initial_conditions(n_subjects): "age": jnp.full(n_subjects, 20.0), "wealth": jnp.full(n_subjects, 5.0), "income": jnp.full(n_subjects, 0.0), - "regime": jnp.zeros(n_subjects, dtype=jnp.int32), + "regime_id": jnp.zeros(n_subjects, dtype=jnp.int32), } diff --git a/docs/development/benchmarking.md b/docs/development/benchmarking.md index 0d660e08a..8e6cc5748 100644 --- a/docs/development/benchmarking.md +++ b/docs/development/benchmarking.md @@ -129,7 +129,7 @@ class TimeMyModel: self.model_params = my_model.get_params() self.initial_conditions = { "wealth": jnp.full(1_000, 5.0), - "regime": jnp.zeros(1_000, dtype=jnp.int32), + "regime_id": jnp.zeros(1_000, dtype=jnp.int32), } # JIT warmup (timed separately) diff --git a/docs/examples/mahler_yum_2024.md b/docs/examples/mahler_yum_2024.md index 13cabbe99..4ce0df0a8 100644 --- a/docs/examples/mahler_yum_2024.md +++ b/docs/examples/mahler_yum_2024.md @@ -40,7 +40,7 @@ result = MAHLER_YUM_MODEL.simulate( params={"alive": params}, initial_conditions={ **initial_states, - "regime": jnp.full( + "regime_id": jnp.full( n_subjects, MAHLER_YUM_MODEL.regime_names_to_ids["alive"], ), diff --git a/docs/examples/mortality.md b/docs/examples/mortality.md index 8dcdf9c9b..687fb528b 100644 --- a/docs/examples/mortality.md +++ b/docs/examples/mortality.md @@ -26,7 +26,7 @@ result = model.simulate( initial_conditions={ "age": jnp.full(100, model.ages.values[0]), "wealth": jnp.linspace(1, 100, 100), - "regime": jnp.full(100, model.regime_names_to_ids["working_life"]), + "regime_id": jnp.full(100, model.regime_names_to_ids["working_life"]), }, period_to_regime_to_V_arr=None, seed=1234, diff --git a/docs/examples/precautionary_savings.md b/docs/examples/precautionary_savings.md index 4fe1c1568..6e92d327a 100644 --- a/docs/examples/precautionary_savings.md +++ b/docs/examples/precautionary_savings.md @@ -27,7 +27,7 @@ result = model.simulate( "age": jnp.full(100, model.ages.values[0]), "wealth": jnp.linspace(1, 10, 100), "income": jnp.zeros(100), - "regime": jnp.full(100, model.regime_names_to_ids["alive"]), + "regime_id": jnp.full(100, model.regime_names_to_ids["alive"]), }, period_to_regime_to_V_arr=None, ) diff --git a/docs/examples/precautionary_savings_health.md b/docs/examples/precautionary_savings_health.md index 7b4cf8b4d..38a244125 100644 --- a/docs/examples/precautionary_savings_health.md +++ b/docs/examples/precautionary_savings_health.md @@ -32,7 +32,7 @@ result = model.simulate( "age": jnp.full(1_000, model.ages.values[0]), "wealth": jnp.full(1_000, 1.0), "health": jnp.full(1_000, 1.0), - "regime": jnp.full(1_000, model.regime_names_to_ids["working_life"]), + "regime_id": jnp.full(1_000, model.regime_names_to_ids["working_life"]), }, period_to_regime_to_V_arr=None, ) diff --git a/docs/examples/tiny.md b/docs/examples/tiny.md index 24d64fb76..300abf3ab 100644 --- a/docs/examples/tiny.md +++ b/docs/examples/tiny.md @@ -25,7 +25,7 @@ params = get_params() initial_df = pd.DataFrame( { - "regime": "working_life", + "regime_id": "working_life", "age": model.ages.values[0], "wealth": np.linspace(1, 20, 100), } diff --git a/docs/user_guide/benchmarking.md b/docs/user_guide/benchmarking.md index 85de82ddb..0140cfe97 100644 --- a/docs/user_guide/benchmarking.md +++ b/docs/user_guide/benchmarking.md @@ -91,7 +91,7 @@ class TimeSolveSimulate: self.initial_conditions = { "age": jnp.full(500, 25.0), "wealth": jnp.full(500, 5.0), - "regime": jnp.zeros(500, dtype=jnp.int32), + "regime_id": jnp.zeros(500, dtype=jnp.int32), } # --- JAX warmup -------------------------------------------------- diff --git a/docs/user_guide/pandas_interop.md b/docs/user_guide/pandas_interop.md index 5a04d8a2c..d6fd33201 100644 --- a/docs/user_guide/pandas_interop.md +++ b/docs/user_guide/pandas_interop.md @@ -12,12 +12,12 @@ is DataFrame in, DataFrame out. ## Initial Conditions as a DataFrame Pass a pandas DataFrame directly to `simulate()` as `initial_conditions`. One row per -agent, one column per state variable, plus a `"regime"` column: +agent, one column per state variable, plus a `"regime_id"` column: ```python df = pd.DataFrame( { - "regime": ["working", "working", "retired"], + "regime_id": ["working", "working", "retired"], "wealth": [10.0, 50.0, 30.0], "health": ["good", "bad", "good"], "age": [25.0, 25.0, 25.0], @@ -31,7 +31,7 @@ result = model.simulate( ) ``` -- `"regime"` column is required. Use regime names as strings (e.g., `"working"`). +- `"regime_id"` column is required. Use regime names as strings (e.g., `"working"`). - Discrete states use string labels from the model's categorical classes (e.g., `"good"` instead of `0`). Labels are validated and mapped to integer codes automatically. - Continuous states pass through as-is. diff --git a/docs/user_guide/solving_and_simulating.md b/docs/user_guide/solving_and_simulating.md index 873974fb1..7f8a21e7c 100644 --- a/docs/user_guide/solving_and_simulating.md +++ b/docs/user_guide/solving_and_simulating.md @@ -74,7 +74,7 @@ import pandas as pd df = pd.DataFrame( { - "regime": ["working_life", "working_life", "retirement", "working_life"], + "regime_id": ["working_life", "working_life", "retirement", "working_life"], "age": [25.0, 25.0, 25.0, 25.0], "wealth": [1.0, 5.0, 10.0, 20.0], "health": ["good", "bad", "bad", "good"], # string labels, auto-converted @@ -102,7 +102,7 @@ initial_conditions = { "age": jnp.array([25.0, 25.0, 25.0, 25.0]), "wealth": jnp.array([1.0, 5.0, 10.0, 20.0]), "health": jnp.array([0, 1, 1, 0]), # integer codes for discrete states - "regime": jnp.array( + "regime_id": jnp.array( [ RegimeId.working_life, RegimeId.working_life, @@ -114,7 +114,7 @@ initial_conditions = { ``` - Every non-shock state must have an entry. -- `"regime"` must be included, with integer codes from the `regime_id_class`. +- `"regime_id"` must be included, with integer codes from the `regime_id_class`. - All arrays must have the same length (= number of agents). - Shock states are drawn automatically. @@ -140,7 +140,7 @@ Subjects can start at different ages: initial_conditions = { "age": jnp.array([40.0, 60.0]), "wealth": jnp.array([50.0, 50.0]), - "regime": jnp.array( + "regime_id": jnp.array( [ model.regime_names_to_ids["working_life"], model.regime_names_to_ids["working_life"], @@ -160,7 +160,7 @@ earlier periods are omitted, not filled with placeholders. df = result.to_dataframe() ``` -Returns a pandas DataFrame with columns: `subject_id`, `period`, `age`, `regime`, +Returns a pandas DataFrame with columns: `subject_id`, `period`, `age`, `regime_name`, `value`, plus all states and actions. Discrete variables are pandas Categorical with string labels. @@ -240,7 +240,7 @@ params = { # 3. Prepare initial conditions as a DataFrame initial_df = pd.DataFrame({ - "regime": "working_life", + "regime_id": "working_life", "age": model.ages.values[0], "wealth": np.linspace(1, 50, 100), }) diff --git a/src/lcm/model.py b/src/lcm/model.py index f285154dc..afd2f5e48 100644 --- a/src/lcm/model.py +++ b/src/lcm/model.py @@ -383,7 +383,9 @@ def simulate( self, *, params: UserParams, - initial_conditions: Mapping[StateName | Literal["regime"], Array | np.ndarray] + initial_conditions: Mapping[ + StateName | Literal["regime_id"], Array | np.ndarray + ] | pd.DataFrame, period_to_regime_to_V_arr: PeriodToRegimeToVArr | None, check_initial_conditions: bool = True, @@ -410,11 +412,11 @@ def simulate( specification Values may be `pd.Series` with labeled indices; they are auto-converted to JAX arrays. - initial_conditions: Mapping of state names (plus `"regime"`) to arrays. + initial_conditions: Mapping of state names (plus `"regime_id"`) to arrays. All arrays must have the same length (number of subjects). The - `"regime"` entry must contain integer regime codes (from + `"regime_id"` entry must contain integer regime codes (from `model.regime_names_to_ids`). May also be a `pd.DataFrame` - with a `"regime"` column (auto-converted). + with a `"regime_id"` column (auto-converted). period_to_regime_to_V_arr: Value function arrays from `solve()`. When `None`, the model is solved automatically before simulating. check_initial_conditions: Whether to validate initial conditions. diff --git a/src/lcm/pandas_utils.py b/src/lcm/pandas_utils.py index 0fee26ef6..2bf4ba458 100644 --- a/src/lcm/pandas_utils.py +++ b/src/lcm/pandas_utils.py @@ -58,24 +58,24 @@ def initial_conditions_from_dataframe( # noqa: C901 """Convert a DataFrame of initial conditions to LCM initial conditions format. Args: - df: DataFrame with columns for states and a "regime" column. + df: DataFrame with columns for states and a "regime_id" column. regimes: Mapping of regime names to user Regime instances. regime_names_to_ids: Immutable mapping from regime names to integer indices. Returns: - Dict mapping state names (plus `"regime"`) to JAX arrays. The - `"regime"` entry contains integer codes derived from the `"regime"` - column via `regime_names_to_ids`. + Dict mapping state names (plus `"regime_id"`) to JAX arrays. The + `"regime_id"` entry contains integer codes derived from the + `"regime_id"` column via `regime_names_to_ids`. Raises: - ValueError: If the DataFrame is empty, the "regime" column is missing, + ValueError: If the DataFrame is empty, the "regime_id" column is missing, contains invalid regime names, has unknown columns, is missing required states, or categorical columns contain invalid labels. """ - if "regime" not in df.columns: - msg = "DataFrame must contain a 'regime' column." + if "regime_id" not in df.columns: + msg = "DataFrame must contain a 'regime_id' column." raise ValueError(msg) if len(df) == 0: @@ -84,23 +84,24 @@ def initial_conditions_from_dataframe( # noqa: C901 # Validate regime names valid_regimes = set(regime_names_to_ids.keys()) - invalid_regimes = set(df["regime"]) - valid_regimes + invalid_regimes = set(df["regime_id"]) - valid_regimes if invalid_regimes: msg = ( - f"Invalid regime names in 'regime' column: {sorted(invalid_regimes)}. " + f"Invalid regime names in 'regime_id' column: " + f"{sorted(invalid_regimes)}. " f"Valid regimes: {sorted(valid_regimes)}." ) raise ValueError(msg) - state_columns = {col for col in df.columns if col != "regime"} + state_columns = {col for col in df.columns if col != "regime_id"} _validate_state_columns( state_columns=state_columns, regimes=regimes, - initial_regimes=df["regime"].tolist(), + initial_regimes=df["regime_id"].tolist(), ) n_subjects = len(df) - state_cols = [col for col in df.columns if col != "regime"] + state_cols = [col for col in df.columns if col != "regime_id"] # Pre-allocate result arrays (NaN default surfaces bugs for missing states) result_arrays: dict[str, np.ndarray] = { @@ -109,7 +110,7 @@ def initial_conditions_from_dataframe( # noqa: C901 discrete_state_names: set[StateName] = set() # Process per regime group (vectorised .map() within each group) - for regime_name, group in df.groupby("regime"): + for regime_name, group in df.groupby("regime_id"): regime = regimes[str(regime_name)] idx = group.index discrete_grids = { @@ -149,14 +150,14 @@ def initial_conditions_from_dataframe( # noqa: C901 nan_mask = np.isnan(result_arrays[col]) result_arrays[col][nan_mask] = MISSING_CAT_CODE - initial_conditions: dict[StateName | Literal["regime"], FloatND | IntND] = { + initial_conditions: dict[StateName | Literal["regime_id"], FloatND | IntND] = { col: jnp.array(arr, dtype=jnp.int32) if col in discrete_state_names else jnp.array(arr, dtype=canonical_float_dtype()) for col, arr in result_arrays.items() } - initial_conditions["regime"] = jnp.array( - df["regime"].map(dict(regime_names_to_ids)).to_numpy(), + initial_conditions["regime_id"] = jnp.array( + df["regime_id"].map(dict(regime_names_to_ids)).to_numpy(), dtype=jnp.int32, ) diff --git a/src/lcm/persistence.py b/src/lcm/persistence.py index 639979a9a..005e7e859 100644 --- a/src/lcm/persistence.py +++ b/src/lcm/persistence.py @@ -75,8 +75,8 @@ class SimulateSnapshot: params: UserParams | None """User parameters passed to simulate.""" - initial_conditions: Mapping[StateName | Literal["regime"], Array] | None - """Mapping of state names and "regime" to arrays.""" + initial_conditions: Mapping[StateName | Literal["regime_id"], Array] | None + """Mapping of state names and "regime_id" to arrays.""" period_to_regime_to_V_arr: PeriodToRegimeToVArr | None """Immutable mapping of periods to regime value function arrays.""" @@ -207,7 +207,7 @@ def save_simulate_snapshot( *, model: Model, params: UserParams, - initial_conditions: Mapping[StateName | Literal["regime"], Array], + initial_conditions: Mapping[StateName | Literal["regime_id"], Array], period_to_regime_to_V_arr: PeriodToRegimeToVArr, result: SimulationResult, log_path: Path, @@ -218,7 +218,7 @@ def save_simulate_snapshot( Args: model: The Model instance. params: User parameters passed to simulate. - initial_conditions: Mapping of state names and "regime" to arrays. + initial_conditions: Mapping of state names and "regime_id" to arrays. period_to_regime_to_V_arr: Value function arrays. result: SimulationResult object. log_path: Parent directory for snapshot directories. diff --git a/src/lcm/simulation/initial_conditions.py b/src/lcm/simulation/initial_conditions.py index f799564d0..2b7b09cb7 100644 --- a/src/lcm/simulation/initial_conditions.py +++ b/src/lcm/simulation/initial_conditions.py @@ -54,20 +54,20 @@ def canonicalize_initial_conditions( *, - initial_conditions: Mapping[StateName | Literal["regime"], Array | np.ndarray], + initial_conditions: Mapping[StateName | Literal["regime_id"], Array | np.ndarray], internal_regimes: MappingProxyType[RegimeName, InternalRegime], ) -> dict[str, FloatND | IntND]: """Cast every initial-conditions array to its canonical pylcm dtype. - This is pylcm's simulation input boundary: `"regime"` and discrete states - cast to `int32`; `"age"` and continuous states cast to the canonical float - dtype. Keys that match no model state are cast by their array kind (integer - arrays to `int32`, otherwise to canonical float) and left for + This is pylcm's simulation input boundary: `"regime_id"` and discrete + states cast to `int32`; `"age"` and continuous states cast to the canonical + float dtype. Keys that match no model state are cast by their array kind + (integer arrays to `int32`, otherwise to canonical float) and left for `validate_initial_conditions` to report. Downstream validation and the simulate stack receive canonical-dtype arrays and do not re-cast. Args: - initial_conditions: Mapping of state names (plus `"regime"`) to + initial_conditions: Mapping of state names (plus `"regime_id"`) to user-supplied arrays of any integer or floating dtype. internal_regimes: Immutable mapping of regime names to internal regime instances, used to classify each state as discrete or continuous. @@ -89,7 +89,7 @@ def canonicalize_initial_conditions( } canonical: dict[str, FloatND | IntND] = {} for name, value in initial_conditions.items(): - if name == "regime" or name in discrete_state_names: + if name == "regime_id" or name in discrete_state_names: canonical[name] = safe_to_int_dtype(value, name=name) elif name == "age" or name in known_state_names: canonical[name] = safe_to_float_dtype(value, name=name) @@ -179,7 +179,7 @@ def build_initial_states( def validate_initial_conditions( *, - initial_conditions: Mapping[StateName | Literal["regime"], FloatND | IntND], + initial_conditions: Mapping[StateName | Literal["regime_id"], FloatND | IntND], internal_regimes: MappingProxyType[RegimeName, InternalRegime], regime_names_to_ids: RegimeNamesToIds, internal_params: InternalParams, @@ -188,14 +188,14 @@ def validate_initial_conditions( """Validate initial conditions (regimes, states, and feasibility). Checks that: - 1. `"regime"` is present, non-empty, and contains only valid regime IDs + 1. `"regime_id"` is present, non-empty, and contains only valid regime IDs 2. All required state names (across all regimes) are provided, with no extras 3. All arrays have the same length 4. Discrete state values are valid codes 5. Each subject has at least one feasible action combination Args: - initial_conditions: Mapping of state names (plus `"regime"`) to arrays. + initial_conditions: Mapping of state names (plus `"regime_id"`) to arrays. internal_regimes: Immutable mapping of regime names to internal regime instances. regime_names_to_ids: Immutable mapping of regime names to integer IDs. @@ -212,10 +212,10 @@ def validate_initial_conditions( regime_ids_to_names = invert_regime_ids(regime_names_to_ids) # Extract regime array - regime_arr = initial_conditions.get("regime") + regime_arr = initial_conditions.get("regime_id") if regime_arr is None: raise InvalidInitialConditionsError( - format_messages(["'regime' must be provided in initial_conditions."]) + format_messages(["'regime_id' must be provided in initial_conditions."]) ) # Vectorized regime ID validity check @@ -232,7 +232,7 @@ def validate_initial_conditions( ) ) - initial_states = {k: v for k, v in initial_conditions.items() if k != "regime"} + initial_states = {k: v for k, v in initial_conditions.items() if k != "regime_id"} # Validate regime names and state names/shapes first; early-exit on errors so that # downstream checks (discrete codes, feasibility) can assume correct names. diff --git a/src/lcm/simulation/result.py b/src/lcm/simulation/result.py index da75dc59a..44fb57907 100644 --- a/src/lcm/simulation/result.py +++ b/src/lcm/simulation/result.py @@ -239,7 +239,7 @@ def get_simulation_output_dtypes( Returns: Immutable mapping of variable name to `pd.CategoricalDtype`. Includes - all discrete state/action variables plus the `"regime"` column. + all discrete state/action variables plus the `"regime_name"` column. """ merged_categories, ordered_flags = compute_merged_discrete_categories(regimes) @@ -251,7 +251,7 @@ def get_simulation_output_dtypes( ordered=ordered_flags[var_name], ) - dtypes["regime"] = pd.CategoricalDtype( + dtypes["regime_name"] = pd.CategoricalDtype( categories=list(regime_names_to_ids.keys()), ordered=False, ) @@ -323,7 +323,7 @@ def _compute_metadata( discrete_categories: dict[str, tuple[str, ...]] = {} discrete_ordered: dict[str, bool] = {} for var_name, dtype in simulation_output_dtypes.items(): - if var_name == "regime": + if var_name == "regime_name": continue discrete_categories[var_name] = tuple(dtype.categories) discrete_ordered[var_name] = bool(dtype.ordered) @@ -499,7 +499,7 @@ def _process_regime( data["age"] = ages.values[data["period"]] # noqa: PD011 # Add regime name - data["regime"] = [internal_regime.name] * len(data["period"]) + data["regime_name"] = [internal_regime.name] * len(data["period"]) # Compute additional targets if additional_targets: @@ -590,7 +590,7 @@ def _empty_dataframe( action_names: list[ActionName], ) -> pd.DataFrame: """Create empty DataFrame with correct columns.""" - columns = ["subject_id", "period", "regime", "value"] + columns = ["subject_id", "period", "regime_name", "value"] columns.extend(state_names) columns.extend(action_names) return pd.DataFrame(columns=pd.Index(columns)) @@ -618,8 +618,8 @@ def _reorder_columns( state_names: list[StateName], action_names: list[ActionName], ) -> pd.DataFrame: - """Reorder columns: subject_id, period, regime, value, states, actions, rest.""" - base = ["subject_id", "period", "regime", "value"] + """Reorder columns: id, period, regime_name, value, states, actions, rest.""" + base = ["subject_id", "period", "regime_name", "value"] known = set(base) | set(state_names) | set(action_names) rest = [c for c in df.columns if c not in known] return df[base + state_names + action_names + rest] @@ -633,14 +633,16 @@ def _convert_to_categorical( """Convert discrete columns to pandas Categorical dtype with string labels. Converts: - - regime column: uses regime_names as categories + - regime_name column: uses regime_names as categories - discrete state/action columns: uses categories from simulation metadata """ df = df.copy() - # Convert regime column - df["regime"] = pd.Categorical(df["regime"], categories=metadata.regime_names) + # Convert regime name column + df["regime_name"] = pd.Categorical( + df["regime_name"], categories=metadata.regime_names + ) # Convert discrete state and action columns for var_name, merged_categories in metadata.discrete_categories.items(): @@ -695,7 +697,7 @@ def _remap_codes_per_regime( if regime_cats is None: continue - mask = df["regime"] == regime_name + mask = df["regime_name"] == regime_name if not mask.any(): continue diff --git a/src/lcm/simulation/simulate.py b/src/lcm/simulation/simulate.py index 897537eb9..6cf0561e2 100644 --- a/src/lcm/simulation/simulate.py +++ b/src/lcm/simulation/simulate.py @@ -54,7 +54,7 @@ def simulate( *, internal_params: InternalParams, - initial_conditions: Mapping[StateName | Literal["regime"], FloatND | IntND], + initial_conditions: Mapping[StateName | Literal["regime_id"], FloatND | IntND], internal_regimes: MappingProxyType[RegimeName, InternalRegime], regime_names_to_ids: RegimeNamesToIds, logger: logging.Logger, @@ -69,10 +69,11 @@ def simulate( Args: internal_params: Immutable mapping of regime names to flat parameter mappings. - initial_conditions: Flat mapping of state names (plus `"regime"`) to arrays. - All arrays must have the same length (number of subjects). The `"regime"` - entry must contain integer regime codes. - Example: {"wealth": jnp.array([10.0, 50.0]), "regime": jnp.array([0, 0])} + initial_conditions: Flat mapping of state names (plus `"regime_id"`) to + arrays. All arrays must have the same length (number of subjects). + The `"regime_id"` entry must contain integer regime codes. + Example: + {"wealth": jnp.array([10.0, 50.0]), "regime_id": jnp.array([0, 0])} internal_regimes: Immutable mapping of regime names to internal regime instances. regime_names_to_ids: Immutable mapping of regime names to integer indices. @@ -96,7 +97,7 @@ def simulate( total_start = time.monotonic() # Extract state arrays from initial conditions, which include the regime on top. - initial_states = {k: v for k, v in initial_conditions.items() if k != "regime"} + initial_states = {k: v for k, v in initial_conditions.items() if k != "regime_id"} # Preparations key = jax.random.key(seed=seed) @@ -109,7 +110,7 @@ def simulate( initial_ages=initial_states["age"], ages=ages ) subject_regime_ids = jnp.full_like( - initial_conditions["regime"], MISSING_CAT_CODE, dtype=jnp.int32 + initial_conditions["regime_id"], MISSING_CAT_CODE, dtype=jnp.int32 ) # Forward simulation @@ -127,7 +128,7 @@ def simulate( # Activate subjects whose starting period matches the current period subject_regime_ids = jnp.where( starting_periods == period, - initial_conditions["regime"], + initial_conditions["regime_id"], subject_regime_ids, ) diff --git a/tests/data/regression_tests/f32/mahler_yum_simulation.pkl b/tests/data/regression_tests/f32/mahler_yum_simulation.pkl index 3b2191c53ee3686bcd8b1319f8410c792f546b82..413a1213b8ad64daf91f0d08948e42b05c37c566 100644 GIT binary patch delta 184 zcmZ1;yd{{WfpzN2jV#yH>!leWKmkhYLum^r?F6MgpmZ>lj)&5PP`VsS*Fxz|DBTaG zCqd~cty6-gWav+W@}(GxOOvuvlS|?=QwmaxGV@c4QqwbYQ{(dzC+BJOO!j7yoZO(y JIax$=G5~JS9_au8 delta 175 zcmdlIygZntfpzMfjV#yH>%|x#KpIMGL1|+sZ40HHp|l^Aj)KxTP`U_8S3v0&DBT66 zd!h7{)+s?#GW4fG`JxQPrAb+-$tCfbDFvxTnfWP2sp*-Mi#2*C=POH1R@9sf06PI2 Ar2qf` diff --git a/tests/data/regression_tests/f32/mortality_simulation.pkl b/tests/data/regression_tests/f32/mortality_simulation.pkl index c2c8965f4ca0fdb2d01356834eea8360f43f44c8..123d5f888a38331ceaaaa81f0d2da2d94db21d55 100644 GIT binary patch delta 98 zcmX>ga8`h&fpx0WMixePOKAoOP=L~EP}&em+d^pvC_SZhO3;)H{b>%9?bv0Rra6o{ifpx0sMixePOECrrkcQHVP+A8{TR>@RC_SZhO3;)H{b|;d?bu}}C$L*g M{=y*^ru-(zRM=d9G{ms X`3IW`R}XtZWnxiLe)*KidhDwKJscFI delta 86 zcmaFHf0Li3fpzM+jVue;EX5cgKpILbLTPm*^rx9lzRM;%`9GTx LQ-;u=6QFbsl&*u)El|1(N>78* zbD;EmC_SZhO3;)HeWhtIf#TAntkmR^_{@}o)S}G%l%mx1%-q!Yyu`^DEM`vjW|Ev_ K&N;c+axwrjX&+Po delta 183 zcmZ2nGOdKAfpsePMiv8$dNBqFkcQG)P}&?yJ3whKC>;Q$qoH&7B+ z)1mY%C_SZhO3;)Hec5R+f#TAntkmR^_{@}o)S}G%l%mx1%*j_RW=lbX|z0fpx0SMiy=kOKAoOP=L~EP}&em+d^pvC_SZhO3;)H{b>%9Jvd~U@RC_SZhO3;)H{b|;dJvd}1XK`3e M4r7;^e1u~%004guNdN!< diff --git a/tests/data/regression_tests/f64/precautionary_savings_simulation.pkl b/tests/data/regression_tests/f64/precautionary_savings_simulation.pkl index b24163be2ac561774c2585403f875fc86e345224..3853de29f6a8a5618ad38eac80764b74c5a4bdf0 100644 GIT binary patch delta 91 zcmew&)F{l-z&f>TBa1bMr8ENsC_rg7D6Ipf&7rgvl%CQ$C1^^9{xqw}6&$k6@p*}p QCv%ug_GXfte1l^S08}&(BLDyZ delta 86 zcmZn^{vyQEz&bT^Ba1bMr5FPQNJD8wD6I~qji9tCl%CQ$C1^^9{xs9c6&$jYXK@%Y LW!O%>#<3az{^t&Y diff --git a/tests/data/regression_tests/generate_benchmark_data.py b/tests/data/regression_tests/generate_benchmark_data.py index 748ddd516..b4f4eb4b9 100644 --- a/tests/data/regression_tests/generate_benchmark_data.py +++ b/tests/data/regression_tests/generate_benchmark_data.py @@ -62,7 +62,7 @@ def _generate_precautionary_savings(data_dir: Path) -> None: "age": jnp.full(n_subjects, 20.0), "wealth": jnp.full(n_subjects, 5.0), "income": jnp.full(n_subjects, 0.0), - "regime": jnp.zeros(n_subjects, dtype=jnp.int32), + "regime_id": jnp.zeros(n_subjects, dtype=jnp.int32), }, period_to_regime_to_V_arr=None, seed=12345, @@ -82,7 +82,7 @@ def _generate_mortality(data_dir: Path) -> None: initial_conditions={ "age": jnp.full(n_subjects, 40.0), "wealth": jnp.full(n_subjects, 100.0), - "regime": jnp.zeros(n_subjects, dtype=jnp.int32), + "regime_id": jnp.zeros(n_subjects, dtype=jnp.int32), }, period_to_regime_to_V_arr=None, seed=12345, @@ -102,7 +102,7 @@ def _generate_mahler_yum(data_dir: Path) -> None: params = {"alive": common_params} initial_conditions = { **initial_states, - "regime": jnp.full( + "regime_id": jnp.full( n_subjects, model.regime_names_to_ids["alive"], dtype=jnp.int32, diff --git a/tests/data/regression_tests/simulation.pkl b/tests/data/regression_tests/simulation.pkl index 9dbe57257d045ea94f55462c34eaa5dbbb5fa0cf..305d19b2c28d5df5da06954f3234acd2344594b2 100644 GIT binary patch delta 122 zcmdlca9Dt)fpx0IMwahvkBwuRCTPBI#L52bR$M*o1(k_KMfv4ZCU>*11^@(y8;Ae^ delta 110 zcmX>suuXuafpx0(MwahvAz};=APuDzp|lQ^wt&*sPjBa0q;qBH{pC_rg7D6Ipfji9tGly-p9Q(C74P07%o2ICi(CS|22 km&9kL6r>hq=BE^;rf24+#^)tY?qj!_?9C)O`6l}u08{%IWdHyG delta 118 zcmZn?{3^iGz&bT+Ba0q;j2Ht1NJD8wD6I~qb)mEcl(vS_Q(C74P07%o2ICi(CS|22 fm&9kL6r>hq=BE^;re{u`%x*Qgkxgpyd-lly$-WkN diff --git a/tests/data/shocks/simulation_normal.pkl b/tests/data/shocks/simulation_normal.pkl index 6d5a01f9da22947387fc6f0186c7e3e601516eba..8e9d616c383314336513efdc5a7e543e23599733 100644 GIT binary patch delta 123 zcmew=&?Lyxz&f>jBa0q;qBH{pC_rg7D6Ipfji9tGly-p9Q(C74P07%o2ICi(CS|22 km&9kL6r>hq=BE^;rf24+#^)tY?qj!_?9C)O`6l}u08{%IWdHyG delta 118 zcmZn?{3^iGz&bT+Ba0q;j2Ht1NJD8wD6I~qb)mEcl(vS_Q(C74P07%o2ICi(CS|22 fm&9kL6r>hq=BE^;re{u`%x*Qgkxgpyd-lly$-WkN diff --git a/tests/data/shocks/simulation_rouwenhorst.pkl b/tests/data/shocks/simulation_rouwenhorst.pkl index 7dc0961e2449db494298005b1225a7118218cec7..cdb9eafe1728b8542cb3715d9cd008fe5d1e46b0 100644 GIT binary patch delta 123 zcmew=&?Lyxz&f>jBa0q;qBH{pC_rg7D6Ipfji9tGly-p9Q(C74P07%o2ICi(CS|22 km&9kL6r>hq=BE^;rf24+#^)tY?qj!_?9C)O`6l}u08{%IWdHyG delta 118 zcmZn?{3^iGz&bT+Ba0q;j2Ht1NJD8wD6I~qb)mEcl(vS_Q(C74P07%o2ICi(CS|22 fm&9kL6r>hq=BE^;re{u`%x*Qgkxgpyd-lly$-WkN diff --git a/tests/data/shocks/simulation_tauchen.pkl b/tests/data/shocks/simulation_tauchen.pkl index 6dbec3bc1915439e179504664f8b1d290803f5bd..004c2421410e2f143f7c24225445576194e9a607 100644 GIT binary patch delta 123 zcmew=&?Lyxz&f>jBa0q;qBH{pC_rg7D6Ipfji9tGly-p9Q(C74P07%o2ICi(CS|22 km&9kL6r>hq=BE^;rf24+#^)tY?qj!_?9C)O`6l}u08{%IWdHyG delta 118 zcmZn?{3^iGz&bT+Ba0q;j2Ht1NJD8wD6I~qb)mEcl(vS_Q(C74P07%o2ICi(CS|22 fm&9kL6r>hq=BE^;re{u`%x*Qgkxgpyd-lly$-WkN diff --git a/tests/data/shocks/simulation_uniform.pkl b/tests/data/shocks/simulation_uniform.pkl index aade3cc8a3334dafe6df43007e078f61a07f62b7..d4eff882cb56f20f2e0788722633bc50dedd5c29 100644 GIT binary patch delta 123 zcmew=&?Lyxz&f>jBa0q;qBH{pC_rg7D6Ipfji9tGly-p9Q(C74P07%o2ICi(CS|22 km&9kL6r>hq=BE^;rf24+#^)tY?qj!_?9C)O`6l}u08{%IWdHyG delta 118 zcmZn?{3^iGz&bT+Ba0q;j2Ht1NJD8wD6I~qb)mEcl(vS_Q(C74P07%o2ICi(CS|22 fm&9kL6r>hq=BE^;re{u`%x*Qgkxgpyd-lly$-WkN diff --git a/tests/simulation/test_initial_conditions.py b/tests/simulation/test_initial_conditions.py index 882e0ffca..2d44a9e35 100644 --- a/tests/simulation/test_initial_conditions.py +++ b/tests/simulation/test_initial_conditions.py @@ -114,7 +114,7 @@ def test_validate_initial_conditions_valid_input( "age": jnp.array([0.0, 0.0]), "wealth": jnp.array([10.0, 50.0]), "health": jnp.array([0, 1]), - "regime": jnp.array([_ACTIVE, _ACTIVE]), + "regime_id": jnp.array([_ACTIVE, _ACTIVE]), }, internal_regimes=model.internal_regimes, regime_names_to_ids=model.regime_names_to_ids, @@ -134,7 +134,7 @@ def test_validate_initial_conditions_missing_state( initial_conditions={ "age": jnp.array([0.0, 0.0]), "wealth": jnp.array([10.0, 50.0]), - "regime": jnp.array([_ACTIVE, _ACTIVE]), + "regime_id": jnp.array([_ACTIVE, _ACTIVE]), }, internal_regimes=model.internal_regimes, regime_names_to_ids=model.regime_names_to_ids, @@ -154,7 +154,7 @@ def test_validate_initial_conditions_extra_state( "wealth": jnp.array([10.0]), "health": jnp.array([0]), "unknown": jnp.array([1.0]), - "regime": jnp.array([_ACTIVE]), + "regime_id": jnp.array([_ACTIVE]), }, internal_regimes=model.internal_regimes, regime_names_to_ids=model.regime_names_to_ids, @@ -173,7 +173,7 @@ def test_validate_initial_conditions_inconsistent_lengths( "age": jnp.array([0.0, 0.0]), "wealth": jnp.array([10.0, 20.0]), "health": jnp.array([0]), - "regime": jnp.array([_ACTIVE, _ACTIVE]), + "regime_id": jnp.array([_ACTIVE, _ACTIVE]), }, internal_regimes=model.internal_regimes, regime_names_to_ids=model.regime_names_to_ids, @@ -192,7 +192,7 @@ def test_validate_initial_conditions_invalid_discrete_value( "age": jnp.array([0.0]), "wealth": jnp.array([10.0]), "health": jnp.array([5]), - "regime": jnp.array([_ACTIVE]), + "regime_id": jnp.array([_ACTIVE]), }, internal_regimes=model.internal_regimes, regime_names_to_ids=model.regime_names_to_ids, @@ -211,7 +211,7 @@ def test_validate_initial_conditions_invalid_regime_id( "age": jnp.array([0.0]), "wealth": jnp.array([10.0]), "health": jnp.array([0]), - "regime": jnp.array([99]), + "regime_id": jnp.array([99]), }, internal_regimes=model.internal_regimes, regime_names_to_ids=model.regime_names_to_ids, @@ -230,7 +230,7 @@ def test_validate_initial_conditions_invalid_age_values( "age": jnp.array([0.0, 99.0]), "wealth": jnp.array([10.0, 50.0]), "health": jnp.array([0, 1]), - "regime": jnp.array([_ACTIVE, _ACTIVE]), + "regime_id": jnp.array([_ACTIVE, _ACTIVE]), }, internal_regimes=model.internal_regimes, regime_names_to_ids=model.regime_names_to_ids, @@ -315,7 +315,7 @@ def test_infeasible_initial_states_detected(): initial_conditions={ "age": jnp.array([0.0]), "wealth": jnp.array([0.25]), - "regime": jnp.array([_working_life]), + "regime_id": jnp.array([_working_life]), }, period_to_regime_to_V_arr=None, ) @@ -341,7 +341,7 @@ def test_on_grid_state_but_combination_infeasible(): initial_conditions={ "age": jnp.array([0.0]), "wealth": jnp.array([0.3]), - "regime": jnp.array([_working_life]), + "regime_id": jnp.array([_working_life]), }, period_to_regime_to_V_arr=None, ) @@ -362,7 +362,7 @@ def test_extrapolated_initial_states_accepted(): initial_conditions={ "age": jnp.array([0.0]), "wealth": jnp.array([1.0]), - "regime": jnp.array([_working_life]), + "regime_id": jnp.array([_working_life]), }, period_to_regime_to_V_arr=None, ) @@ -383,7 +383,7 @@ def test_on_grid_initial_states_accepted(): initial_conditions={ "age": jnp.array([0.0]), "wealth": jnp.array([5.0]), - "regime": jnp.array([_working_life]), + "regime_id": jnp.array([_working_life]), }, period_to_regime_to_V_arr=None, ) @@ -405,7 +405,7 @@ def test_irreg_spaced_grid_with_runtime_points(): params=params, initial_conditions={ "wealth": jnp.array([0.3]), - "regime": jnp.array([_working_life]), + "regime_id": jnp.array([_working_life]), }, period_to_regime_to_V_arr=None, ) @@ -423,7 +423,7 @@ def test_missing_age_error_message( initial_conditions={ "wealth": jnp.array([10.0]), "health": jnp.array([0]), - "regime": jnp.array([_ACTIVE]), + "regime_id": jnp.array([_ACTIVE]), }, internal_regimes=model.internal_regimes, regime_names_to_ids=model.regime_names_to_ids, @@ -562,7 +562,7 @@ def test_subject_in_inactive_regime_at_starting_age() -> None: initial_conditions={ "age": jnp.array([0.0]), "wealth": jnp.array([10.0]), - "regime": jnp.array([_dead]), + "regime_id": jnp.array([_dead]), }, internal_regimes=model.internal_regimes, regime_names_to_ids=model.regime_names_to_ids, @@ -583,7 +583,7 @@ def test_all_subjects_in_regime_with_fewer_states() -> None: initial_conditions={ "age": jnp.array([2.0, 2.0]), "wealth": jnp.array([10.0, 50.0]), - "regime": jnp.array([_dead, _dead]), + "regime_id": jnp.array([_dead, _dead]), }, internal_regimes=model.internal_regimes, regime_names_to_ids=model.regime_names_to_ids, @@ -609,7 +609,7 @@ def test_mixed_regimes_all_union_states_provided() -> None: "age": jnp.array([0.0, 2.0]), "wealth": jnp.array([10.0, 50.0]), "health": jnp.array([0, 0]), - "regime": jnp.array([_alive, _dead]), + "regime_id": jnp.array([_alive, _dead]), }, internal_regimes=model.internal_regimes, regime_names_to_ids=model.regime_names_to_ids, @@ -630,7 +630,7 @@ def test_constraint_not_checked_for_unused_regime() -> None: initial_conditions={ "age": jnp.array([2.0]), "wealth": jnp.array([40.0]), - "regime": jnp.array([_dead]), + "regime_id": jnp.array([_dead]), }, internal_regimes=model.internal_regimes, regime_names_to_ids=model.regime_names_to_ids, @@ -652,7 +652,7 @@ def test_constraint_checked_for_starting_regime() -> None: initial_conditions={ "age": jnp.array([0.0]), "wealth": jnp.array([40.0]), - "regime": jnp.array([_alive]), + "regime_id": jnp.array([_alive]), }, internal_regimes=model.internal_regimes, regime_names_to_ids=model.regime_names_to_ids, @@ -678,7 +678,7 @@ def test_mixed_regimes_constraint_only_checked_for_starting_regime() -> None: initial_conditions={ "age": jnp.array([0.0, 2.0]), "wealth": jnp.array([40.0, 40.0]), - "regime": jnp.array([_alive, _dead]), + "regime_id": jnp.array([_alive, _dead]), }, internal_regimes=model.internal_regimes, regime_names_to_ids=model.regime_names_to_ids, diff --git a/tests/simulation/test_simulate.py b/tests/simulation/test_simulate.py index a90c3c2b3..4802f21a8 100644 --- a/tests/simulation/test_simulate.py +++ b/tests/simulation/test_simulate.py @@ -89,14 +89,14 @@ def test_simulate_using_raw_inputs(simulate_inputs): initial_conditions={ "wealth": jnp.array([1.0, 50.400803]), "age": jnp.array([0.0, 0.0]), - "regime": jnp.array( + "regime_id": jnp.array( [simulate_inputs["regime_names_to_ids"]["working_life"]] * 2 ), }, logger=get_logger(log_level="off"), **simulate_inputs, ) - got = result.to_dataframe().query('regime == "working_life"') + got = result.to_dataframe().query('regime_name == "working_life"') assert (got["labor_supply"] == "retire").all() assert_array_almost_equal(got["consumption"], [1.0, 50.400803]) @@ -148,12 +148,12 @@ def test_simulate_using_model_methods( initial_conditions={ "wealth": jnp.array([20.0, 150, 250, 320]), "age": jnp.array([18.0, 18.0, 18.0, 18.0]), - "regime": jnp.array([RegimeId.working_life] * 4), + "regime_id": jnp.array([RegimeId.working_life] * 4), }, ) df = result.to_dataframe( additional_targets=["utility", "borrowing_constraint"] - ).query('regime == "working_life"') + ).query('regime_name == "working_life"') # Check expected columns expected_cols = { @@ -166,7 +166,7 @@ def test_simulate_using_model_methods( "utility", "borrowing_constraint", "subject_id", - "regime", + "regime_name", } assert expected_cols == set(df.columns) @@ -201,11 +201,11 @@ def test_simulate_with_only_discrete_actions(): initial_conditions={ "wealth": jnp.array([0.0, 2.0]), "age": jnp.array([50.0, 50.0]), - "regime": jnp.array([DiscreteRegimeId.working_life] * 2), + "regime_id": jnp.array([DiscreteRegimeId.working_life] * 2), }, period_to_regime_to_V_arr=None, ) - got = result.to_dataframe().query('regime == "working_life"') + got = result.to_dataframe().query('regime_name == "working_life"') # Expected: sorted by (subject_id, period) # Subject 0: wealth=low -> works, low consumption; wealth=high -> retires, high @@ -265,12 +265,12 @@ def test_effect_of_discount_factor_on_last_period(): initial_conditions={ "wealth": initial_wealth, "age": jnp.array([18.0, 18.0, 18.0]), - "regime": jnp.array([RegimeId.working_life] * 3), + "regime_id": jnp.array([RegimeId.working_life] * 3), }, period_to_regime_to_V_arr=None, ) .to_dataframe() - .query('regime == "working_life"') + .query('regime_name == "working_life"') ) df_high = ( @@ -279,12 +279,12 @@ def test_effect_of_discount_factor_on_last_period(): initial_conditions={ "wealth": initial_wealth, "age": jnp.array([18.0, 18.0, 18.0]), - "regime": jnp.array([RegimeId.working_life] * 3), + "regime_id": jnp.array([RegimeId.working_life] * 3), }, period_to_regime_to_V_arr=None, ) .to_dataframe() - .query('regime == "working_life"') + .query('regime_name == "working_life"') ) # Higher beta (more patient) should lead to higher value in later periods @@ -330,12 +330,12 @@ def test_effect_of_disutility_of_work(): initial_conditions={ "wealth": initial_wealth, "age": jnp.array([18.0, 18.0, 18.0]), - "regime": jnp.array([RegimeId.working_life] * 3), + "regime_id": jnp.array([RegimeId.working_life] * 3), }, period_to_regime_to_V_arr=None, ) .to_dataframe() - .query('regime == "working_life"') + .query('regime_name == "working_life"') ) df_high = ( @@ -344,12 +344,12 @@ def test_effect_of_disutility_of_work(): initial_conditions={ "wealth": initial_wealth, "age": jnp.array([18.0, 18.0, 18.0]), - "regime": jnp.array([RegimeId.working_life] * 3), + "regime_id": jnp.array([RegimeId.working_life] * 3), }, period_to_regime_to_V_arr=None, ) .to_dataframe() - .query('regime == "working_life"') + .query('regime_name == "working_life"') ) # Merge results for easy comparison @@ -377,14 +377,14 @@ def test_to_dataframe_use_labels_parameter(): initial_conditions={ "wealth": jnp.array([20.0, 50.0]), "age": jnp.array([18.0, 18.0]), - "regime": jnp.array([RegimeId.working_life] * 2), + "regime_id": jnp.array([RegimeId.working_life] * 2), }, period_to_regime_to_V_arr=None, ) # use_labels=True (default): discrete columns are Categorical with string labels df_labels = result.to_dataframe() - for col in ["regime", "labor_supply"]: + for col in ["regime_name", "labor_supply"]: assert df_labels[col].dtype.name == "category", f"{col} should be categorical" assert set(df_labels["labor_supply"].cat.categories) == {"work", "retire"} @@ -405,7 +405,7 @@ def regression_simulation_result(): initial_conditions={ "wealth": jnp.array([20.0, 50.0]), "age": jnp.array([18.0, 18.0]), - "regime": jnp.array([RegimeId.working_life] * 2), + "regime_id": jnp.array([RegimeId.working_life] * 2), }, period_to_regime_to_V_arr=None, ) @@ -450,7 +450,7 @@ def test_additional_targets_all_with_stochastic_transitions(): "health": jnp.array([Health.good, Health.bad]), "partner": jnp.array([PartnerStatus.single, PartnerStatus.partnered]), "age": jnp.array([40.0, 40.0]), - "regime": jnp.array([StochasticRegimeId.working_life] * 2), + "regime_id": jnp.array([StochasticRegimeId.working_life] * 2), }, period_to_regime_to_V_arr=None, ) @@ -488,7 +488,7 @@ def test_simulation_result_pickle_roundtrip(tmp_path: Path): initial_conditions={ "wealth": jnp.array([20.0, 50.0]), "age": jnp.array([18.0, 18.0]), - "regime": jnp.array([RegimeId.working_life] * 2), + "regime_id": jnp.array([RegimeId.working_life] * 2), }, period_to_regime_to_V_arr=None, ) diff --git a/tests/simulation/test_simulate_aot.py b/tests/simulation/test_simulate_aot.py index f778722e9..ee63aac17 100644 --- a/tests/simulation/test_simulate_aot.py +++ b/tests/simulation/test_simulate_aot.py @@ -52,7 +52,7 @@ def _build_initial_conditions(*, n_subjects: int) -> dict[str, Array]: return { "wealth": wealths, "age": jnp.full((n_subjects,), 18.0), - "regime": jnp.array([RegimeId.working_life] * n_subjects), + "regime_id": jnp.array([RegimeId.working_life] * n_subjects), } diff --git a/tests/solution/test_beta_delta.py b/tests/solution/test_beta_delta.py index d36b3d898..bcaaafc0f 100644 --- a/tests/solution/test_beta_delta.py +++ b/tests/solution/test_beta_delta.py @@ -185,7 +185,7 @@ def test_beta_delta_consumption(label, beta, delta): initial_conditions = { "age": initial_age, "wealth": initial_wealth, - "regime": jnp.array([RegimeId.working]), + "regime_id": jnp.array([RegimeId.working]), } if label == "naive_phase_variant": @@ -227,7 +227,7 @@ def test_beta_delta_consumption(label, beta, delta): period_to_regime_to_V_arr=None, ) - df = result.to_dataframe().query('regime == "working"') + df = result.to_dataframe().query('regime_name == "working"') got_c0 = df.loc[df["age"] == 0, "consumption"].iloc[0] got_c1 = df.loc[df["age"] == 1, "consumption"].iloc[0] diff --git a/tests/test_ages.py b/tests/test_ages.py index 62e9b7cf1..d80d1cc3f 100644 --- a/tests/test_ages.py +++ b/tests/test_ages.py @@ -150,7 +150,7 @@ def test_model_with_quarterly_steps(): initial_conditions={ "wealth": jnp.array([50.0, 100.0, 150.0]), "age": jnp.array([18.0, 18.0, 18.0]), - "regime": jnp.array([RegimeId.working_life] * 3), + "regime_id": jnp.array([RegimeId.working_life] * 3), }, period_to_regime_to_V_arr=None, ) @@ -161,7 +161,7 @@ def test_model_with_quarterly_steps(): assert set(df["age"].unique()) == {18.0, 18.25, 18.5, 18.75, 19.0} # Check working/retired regimes only have ages < 19 - non_dead_df = df.query('regime != "dead"') + non_dead_df = df.query('regime_name != "dead"') assert all(non_dead_df["age"] < 19) @@ -293,7 +293,7 @@ def test_model_with_integer_ages(): initial_conditions={ "wealth": jnp.array([50.0, 100.0, 150.0]), "age": jnp.array([40, 40, 40]), - "regime": jnp.array([RegimeId.working_life] * 3), + "regime_id": jnp.array([RegimeId.working_life] * 3), }, period_to_regime_to_V_arr=None, ) diff --git a/tests/test_chained_state_transitions.py b/tests/test_chained_state_transitions.py index 8bc4dc645..2f22bdac6 100644 --- a/tests/test_chained_state_transitions.py +++ b/tests/test_chained_state_transitions.py @@ -107,7 +107,7 @@ def test_simulate_with_chained_transitions_yields_expected_next_wealth() -> None "age": jnp.array([0.0, 0.0]), "aime": jnp.array([0.0, 1.0]), "wealth": jnp.array([2.0, 3.0]), - "regime": jnp.array([_RegimeId.active, _RegimeId.active]), + "regime_id": jnp.array([_RegimeId.active, _RegimeId.active]), } df = ( @@ -117,7 +117,7 @@ def test_simulate_with_chained_transitions_yields_expected_next_wealth() -> None period_to_regime_to_V_arr=None, ) .to_dataframe() - .query('regime == "active"') + .query('regime_name == "active"') .sort_values(["subject_id", "period"]) .reset_index(drop=True) ) diff --git a/tests/test_distributed.py b/tests/test_distributed.py index 891a89f24..460fffeae 100644 --- a/tests/test_distributed.py +++ b/tests/test_distributed.py @@ -166,7 +166,7 @@ def test_simulation_running_on_multiple_cpus(correct_distributed_model): "wealth": jnp.full(36, 100.0), "type1": jnp.full(36, 1), "type2": jnp.full(36, 1), - "regime": jnp.zeros(36, dtype=jnp.int32), + "regime_id": jnp.zeros(36, dtype=jnp.int32), }, period_to_regime_to_V_arr=None, seed=12345, @@ -201,7 +201,7 @@ def test_simulation_error_if_not_multiple(correct_distributed_model): "wealth": jnp.full(5, 100.0), "type1": jnp.full(5, 1), "type2": jnp.full(5, 1), - "regime": jnp.zeros(5, dtype=jnp.int32), + "regime_id": jnp.zeros(5, dtype=jnp.int32), }, period_to_regime_to_V_arr=None, seed=12345, diff --git a/tests/test_dtypes.py b/tests/test_dtypes.py index d0083dab0..3fd74a872 100644 --- a/tests/test_dtypes.py +++ b/tests/test_dtypes.py @@ -52,8 +52,8 @@ def test_safe_to_int_dtype_raises_on_array_overflow(): # under `jax_enable_x64=False` and trips JAX's own overflow guard before # `safe_to_int_dtype` ever sees the value. arr = np.asarray([1, 2, 2**32], dtype=np.int64) - with pytest.raises(ValueError, match="regime"): - safe_to_int_dtype(arr, name="regime") + with pytest.raises(ValueError, match="regime_id"): + safe_to_int_dtype(arr, name="regime_id") def test_safe_to_int_dtype_raises_on_underflow(): diff --git a/tests/test_economic_validation.py b/tests/test_economic_validation.py index a3c2a1d36..85ab313b1 100644 --- a/tests/test_economic_validation.py +++ b/tests/test_economic_validation.py @@ -29,7 +29,7 @@ def _simulate(shock_type, *, sigma, rho=0.0, mu=0.0): "wealth": jnp.full(_N_SUBJECTS, 5.0), "income": jnp.full(_N_SUBJECTS, unconditional_mean), "age": jnp.full(_N_SUBJECTS, 20.0), - "regime": jnp.array([RegimeId.alive] * _N_SUBJECTS), + "regime_id": jnp.array([RegimeId.alive] * _N_SUBJECTS), }, period_to_regime_to_V_arr=None, seed=_SEED, @@ -38,7 +38,7 @@ def _simulate(shock_type, *, sigma, rho=0.0, mu=0.0): def _mean_wealth_in_final_alive_period(df): - rows = df[(df["period"] == _N_PERIODS - 2) & (df["regime"] == "alive")] + rows = df[(df["period"] == _N_PERIODS - 2) & (df["regime_name"] == "alive")] assert len(rows) > 0, "No rows in final alive period" return rows["wealth"].mean() @@ -48,7 +48,7 @@ def test_deterministic_when_sigma_zero(shock_type): rho = 0.5 if shock_type in ("rouwenhorst", "tauchen") else 0.0 df = _simulate(shock_type, sigma=_SIGMA_ZERO, rho=rho) - alive_df = df[df["regime"] == "alive"] + alive_df = df[df["regime_name"] == "alive"] for period in alive_df["period"].unique(): period_data = alive_df[alive_df["period"] == period] assert period_data["wealth"].std() == pytest.approx(0, abs=_DETERMINISTIC_ATOL) diff --git a/tests/test_error_handling_invalid_vf.py b/tests/test_error_handling_invalid_vf.py index 9b9fe1d39..e86ba7d2a 100644 --- a/tests/test_error_handling_invalid_vf.py +++ b/tests/test_error_handling_invalid_vf.py @@ -183,7 +183,7 @@ def test_simulate_model_with_nan_value_function_array_raises_error( "wealth": jnp.array([0.9, 1.0]), "health": jnp.array([1.0, 1.0]), "age": jnp.array([0.0, 0.0]), - "regime": jnp.array([RegimeId.non_terminal] * 2), + "regime_id": jnp.array([RegimeId.non_terminal] * 2), } with pytest.raises(InvalidValueFunctionError): @@ -202,7 +202,7 @@ def test_simulate_model_with_inf_value_function_array_does_not_raise_error( "wealth": jnp.array([1.5, 2.0]), "health": jnp.array([1.0, 1.0]), "age": jnp.array([0.0, 0.0]), - "regime": jnp.array([RegimeId.non_terminal] * 2), + "regime_id": jnp.array([RegimeId.non_terminal] * 2), } # This should not raise an error. Subject 1 (wealth=2.0, health=1.0) triggers the diff --git a/tests/test_fgp_model.py b/tests/test_fgp_model.py index 0d0edc3ec..301107819 100644 --- a/tests/test_fgp_model.py +++ b/tests/test_fgp_model.py @@ -45,7 +45,7 @@ def _simulate(shock_type): "wealth": jnp.full(_N_SUBJECTS, 5.0), "income": jnp.zeros(_N_SUBJECTS), "age": jnp.full(_N_SUBJECTS, 20.0), - "regime": jnp.array([RegimeId.alive] * _N_SUBJECTS), + "regime_id": jnp.array([RegimeId.alive] * _N_SUBJECTS), }, period_to_regime_to_V_arr=None, seed=_SEED, @@ -92,7 +92,7 @@ def test_model_simulates(shock_type): def test_simulated_income_moments(shock_type): """Simulated income moments are in the right ballpark.""" df = _simulate(shock_type) - alive_df = df[df["regime"] == "alive"] + alive_df = df[df["regime_name"] == "alive"] # Income on the grid should have mean near 0 and std near sigma_y income_vals = alive_df["income"].to_numpy() @@ -109,8 +109,8 @@ def test_rouwenhorst_income_moments_closer_to_theory(): df_r = _simulate("rouwenhorst") df_t = _simulate("tauchen") - r_var = df_r[df_r["regime"] == "alive"]["income"].var() - t_var = df_t[df_t["regime"] == "alive"]["income"].var() + r_var = df_r[df_r["regime_name"] == "alive"]["income"].var() + t_var = df_t[df_t["regime_name"] == "alive"]["income"].var() # Both should be in the right ballpark; Rouwenhorst should be at least as close r_err = abs(r_var - expected_var) diff --git a/tests/test_float_dtype_invariants.py b/tests/test_float_dtype_invariants.py index fbc9202de..4b031e50c 100644 --- a/tests/test_float_dtype_invariants.py +++ b/tests/test_float_dtype_invariants.py @@ -170,7 +170,7 @@ def test_simulate_state_pool_dtype_stable_across_periods(x64_disabled: None): initial = { "wealth": jnp.asarray([20.0, 50.0, 80.0]), "age": jnp.asarray([18.0, 18.0, 18.0]), - "regime": jnp.asarray([RegimeId.working_life] * 3), + "regime_id": jnp.asarray([RegimeId.working_life] * 3), } result = model.simulate( diff --git a/tests/test_heterogeneous_initial_ages.py b/tests/test_heterogeneous_initial_ages.py index 43a251da5..47cee4356 100644 --- a/tests/test_heterogeneous_initial_ages.py +++ b/tests/test_heterogeneous_initial_ages.py @@ -22,7 +22,7 @@ def test_simulation_with_heterogeneous_initial_ages(): initial_conditions={ "age": jnp.array([40.0, 60.0]), "wealth": jnp.array([50.0, 50.0]), - "regime": jnp.array([RegimeId.working_life] * 2), + "regime_id": jnp.array([RegimeId.working_life] * 2), }, period_to_regime_to_V_arr=None, ) diff --git a/tests/test_int_dtype_invariants.py b/tests/test_int_dtype_invariants.py index a885ea9c3..a84a81fa4 100644 --- a/tests/test_int_dtype_invariants.py +++ b/tests/test_int_dtype_invariants.py @@ -244,11 +244,11 @@ def test_simulate_accepts_int64_regime_initial_condition_and_round_trips() -> No } initial_conditions_int32 = { **common, - "regime": jnp.asarray([RegimeId.working_life] * 4, dtype=jnp.int32), + "regime_id": jnp.asarray([RegimeId.working_life] * 4, dtype=jnp.int32), } initial_conditions_int64 = { **common, - "regime": jnp.asarray([RegimeId.working_life] * 4, dtype=jnp.int64), + "regime_id": jnp.asarray([RegimeId.working_life] * 4, dtype=jnp.int64), } df_int32 = model.simulate( diff --git a/tests/test_pandas_utils.py b/tests/test_pandas_utils.py index e0f2620b9..1dae18509 100644 --- a/tests/test_pandas_utils.py +++ b/tests/test_pandas_utils.py @@ -132,7 +132,7 @@ def test_continuous_states_and_age(): model = get_basic_model() df = pd.DataFrame( { - "regime": ["working_life", "working_life"], + "regime_id": ["working_life", "working_life"], "health": ["bad", "good"], "wealth": [10.0, 50.0], "age": [25.0, 35.0], @@ -144,7 +144,7 @@ def test_continuous_states_and_age(): regime_names_to_ids=model.regime_names_to_ids, ) assert jnp.array_equal( - conditions["regime"], + conditions["regime_id"], jnp.array([BasicRegimeId.working_life, BasicRegimeId.working_life]), ) assert jnp.allclose(conditions["wealth"], jnp.array([10.0, 50.0])) @@ -155,7 +155,7 @@ def test_categorical_string_labels(): model = get_basic_model() df = pd.DataFrame( { - "regime": ["working_life", "retirement"], + "regime_id": ["working_life", "retirement"], "health": ["bad", "good"], "wealth": [10.0, 50.0], "age": [25.0, 25.0], @@ -167,7 +167,7 @@ def test_categorical_string_labels(): regime_names_to_ids=model.regime_names_to_ids, ) assert jnp.array_equal( - conditions["regime"], + conditions["regime_id"], jnp.array([BasicRegimeId.working_life, BasicRegimeId.retirement]), ) assert jnp.array_equal(conditions["health"], jnp.array([Health.bad, Health.good])) @@ -178,7 +178,7 @@ def test_categorical_pd_categorical_column(): health_dtype = Health.to_categorical_dtype() # ty: ignore[unresolved-attribute] df = pd.DataFrame( { - "regime": ["working_life", "working_life"], + "regime_id": ["working_life", "working_life"], "health": pd.Categorical(["good", "bad"], dtype=health_dtype), "wealth": [10.0, 50.0], "age": [25.0, 25.0], @@ -196,7 +196,7 @@ def test_multi_regime(): model = get_basic_model() df = pd.DataFrame( { - "regime": ["working_life", "retirement", "working_life"], + "regime_id": ["working_life", "retirement", "working_life"], "health": ["good", "bad", "good"], "wealth": [10.0, 50.0, 30.0], "age": [25.0, 25.0, 25.0], @@ -208,7 +208,7 @@ def test_multi_regime(): regime_names_to_ids=model.regime_names_to_ids, ) assert jnp.array_equal( - conditions["regime"], + conditions["regime_id"], jnp.array( [ BasicRegimeId.working_life, @@ -223,7 +223,7 @@ def test_multi_regime(): def test_missing_regime_column_raises(): model = get_basic_model() df = pd.DataFrame({"wealth": [10.0]}) - with pytest.raises(ValueError, match="'regime' column"): + with pytest.raises(ValueError, match="'regime_id' column"): initial_conditions_from_dataframe( df=df, regimes=model.regimes, @@ -235,7 +235,7 @@ def test_invalid_regime_name_raises(): model = get_basic_model() df = pd.DataFrame( { - "regime": ["working_life", "nonexistent"], + "regime_id": ["working_life", "nonexistent"], "wealth": [10.0, 50.0], } ) @@ -251,7 +251,7 @@ def test_invalid_category_label_raises(): model = get_basic_model() df = pd.DataFrame( { - "regime": ["working_life"], + "regime_id": ["working_life"], "health": ["excellent"], "wealth": [10.0], "age": [25.0], @@ -268,7 +268,7 @@ def test_invalid_category_label_raises(): def test_empty_dataframe_raises(): model = get_basic_model() df = pd.DataFrame( - {"regime": pd.Series([], dtype=str), "wealth": pd.Series([], dtype=float)} + {"regime_id": pd.Series([], dtype=str), "wealth": pd.Series([], dtype=float)} ) with pytest.raises(ValueError, match="empty"): initial_conditions_from_dataframe( @@ -282,7 +282,7 @@ def test_unknown_column_raises(): model = get_basic_model() df = pd.DataFrame( { - "regime": ["working_life"], + "regime_id": ["working_life"], "health": ["bad"], "wealth": [10.0], "age": [25.0], @@ -301,7 +301,7 @@ def test_missing_state_column_raises(): model = get_basic_model() df = pd.DataFrame( { - "regime": ["working_life"], + "regime_id": ["working_life"], "age": [25.0], # missing "health" and "wealth" } @@ -319,7 +319,7 @@ def test_shock_state_columns_accepted(): model = get_shock_model(n_periods=4, distribution_type="uniform") df = pd.DataFrame( { - "regime": ["alive", "alive"], + "regime_id": ["alive", "alive"], "wealth": [2.0, 4.0], "health": ["bad", "good"], "income": [0.3, 0.7], @@ -333,7 +333,7 @@ def test_shock_state_columns_accepted(): ) assert jnp.allclose(conditions["income"], jnp.array([0.3, 0.7])) assert jnp.allclose(conditions["wealth"], jnp.array([2.0, 4.0])) - assert "regime" in conditions + assert "regime_id" in conditions def test_shock_state_columns_required(): @@ -341,7 +341,7 @@ def test_shock_state_columns_required(): model = get_shock_model(n_periods=4, distribution_type="uniform") df = pd.DataFrame( { - "regime": ["alive", "alive"], + "regime_id": ["alive", "alive"], "wealth": [2.0, 4.0], "health": ["bad", "good"], "age": [0.0, 0.0], @@ -372,7 +372,7 @@ def test_round_trip_with_discrete_model(): raw_conditions = { "wealth": jnp.array([DiscreteWealth.low, DiscreteWealth.high]), "age": jnp.array([50.0, 50.0]), - "regime": jnp.array([RegimeId.working_life, RegimeId.working_life]), + "regime_id": jnp.array([RegimeId.working_life, RegimeId.working_life]), } result_raw = model.simulate( params=params, @@ -383,7 +383,7 @@ def test_round_trip_with_discrete_model(): # DataFrame approach df = pd.DataFrame( { - "regime": ["working_life", "working_life"], + "regime_id": ["working_life", "working_life"], "wealth": ["low", "high"], "age": [50.0, 50.0], } @@ -472,7 +472,7 @@ def test_initial_conditions_heterogeneous_health_grids() -> None: model = _get_heterogeneous_health_model() df = pd.DataFrame( { - "regime": ["pre65", "pre65", "post65", "post65"], + "regime_id": ["pre65", "pre65", "post65", "post65"], "health": ["disabled", "good", "bad", "good"], "wealth": [10.0, 50.0, 30.0, 70.0], "age": [50.0, 50.0, 70.0, 70.0], @@ -488,7 +488,7 @@ def test_initial_conditions_heterogeneous_health_grids() -> None: assert jnp.array_equal(result["health"], jnp.array([0, 2, 0, 1])) assert jnp.allclose(result["wealth"], jnp.array([10.0, 50.0, 30.0, 70.0])) assert jnp.array_equal( - result["regime"], + result["regime_id"], jnp.array( [ _HetRegimeId.pre65, @@ -552,7 +552,7 @@ def _utility_without_status(wealth: float) -> float: df = pd.DataFrame( { - "regime": ["with_status", "with_status", "without_status"], + "regime_id": ["with_status", "with_status", "without_status"], "wealth": [10.0, 20.0, 30.0], "status": ["low", "high", pd.NA], "age": [50.0, 51.0, 50.0], @@ -616,7 +616,7 @@ def _retiree_utility(wealth: float) -> float: df = pd.DataFrame( { - "regime": ["earner", "earner", "retiree"], + "regime_id": ["earner", "earner", "retiree"], "wealth": [10.0, 20.0, 30.0], "income": [0.3, 0.7, float("nan")], "age": [50.0, 51.0, 50.0], @@ -704,7 +704,7 @@ def test_heterogeneous_health_solve_simulate() -> None: model = _get_heterogeneous_health_model() df = pd.DataFrame( { - "regime": ["pre65", "pre65", "post65", "post65"], + "regime_id": ["pre65", "pre65", "post65", "post65"], "health": ["disabled", "good", "bad", "good"], "wealth": [10.0, 50.0, 30.0, 70.0], "age": [50.0, 50.0, 70.0, 70.0], @@ -732,7 +732,9 @@ def test_heterogeneous_health_solve_simulate() -> None: assert list(period_0["health"]) == ["disabled", "good"] # Period 2: post65 subjects have correct health labels - period_2 = out.query("period == 2 and regime == 'post65'").sort_values("subject_id") + period_2 = out.query("period == 2 and regime_name == 'post65'").sort_values( + "subject_id" + ) assert list(period_2["health"]) == ["bad", "good"] @@ -741,7 +743,7 @@ def test_heterogeneous_health_simulate_use_labels_false() -> None: model = _get_heterogeneous_health_model() df = pd.DataFrame( { - "regime": ["pre65", "post65"], + "regime_id": ["pre65", "post65"], "health": ["disabled", "good"], "wealth": [10.0, 70.0], "age": [50.0, 70.0], diff --git a/tests/test_persistence.py b/tests/test_persistence.py index d99ac6d5a..09c917c50 100644 --- a/tests/test_persistence.py +++ b/tests/test_persistence.py @@ -71,7 +71,7 @@ def _initial_conditions(): return { "wealth": jnp.array([2.0, 3.0]), "age": jnp.array([0.0, 0.0]), - "regime": jnp.array([_RegimeId.working] * 2), + "regime_id": jnp.array([_RegimeId.working] * 2), } diff --git a/tests/test_regime_state_mismatch.py b/tests/test_regime_state_mismatch.py index 20e22fed2..d3d0fea8f 100644 --- a/tests/test_regime_state_mismatch.py +++ b/tests/test_regime_state_mismatch.py @@ -190,12 +190,12 @@ def next_regime(age: float) -> ScalarInt: initial_conditions={ "age": jnp.array([0.0, 0.0]), "wealth": jnp.array([20.0, 80.0]), - "regime": jnp.array([_RegimeId.alive] * 2), + "regime_id": jnp.array([_RegimeId.alive] * 2), }, period_to_regime_to_V_arr=period_to_regime_to_V_arr, ) df = result.to_dataframe(use_labels=False) - dead_rows = df[df["regime"] == "dead"] + dead_rows = df[df["regime_name"] == "dead"] valid_codes = {float(HeirPresent.no), float(HeirPresent.yes)} assert not dead_rows.empty, "Expected some rows in the dead regime" assert dead_rows["heir_present"].isin(valid_codes).all() @@ -282,12 +282,12 @@ def utility_dead(wealth: ContinuousState, heir_present: DiscreteState) -> FloatN initial_conditions={ "age": jnp.array([0.0, 0.0]), "wealth": jnp.array([20.0, 80.0]), - "regime": jnp.array([_RegimeId.alive] * 2), + "regime_id": jnp.array([_RegimeId.alive] * 2), }, period_to_regime_to_V_arr=period_to_regime_to_V_arr, ) df = result.to_dataframe(use_labels=False) - dead_rows = df[df["regime"] == "dead"] + dead_rows = df[df["regime_name"] == "dead"] valid_codes = {float(HeirPresent.no), float(HeirPresent.yes)} assert not dead_rows.empty, "Expected some rows in the dead regime" assert dead_rows["heir_present"].isin(valid_codes).all() @@ -369,13 +369,13 @@ def test_per_target_dict_transitions(): initial_conditions={ "age": jnp.zeros(n_subjects), "health": initial_health, - "regime": jnp.array([RegimeId.working_life] * n_subjects), + "regime_id": jnp.array([RegimeId.working_life] * n_subjects), }, period_to_regime_to_V_arr=period_to_regime_to_V_arr, ) df = result.to_dataframe(use_labels=False) - retired_rows = df[df["regime"] == "retirement"] + retired_rows = df[df["regime_name"] == "retirement"] valid_retired_codes = {float(HealthRetirement.bad), float(HealthRetirement.good)} assert not retired_rows.empty, "Expected some rows in the retired regime" assert retired_rows["health"].isin(valid_retired_codes).all(), ( diff --git a/tests/test_regression_test.py b/tests/test_regression_test.py index a6a47168e..887f0edf2 100644 --- a/tests/test_regression_test.py +++ b/tests/test_regression_test.py @@ -59,7 +59,7 @@ def test_regression_test(): initial_conditions={ "wealth": jnp.array([5.0, 20, 40, 70]), "age": jnp.array([18.0, 18.0, 18.0, 18.0]), - "regime": jnp.array([RegimeId.working_life] * 4), + "regime_id": jnp.array([RegimeId.working_life] * 4), }, period_to_regime_to_V_arr=None, ).to_dataframe() @@ -101,7 +101,7 @@ def test_regression_precautionary_savings(): "age": jnp.full(n_subjects, 20.0), "wealth": jnp.full(n_subjects, 5.0), "income": jnp.full(n_subjects, 0.0), - "regime": jnp.zeros(n_subjects, dtype=jnp.int32), + "regime_id": jnp.zeros(n_subjects, dtype=jnp.int32), }, period_to_regime_to_V_arr=None, seed=12345, @@ -134,7 +134,7 @@ def test_regression_mortality(): initial_conditions={ "age": jnp.full(n_subjects, 40.0), "wealth": jnp.full(n_subjects, 100.0), - "regime": jnp.zeros(n_subjects, dtype=jnp.int32), + "regime_id": jnp.zeros(n_subjects, dtype=jnp.int32), }, period_to_regime_to_V_arr=None, seed=12345, @@ -176,7 +176,7 @@ def test_regression_mahler_yum(): params = {"alive": common_params} initial_conditions = { **initial_states, - "regime": jnp.full( + "regime_id": jnp.full( n_subjects, model.regime_names_to_ids["alive"], dtype=jnp.int32, @@ -283,7 +283,7 @@ def test_model_with_different_grid_types(grid_type: str): initial_conditions={ "wealth": jnp.array([5.0, 20, 40, 70]), "age": jnp.array([18.0, 18.0, 18.0, 18.0]), - "regime": jnp.array([RegimeId.working_life] * 4), + "regime_id": jnp.array([RegimeId.working_life] * 4), }, period_to_regime_to_V_arr=None, ) diff --git a/tests/test_runtime_params.py b/tests/test_runtime_params.py index 58fdfc27c..aba678941 100644 --- a/tests/test_runtime_params.py +++ b/tests/test_runtime_params.py @@ -300,7 +300,7 @@ def test_simulate_with_runtime_action_grid_no_nan() -> None: "alive": {"consumption": {"points": jnp.linspace(0.1, 5.0, 5)}}, } initial_conditions = { - "regime": jnp.array([RegimeId.alive, RegimeId.alive, RegimeId.alive]), + "regime_id": jnp.array([RegimeId.alive, RegimeId.alive, RegimeId.alive]), "age": jnp.array([0.0, 0.0, 0.0]), "wealth": jnp.array([2.0, 5.0, 9.0]), } diff --git a/tests/test_shock_grids.py b/tests/test_shock_grids.py index 84d1ca794..be478e049 100644 --- a/tests/test_shock_grids.py +++ b/tests/test_shock_grids.py @@ -39,7 +39,7 @@ def test_model_with_shock(distribution_type): "income": jnp.asarray([0.0, 0.0]), "wealth": jnp.asarray([1.0, 1.0]), "age": jnp.asarray([0.0, 0.0]), - "regime": jnp.array([RegimeId.alive] * 2), + "regime_id": jnp.array([RegimeId.alive] * 2), }, period_to_regime_to_V_arr=got_solve, seed=42, @@ -88,12 +88,12 @@ def test_model_with_cross_regime_shocks(distribution_type: str) -> None: "income": jnp.zeros(2), "wealth": jnp.ones(2), "age": jnp.zeros(2), - "regime": jnp.full(2, MultiRegimeId.work, dtype=jnp.int32), + "regime_id": jnp.full(2, MultiRegimeId.work, dtype=jnp.int32), }, period_to_regime_to_V_arr=None, seed=42, ).to_dataframe() - assert set(result["regime"]) >= {"work", "retire"} + assert set(result["regime_name"]) >= {"work", "retire"} _GRID_CLASSES_WITH_GH_KWARG = [ diff --git a/tests/test_single_feasible_action.py b/tests/test_single_feasible_action.py index 08b61ef7a..f35a58d07 100644 --- a/tests/test_single_feasible_action.py +++ b/tests/test_single_feasible_action.py @@ -232,7 +232,7 @@ def test_simulate_with_constrained_action_grid(wealth_lo, consumption_lo, label) initial_conditions = { "age": jnp.array([0.0, 0.0, 0.0]), "wealth": jnp.array([wealth_lo, 5.0, 20.0]), - "regime": jnp.array( + "regime_id": jnp.array( [RegimeId.alive, RegimeId.alive, RegimeId.alive], dtype=jnp.int32 ), } @@ -500,7 +500,7 @@ def next_regime(age, last_alive_age): initial_conditions = { "age": jnp.array([0.0, 0.0, 0.0]), "wealth": jnp.array([2.0, 5.0, 9.0]), - "regime": jnp.array( + "regime_id": jnp.array( [RuntimeRegimeId.alive, RuntimeRegimeId.alive, RuntimeRegimeId.alive], dtype=jnp.int32, ), @@ -524,7 +524,7 @@ def test_runtime_action_grid_passes_initial_conditions_validation(): initial_conditions = { "age": jnp.array([0.0, 0.0, 0.0]), "wealth": jnp.array([10.0, 15.0, 20.0]), - "regime": jnp.array( + "regime_id": jnp.array( [RegimeId.alive, RegimeId.alive, RegimeId.alive], dtype=jnp.int32 ), } diff --git a/tests/test_solution_on_toy_model_deterministic.py b/tests/test_solution_on_toy_model_deterministic.py index e163caee6..890c80ad3 100644 --- a/tests/test_solution_on_toy_model_deterministic.py +++ b/tests/test_solution_on_toy_model_deterministic.py @@ -156,7 +156,7 @@ def analytical_solve_deterministic(wealth_grid, params): def analytical_simulate_deterministic(initial_wealth, params): """Compute analytical simulation results in the same format as to_dataframe(). - Returns DataFrame with columns: period, subject_id, regime, value, wealth, + Returns DataFrame with columns: period, subject_id, regime_name, value, wealth, consumption, working. Sorted by (subject_id, period). Uses categorical dtypes for discrete variables to match to_dataframe() output. """ @@ -186,7 +186,7 @@ def analytical_simulate_deterministic(initial_wealth, params): [np.zeros(n_subjects), np.ones(n_subjects)] ).astype(int), "subject_id": np.tile(np.arange(n_subjects), 2), - "regime": pd.Categorical( + "regime_name": pd.Categorical( ["alive"] * (2 * n_subjects), categories=["alive", "dead"] ), "value": np.concatenate([V_arr_0, V_arr_1]), @@ -292,14 +292,14 @@ def test_deterministic_simulate(discount_factor, n_wealth_points): initial_conditions={ "wealth": jnp.array([0.25, 0.75, 1.25, 1.75]), "age": jnp.array([0.0, 0.0, 0.0, 0.0]), - "regime": jnp.array([RegimeId.alive] * 4), + "regime_id": jnp.array([RegimeId.alive] * 4), }, period_to_regime_to_V_arr=None, ) # Filter to alive regime only (dead regime has trivial values) got = ( result.to_dataframe() - .query('regime == "alive"') + .query('regime_name == "alive"') .drop(columns=["age"]) # Analytical function doesn't include age .reset_index(drop=True) ) diff --git a/tests/test_solution_on_toy_model_stochastic.py b/tests/test_solution_on_toy_model_stochastic.py index 5e0ffd945..d4a3ba58e 100644 --- a/tests/test_solution_on_toy_model_stochastic.py +++ b/tests/test_solution_on_toy_model_stochastic.py @@ -144,8 +144,8 @@ def analytical_solve_stochastic(wealth_grid, health_grid, params): def analytical_simulate_stochastic(initial_wealth, initial_health, health_1, params): """Compute analytical simulation results in the same format as to_dataframe(). - Returns DataFrame with columns: period, subject_id, regime, value, health, wealth, - consumption, working. Sorted by (subject_id, period). + Returns DataFrame with columns: period, subject_id, regime_name, value, + health, wealth, consumption, working. Sorted by (subject_id, period). Uses categorical dtypes for discrete variables to match to_dataframe() output. """ n_subjects = len(initial_wealth) @@ -179,7 +179,7 @@ def analytical_simulate_stochastic(initial_wealth, initial_health, health_1, par [np.zeros(n_subjects), np.ones(n_subjects)] ).astype(int), "subject_id": np.tile(np.arange(n_subjects), 2), - "regime": pd.Categorical( + "regime_name": pd.Categorical( ["alive"] * (2 * n_subjects), categories=["alive", "dead"] ), "value": np.concatenate([V_arr_0, V_arr_1]), @@ -296,7 +296,7 @@ def test_stochastic_simulate(discount_factor, n_wealth_points, probs_array): "wealth": jnp.array([0.25, 0.75, 1.25, 1.75, 2.0]), "health": jnp.array([0, 1, 0, 1, 1]), "age": jnp.array([0.0, 0.0, 0.0, 0.0, 0.0]), - "regime": jnp.array([RegimeId.alive] * 5), + "regime_id": jnp.array([RegimeId.alive] * 5), } result = model.simulate( params={"discount_factor": discount_factor, "alive": params_alive}, @@ -304,7 +304,7 @@ def test_stochastic_simulate(discount_factor, n_wealth_points, probs_array): period_to_regime_to_V_arr=None, ) # Filter to alive regime only (dead regime has trivial values) - got = result.to_dataframe().query('regime == "alive"').reset_index(drop=True) + got = result.to_dataframe().query('regime_name == "alive"').reset_index(drop=True) # Need to use health of second period from LCM output, to assure that the same # stochastic draws are used in the analytical simulation. diff --git a/tests/test_static_params.py b/tests/test_static_params.py index 4669145ca..7e980acf7 100644 --- a/tests/test_static_params.py +++ b/tests/test_static_params.py @@ -110,7 +110,7 @@ def test_simulate_with_fixed_params(): initial_conditions={ "wealth": jnp.array([5.0, 7.0]), "age": jnp.array([0.0, 0.0]), - "regime": jnp.array([RegimeId.alive] * 2), + "regime_id": jnp.array([RegimeId.alive] * 2), }, period_to_regime_to_V_arr=None, log_level="off", @@ -126,7 +126,7 @@ def test_simulate_with_fixed_params(): initial_conditions={ "wealth": jnp.array([5.0, 7.0]), "age": jnp.array([0.0, 0.0]), - "regime": jnp.array([RegimeId.alive] * 2), + "regime_id": jnp.array([RegimeId.alive] * 2), }, period_to_regime_to_V_arr=None, log_level="off", @@ -225,7 +225,7 @@ def test_all_params_fixed(): "wealth": jnp.array([50.0, 80.0]), "health": jnp.array([Health.bad, Health.good]), "age": jnp.array([60.0, 60.0]), - "regime": jnp.array([MarkovRegimeId.alive] * 2), + "regime_id": jnp.array([MarkovRegimeId.alive] * 2), } @@ -362,7 +362,7 @@ def test_series_fixed_param_with_derived_categoricals(): initial_conditions={ "wealth": jnp.array([3.0, 8.0]), "age": jnp.array([0.0, 0.0]), - "regime": jnp.array([RegimeId.alive] * 2), + "regime_id": jnp.array([RegimeId.alive] * 2), }, period_to_regime_to_V_arr=None, log_level="off", diff --git a/tests/test_stochastic.py b/tests/test_stochastic.py index 6b1014016..9bcd91f4b 100644 --- a/tests/test_stochastic.py +++ b/tests/test_stochastic.py @@ -45,11 +45,11 @@ def test_model_simulate_with_stochastic_model(): "partner": jnp.array([0, 0, 1, 0], dtype=jnp.int32), "wealth": jnp.array([10.0, 50.0, 30, 80.0]), "age": jnp.array([40.0, 40.0, 40.0, 40.0]), - "regime": jnp.array([RegimeId.working_life] * 4), + "regime_id": jnp.array([RegimeId.working_life] * 4), }, period_to_regime_to_V_arr=None, ) - df = result.to_dataframe().query('regime == "working_life"') + df = result.to_dataframe().query('regime_name == "working_life"') # Verify expected columns required_cols = {"period", "subject_id", "partner", "labor_supply"} @@ -194,7 +194,7 @@ def test_compare_deterministic_and_stochastic_results_value_function( "partner": jnp.array([0, 0, 0, 0], dtype=jnp.int32), "wealth": jnp.array([10.0, 50.0, 30, 80.0]), "age": jnp.array([40.0, 40.0, 40.0, 40.0]), - "regime": jnp.array([RegimeId.working_life] * 4), + "regime_id": jnp.array([RegimeId.working_life] * 4), } simulation_deterministic = model_deterministic.simulate( @@ -208,10 +208,10 @@ def test_compare_deterministic_and_stochastic_results_value_function( initial_conditions=initial_conditions, ) df_deterministic = simulation_deterministic.to_dataframe().query( - 'regime == "working_life"' + 'regime_name == "working_life"' ) df_stochastic = simulation_stochastic.to_dataframe().query( - 'regime == "working_life"' + 'regime_name == "working_life"' ) pd.testing.assert_frame_equal( df_deterministic.reset_index(drop=True), diff --git a/tests/test_validate_regime_transition_probs.py b/tests/test_validate_regime_transition_probs.py index 108a6450d..9f121344b 100644 --- a/tests/test_validate_regime_transition_probs.py +++ b/tests/test_validate_regime_transition_probs.py @@ -318,7 +318,7 @@ def test_simulate_raises_for_invalid_regime_transition_probs(): initial_conditions = { "age": jnp.array([40.0]), "wealth": jnp.array([10.0]), - "regime": jnp.array([MortalityRegimeId.working_life]), + "regime_id": jnp.array([MortalityRegimeId.working_life]), } with pytest.raises(InvalidRegimeTransitionProbabilitiesError): model.simulate( @@ -335,7 +335,7 @@ def test_simulate_with_solve_raises_for_invalid_regime_transition_probs(): initial_conditions = { "age": jnp.array([40.0]), "wealth": jnp.array([10.0]), - "regime": jnp.array([MortalityRegimeId.working_life]), + "regime_id": jnp.array([MortalityRegimeId.working_life]), } with pytest.raises(InvalidRegimeTransitionProbabilitiesError): model.simulate( diff --git a/tests/test_validation_scalar_actions.py b/tests/test_validation_scalar_actions.py index 91040597c..1f982f694 100644 --- a/tests/test_validation_scalar_actions.py +++ b/tests/test_validation_scalar_actions.py @@ -108,7 +108,7 @@ def test_validation_vmaps_over_action_combos(): initial_conditions={ "wealth": jnp.array([5.0, 7.0]), "age": jnp.array([0.0, 0.0]), - "regime": jnp.array([RegimeId.alive] * 2), + "regime_id": jnp.array([RegimeId.alive] * 2), }, period_to_regime_to_V_arr=None, log_level="off", From 97f4224dc94066064e7f5504a71d9db1eae7df7a Mon Sep 17 00:00:00 2001 From: Hans-Martin von Gaudecker Date: Fri, 15 May 2026 09:01:57 +0200 Subject: [PATCH 68/77] DataFrame initial-conditions column is `regime_name`, not `regime_id` The DataFrame side of `initial_conditions_from_dataframe` carries regime-label strings, mirroring how `DiscreteGrid` state columns already carry string category labels. The dict-side `"regime_id"` key continues to hold integer codes; only the DataFrame column name changes. Co-Authored-By: Claude Opus 4.7 --- docs/examples/tiny.md | 2 +- docs/user_guide/pandas_interop.md | 6 +-- docs/user_guide/solving_and_simulating.md | 4 +- src/lcm/model.py | 3 +- src/lcm/pandas_utils.py | 30 ++++++------ tests/test_pandas_utils.py | 59 ++++++++++++++++------- 6 files changed, 65 insertions(+), 39 deletions(-) diff --git a/docs/examples/tiny.md b/docs/examples/tiny.md index 300abf3ab..aa943d395 100644 --- a/docs/examples/tiny.md +++ b/docs/examples/tiny.md @@ -25,7 +25,7 @@ params = get_params() initial_df = pd.DataFrame( { - "regime_id": "working_life", + "regime_name": "working_life", "age": model.ages.values[0], "wealth": np.linspace(1, 20, 100), } diff --git a/docs/user_guide/pandas_interop.md b/docs/user_guide/pandas_interop.md index d6fd33201..1bca397ab 100644 --- a/docs/user_guide/pandas_interop.md +++ b/docs/user_guide/pandas_interop.md @@ -12,12 +12,12 @@ is DataFrame in, DataFrame out. ## Initial Conditions as a DataFrame Pass a pandas DataFrame directly to `simulate()` as `initial_conditions`. One row per -agent, one column per state variable, plus a `"regime_id"` column: +agent, one column per state variable, plus a `"regime_name"` column: ```python df = pd.DataFrame( { - "regime_id": ["working", "working", "retired"], + "regime_name": ["working", "working", "retired"], "wealth": [10.0, 50.0, 30.0], "health": ["good", "bad", "good"], "age": [25.0, 25.0, 25.0], @@ -31,7 +31,7 @@ result = model.simulate( ) ``` -- `"regime_id"` column is required. Use regime names as strings (e.g., `"working"`). +- `"regime_name"` column is required. Use regime names as strings (e.g., `"working"`). - Discrete states use string labels from the model's categorical classes (e.g., `"good"` instead of `0`). Labels are validated and mapped to integer codes automatically. - Continuous states pass through as-is. diff --git a/docs/user_guide/solving_and_simulating.md b/docs/user_guide/solving_and_simulating.md index 7f8a21e7c..e85d5aaac 100644 --- a/docs/user_guide/solving_and_simulating.md +++ b/docs/user_guide/solving_and_simulating.md @@ -74,7 +74,7 @@ import pandas as pd df = pd.DataFrame( { - "regime_id": ["working_life", "working_life", "retirement", "working_life"], + "regime_name": ["working_life", "working_life", "retirement", "working_life"], "age": [25.0, 25.0, 25.0, 25.0], "wealth": [1.0, 5.0, 10.0, 20.0], "health": ["good", "bad", "bad", "good"], # string labels, auto-converted @@ -240,7 +240,7 @@ params = { # 3. Prepare initial conditions as a DataFrame initial_df = pd.DataFrame({ - "regime_id": "working_life", + "regime_name": "working_life", "age": model.ages.values[0], "wealth": np.linspace(1, 50, 100), }) diff --git a/src/lcm/model.py b/src/lcm/model.py index afd2f5e48..daf355f87 100644 --- a/src/lcm/model.py +++ b/src/lcm/model.py @@ -416,7 +416,8 @@ def simulate( All arrays must have the same length (number of subjects). The `"regime_id"` entry must contain integer regime codes (from `model.regime_names_to_ids`). May also be a `pd.DataFrame` - with a `"regime_id"` column (auto-converted). + with a `"regime_name"` column carrying regime label strings + (auto-converted via `initial_conditions_from_dataframe`). period_to_regime_to_V_arr: Value function arrays from `solve()`. When `None`, the model is solved automatically before simulating. check_initial_conditions: Whether to validate initial conditions. diff --git a/src/lcm/pandas_utils.py b/src/lcm/pandas_utils.py index 2bf4ba458..0433acd83 100644 --- a/src/lcm/pandas_utils.py +++ b/src/lcm/pandas_utils.py @@ -58,7 +58,8 @@ def initial_conditions_from_dataframe( # noqa: C901 """Convert a DataFrame of initial conditions to LCM initial conditions format. Args: - df: DataFrame with columns for states and a "regime_id" column. + df: DataFrame with columns for states and a "regime_name" column + carrying the regime label strings. regimes: Mapping of regime names to user Regime instances. regime_names_to_ids: Immutable mapping from regime names to integer indices. @@ -66,16 +67,17 @@ def initial_conditions_from_dataframe( # noqa: C901 Returns: Dict mapping state names (plus `"regime_id"`) to JAX arrays. The `"regime_id"` entry contains integer codes derived from the - `"regime_id"` column via `regime_names_to_ids`. + `"regime_name"` column via `regime_names_to_ids`. Raises: - ValueError: If the DataFrame is empty, the "regime_id" column is missing, - contains invalid regime names, has unknown columns, is missing required - states, or categorical columns contain invalid labels. + ValueError: If the DataFrame is empty, the "regime_name" column is + missing, contains invalid regime names, has unknown columns, is + missing required states, or categorical columns contain invalid + labels. """ - if "regime_id" not in df.columns: - msg = "DataFrame must contain a 'regime_id' column." + if "regime_name" not in df.columns: + msg = "DataFrame must contain a 'regime_name' column." raise ValueError(msg) if len(df) == 0: @@ -84,24 +86,24 @@ def initial_conditions_from_dataframe( # noqa: C901 # Validate regime names valid_regimes = set(regime_names_to_ids.keys()) - invalid_regimes = set(df["regime_id"]) - valid_regimes + invalid_regimes = set(df["regime_name"]) - valid_regimes if invalid_regimes: msg = ( - f"Invalid regime names in 'regime_id' column: " + f"Invalid regime names in 'regime_name' column: " f"{sorted(invalid_regimes)}. " f"Valid regimes: {sorted(valid_regimes)}." ) raise ValueError(msg) - state_columns = {col for col in df.columns if col != "regime_id"} + state_columns = {col for col in df.columns if col != "regime_name"} _validate_state_columns( state_columns=state_columns, regimes=regimes, - initial_regimes=df["regime_id"].tolist(), + initial_regimes=df["regime_name"].tolist(), ) n_subjects = len(df) - state_cols = [col for col in df.columns if col != "regime_id"] + state_cols = [col for col in df.columns if col != "regime_name"] # Pre-allocate result arrays (NaN default surfaces bugs for missing states) result_arrays: dict[str, np.ndarray] = { @@ -110,7 +112,7 @@ def initial_conditions_from_dataframe( # noqa: C901 discrete_state_names: set[StateName] = set() # Process per regime group (vectorised .map() within each group) - for regime_name, group in df.groupby("regime_id"): + for regime_name, group in df.groupby("regime_name"): regime = regimes[str(regime_name)] idx = group.index discrete_grids = { @@ -157,7 +159,7 @@ def initial_conditions_from_dataframe( # noqa: C901 for col, arr in result_arrays.items() } initial_conditions["regime_id"] = jnp.array( - df["regime_id"].map(dict(regime_names_to_ids)).to_numpy(), + df["regime_name"].map(dict(regime_names_to_ids)).to_numpy(), dtype=jnp.int32, ) diff --git a/tests/test_pandas_utils.py b/tests/test_pandas_utils.py index 1dae18509..e406e8015 100644 --- a/tests/test_pandas_utils.py +++ b/tests/test_pandas_utils.py @@ -128,11 +128,34 @@ class HealthAlt: _build_discrete_grid_lookup(regimes) +def test_regime_name_column_maps_to_regime_id_codes(): + """`regime_name` column (strings) yields a `regime_id` dict entry of int codes.""" + model = get_basic_model() + df = pd.DataFrame( + { + "regime_name": ["working_life", "retirement"], + "health": ["bad", "good"], + "wealth": [10.0, 50.0], + "age": [25.0, 25.0], + } + ) + conditions = initial_conditions_from_dataframe( + df=df, + regimes=model.regimes, + regime_names_to_ids=model.regime_names_to_ids, + ) + assert jnp.array_equal( + conditions["regime_id"], + jnp.array([BasicRegimeId.working_life, BasicRegimeId.retirement]), + ) + assert "regime_name" not in conditions + + def test_continuous_states_and_age(): model = get_basic_model() df = pd.DataFrame( { - "regime_id": ["working_life", "working_life"], + "regime_name": ["working_life", "working_life"], "health": ["bad", "good"], "wealth": [10.0, 50.0], "age": [25.0, 35.0], @@ -155,7 +178,7 @@ def test_categorical_string_labels(): model = get_basic_model() df = pd.DataFrame( { - "regime_id": ["working_life", "retirement"], + "regime_name": ["working_life", "retirement"], "health": ["bad", "good"], "wealth": [10.0, 50.0], "age": [25.0, 25.0], @@ -178,7 +201,7 @@ def test_categorical_pd_categorical_column(): health_dtype = Health.to_categorical_dtype() # ty: ignore[unresolved-attribute] df = pd.DataFrame( { - "regime_id": ["working_life", "working_life"], + "regime_name": ["working_life", "working_life"], "health": pd.Categorical(["good", "bad"], dtype=health_dtype), "wealth": [10.0, 50.0], "age": [25.0, 25.0], @@ -196,7 +219,7 @@ def test_multi_regime(): model = get_basic_model() df = pd.DataFrame( { - "regime_id": ["working_life", "retirement", "working_life"], + "regime_name": ["working_life", "retirement", "working_life"], "health": ["good", "bad", "good"], "wealth": [10.0, 50.0, 30.0], "age": [25.0, 25.0, 25.0], @@ -223,7 +246,7 @@ def test_multi_regime(): def test_missing_regime_column_raises(): model = get_basic_model() df = pd.DataFrame({"wealth": [10.0]}) - with pytest.raises(ValueError, match="'regime_id' column"): + with pytest.raises(ValueError, match="'regime_name' column"): initial_conditions_from_dataframe( df=df, regimes=model.regimes, @@ -235,7 +258,7 @@ def test_invalid_regime_name_raises(): model = get_basic_model() df = pd.DataFrame( { - "regime_id": ["working_life", "nonexistent"], + "regime_name": ["working_life", "nonexistent"], "wealth": [10.0, 50.0], } ) @@ -251,7 +274,7 @@ def test_invalid_category_label_raises(): model = get_basic_model() df = pd.DataFrame( { - "regime_id": ["working_life"], + "regime_name": ["working_life"], "health": ["excellent"], "wealth": [10.0], "age": [25.0], @@ -268,7 +291,7 @@ def test_invalid_category_label_raises(): def test_empty_dataframe_raises(): model = get_basic_model() df = pd.DataFrame( - {"regime_id": pd.Series([], dtype=str), "wealth": pd.Series([], dtype=float)} + {"regime_name": pd.Series([], dtype=str), "wealth": pd.Series([], dtype=float)} ) with pytest.raises(ValueError, match="empty"): initial_conditions_from_dataframe( @@ -282,7 +305,7 @@ def test_unknown_column_raises(): model = get_basic_model() df = pd.DataFrame( { - "regime_id": ["working_life"], + "regime_name": ["working_life"], "health": ["bad"], "wealth": [10.0], "age": [25.0], @@ -301,7 +324,7 @@ def test_missing_state_column_raises(): model = get_basic_model() df = pd.DataFrame( { - "regime_id": ["working_life"], + "regime_name": ["working_life"], "age": [25.0], # missing "health" and "wealth" } @@ -319,7 +342,7 @@ def test_shock_state_columns_accepted(): model = get_shock_model(n_periods=4, distribution_type="uniform") df = pd.DataFrame( { - "regime_id": ["alive", "alive"], + "regime_name": ["alive", "alive"], "wealth": [2.0, 4.0], "health": ["bad", "good"], "income": [0.3, 0.7], @@ -341,7 +364,7 @@ def test_shock_state_columns_required(): model = get_shock_model(n_periods=4, distribution_type="uniform") df = pd.DataFrame( { - "regime_id": ["alive", "alive"], + "regime_name": ["alive", "alive"], "wealth": [2.0, 4.0], "health": ["bad", "good"], "age": [0.0, 0.0], @@ -383,7 +406,7 @@ def test_round_trip_with_discrete_model(): # DataFrame approach df = pd.DataFrame( { - "regime_id": ["working_life", "working_life"], + "regime_name": ["working_life", "working_life"], "wealth": ["low", "high"], "age": [50.0, 50.0], } @@ -472,7 +495,7 @@ def test_initial_conditions_heterogeneous_health_grids() -> None: model = _get_heterogeneous_health_model() df = pd.DataFrame( { - "regime_id": ["pre65", "pre65", "post65", "post65"], + "regime_name": ["pre65", "pre65", "post65", "post65"], "health": ["disabled", "good", "bad", "good"], "wealth": [10.0, 50.0, 30.0, 70.0], "age": [50.0, 50.0, 70.0, 70.0], @@ -552,7 +575,7 @@ def _utility_without_status(wealth: float) -> float: df = pd.DataFrame( { - "regime_id": ["with_status", "with_status", "without_status"], + "regime_name": ["with_status", "with_status", "without_status"], "wealth": [10.0, 20.0, 30.0], "status": ["low", "high", pd.NA], "age": [50.0, 51.0, 50.0], @@ -616,7 +639,7 @@ def _retiree_utility(wealth: float) -> float: df = pd.DataFrame( { - "regime_id": ["earner", "earner", "retiree"], + "regime_name": ["earner", "earner", "retiree"], "wealth": [10.0, 20.0, 30.0], "income": [0.3, 0.7, float("nan")], "age": [50.0, 51.0, 50.0], @@ -704,7 +727,7 @@ def test_heterogeneous_health_solve_simulate() -> None: model = _get_heterogeneous_health_model() df = pd.DataFrame( { - "regime_id": ["pre65", "pre65", "post65", "post65"], + "regime_name": ["pre65", "pre65", "post65", "post65"], "health": ["disabled", "good", "bad", "good"], "wealth": [10.0, 50.0, 30.0, 70.0], "age": [50.0, 50.0, 70.0, 70.0], @@ -743,7 +766,7 @@ def test_heterogeneous_health_simulate_use_labels_false() -> None: model = _get_heterogeneous_health_model() df = pd.DataFrame( { - "regime_id": ["pre65", "post65"], + "regime_name": ["pre65", "post65"], "health": ["disabled", "good"], "wealth": [10.0, 70.0], "age": [50.0, 70.0], From b46f8653e93280bec47e6f2c83d66bb06ff6e352 Mon Sep 17 00:00:00 2001 From: Hans-Martin von Gaudecker Date: Fri, 15 May 2026 09:09:26 +0200 Subject: [PATCH 69/77] Drop redundant FloatND | IntND rationale from action/state field docstrings The dtype-follows-grid rule is already visible in the type annotation. Co-Authored-By: Claude Opus 4.7 --- src/lcm/interfaces.py | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/src/lcm/interfaces.py b/src/lcm/interfaces.py index 2cb178bf8..dbb2e0745 100644 --- a/src/lcm/interfaces.py +++ b/src/lcm/interfaces.py @@ -468,18 +468,10 @@ class PeriodRegimeSimulationData: """Value function array for all subjects at this period.""" actions: MappingProxyType[ActionName, FloatND | IntND] - """Immutable mapping of action names to optimal action arrays for all subjects. - - Action arrays carry the dtype of their grid — discrete actions are int, - continuous actions are float — hence the `FloatND | IntND` value type. - """ + """Immutable mapping of action names to optimal action arrays for all subjects.""" states: MappingProxyType[StateName, FloatND | IntND] - """Immutable mapping of state names to state value arrays for all subjects. - - State arrays carry the dtype of their grid — discrete states are int, - continuous states are float — hence the `FloatND | IntND` value type. - """ + """Immutable mapping of state names to state value arrays for all subjects.""" in_regime: Bool1D """Boolean mask indicating which subjects are in this regime at this period.""" From 1376cc23074ed416a7ae942a3ff40632d0fc7601 Mon Sep 17 00:00:00 2001 From: Hans-Martin von Gaudecker Date: Fri, 15 May 2026 09:12:02 +0200 Subject: [PATCH 70/77] Tighten initial_conditions_from_dataframe return type The internal dict is already typed as `dict[StateName | Literal["regime_id"], FloatND | IntND]`; the signature was widening it to bare `str` keys. Co-Authored-By: Claude Opus 4.7 --- src/lcm/pandas_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lcm/pandas_utils.py b/src/lcm/pandas_utils.py index 0433acd83..4b14437e9 100644 --- a/src/lcm/pandas_utils.py +++ b/src/lcm/pandas_utils.py @@ -54,7 +54,7 @@ def initial_conditions_from_dataframe( # noqa: C901 df: pd.DataFrame, regimes: Mapping[RegimeName, Regime], regime_names_to_ids: RegimeNamesToIds, -) -> dict[str, FloatND | IntND]: +) -> dict[StateName | Literal["regime_id"], FloatND | IntND]: """Convert a DataFrame of initial conditions to LCM initial conditions format. Args: From 52b6a0ab5ebaa774af74fe8aa7d668de4715bea2 Mon Sep 17 00:00:00 2001 From: Hans-Martin von Gaudecker Date: Fri, 15 May 2026 09:39:48 +0200 Subject: [PATCH 71/77] Add UserInitialConditions / InitialConditions type aliases Mirrors the UserParams / Params split: - UserInitialConditions: boundary form accepted by `Model.simulate` and `canonicalize_initial_conditions` (Array | np.ndarray values). - InitialConditions: post-canonicalization form emitted by `canonicalize_initial_conditions` / `initial_conditions_from_dataframe` and consumed by `validate_initial_conditions`, `simulate`, and persistence (FloatND | IntND values). Both aliases are read-protocol (`Mapping[...]`) so callers can pass a plain dict; pylcm producers still wrap returns in `MappingProxyType` at runtime. Co-Authored-By: Claude Opus 4.7 --- src/lcm/model.py | 10 ++-------- src/lcm/pandas_utils.py | 11 ++++++----- src/lcm/persistence.py | 13 ++++++------- src/lcm/simulation/initial_conditions.py | 13 +++++++------ src/lcm/simulation/simulate.py | 5 ++--- src/lcm/typing.py | 15 ++++++++++++++- 6 files changed, 37 insertions(+), 30 deletions(-) diff --git a/src/lcm/model.py b/src/lcm/model.py index daf355f87..1b5de6dfa 100644 --- a/src/lcm/model.py +++ b/src/lcm/model.py @@ -6,12 +6,9 @@ from collections.abc import Mapping from pathlib import Path from types import MappingProxyType -from typing import Literal -import numpy as np import pandas as pd from beartype import beartype -from jax import Array from lcm._beartype_conf import MODEL_CONF, PARAMS_CONF from lcm.ages import AgeGrid @@ -52,8 +49,8 @@ PeriodToRegimeToVArr, RegimeName, RegimeNamesToIds, - StateName, UserFacingParamsTemplate, + UserInitialConditions, UserParams, ) from lcm.utils.containers import ( @@ -383,10 +380,7 @@ def simulate( self, *, params: UserParams, - initial_conditions: Mapping[ - StateName | Literal["regime_id"], Array | np.ndarray - ] - | pd.DataFrame, + initial_conditions: UserInitialConditions | pd.DataFrame, period_to_regime_to_V_arr: PeriodToRegimeToVArr | None, check_initial_conditions: bool = True, seed: int | None = None, diff --git a/src/lcm/pandas_utils.py b/src/lcm/pandas_utils.py index 4b14437e9..6ae42aa78 100644 --- a/src/lcm/pandas_utils.py +++ b/src/lcm/pandas_utils.py @@ -20,6 +20,7 @@ from lcm.typing import ( FloatND, FunctionName, + InitialConditions, InternalParams, IntND, RegimeName, @@ -54,7 +55,7 @@ def initial_conditions_from_dataframe( # noqa: C901 df: pd.DataFrame, regimes: Mapping[RegimeName, Regime], regime_names_to_ids: RegimeNamesToIds, -) -> dict[StateName | Literal["regime_id"], FloatND | IntND]: +) -> InitialConditions: """Convert a DataFrame of initial conditions to LCM initial conditions format. Args: @@ -65,9 +66,9 @@ def initial_conditions_from_dataframe( # noqa: C901 indices. Returns: - Dict mapping state names (plus `"regime_id"`) to JAX arrays. The - `"regime_id"` entry contains integer codes derived from the - `"regime_name"` column via `regime_names_to_ids`. + Immutable mapping of state names (plus `"regime_id"`) to JAX + arrays. The `"regime_id"` entry contains integer codes derived + from the `"regime_name"` column via `regime_names_to_ids`. Raises: ValueError: If the DataFrame is empty, the "regime_name" column is @@ -163,7 +164,7 @@ def initial_conditions_from_dataframe( # noqa: C901 dtype=jnp.int32, ) - return initial_conditions + return MappingProxyType(initial_conditions) def _map_discrete_labels( diff --git a/src/lcm/persistence.py b/src/lcm/persistence.py index 005e7e859..69fa0a2a8 100644 --- a/src/lcm/persistence.py +++ b/src/lcm/persistence.py @@ -9,23 +9,22 @@ import shutil import tempfile import textwrap -from collections.abc import Mapping, Sequence +from collections.abc import Sequence from dataclasses import dataclass from pathlib import Path from types import MappingProxyType -from typing import TYPE_CHECKING, Any, Literal +from typing import TYPE_CHECKING, Any import cloudpickle import h5py import jax.numpy as jnp import numpy as np -from jax import Array from lcm.typing import ( FloatND, + InitialConditions, PeriodToRegimeToVArr, RegimeName, - StateName, UserParams, ) @@ -75,8 +74,8 @@ class SimulateSnapshot: params: UserParams | None """User parameters passed to simulate.""" - initial_conditions: Mapping[StateName | Literal["regime_id"], Array] | None - """Mapping of state names and "regime_id" to arrays.""" + initial_conditions: InitialConditions | None + """Immutable mapping of state names and `"regime_id"` to canonical-dtype arrays.""" period_to_regime_to_V_arr: PeriodToRegimeToVArr | None """Immutable mapping of periods to regime value function arrays.""" @@ -207,7 +206,7 @@ def save_simulate_snapshot( *, model: Model, params: UserParams, - initial_conditions: Mapping[StateName | Literal["regime_id"], Array], + initial_conditions: InitialConditions, period_to_regime_to_V_arr: PeriodToRegimeToVArr, result: SimulationResult, log_path: Path, diff --git a/src/lcm/simulation/initial_conditions.py b/src/lcm/simulation/initial_conditions.py index 2b7b09cb7..b2a0579fa 100644 --- a/src/lcm/simulation/initial_conditions.py +++ b/src/lcm/simulation/initial_conditions.py @@ -7,12 +7,11 @@ from collections.abc import Callable, Mapping, Sequence from types import MappingProxyType -from typing import Literal, NoReturn, cast +from typing import NoReturn, cast import jax import numpy as np import pandas as pd -from jax import Array from jax import numpy as jnp from lcm.ages import PSEUDO_STATE_NAMES, AgeGrid @@ -34,6 +33,7 @@ BoolND, FlatRegimeParams, FloatND, + InitialConditions, Int1D, InternalParams, IntND, @@ -42,6 +42,7 @@ RegimeNamesToIds, StateName, StatesPerRegime, + UserInitialConditions, ) from lcm.utils.containers import invert_regime_ids from lcm.utils.functools import get_union_of_args @@ -54,9 +55,9 @@ def canonicalize_initial_conditions( *, - initial_conditions: Mapping[StateName | Literal["regime_id"], Array | np.ndarray], + initial_conditions: UserInitialConditions, internal_regimes: MappingProxyType[RegimeName, InternalRegime], -) -> dict[str, FloatND | IntND]: +) -> InitialConditions: """Cast every initial-conditions array to its canonical pylcm dtype. This is pylcm's simulation input boundary: `"regime_id"` and discrete @@ -97,7 +98,7 @@ def canonicalize_initial_conditions( canonical[name] = safe_to_int_dtype(value, name=name) else: canonical[name] = safe_to_float_dtype(value, name=name) - return canonical + return MappingProxyType(canonical) def build_initial_states( @@ -179,7 +180,7 @@ def build_initial_states( def validate_initial_conditions( *, - initial_conditions: Mapping[StateName | Literal["regime_id"], FloatND | IntND], + initial_conditions: InitialConditions, internal_regimes: MappingProxyType[RegimeName, InternalRegime], regime_names_to_ids: RegimeNamesToIds, internal_params: InternalParams, diff --git a/src/lcm/simulation/simulate.py b/src/lcm/simulation/simulate.py index 6cf0561e2..efcd61f3d 100644 --- a/src/lcm/simulation/simulate.py +++ b/src/lcm/simulation/simulate.py @@ -2,7 +2,6 @@ import time from collections.abc import Mapping from types import MappingProxyType -from typing import Literal import jax import jax.numpy as jnp @@ -28,6 +27,7 @@ from lcm.typing import ( Float1D, FloatND, + InitialConditions, Int1D, InternalParams, IntND, @@ -36,7 +36,6 @@ RegimeNamesToIds, ScalarFloat, ScalarInt, - StateName, StateOrActionName, StatesPerRegime, ) @@ -54,7 +53,7 @@ def simulate( *, internal_params: InternalParams, - initial_conditions: Mapping[StateName | Literal["regime_id"], FloatND | IntND], + initial_conditions: InitialConditions, internal_regimes: MappingProxyType[RegimeName, InternalRegime], regime_names_to_ids: RegimeNamesToIds, logger: logging.Logger, diff --git a/src/lcm/typing.py b/src/lcm/typing.py index 16cd28e22..f9604fbb3 100644 --- a/src/lcm/typing.py +++ b/src/lcm/typing.py @@ -1,6 +1,6 @@ from collections.abc import Mapping from types import MappingProxyType -from typing import Any, Protocol, runtime_checkable +from typing import Any, Literal, Protocol, runtime_checkable import numpy as np import pandas as pd @@ -54,6 +54,19 @@ type RegimeStates = MappingProxyType[StateName, FloatND | IntND] type StatesPerRegime = MappingProxyType[RegimeName, RegimeStates] +# Boundary form of initial conditions — accepted by `Model.simulate` and +# canonicalized by `canonicalize_initial_conditions`. +type UserInitialConditions = Mapping[ + StateName | Literal["regime_id"], Array | np.ndarray +] + +# Post-canonicalization form — emitted by `canonicalize_initial_conditions` +# and consumed by `validate_initial_conditions`, `simulate`, and persistence. +# Read-protocol typing so callers don't have to wrap a dict in +# `MappingProxyType` before passing it in; pylcm producers still wrap on +# the way out to preserve immutability at runtime. +type InitialConditions = Mapping[StateName | Literal["regime_id"], FloatND | IntND] + # Boundary leaf type — accepted by `Model.__init__` / `Model.solve` / # `Model.simulate` and canonicalized by `cast_params_to_canonical_dtypes`. From e54b802c4d99fb5f2096cd11ab732bf05092d58a Mon Sep 17 00:00:00 2001 From: Hans-Martin von Gaudecker Date: Fri, 15 May 2026 10:35:23 +0200 Subject: [PATCH 72/77] Post-review cleanup sweep - Pin `active_periods` array to `int32` in `_validate_initial_regime_active` (matches the int32-period invariant established for sibling arrays). - Hoist `lcm.params` imports in `lcm.utils.containers` to module top; the dual lazy imports inside `mapping_leaf` / `sequence_leaf` keep the cycle resolved. - Drop the redundant class-level `@beartype` on `LogSpacedGrid` (the `__init__`-level decorator already covers it; matches `LinSpacedGrid`). - Delete the dead `ValueError` branch in `vmap_1d` (unreachable under the package claw's `Literal` check); the test asserts the claw violation directly. - Replace ad-hoc `_persistence.Model = Model` injection in `lcm.__init__` with `_bind_forward_refs` helpers in `lcm.persistence` and `lcm.variables`; add a regression test that the bindings survive a fresh import. - Tighten `test_beartype_claw.py` test docstrings to behaviour sentences. - Reconcile `UserMappingLeaf` "frozen" docstring with `__hash__ = None`. - Add `UserAge = int | Fraction` alias; sweep `AgeGrid.__init__`, `_validate_age_grid`, `_validate_range`, `_validate_values` to use it. - Trim Claudish prose in the `lcm.__init__` claw block and the `_emit_post_loop_diagnostics` / `_raise_first_nan_row` docstrings. Co-Authored-By: Claude Opus 4.7 --- src/lcm/__init__.py | 43 +++++-------- src/lcm/ages.py | 28 ++++---- src/lcm/grids/continuous.py | 1 - src/lcm/params/mapping_leaf.py | 6 +- src/lcm/persistence.py | 19 ++++++ src/lcm/simulation/initial_conditions.py | 5 +- src/lcm/solution/solve_brute.py | 11 ++-- src/lcm/typing.py | 4 ++ src/lcm/utils/containers.py | 6 +- src/lcm/utils/dispatchers.py | 7 +- src/lcm/variables.py | 13 ++++ tests/test_beartype_claw.py | 82 +++--------------------- tests/test_persistence.py | 17 +++++ 13 files changed, 108 insertions(+), 134 deletions(-) diff --git a/src/lcm/__init__.py b/src/lcm/__init__.py index 85645b71f..4cce08bf8 100644 --- a/src/lcm/__init__.py +++ b/src/lcm/__init__.py @@ -32,35 +32,21 @@ with contextlib.suppress(ImportError): import pdbp # noqa: F401 -# Install beartype's AST-rewriting claw on the entire `lcm` package -# before any of its submodules is imported. The claw transforms each -# matching module's AST at first import to insert runtime type checks; -# if it isn't registered before the import happens, the affected module -# loads uninstrumented and `sys.modules` caches the unchecked version -# for the rest of the process. The claw uses `INTERNAL_CONF`, which -# surfaces violations as beartype's own `BeartypeCallHintViolation`. -# User-facing constructors (`Model`, `Regime`, `MarkovTransition`, -# every grid and shock class, `@categorical`) carry their own explicit -# `@beartype(conf=...)` decorators that map violations to the relevant -# project exception (`ModelInitializationError`, -# `RegimeInitializationError`, `GridInitializationError`, etc.); those -# decorators stack on top of the claw and win at the user boundary. -# See `lcm._beartype_conf`. +# Register beartype's package claw before any submodule import so every +# `lcm.*` module loads with runtime type checks installed via +# `INTERNAL_CONF`. User-facing constructors stack an explicit +# `@beartype(conf=...)` decorator that maps violations to the relevant +# project exception (see `lcm._beartype_conf`). from beartype.claw import beartype_package from lcm._beartype_conf import INTERNAL_CONF beartype_package("lcm", conf=INTERNAL_CONF) -# Several modules annotate signatures with forward references that are -# `TYPE_CHECKING`-only at definition time (to break import cycles). The -# package claw rewrites those annotations into runtime forward references -# resolved against the module's globals at call time. Inject the resolved -# names here, after every involved module is loaded, so beartype can -# resolve them. -from lcm import persistence as _persistence # noqa: E402 +# Modules with TYPE_CHECKING-only forward references expose a +# `_bind_forward_refs` helper; calling it here makes the claw's +# rewritten string annotations resolve at call time. from lcm import shocks # noqa: E402 -from lcm import variables as _variables # noqa: E402 from lcm._version import __version__ # noqa: E402 from lcm.ages import AgeGrid # noqa: E402 from lcm.grids import ( # noqa: E402 @@ -82,15 +68,20 @@ load_solution, save_solution, ) +from lcm.persistence import ( # noqa: E402 + _bind_forward_refs as _bind_persistence_forward_refs, +) from lcm.regime import MarkovTransition, Regime # noqa: E402 from lcm.simulation.result import SimulationResult # noqa: E402 from lcm.utils.containers import invert_regime_ids # noqa: E402 from lcm.utils.error_handling import validate_transition_probs # noqa: E402 +from lcm.variables import ( # noqa: E402 + _bind_forward_refs as _bind_variables_forward_refs, +) -_variables.Regime = Regime -_persistence.Model = Model -_persistence.SimulationResult = SimulationResult -del _persistence, _variables +_bind_variables_forward_refs(regime_cls=Regime) +_bind_persistence_forward_refs(model_cls=Model, simulation_result_cls=SimulationResult) +del _bind_persistence_forward_refs, _bind_variables_forward_refs # Register MappingProxyType as a JAX pytree so it can be used in JIT-traced functions. # This allows regime transition probabilities to use immutable mappings. diff --git a/src/lcm/ages.py b/src/lcm/ages.py index 0fc41141a..b7736951f 100644 --- a/src/lcm/ages.py +++ b/src/lcm/ages.py @@ -12,7 +12,7 @@ from lcm._beartype_conf import GRID_CONF from lcm.exceptions import GridInitializationError, format_messages -from lcm.typing import Float1D, Int1D +from lcm.typing import Float1D, Int1D, UserAge STEP_UNITS: MappingProxyType[str, Fraction] = MappingProxyType( { @@ -40,8 +40,8 @@ class AgeGrid: def __init__( self, *, - start: int | Fraction, - stop: int | Fraction, + start: UserAge, + stop: UserAge, step: str, ) -> None: ... @@ -49,17 +49,17 @@ def __init__( def __init__( self, *, - exact_values: Iterable[int | Fraction], + exact_values: Iterable[UserAge], ) -> None: ... @beartype(conf=GRID_CONF) def __init__( self, *, - start: int | Fraction | None = None, - stop: int | Fraction | None = None, + start: UserAge | None = None, + stop: UserAge | None = None, step: str | None = None, - exact_values: Iterable[int | Fraction] | None = None, + exact_values: Iterable[UserAge] | None = None, ) -> None: _validate_age_grid(start=start, stop=stop, step=step, exact_values=exact_values) @@ -100,7 +100,7 @@ def values(self) -> Int1D | Float1D: return self._values @property - def exact_values(self) -> tuple[int | Fraction, ...]: + def exact_values(self) -> tuple[UserAge, ...]: """Exact ages; indexed by period. Could be: @@ -224,10 +224,10 @@ def _is_integer_valued(value: int | Fraction) -> bool: def _validate_age_grid( *, - start: int | Fraction | None, - stop: int | Fraction | None, + start: UserAge | None, + stop: UserAge | None, step: str | None, - exact_values: Iterable[int | Fraction] | None, + exact_values: Iterable[UserAge] | None, ) -> None: error_messages: list[str] = [] @@ -252,9 +252,7 @@ def _validate_age_grid( raise GridInitializationError(format_messages(error_messages)) -def _validate_range( - *, start: int | Fraction, stop: int | Fraction, step: str -) -> list[str]: +def _validate_range(*, start: UserAge, stop: UserAge, step: str) -> list[str]: errors: list[str] = [] if start >= stop: @@ -285,7 +283,7 @@ def _validate_range( return errors -def _validate_values(values: Iterable[int | Fraction]) -> list[str]: +def _validate_values(values: Iterable[UserAge]) -> list[str]: errors: list[str] = [] try: diff --git a/src/lcm/grids/continuous.py b/src/lcm/grids/continuous.py index 0febf27f9..c9ad5e725 100644 --- a/src/lcm/grids/continuous.py +++ b/src/lcm/grids/continuous.py @@ -127,7 +127,6 @@ def get_coordinate(self, value: FloatND) -> FloatND: ) -@beartype(conf=GRID_CONF) class LogSpacedGrid(UniformContinuousGrid): """A logarithmically spaced grid of continuous values. diff --git a/src/lcm/params/mapping_leaf.py b/src/lcm/params/mapping_leaf.py index 148b91512..fc68be584 100644 --- a/src/lcm/params/mapping_leaf.py +++ b/src/lcm/params/mapping_leaf.py @@ -18,8 +18,10 @@ class UserMappingLeaf: `UserSequenceLeaf`). Prevents `flatten_regime_namespace` from recursing into contents while - allowing JAX to trace through array values. Data is frozen to - immutable containers on construction. + allowing JAX to trace through array values. The `data` attribute is + deep-converted to immutable containers (`MappingProxyType`, `tuple`, + `frozenset`) on construction; instances themselves are not hashable + (`__hash__ = None`), since `MappingProxyType` isn't. The constructor accepts `Mapping[str, Any]` at runtime so beartype's O(n) per-leaf check doesn't fire on user-supplied scalars or arrays; diff --git a/src/lcm/persistence.py b/src/lcm/persistence.py index 69fa0a2a8..e0cefdc6d 100644 --- a/src/lcm/persistence.py +++ b/src/lcm/persistence.py @@ -44,6 +44,25 @@ _ModelOrNone = Any _SimulationResultOrNone = Any + +def _bind_forward_refs( + *, + model_cls: type, + simulation_result_cls: type, +) -> None: + """Bind `Model` and `SimulationResult` into this module's globals. + + The package claw rewrites string annotations in `save_*_snapshot` into + runtime forward references resolved against this module's globals. + `lcm.__init__` calls this helper once both classes are loaded so the + refs resolve at call time without depending on an ad-hoc assignment + from outside the module. + """ + global Model, SimulationResult # noqa: PLW0603 + Model = model_cls # ty: ignore[invalid-assignment] + SimulationResult = simulation_result_cls # ty: ignore[invalid-assignment] + + logger = logging.getLogger(__name__) diff --git a/src/lcm/simulation/initial_conditions.py b/src/lcm/simulation/initial_conditions.py index b2a0579fa..6b97e2472 100644 --- a/src/lcm/simulation/initial_conditions.py +++ b/src/lcm/simulation/initial_conditions.py @@ -435,7 +435,10 @@ def _collect_structural_errors( active_mask = jnp.ones(regime_id_arr.size, dtype=bool) for regime_name, internal_regime in internal_regimes.items(): in_regime = regime_id_arr == regime_names_to_ids[regime_name] - period_active = jnp.isin(periods, jnp.array(internal_regime.active_periods)) + period_active = jnp.isin( + periods, + jnp.array(internal_regime.active_periods, dtype=jnp.int32), + ) active_mask = active_mask & (~in_regime | period_active) if not jnp.all(active_mask): diff --git a/src/lcm/solution/solve_brute.py b/src/lcm/solution/solve_brute.py index 441023d52..4d88c6a05 100644 --- a/src/lcm/solution/solve_brute.py +++ b/src/lcm/solution/solve_brute.py @@ -479,10 +479,9 @@ def _emit_post_loop_diagnostics( ) -> None: """Flush async diagnostics: raise on NaN, warn on Inf, log debug stats. - The two `.item()` calls on the running scalars decide whether to - enter the per-row failure-path localisation. On a healthy solve - neither inner walk runs and no per-row scalar is materialised, so - device memory stays bounded by the V templates currently in flight. + Only enters the per-row failure path when the running NaN or Inf + accumulators are set, so a healthy solve incurs no host-side scalar + materialisation here. """ if running_any_nan.item(): _raise_first_nan_row( @@ -516,9 +515,7 @@ def _raise_first_nan_row( ) -> None: """Find the first NaN-bearing (regime, period) and raise. - Only invoked on the failure path (`running_any_nan` was True). - Materialises one host-side bool per row until the first hit; on - a healthy solve this function is never called. + Failure-path only — walks rows until the first NaN hit. """ for row in diagnostic_rows: V_arr = solution[row.period][row.regime_name] diff --git a/src/lcm/typing.py b/src/lcm/typing.py index f9604fbb3..3e3c8202b 100644 --- a/src/lcm/typing.py +++ b/src/lcm/typing.py @@ -1,4 +1,5 @@ from collections.abc import Mapping +from fractions import Fraction from types import MappingProxyType from typing import Any, Literal, Protocol, runtime_checkable @@ -35,6 +36,9 @@ type Period = ScalarInt type Age = ScalarInt | ScalarFloat +# Boundary form accepted by `AgeGrid.__init__` for `start`, `stop`, and +# `exact_values` entries — converted to canonical JAX scalars internally. +type UserAge = int | Fraction type RegimeName = str type StateName = str type ActionName = str diff --git a/src/lcm/utils/containers.py b/src/lcm/utils/containers.py index fcfe5564b..346ebe88d 100644 --- a/src/lcm/utils/containers.py +++ b/src/lcm/utils/containers.py @@ -5,6 +5,8 @@ from types import MappingProxyType from typing import Any, TypeVar, cast +from lcm.params import UserMappingLeaf, UserSequenceLeaf + T = TypeVar("T") @@ -121,8 +123,6 @@ def first_non_none(*args: T | None) -> T: def _make_immutable(value: Any) -> Any: # noqa: ANN401 """Recursively convert a value to its immutable equivalent.""" - from lcm.params import UserMappingLeaf, UserSequenceLeaf # noqa: PLC0415 - if isinstance(value, (UserMappingLeaf, UserSequenceLeaf)): return value # already immutable by construction if isinstance(value, (MappingProxyType, tuple, frozenset)): @@ -138,8 +138,6 @@ def _make_immutable(value: Any) -> Any: # noqa: ANN401 def _make_mutable(value: Any) -> Any: # noqa: ANN401, PLR0911 """Recursively convert a value to its mutable equivalent.""" - from lcm.params import UserMappingLeaf, UserSequenceLeaf # noqa: PLC0415 - if isinstance(value, UserMappingLeaf): return {k: _make_mutable(v) for k, v in value.data.items()} if isinstance(value, UserSequenceLeaf): diff --git a/src/lcm/utils/dispatchers.py b/src/lcm/utils/dispatchers.py index 0d55a0ac5..0bb018a4c 100644 --- a/src/lcm/utils/dispatchers.py +++ b/src/lcm/utils/dispatchers.py @@ -151,13 +151,8 @@ def vmap_1d( if callable_with == "only_kwargs": out = allow_only_kwargs(vmapped, enforce=False) - elif callable_with == "only_args": - out = vmapped else: - raise ValueError( - f"Invalid callable_with option: {callable_with}. Possible options are " - "('only_args', 'only_kwargs')", - ) + out = vmapped return cast("FunctionWithArrayReturn", out) diff --git a/src/lcm/variables.py b/src/lcm/variables.py index 64eec92cb..81c1fbbbe 100644 --- a/src/lcm/variables.py +++ b/src/lcm/variables.py @@ -27,6 +27,19 @@ from lcm.regime import Regime +def _bind_forward_refs(*, regime_cls: type) -> None: + """Bind `Regime` into this module's globals. + + The package claw rewrites string annotations on `from_regime`, + `get_grids`, and similar helpers into runtime forward references + resolved against this module's globals. `lcm.__init__` calls this + helper once `Regime` is loaded so the refs resolve at call time + without depending on an ad-hoc assignment from outside the module. + """ + global Regime # noqa: PLW0603 + Regime = regime_cls # ty: ignore[invalid-assignment] + + @dataclasses.dataclass(frozen=True) class VariableInfo: """Kind/topology/shock tags for one state or action variable.""" diff --git a/tests/test_beartype_claw.py b/tests/test_beartype_claw.py index fb7333f22..6aae29229 100644 --- a/tests/test_beartype_claw.py +++ b/tests/test_beartype_claw.py @@ -40,12 +40,7 @@ def test_claw_checks_lcm_simulation() -> None: - """An ill-typed argument to an `lcm.simulation` function is rejected. - - `_compute_starting_periods` annotates `initial_ages` as `Float1D` (a JAX - array). A NumPy array would otherwise be accepted by `jnp.searchsorted` - and the call would return cleanly; the claw turns it into a violation. - """ + """Type-violating arguments to internal `lcm.simulation` helpers raise.""" with pytest.raises(BeartypeCallHintViolation): _compute_starting_periods( initial_ages=np.array([25.0]), # ty: ignore[invalid-argument-type] @@ -54,12 +49,7 @@ def test_claw_checks_lcm_simulation() -> None: def test_claw_checks_lcm_solution() -> None: - """An ill-typed argument to an `lcm.solution` function is rejected. - - `_log_per_period_stats` annotates `logger` as `logging.Logger`. With an - empty `diagnostic_rows` the body never runs, so an un-instrumented call - would return `None`; the claw turns the bad `logger` into a violation. - """ + """Type-violating arguments to internal `lcm.solution` helpers raise.""" with pytest.raises(BeartypeCallHintViolation): _log_per_period_stats( logger="not a logger", # ty: ignore[invalid-argument-type] @@ -71,13 +61,7 @@ def test_claw_checks_lcm_solution() -> None: def test_claw_checks_lcm_utils_error_handling() -> None: - """An ill-typed argument to an `lcm.utils.error_handling` function is rejected. - - `validate_regime_transition_probs` annotates `regime_transition_probs` as - `MappingProxyType[RegimeName, FloatND]`. A plain `dict` whose values are JAX - arrays would be accepted by `jnp.stack(list(...))` and the body would run to - completion; the claw turns the wrong container type into a violation. - """ + """Type-violating arguments to `lcm.utils.error_handling` helpers raise.""" with pytest.raises(BeartypeCallHintViolation): validate_regime_transition_probs( regime_transition_probs={"working": jnp.array([1.0])}, # ty: ignore[invalid-argument-type] @@ -89,14 +73,7 @@ def test_claw_checks_lcm_utils_error_handling() -> None: def test_claw_checks_lcm_state_action_space() -> None: - """An ill-typed argument to an `lcm.state_action_space` function is rejected. - - `_validate_all_states_present` annotates `provided_states` as a - `dict[StateName, FloatND | IntND]`. An empty `str` `provided_states` - yields an empty `set(provided_states)`, which equals an empty - `required_state_names`, so an un-instrumented call would return cleanly; - the claw turns the wrong container type into a violation. - """ + """Type-violating arguments to `lcm.state_action_space` helpers raise.""" with pytest.raises(BeartypeCallHintViolation): _validate_all_states_present( provided_states="", # ty: ignore[invalid-argument-type] @@ -105,13 +82,7 @@ def test_claw_checks_lcm_state_action_space() -> None: def test_claw_checks_lcm_interfaces() -> None: - """An ill-typed argument to an `lcm.interfaces` function is rejected. - - `_build_regime_sharding` annotates `n_devices` as `int`. With an empty - `grids` mapping the function returns `None` before `n_devices` is ever - used, so an un-instrumented call would return cleanly; the claw turns the - wrong `n_devices` type into a violation. - """ + """Type-violating arguments to `lcm.interfaces` helpers raise.""" with pytest.raises(BeartypeCallHintViolation): _build_regime_sharding( grids=MappingProxyType({}), @@ -120,13 +91,7 @@ def test_claw_checks_lcm_interfaces() -> None: def test_claw_checks_lcm_regime() -> None: - """An ill-typed argument to an `lcm.regime` function is rejected. - - `_default_H` annotates `utility` as `FloatND` (a JAX array). A NumPy array - would otherwise flow through `utility + discount_factor * E_next_V` and the - call would return a NumPy array cleanly; the claw turns the wrong array - library into a violation. - """ + """Type-violating arguments to `lcm.regime` helpers raise.""" with pytest.raises(BeartypeCallHintViolation): _default_H( utility=np.array([1.0]), # ty: ignore[invalid-argument-type] @@ -136,14 +101,7 @@ def test_claw_checks_lcm_regime() -> None: def test_regime_with_bad_arg_raises_project_exception() -> None: - """A bad `Regime` argument still raises `RegimeInitializationError`. - - The package claw instruments `lcm.regime`'s private helpers with - `INTERNAL_CONF`, but the explicit `@beartype(conf=REGIME_CONF)` decorator - on the `Regime` constructor still wins: a type violation at construction - surfaces as the project's `RegimeInitializationError`, not beartype's own - `BeartypeCallHintViolation`. - """ + """A bad `Regime` argument surfaces as `RegimeInitializationError`.""" with pytest.raises(RegimeInitializationError): Regime( transition=None, @@ -153,13 +111,7 @@ def test_regime_with_bad_arg_raises_project_exception() -> None: def test_claw_checks_lcm_model() -> None: - """An ill-typed argument to an `lcm.model` function is rejected. - - `_validate_log_args` annotates `log_path` as `str | Path | None`. With - `log_level="progress"` the function returns before `log_path` is ever - inspected, so an un-instrumented call would return cleanly; the claw turns - the wrong `log_path` type into a violation. - """ + """Type-violating arguments to internal `lcm.model` helpers raise.""" with pytest.raises(BeartypeCallHintViolation): _validate_log_args( log_level="progress", @@ -168,14 +120,7 @@ def test_claw_checks_lcm_model() -> None: def test_model_with_bad_arg_raises_project_exception() -> None: - """A bad `Model` argument still raises `ModelInitializationError`. - - The package claw instruments `lcm.model`'s private helpers with - `INTERNAL_CONF`, but the explicit `@beartype(conf=MODEL_CONF)` decorator on - `Model.__init__` still wins: a type violation at construction surfaces as - the project's `ModelInitializationError`, not beartype's own - `BeartypeCallHintViolation`. - """ + """A bad `Model` argument surfaces as `ModelInitializationError`.""" with pytest.raises(ModelInitializationError): Model( ages=AgeGrid(start=25, stop=75, step="Y"), @@ -185,14 +130,7 @@ def test_model_with_bad_arg_raises_project_exception() -> None: def test_linspaced_grid_with_bad_arg_raises_project_exception() -> None: - """A bad `LinSpacedGrid` argument still raises `GridInitializationError`. - - The package claw instruments `lcm.grids`'s private helpers with - `INTERNAL_CONF`, but the explicit `@beartype(conf=GRID_CONF)` decorator on - `LinSpacedGrid.__init__` still wins: a type violation at construction - surfaces as the project's `GridInitializationError`, not beartype's own - `BeartypeCallHintViolation`. - """ + """A bad `LinSpacedGrid` argument surfaces as `GridInitializationError`.""" with pytest.raises(GridInitializationError): LinSpacedGrid( start="not a number", # ty: ignore[invalid-argument-type] diff --git a/tests/test_persistence.py b/tests/test_persistence.py index 09c917c50..917f29b49 100644 --- a/tests/test_persistence.py +++ b/tests/test_persistence.py @@ -5,6 +5,7 @@ import jax.numpy as jnp import pytest +import lcm from lcm import ( AgeGrid, LinSpacedGrid, @@ -15,10 +16,26 @@ categorical, load_snapshot, ) +from lcm import persistence as _persistence +from lcm import variables as _variables from lcm.persistence import _get_platform, load_solution, save_solution +from lcm.simulation.result import SimulationResult as _PublicSimulationResult from lcm.typing import ContinuousAction, ContinuousState, FloatND, ScalarInt +def test_forward_refs_bound_after_import() -> None: + """`Model` and `SimulationResult` are present in `lcm.persistence`'s globals. + + The package claw rewrites their string annotations on `save_simulate_snapshot` + into runtime forward references resolved against this module's globals at + call time. Missing the binding leaves those calls failing with + `BeartypeCallHintForwardRefException`. + """ + assert _persistence.Model is lcm.Model + assert _persistence.SimulationResult is _PublicSimulationResult + assert _variables.Regime is lcm.Regime + + @categorical(ordered=False) class _RegimeId: working: ScalarInt From 415a85c6f634457526bd073345018034543f5e4a Mon Sep 17 00:00:00 2001 From: Hans-Martin von Gaudecker Date: Fri, 15 May 2026 11:20:29 +0200 Subject: [PATCH 73/77] Tighten InitialConditions / RegimeStates to Float1D | Int1D MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per-subject vectors carry a single broadcast axis (length n_subjects). The validator already enforces rank-1 by checking equal lengths across arrays; tightening the type makes the contract explicit and lets beartype catch rank slips at the canonicalize boundary. - `InitialConditions` alias narrows from `FloatND | IntND` to `Float1D | Int1D`. - `RegimeStates` alias likewise narrows; `build_initial_states`, `_advance_states_for_subjects`, and the local intermediate in `pandas_utils.initial_conditions_from_dataframe` follow. - `UserInitialConditions` stays wide at the boundary so user-supplied NumPy arrays of any rank pass through to canonicalization. Also bump the pylcm-benchmarks pin of aca-model from 6e282a5 (pre-regime rename) to bce9101 — the CI run failed with `KeyError: 'regime_id'` because the cached benchmark snapshot still emitted the old `"regime"` key. Co-Authored-By: Claude Opus 4.7 --- pyproject.toml | 2 +- src/lcm/pandas_utils.py | 5 +++-- src/lcm/simulation/initial_conditions.py | 7 ++++--- src/lcm/simulation/transitions.py | 2 +- src/lcm/typing.py | 7 ++++--- 5 files changed, 13 insertions(+), 10 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 76f0f81bc..4decbb38e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -99,7 +99,7 @@ tests-cuda13 = { features = [ "tests", "cuda13" ], solve-group = "cuda13" } tests-metal = { features = [ "tests", "metal" ], solve-group = "metal" } type-checking = { features = [ "type-checking", "tests" ], solve-group = "default" } [tool.pixi.feature.benchmarks.pypi-dependencies] -aca-model = { git = "https://github.com/OpenSourceEconomics/aca-model.git", rev = "6e282a5b7447c8198b667fe77c52d06940c64201" } +aca-model = { git = "https://github.com/OpenSourceEconomics/aca-model.git", rev = "bce9101b049f4088789c19be14ab7b436daf44f7" } [tool.pixi.feature.cuda12] platforms = [ "linux-64" ] system-requirements = { cuda = "12" } diff --git a/src/lcm/pandas_utils.py b/src/lcm/pandas_utils.py index 6ae42aa78..044087547 100644 --- a/src/lcm/pandas_utils.py +++ b/src/lcm/pandas_utils.py @@ -18,11 +18,12 @@ from lcm.shocks import _ShockGrid from lcm.simulation.initial_conditions import MISSING_CAT_CODE from lcm.typing import ( + Float1D, FloatND, FunctionName, InitialConditions, + Int1D, InternalParams, - IntND, RegimeName, RegimeNamesToIds, StateName, @@ -153,7 +154,7 @@ def initial_conditions_from_dataframe( # noqa: C901 nan_mask = np.isnan(result_arrays[col]) result_arrays[col][nan_mask] = MISSING_CAT_CODE - initial_conditions: dict[StateName | Literal["regime_id"], FloatND | IntND] = { + initial_conditions: dict[StateName | Literal["regime_id"], Float1D | Int1D] = { col: jnp.array(arr, dtype=jnp.int32) if col in discrete_state_names else jnp.array(arr, dtype=canonical_float_dtype()) diff --git a/src/lcm/simulation/initial_conditions.py b/src/lcm/simulation/initial_conditions.py index 6b97e2472..725e48495 100644 --- a/src/lcm/simulation/initial_conditions.py +++ b/src/lcm/simulation/initial_conditions.py @@ -32,6 +32,7 @@ ActionName, BoolND, FlatRegimeParams, + Float1D, FloatND, InitialConditions, Int1D, @@ -103,7 +104,7 @@ def canonicalize_initial_conditions( def build_initial_states( *, - initial_states: Mapping[StateName, FloatND | IntND], + initial_states: Mapping[StateName, Float1D | Int1D], internal_regimes: MappingProxyType[RegimeName, InternalRegime], ) -> StatesPerRegime: """Build the regime-keyed state carrier from user-provided initial states. @@ -124,11 +125,11 @@ def build_initial_states( """ n_subjects = len(next(iter(initial_states.values()))) states_per_regime: dict[ - RegimeName, MappingProxyType[StateName, FloatND | IntND] + RegimeName, MappingProxyType[StateName, Float1D | Int1D] ] = {} for regime_name, internal_regime in internal_regimes.items(): - regime_states: dict[StateName, FloatND | IntND] = {} + regime_states: dict[StateName, Float1D | Int1D] = {} # Logic for distribution of subjects over devices distributed = any(grid.distributed for grid in internal_regime.grids.values()) devices = jax.devices() diff --git a/src/lcm/simulation/transitions.py b/src/lcm/simulation/transitions.py index cedd90a39..9d407a587 100644 --- a/src/lcm/simulation/transitions.py +++ b/src/lcm/simulation/transitions.py @@ -302,7 +302,7 @@ def _advance_states_for_subjects( Updated carrier with next-period values written in for selected subjects. """ - updated: dict[RegimeName, dict[StateName, FloatND | IntND]] = { + updated: dict[RegimeName, dict[StateName, Float1D | Int1D]] = { regime_name: dict(regime_states) for regime_name, regime_states in states_per_regime.items() } diff --git a/src/lcm/typing.py b/src/lcm/typing.py index 3e3c8202b..627bcb5b9 100644 --- a/src/lcm/typing.py +++ b/src/lcm/typing.py @@ -55,7 +55,7 @@ RegimeName, MappingProxyType[TransitionFunctionName, InternalUserFunction] ] -type RegimeStates = MappingProxyType[StateName, FloatND | IntND] +type RegimeStates = MappingProxyType[StateName, Float1D | Int1D] type StatesPerRegime = MappingProxyType[RegimeName, RegimeStates] # Boundary form of initial conditions — accepted by `Model.simulate` and @@ -68,8 +68,9 @@ # and consumed by `validate_initial_conditions`, `simulate`, and persistence. # Read-protocol typing so callers don't have to wrap a dict in # `MappingProxyType` before passing it in; pylcm producers still wrap on -# the way out to preserve immutability at runtime. -type InitialConditions = Mapping[StateName | Literal["regime_id"], FloatND | IntND] +# the way out to preserve immutability at runtime. Values are 1-D arrays +# of length `n_subjects`; the validator checks the rank-1 invariant. +type InitialConditions = Mapping[StateName | Literal["regime_id"], Float1D | Int1D] # Boundary leaf type — accepted by `Model.__init__` / `Model.solve` / From 748caaccd9e924e1cd54df8b7ced068a36d27f96 Mon Sep 17 00:00:00 2001 From: Hans-Martin von Gaudecker Date: Fri, 15 May 2026 11:22:30 +0200 Subject: [PATCH 74/77] Re-lock for the benchmarks aca-model rev bump (bce9101) The previous tightening commit forgot to stage pixi.lock; CI's locked install failed on the stale rev. Co-Authored-By: Claude Opus 4.7 --- pixi.lock | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pixi.lock b/pixi.lock index 6312f04de..e76a05398 100644 --- a/pixi.lock +++ b/pixi.lock @@ -270,7 +270,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-1.3.2-h25fd6f3_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda - - pypi: git+https://github.com/OpenSourceEconomics/aca-model.git?rev=6e282a5b7447c8198b667fe77c52d06940c64201#6e282a5b7447c8198b667fe77c52d06940c64201 + - pypi: git+https://github.com/OpenSourceEconomics/aca-model.git?rev=bce9101b049f4088789c19be14ab7b436daf44f7#bce9101b049f4088789c19be14ab7b436daf44f7 - pypi: https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ae/44/c1221527f6a71a01ec6fbad7fa78f1d50dfa02217385cf0fa3eec7087d59/click-8.3.3-py3-none-any.whl @@ -5347,9 +5347,9 @@ packages: purls: [] size: 8191 timestamp: 1744137672556 -- pypi: git+https://github.com/OpenSourceEconomics/aca-model.git?rev=6e282a5b7447c8198b667fe77c52d06940c64201#6e282a5b7447c8198b667fe77c52d06940c64201 +- pypi: git+https://github.com/OpenSourceEconomics/aca-model.git?rev=bce9101b049f4088789c19be14ab7b436daf44f7#bce9101b049f4088789c19be14ab7b436daf44f7 name: aca-model - version: 0.1.dev83+g6e282a5b7 + version: 0.1.dev84+gbce9101b0 requires_dist: - attrs - beartype @@ -14086,8 +14086,8 @@ packages: timestamp: 1774796815820 - pypi: ./ name: pylcm - version: 0.0.2.dev212+g601cb47bd.d20260514 - sha256: d66f80e732b584ed1cfea9766b342f41c63161643fcc39fb5442ee59031cc726 + version: 0.0.2.dev233+ge54b802c4.d20260515 + sha256: a5709a048bcaff108fd93227188a39e2ac94a7930d6c789af4a8d2fb6ca4eae8 requires_dist: - beartype>=0.21 - cloudpickle>=3.1.2 From 36a531ce413917ec5b4865ccccf828617d39bd95 Mon Sep 17 00:00:00 2001 From: Hans-Martin von Gaudecker Date: Fri, 15 May 2026 12:23:36 +0200 Subject: [PATCH 75/77] Loosen _validate_irreg_spaced_grid element type to Any Under cuda12 + 32-bit precision + cumulative test-suite state, the package claw's deep-check on `Sequence[float]` fires inside this inner helper before the manual `isinstance(p, int | float)` loop can raise the user-facing `GridInitializationError`. Reproduced locally with `pixi run -e tests-cuda12 tests-32bit`; CI's GPU 32-bit job hit the same. Setting the element type to `Any` tells beartype not to deep-check, restoring the manual loop as the single source of truth for the "non-numeric points" error. Co-Authored-By: Claude Opus 4.7 --- src/lcm/grids/continuous.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/lcm/grids/continuous.py b/src/lcm/grids/continuous.py index c9ad5e725..0c7865d92 100644 --- a/src/lcm/grids/continuous.py +++ b/src/lcm/grids/continuous.py @@ -2,6 +2,7 @@ from abc import ABC, abstractmethod from collections.abc import Sequence from dataclasses import dataclass +from typing import Any import jax.numpy as jnp from beartype import beartype @@ -361,9 +362,16 @@ def _validate_continuous_grid( raise GridInitializationError(msg) -def _validate_irreg_spaced_grid(points: Sequence[float] | Float1D) -> None: +def _validate_irreg_spaced_grid(points: Sequence[Any] | Float1D) -> None: """Validate the irregular spaced grid parameters. + The element type is `Any` because the function's manual loop is what + surfaces the user-facing `GridInitializationError` for non-numeric + entries; tightening to `Sequence[float]` makes beartype's package- + claw deep-check intercept first (observed flaking under cuda12 + + 32-bit precision in CI), raising raw `BeartypeCallHintViolation` + before the manual check fires. + Args: points: The grid points. From 39c0f66fa27b330a196a8d6f4ea665e79b3a41f7 Mon Sep 17 00:00:00 2001 From: Hans-Martin von Gaudecker Date: Mon, 18 May 2026 07:15:51 +0200 Subject: [PATCH 76/77] Use dags main. --- pixi.lock | 5206 ++++++++++++++++++++++++------------------------ pyproject.toml | 11 +- 2 files changed, 2632 insertions(+), 2585 deletions(-) diff --git a/pixi.lock b/pixi.lock index e76a05398..d103c693d 100644 --- a/pixi.lock +++ b/pixi.lock @@ -20,26 +20,26 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/asv_runner-0.2.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/async-lru-2.3.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-26.1.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-auth-0.10.1-h2d2dd48_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-auth-0.10.1-ha62d5e7_3.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-cal-0.9.13-h2c9d079_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-common-0.12.6-hb03c661_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-compression-0.3.2-h8b1a151_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-event-stream-0.6.0-h9b893ba_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-http-0.10.12-h4bacb7b_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-io-0.26.3-hc87160b_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-mqtt-0.15.2-he9ea9c5_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-s3-0.11.5-h6d69fc9_5.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-event-stream-0.7.0-h9b893ba_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-http-0.10.13-h4bacb7b_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-io-0.26.3-h692f434_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-mqtt-0.15.2-hc1936db_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-s3-0.12.2-he6ee468_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-sdkutils-0.2.4-h8b1a151_4.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-checksums-0.2.10-h8b1a151_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-crt-cpp-0.37.4-h4c8aef7_3.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-sdk-cpp-1.11.747-hc3785e1_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-crt-cpp-0.38.3-h745e52d_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-sdk-cpp-1.11.747-h41c0014_4.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/azure-core-cpp-1.16.2-h206d751_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/azure-identity-cpp-1.13.3-hed0cdb0_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/azure-storage-blobs-cpp-12.16.0-hdd73cc9_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/azure-storage-common-cpp-12.12.0-ha7a2c86_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/azure-storage-files-datalake-cpp-12.14.0-h52c5a47_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/azure-storage-blobs-cpp-12.16.0-hf824e48_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/azure-storage-common-cpp-12.13.0-ha7a2c86_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/azure-storage-files-datalake-cpp-12.14.0-h539c000_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.18.0-pyhcf101f3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/backports.zstd-1.3.0-py314h680f03e_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/backports.zstd-1.5.0-py314h680f03e_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.14.3-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils_impl_linux-64-2.45.1-default_hfdba357_102.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils_linux-64-2.45.1-default_h4852527_102.conda @@ -77,23 +77,23 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-nvvm-tools-12.9.86-h4bc722e_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/cuda-version-12.9-h4f385c5_3.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/debugpy-1.8.20-py314h42812f9_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.3.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/distlib-0.4.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.2.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.29.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/fqdn-1.5.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_impl_linux-64-14.3.0-hbdf3cc3_18.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_linux-64-14.3.0-h298d278_23.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_impl_linux-64-14.3.0-h235f0fe_19.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_linux-64-14.3.0-h50e9bb6_24.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/gflags-2.2.2-h5888daf_1005.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/glog-0.7.1-hbabe93e_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_impl_linux-64-14.3.0-h2185e75_18.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_linux-64-14.3.0-h91b0f8e_23.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_impl_linux-64-14.3.0-h2185e75_19.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_linux-64-14.3.0-h8a413ad_24.conda - conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/h5py-3.16.0-nompi_py314hddf7a69_102.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/hdf5-2.1.0-nompi_hd4fcb43_104.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/hdf5-2.1.0-nompi_h87a9417_105.conda - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda @@ -102,7 +102,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.13-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.8.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-7.2.0-pyha191276_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.12.0-pyhecfbec7_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.13.0-pyh53cf698_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ipython_pygments_lexers-1.1.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/isoduration-20.11.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jedi-0.19.2-pyhd8ed1ab_1.conda @@ -116,9 +116,9 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_client-8.8.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_core-5.9.1-pyhc90fa1f_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_events-0.12.1-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.17.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.18.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server_terminals-0.5.4-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.5.6-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.5.7-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_pygments-0.3.0-pyhd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_server-2.28.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/kernel-headers_linux-64-4.18.0-he073ed8_9.conda @@ -128,53 +128,53 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45.1-default_hbd61a6d_102.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libabseil-20260107.1-cxx17_h7b12aa8_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libaec-1.1.5-h088129d_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-24.0.0-ha7f89c6_0_cpu.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-acero-24.0.0-h635bf11_0_cpu.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-compute-24.0.0-h53684a4_0_cpu.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-dataset-24.0.0-h635bf11_0_cpu.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-substrait-24.0.0-hb4dd7c2_0_cpu.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.11.0-6_h4a7cf45_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-24.0.0-h0935d00_1_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-acero-24.0.0-h635bf11_1_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-compute-24.0.0-h53684a4_1_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-dataset-24.0.0-h635bf11_1_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-substrait-24.0.0-hb4dd7c2_1_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.11.0-7_h4a7cf45_openblas.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.2.0-hb03c661_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.2.0-hb03c661_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.2.0-hb03c661_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.11.0-6_h0358290_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.11.0-7_h0358290_openblas.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libcrc32c-1.1.2-h9c3ff4c_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcurl-8.19.0-hcf29cc6_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcurl-8.20.0-hcf29cc6_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20250104-pl5321h7949ede_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-hd590300_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libevent-2.1.12-hf998b51_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.5-hecca717_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.8.0-hecca717_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.5.2-h3435931_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-he0feb66_18.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/libgcc-devel_linux-64-14.3.0-hf649bbc_118.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.2.0-h69a702a_18.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-15.2.0-h69a702a_18.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.2.0-h68bc16d_18.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-he0feb66_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-he0feb66_19.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/libgcc-devel_linux-64-14.3.0-hf649bbc_119.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.2.0-h69a702a_19.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-15.2.0-h69a702a_19.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.2.0-h68bc16d_19.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-he0feb66_19.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgoogle-cloud-3.3.0-h25dbb67_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgoogle-cloud-storage-3.3.0-hdbdcf42_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgrpc-1.78.1-h1d1128b_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.18-h3b78370_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.11.0-6_h47877c9_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.11.0-7_h47877c9_openblas.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.3-hb03c661_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libmpdec-4.0.0-hb03c661_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.68.1-h877daf1_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libnvptxcompiler-dev-12.9.86-ha770c72_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/libnvptxcompiler-dev_linux-64-12.9.86-ha770c72_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.32-pthreads_h94d23a6_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.33-pthreads_h94d23a6_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libopentelemetry-cpp-1.26.0-h9692893_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libopentelemetry-cpp-headers-1.26.0-ha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libparquet-24.0.0-h7376487_0_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libparquet-24.0.0-h7376487_1_cpu.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libprotobuf-6.33.5-h2b00c02_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libre2-11-2025.11.05-h0dc7533_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libsanitizer-14.3.0-h8f1669f_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsanitizer-14.3.0-h8f1669f_19.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libsodium-1.0.21-h280c20c_3.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.53.0-hf4e2dac_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.53.1-h0c1763c_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.11.1-hcf80075_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h934c35e_18.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/libstdcxx-devel_linux-64-14.3.0-h9f08a49_118.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.2.0-hdf11a46_18.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libthrift-0.22.0-h454ac66_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h934c35e_19.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/libstdcxx-devel_linux-64-14.3.0-h9f08a49_119.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.2.0-hdf11a46_19.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libthrift-0.22.0-h7d032f7_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libutf8proc-2.11.3-hfe17d71_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.42-h5347b49_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-16-2.15.3-hca6bf5a_0.conda @@ -182,29 +182,29 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.2-h25fd6f3_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.10.0-h5888daf_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.3-py314h67df5f8_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.2.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/memory_profiler-0.61.0-pyhcf101f3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.2.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/narwhals-2.20.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.2.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/narwhals-2.21.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbclient-0.10.4-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.17.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbformat-5.10.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.6-hdb14827_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nest-asyncio-1.6.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/nlohmann_json-3.12.0-h54a6638_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/notebook-shim-0.2.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.4.3-py314h2b28147_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.4.5-py314h2b28147_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.2-h35e630c_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/orc-2.3.0-h21090e2_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/overrides-7.7.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.1-pyhc364b38_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.2-pyhc364b38_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pandocfilters-1.5.0-pyhd8ed1ab_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.6-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.7-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pexpect-4.9.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.9.6-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/plotly-6.6.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.6.0-pyhf9edf01_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/prek-0.3.10-hb17b654_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/prek-0.4.0-hb17b654_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/prometheus-cpp-1.3.0-ha5d0236_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.25.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.52-pyha770c72_0.conda @@ -220,23 +220,23 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.14.4-habeac84_100_cp314.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-discovery-1.2.2-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-discovery-1.3.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.21.2-pyhe01879c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.14.4-h4df99d1_100.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-2.0.7-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2026.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-3.2.1-pyh332efcf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2026.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.14-8_cp314.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.3-py314h67df5f8_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pyzmq-27.1.0-py312hda471dd_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/re2-2025.11.05-h5301d42_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.3-h853b02a_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.37.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.33.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.34.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3339-validator-0.1.4-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3986-validator-0.1.1-pyh9f0ad1d_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3987-syntax-1.1.0-pyhe01879c_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/rpds-py-0.30.0-py314h2e6c369_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/s2n-1.7.1-h1cbb8d7_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/s2n-1.7.2-hc5a330e_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/scipy-1.17.1-py314hf07bd8e_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-2.1.0-pyha191276_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-82.0.1-pyh332efcf_0.conda @@ -253,15 +253,15 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h366c992_103.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/tornado-6.5.5-py314h5bd0f2a_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.14.3-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.15.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing_utils-0.1.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/uri-template-1.3.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.6.3-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/virtualenv-21.2.4-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.6.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.7.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/virtualenv-21.3.3-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.7.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/webcolors-25.10.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/webencodings-0.5.1-pyhd8ed1ab_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.9.0-pyhd8ed1ab_0.conda @@ -273,22 +273,23 @@ environments: - pypi: git+https://github.com/OpenSourceEconomics/aca-model.git?rev=bce9101b049f4088789c19be14ab7b436daf44f7#bce9101b049f4088789c19be14ab7b436daf44f7 - pypi: https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ae/44/c1221527f6a71a01ec6fbad7fa78f1d50dfa02217385cf0fa3eec7087d59/click-8.3.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ee/ae/8e92f8058baf87f6c7d86ee7e457668690195cc77efedb8d3797a06e3940/click-8.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2c/1a/aff8bb287a4b1400f69e09a53bd65de96aa5cee5691925b38731c67fc695/click_default_group-1.2.4-py2.py3-none-any.whl - - pypi: git+https://github.com/OpenSourceEconomics/dags.git?rev=cf59c04#cf59c04c6ba07b7c54ca763dc155deea3341a480 + - pypi: git+https://github.com/OpenSourceEconomics/dags.git?branch=main#87ff051077f37470514808a3121e7c90429f6383 - pypi: https://files.pythonhosted.org/packages/8d/2d/f61c918d9edc2127068f0d5ad4604fedd9bfd393f464219090f3279c73f7/estimagic-0.5.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/43/f5/ee39c6e92acc742c052f137b47c210cd0a1b72dcd3f98495528bb4d27761/flatten_dict-0.4.2-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/d5/1f/5f4a3cd9e4440e9d9bc78ad0a91a1c8d46b4d429d5239ebe6793c9fe5c41/fsspec-2026.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/07/49/d4cad6e5381a50947bb973d2f6cf6592621451b09368b8c20d9b8af49c5b/greenlet-3.4.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/72/9f/485516087cd8c44183aaf9ab850247a28e2e4a42a4d62eab77c21f673450/flatten_dict-0.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d5/0c/043d5e551459da400957a1395e0febbf771446ff34291afcbe3d8be2a279/fsspec-2026.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a3/59/1bd6d7428d6ed9106efbb8c52310c60fd04f6672490f452aeaa3829aa436/greenlet-3.5.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/70/aa/dfac6d72cc35bc07e7587115b6946e333ef4ccb2e6cd26ecf639438c5d26/jax-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5f/78/a3d9ceda0793f4fb43daa292af7b801932611a1aed442636ddfc93d58c7a/jax_cuda12_pjrt-0.10.0-py3-none-manylinux_2_27_x86_64.whl - pypi: https://files.pythonhosted.org/packages/71/ec/9ba9f450f8a61c8388a3c8b74fe07d76230961aa6af65e7ed8d1bf9073fa/jax_cuda12_plugin-0.10.0-cp314-cp314-manylinux_2_27_x86_64.whl - pypi: https://files.pythonhosted.org/packages/a1/8e/b2a08ffc51c93842de71f7f988865cebfa7f43d6721957812dc8cc8b9d40/jaxlib-0.10.0-cp314-cp314-manylinux_2_27_x86_64.whl - pypi: https://files.pythonhosted.org/packages/94/05/3e39d416fb92b2738a76e8265e6bfc5d10542f90a7c32ad1eb831eea3fa3/jaxtyping-0.3.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/91/984aca2ec129e2757d1e4e3c81c3fcda9d0f85b74670a094cc443d9ee949/joblib-1.5.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c6/bb/82c7dcf38070b46172a517e2334e665c5bf374a262f99a283ea454bece7c/ml_dtypes-0.5.4-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/25/1f/cca084ca2572810fff12ea9dbdcbe39eac048f40daf4a9077b49fcbe8cee/msgspec-0.21.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/c0/0a517bfe63ccd3b92eb254d264e28fca3c7cab75d07daea315250fb1bf73/nvidia_cublas_cu12-12.9.2.10-py3-none-manylinux_2_27_x86_64.whl - pypi: https://files.pythonhosted.org/packages/18/2a/d4cd8506d2044e082f8cd921be57392e6a9b5ccd3ffdf050362430a3d5d5/nvidia_cuda_cccl_cu12-12.9.27-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl @@ -296,7 +297,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/25/48/b54a06168a2190572a312bfe4ce443687773eb61367ced31e064953dd2f7/nvidia_cuda_nvcc_cu12-12.9.86-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl - pypi: https://files.pythonhosted.org/packages/b8/85/e4af82cc9202023862090bfca4ea827d533329e925c758f0cde964cb54b7/nvidia_cuda_nvrtc_cu12-12.9.86-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl - pypi: https://files.pythonhosted.org/packages/bc/46/a92db19b8309581092a3add7e6fceb4c301a3fd233969856a8cbf042cd3c/nvidia_cuda_runtime_cu12-12.9.79-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/3c/8e/0c3aeef5f8a9507ad0b2d6fb3f28f38997cda1c7e7f614adc00ceb64a901/nvidia_cudnn_cu12-9.21.1.3-py3-none-manylinux_2_27_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/a0/8f/2ede6b758b7524608472010f632bdd3370ea271d715d1d66044614b84cdc/nvidia_cudnn_cu12-9.22.0.52-py3-none-manylinux_2_27_x86_64.whl - pypi: https://files.pythonhosted.org/packages/95/f4/61e6996dd20481ee834f57a8e9dca28b1869366a135e0d42e2aa8493bdd4/nvidia_cufft_cu12-11.4.1.4-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - pypi: https://files.pythonhosted.org/packages/33/40/79b0c64d44d6c166c0964ec1d803d067f4a145cca23e23925fd351d0e642/nvidia_cusolver_cu12-11.7.5.82-py3-none-manylinux_2_27_x86_64.whl - pypi: https://files.pythonhosted.org/packages/12/46/b0fd4b04f86577921feb97d8e2cf028afe04f614d17fb5013de9282c9216/nvidia_cusparse_cu12-12.5.10.65-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl @@ -305,16 +306,17 @@ environments: - pypi: https://files.pythonhosted.org/packages/9e/da/36fa8307cc40889307fed415d70b67d35ec330ffce889a9c03cf8f616cfa/nvidia_nvshmem_cu12-3.6.5-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - pypi: https://files.pythonhosted.org/packages/23/cd/066e86230ae37ed0be70aae89aabf03ca8d9f39c8aea0dec8029455b5540/opt_einsum-3.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/91/85/08c4e13a90a13c509d1fe09596dd8198338b6cfff9ee280f01ae7694889e/optimagic-0.5.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/fe/16/00261f20f467b9e8950a76ec1749f01359bf47f2fc3dac5e206de99835c0/optree-0.19.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/15/88/3cdd54fa279341afa10acf8d2b503556b1375245dccc9315659f795dd2e9/pandas-3.0.2-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/9c/1a/4834b1f2fb1847412353d7342eb7a1d001a4f3bd9d24155e057135a4aa44/optree-0.19.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/58/3b/1cdec6772bdbaf7b25dab360c59f03cadf05492dd724c6540af905389b07/pandas-3.0.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/b1/29/c028a0731e202035f0e2e0bfbf1a3e46ad6c628cbb17f6f1cc9eea5d9ff1/pathlib_abc-0.5.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/51/fe/53ac0cd932db5dcaf55961bc7cb7afdca8d80d8cc7406ed661f0c7dc111a/pdbp-1.8.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/da/9e/21c4088d1d433cfbf0578f138a6df59527dbd9e9d67355ee31b17e6dc774/portion-2.6.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/80/b2/bba963dfce0fcbc5020a4f8b4361e132390c4bd78b46cfc7ae355e678b96/pytask-0.5.8-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d7/54/c30cb1d08258612ece1dfa72c6918998bebecb916c54fca6d806bc780f2b/pytask-0.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/82/3b/64d4899d73f91ba49a8c18a8ff3f0ea8f1c1d75481760df8c68ef5235bf5/rich-15.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2e/84/efc7c0bf3a1c5eef81d397f6fddac855becdbb11cb38ff957888603014a7/sqlalchemy-2.0.49-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/65/44/bb509c3d2c0b5a87e7a5af1d5917a402a32ff026f777a6d7cb6990746cbb/tabcompleter-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cb/fc/8c82be70b8f96d09943360f34cfb2ecdd3035294c51bce4131eeabe56645/tabcompleter-1.4.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c7/18/c86eb8e0202e32dd3df50d43d7ff9854f8e0603945ff398974c1d91ac1ef/tomli_w-1.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/dd/1a/5d9a402b39ec892d856bbdd9db502ff73ce28cdf4aff72eb1ce1d6843506/universal_pathlib-0.3.10-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8d/96/04e7b441807b26b794da5b11e59ed7f83b2cf8af202bd7eba8ad2fa6046e/wadler_lindig-0.1.7-py3-none-any.whl - pypi: ./ @@ -338,26 +340,26 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/asv_runner-0.2.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/async-lru-2.3.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-26.1.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-auth-0.10.1-h2d2dd48_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-auth-0.10.1-ha62d5e7_3.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-cal-0.9.13-h2c9d079_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-common-0.12.6-hb03c661_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-compression-0.3.2-h8b1a151_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-event-stream-0.6.0-h9b893ba_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-http-0.10.12-h4bacb7b_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-io-0.26.3-hc87160b_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-mqtt-0.15.2-he9ea9c5_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-s3-0.11.5-h6d69fc9_5.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-event-stream-0.7.0-h9b893ba_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-http-0.10.13-h4bacb7b_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-io-0.26.3-h692f434_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-mqtt-0.15.2-hc1936db_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-s3-0.12.2-he6ee468_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-sdkutils-0.2.4-h8b1a151_4.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-checksums-0.2.10-h8b1a151_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-crt-cpp-0.37.4-h4c8aef7_3.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-sdk-cpp-1.11.747-hc3785e1_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-crt-cpp-0.38.3-h745e52d_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-sdk-cpp-1.11.747-h41c0014_4.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/azure-core-cpp-1.16.2-h206d751_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/azure-identity-cpp-1.13.3-hed0cdb0_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/azure-storage-blobs-cpp-12.16.0-hdd73cc9_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/azure-storage-common-cpp-12.12.0-ha7a2c86_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/azure-storage-files-datalake-cpp-12.14.0-h52c5a47_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/azure-storage-blobs-cpp-12.16.0-hf824e48_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/azure-storage-common-cpp-12.13.0-ha7a2c86_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/azure-storage-files-datalake-cpp-12.14.0-h539c000_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.18.0-pyhcf101f3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/backports.zstd-1.3.0-py314h680f03e_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/backports.zstd-1.5.0-py314h680f03e_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.14.3-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils_impl_linux-64-2.45.1-default_hfdba357_102.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils_linux-64-2.45.1-default_h4852527_102.conda @@ -395,23 +397,23 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-nvvm-tools-12.9.86-h4bc722e_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/cuda-version-12.9-h4f385c5_3.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/debugpy-1.8.20-py314h42812f9_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.3.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/distlib-0.4.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.2.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.29.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/fqdn-1.5.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_impl_linux-64-14.3.0-hbdf3cc3_18.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_linux-64-14.3.0-h298d278_23.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_impl_linux-64-14.3.0-h235f0fe_19.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_linux-64-14.3.0-h50e9bb6_24.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/gflags-2.2.2-h5888daf_1005.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/glog-0.7.1-hbabe93e_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_impl_linux-64-14.3.0-h2185e75_18.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_linux-64-14.3.0-h91b0f8e_23.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_impl_linux-64-14.3.0-h2185e75_19.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_linux-64-14.3.0-h8a413ad_24.conda - conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/h5py-3.16.0-nompi_py314hddf7a69_102.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/hdf5-2.1.0-nompi_hd4fcb43_104.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/hdf5-2.1.0-nompi_h87a9417_105.conda - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda @@ -420,7 +422,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.13-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.8.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-7.2.0-pyha191276_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.12.0-pyhecfbec7_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.13.0-pyh53cf698_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ipython_pygments_lexers-1.1.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/isoduration-20.11.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jedi-0.19.2-pyhd8ed1ab_1.conda @@ -434,9 +436,9 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_client-8.8.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_core-5.9.1-pyhc90fa1f_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_events-0.12.1-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.17.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.18.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server_terminals-0.5.4-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.5.6-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.5.7-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_pygments-0.3.0-pyhd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_server-2.28.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/kernel-headers_linux-64-4.18.0-he073ed8_9.conda @@ -446,53 +448,53 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45.1-default_hbd61a6d_102.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libabseil-20260107.1-cxx17_h7b12aa8_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libaec-1.1.5-h088129d_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-24.0.0-ha7f89c6_0_cpu.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-acero-24.0.0-h635bf11_0_cpu.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-compute-24.0.0-h53684a4_0_cpu.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-dataset-24.0.0-h635bf11_0_cpu.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-substrait-24.0.0-hb4dd7c2_0_cpu.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.11.0-6_h4a7cf45_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-24.0.0-h0935d00_1_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-acero-24.0.0-h635bf11_1_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-compute-24.0.0-h53684a4_1_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-dataset-24.0.0-h635bf11_1_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-substrait-24.0.0-hb4dd7c2_1_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.11.0-7_h4a7cf45_openblas.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.2.0-hb03c661_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.2.0-hb03c661_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.2.0-hb03c661_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.11.0-6_h0358290_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.11.0-7_h0358290_openblas.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libcrc32c-1.1.2-h9c3ff4c_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcurl-8.19.0-hcf29cc6_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcurl-8.20.0-hcf29cc6_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20250104-pl5321h7949ede_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-hd590300_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libevent-2.1.12-hf998b51_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.5-hecca717_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.8.0-hecca717_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.5.2-h3435931_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-he0feb66_18.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/libgcc-devel_linux-64-14.3.0-hf649bbc_118.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.2.0-h69a702a_18.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-15.2.0-h69a702a_18.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.2.0-h68bc16d_18.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-he0feb66_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-he0feb66_19.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/libgcc-devel_linux-64-14.3.0-hf649bbc_119.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.2.0-h69a702a_19.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-15.2.0-h69a702a_19.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.2.0-h68bc16d_19.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-he0feb66_19.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgoogle-cloud-3.3.0-h25dbb67_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgoogle-cloud-storage-3.3.0-hdbdcf42_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgrpc-1.78.1-h1d1128b_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.18-h3b78370_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.11.0-6_h47877c9_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.11.0-7_h47877c9_openblas.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.3-hb03c661_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libmpdec-4.0.0-hb03c661_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.68.1-h877daf1_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libnvptxcompiler-dev-12.9.86-ha770c72_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/libnvptxcompiler-dev_linux-64-12.9.86-ha770c72_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.32-pthreads_h94d23a6_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.33-pthreads_h94d23a6_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libopentelemetry-cpp-1.26.0-h9692893_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libopentelemetry-cpp-headers-1.26.0-ha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libparquet-24.0.0-h7376487_0_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libparquet-24.0.0-h7376487_1_cpu.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libprotobuf-6.33.5-h2b00c02_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libre2-11-2025.11.05-h0dc7533_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libsanitizer-14.3.0-h8f1669f_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsanitizer-14.3.0-h8f1669f_19.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libsodium-1.0.21-h280c20c_3.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.53.0-hf4e2dac_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.53.1-h0c1763c_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.11.1-hcf80075_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h934c35e_18.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/libstdcxx-devel_linux-64-14.3.0-h9f08a49_118.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.2.0-hdf11a46_18.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libthrift-0.22.0-h454ac66_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h934c35e_19.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/libstdcxx-devel_linux-64-14.3.0-h9f08a49_119.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.2.0-hdf11a46_19.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libthrift-0.22.0-h7d032f7_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libutf8proc-2.11.3-hfe17d71_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.42-h5347b49_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-16-2.15.3-hca6bf5a_0.conda @@ -500,28 +502,28 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.2-h25fd6f3_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.10.0-h5888daf_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.3-py314h67df5f8_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.2.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/memory_profiler-0.61.0-pyhcf101f3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.2.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/narwhals-2.20.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.2.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/narwhals-2.21.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbclient-0.10.4-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.17.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbformat-5.10.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.6-hdb14827_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nest-asyncio-1.6.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/nlohmann_json-3.12.0-h54a6638_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/notebook-shim-0.2.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.4.3-py314h2b28147_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.4.5-py314h2b28147_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.2-h35e630c_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/orc-2.3.0-h21090e2_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/overrides-7.7.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.1-pyhc364b38_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.2-pyhc364b38_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pandocfilters-1.5.0-pyhd8ed1ab_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.6-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.7-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pexpect-4.9.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.9.6-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/plotly-6.6.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/prek-0.3.10-hb17b654_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/prek-0.4.0-hb17b654_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/prometheus-cpp-1.3.0-ha5d0236_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.25.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.52-pyha770c72_0.conda @@ -537,23 +539,23 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.14.4-habeac84_100_cp314.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-discovery-1.2.2-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-discovery-1.3.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.21.2-pyhe01879c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.14.4-h4df99d1_100.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-2.0.7-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2026.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-3.2.1-pyh332efcf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2026.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.14-8_cp314.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.3-py314h67df5f8_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pyzmq-27.1.0-py312hda471dd_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/re2-2025.11.05-h5301d42_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.3-h853b02a_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.37.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.33.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.34.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3339-validator-0.1.4-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3986-validator-0.1.1-pyh9f0ad1d_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3987-syntax-1.1.0-pyhe01879c_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/rpds-py-0.30.0-py314h2e6c369_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/s2n-1.7.1-h1cbb8d7_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/s2n-1.7.2-hc5a330e_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/scipy-1.17.1-py314hf07bd8e_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-2.1.0-pyha191276_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-82.0.1-pyh332efcf_0.conda @@ -570,15 +572,15 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h366c992_103.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/tornado-6.5.5-py314h5bd0f2a_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.14.3-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.15.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing_utils-0.1.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/uri-template-1.3.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.6.3-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/virtualenv-21.2.4-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.6.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.7.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/virtualenv-21.3.3-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.7.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/webcolors-25.10.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/webencodings-0.5.1-pyhd8ed1ab_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.9.0-pyhd8ed1ab_0.conda @@ -588,8 +590,8 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-1.3.2-h25fd6f3_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda - pypi: https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl - - pypi: git+https://github.com/OpenSourceEconomics/dags.git?rev=cf59c04#cf59c04c6ba07b7c54ca763dc155deea3341a480 - - pypi: https://files.pythonhosted.org/packages/43/f5/ee39c6e92acc742c052f137b47c210cd0a1b72dcd3f98495528bb4d27761/flatten_dict-0.4.2-py2.py3-none-any.whl + - pypi: git+https://github.com/OpenSourceEconomics/dags.git?branch=main#87ff051077f37470514808a3121e7c90429f6383 + - pypi: https://files.pythonhosted.org/packages/72/9f/485516087cd8c44183aaf9ab850247a28e2e4a42a4d62eab77c21f673450/flatten_dict-0.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/70/aa/dfac6d72cc35bc07e7587115b6946e333ef4ccb2e6cd26ecf639438c5d26/jax-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5f/78/a3d9ceda0793f4fb43daa292af7b801932611a1aed442636ddfc93d58c7a/jax_cuda12_pjrt-0.10.0-py3-none-manylinux_2_27_x86_64.whl - pypi: https://files.pythonhosted.org/packages/71/ec/9ba9f450f8a61c8388a3c8b74fe07d76230961aa6af65e7ed8d1bf9073fa/jax_cuda12_plugin-0.10.0-cp314-cp314-manylinux_2_27_x86_64.whl @@ -603,7 +605,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/25/48/b54a06168a2190572a312bfe4ce443687773eb61367ced31e064953dd2f7/nvidia_cuda_nvcc_cu12-12.9.86-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl - pypi: https://files.pythonhosted.org/packages/b8/85/e4af82cc9202023862090bfca4ea827d533329e925c758f0cde964cb54b7/nvidia_cuda_nvrtc_cu12-12.9.86-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl - pypi: https://files.pythonhosted.org/packages/bc/46/a92db19b8309581092a3add7e6fceb4c301a3fd233969856a8cbf042cd3c/nvidia_cuda_runtime_cu12-12.9.79-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/3c/8e/0c3aeef5f8a9507ad0b2d6fb3f28f38997cda1c7e7f614adc00ceb64a901/nvidia_cudnn_cu12-9.21.1.3-py3-none-manylinux_2_27_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/a0/8f/2ede6b758b7524608472010f632bdd3370ea271d715d1d66044614b84cdc/nvidia_cudnn_cu12-9.22.0.52-py3-none-manylinux_2_27_x86_64.whl - pypi: https://files.pythonhosted.org/packages/95/f4/61e6996dd20481ee834f57a8e9dca28b1869366a135e0d42e2aa8493bdd4/nvidia_cufft_cu12-11.4.1.4-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - pypi: https://files.pythonhosted.org/packages/33/40/79b0c64d44d6c166c0964ec1d803d067f4a145cca23e23925fd351d0e642/nvidia_cusolver_cu12-11.7.5.82-py3-none-manylinux_2_27_x86_64.whl - pypi: https://files.pythonhosted.org/packages/12/46/b0fd4b04f86577921feb97d8e2cf028afe04f614d17fb5013de9282c9216/nvidia_cusparse_cu12-12.5.10.65-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl @@ -611,11 +613,11 @@ environments: - pypi: https://files.pythonhosted.org/packages/46/0c/c75bbfb967457a0b7670b8ad267bfc4fffdf341c074e0a80db06c24ccfd4/nvidia_nvjitlink_cu12-12.9.86-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl - pypi: https://files.pythonhosted.org/packages/9e/da/36fa8307cc40889307fed415d70b67d35ec330ffce889a9c03cf8f616cfa/nvidia_nvshmem_cu12-3.6.5-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - pypi: https://files.pythonhosted.org/packages/23/cd/066e86230ae37ed0be70aae89aabf03ca8d9f39c8aea0dec8029455b5540/opt_einsum-3.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/15/88/3cdd54fa279341afa10acf8d2b503556b1375245dccc9315659f795dd2e9/pandas-3.0.2-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/58/3b/1cdec6772bdbaf7b25dab360c59f03cadf05492dd724c6540af905389b07/pandas-3.0.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/51/fe/53ac0cd932db5dcaf55961bc7cb7afdca8d80d8cc7406ed661f0c7dc111a/pdbp-1.8.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/da/9e/21c4088d1d433cfbf0578f138a6df59527dbd9e9d67355ee31b17e6dc774/portion-2.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/65/44/bb509c3d2c0b5a87e7a5af1d5917a402a32ff026f777a6d7cb6990746cbb/tabcompleter-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cb/fc/8c82be70b8f96d09943360f34cfb2ecdd3035294c51bce4131eeabe56645/tabcompleter-1.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8d/96/04e7b441807b26b794da5b11e59ed7f83b2cf8af202bd7eba8ad2fa6046e/wadler_lindig-0.1.7-py3-none-any.whl - pypi: ./ cuda13: @@ -638,26 +640,26 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/asv_runner-0.2.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/async-lru-2.3.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-26.1.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-auth-0.10.1-h2d2dd48_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-auth-0.10.1-ha62d5e7_3.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-cal-0.9.13-h2c9d079_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-common-0.12.6-hb03c661_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-compression-0.3.2-h8b1a151_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-event-stream-0.6.0-h9b893ba_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-http-0.10.12-h4bacb7b_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-io-0.26.3-hc87160b_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-mqtt-0.15.2-he9ea9c5_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-s3-0.11.5-h6d69fc9_5.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-event-stream-0.7.0-h9b893ba_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-http-0.10.13-h4bacb7b_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-io-0.26.3-h692f434_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-mqtt-0.15.2-hc1936db_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-s3-0.12.2-he6ee468_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-sdkutils-0.2.4-h8b1a151_4.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-checksums-0.2.10-h8b1a151_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-crt-cpp-0.37.4-h4c8aef7_3.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-sdk-cpp-1.11.747-hc3785e1_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-crt-cpp-0.38.3-h745e52d_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-sdk-cpp-1.11.747-h41c0014_4.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/azure-core-cpp-1.16.2-h206d751_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/azure-identity-cpp-1.13.3-hed0cdb0_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/azure-storage-blobs-cpp-12.16.0-hdd73cc9_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/azure-storage-common-cpp-12.12.0-ha7a2c86_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/azure-storage-files-datalake-cpp-12.14.0-h52c5a47_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/azure-storage-blobs-cpp-12.16.0-hf824e48_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/azure-storage-common-cpp-12.13.0-ha7a2c86_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/azure-storage-files-datalake-cpp-12.14.0-h539c000_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.18.0-pyhcf101f3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/backports.zstd-1.3.0-py314h680f03e_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/backports.zstd-1.5.0-py314h680f03e_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.14.3-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils_impl_linux-64-2.45.1-default_hfdba357_102.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils_linux-64-2.45.1-default_h4852527_102.conda @@ -695,23 +697,23 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-nvvm-tools-13.2.78-h4bc722e_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/cuda-version-13.2-he2cc418_3.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/debugpy-1.8.20-py314h42812f9_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.3.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/distlib-0.4.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.2.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.29.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/fqdn-1.5.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_impl_linux-64-15.2.0-he420e7e_18.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_linux-64-15.2.0-h862fb80_23.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_impl_linux-64-15.2.0-he0086c7_19.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_linux-64-15.2.0-h7be306e_24.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/gflags-2.2.2-h5888daf_1005.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/glog-0.7.1-hbabe93e_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_impl_linux-64-15.2.0-hda75c37_18.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_linux-64-15.2.0-h98b7566_23.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_impl_linux-64-15.2.0-hda75c37_19.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_linux-64-15.2.0-he30e93d_24.conda - conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/h5py-3.16.0-nompi_py314hddf7a69_102.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/hdf5-2.1.0-nompi_hd4fcb43_104.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/hdf5-2.1.0-nompi_h87a9417_105.conda - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda @@ -720,7 +722,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.13-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.8.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-7.2.0-pyha191276_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.12.0-pyhecfbec7_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.13.0-pyh53cf698_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ipython_pygments_lexers-1.1.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/isoduration-20.11.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jedi-0.19.2-pyhd8ed1ab_1.conda @@ -734,9 +736,9 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_client-8.8.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_core-5.9.1-pyhc90fa1f_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_events-0.12.1-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.17.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.18.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server_terminals-0.5.4-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.5.6-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.5.7-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_pygments-0.3.0-pyhd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_server-2.28.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/kernel-headers_linux-64-4.18.0-he073ed8_9.conda @@ -746,53 +748,53 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45.1-default_hbd61a6d_102.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libabseil-20260107.1-cxx17_h7b12aa8_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libaec-1.1.5-h088129d_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-24.0.0-ha7f89c6_0_cpu.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-acero-24.0.0-h635bf11_0_cpu.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-compute-24.0.0-h53684a4_0_cpu.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-dataset-24.0.0-h635bf11_0_cpu.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-substrait-24.0.0-hb4dd7c2_0_cpu.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.11.0-6_h4a7cf45_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-24.0.0-h0935d00_1_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-acero-24.0.0-h635bf11_1_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-compute-24.0.0-h53684a4_1_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-dataset-24.0.0-h635bf11_1_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-substrait-24.0.0-hb4dd7c2_1_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.11.0-7_h4a7cf45_openblas.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.2.0-hb03c661_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.2.0-hb03c661_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.2.0-hb03c661_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.11.0-6_h0358290_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.11.0-7_h0358290_openblas.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libcrc32c-1.1.2-h9c3ff4c_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcurl-8.19.0-hcf29cc6_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcurl-8.20.0-hcf29cc6_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20250104-pl5321h7949ede_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-hd590300_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libevent-2.1.12-hf998b51_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.5-hecca717_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.8.0-hecca717_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.5.2-h3435931_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-he0feb66_18.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/libgcc-devel_linux-64-15.2.0-hcc6f6b0_118.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.2.0-h69a702a_18.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-15.2.0-h69a702a_18.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.2.0-h68bc16d_18.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-he0feb66_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-he0feb66_19.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/libgcc-devel_linux-64-15.2.0-hcc6f6b0_119.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.2.0-h69a702a_19.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-15.2.0-h69a702a_19.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.2.0-h68bc16d_19.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-he0feb66_19.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgoogle-cloud-3.3.0-h25dbb67_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgoogle-cloud-storage-3.3.0-hdbdcf42_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgrpc-1.78.1-h1d1128b_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.18-h3b78370_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.11.0-6_h47877c9_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.11.0-7_h47877c9_openblas.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.3-hb03c661_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libmpdec-4.0.0-hb03c661_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.68.1-h877daf1_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libnvptxcompiler-dev-13.2.78-ha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/libnvptxcompiler-dev_linux-64-13.2.78-ha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.32-pthreads_h94d23a6_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.33-pthreads_h94d23a6_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libopentelemetry-cpp-1.26.0-h9692893_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libopentelemetry-cpp-headers-1.26.0-ha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libparquet-24.0.0-h7376487_0_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libparquet-24.0.0-h7376487_1_cpu.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libprotobuf-6.33.5-h2b00c02_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libre2-11-2025.11.05-h0dc7533_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libsanitizer-15.2.0-h90f66d4_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsanitizer-15.2.0-h90f66d4_19.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libsodium-1.0.21-h280c20c_3.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.53.0-hf4e2dac_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.53.1-h0c1763c_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.11.1-hcf80075_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h934c35e_18.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/libstdcxx-devel_linux-64-15.2.0-hd446a21_118.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.2.0-hdf11a46_18.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libthrift-0.22.0-h454ac66_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h934c35e_19.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/libstdcxx-devel_linux-64-15.2.0-hd446a21_119.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.2.0-hdf11a46_19.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libthrift-0.22.0-h7d032f7_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libutf8proc-2.11.3-hfe17d71_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.42-h5347b49_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-16-2.15.3-hca6bf5a_0.conda @@ -800,28 +802,28 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.2-h25fd6f3_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.10.0-h5888daf_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.3-py314h67df5f8_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.2.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/memory_profiler-0.61.0-pyhcf101f3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.2.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/narwhals-2.20.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.2.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/narwhals-2.21.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbclient-0.10.4-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.17.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbformat-5.10.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.6-hdb14827_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nest-asyncio-1.6.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/nlohmann_json-3.12.0-h54a6638_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/notebook-shim-0.2.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.4.3-py314h2b28147_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.4.5-py314h2b28147_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.2-h35e630c_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/orc-2.3.0-h21090e2_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/overrides-7.7.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.1-pyhc364b38_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.2-pyhc364b38_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pandocfilters-1.5.0-pyhd8ed1ab_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.6-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.7-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pexpect-4.9.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.9.6-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/plotly-6.6.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/prek-0.3.10-hb17b654_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/prek-0.4.0-hb17b654_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/prometheus-cpp-1.3.0-ha5d0236_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.25.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.52-pyha770c72_0.conda @@ -837,23 +839,23 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.14.4-habeac84_100_cp314.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-discovery-1.2.2-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-discovery-1.3.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.21.2-pyhe01879c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.14.4-h4df99d1_100.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-2.0.7-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2026.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-3.2.1-pyh332efcf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2026.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.14-8_cp314.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.3-py314h67df5f8_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pyzmq-27.1.0-py312hda471dd_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/re2-2025.11.05-h5301d42_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.3-h853b02a_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.37.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.33.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.34.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3339-validator-0.1.4-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3986-validator-0.1.1-pyh9f0ad1d_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3987-syntax-1.1.0-pyhe01879c_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/rpds-py-0.30.0-py314h2e6c369_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/s2n-1.7.1-h1cbb8d7_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/s2n-1.7.2-hc5a330e_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/scipy-1.17.1-py314hf07bd8e_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-2.1.0-pyha191276_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-82.0.1-pyh332efcf_0.conda @@ -870,15 +872,15 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h366c992_103.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/tornado-6.5.5-py314h5bd0f2a_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.14.3-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.15.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing_utils-0.1.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/uri-template-1.3.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.6.3-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/virtualenv-21.2.4-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.6.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.7.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/virtualenv-21.3.3-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.7.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/webcolors-25.10.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/webencodings-0.5.1-pyhd8ed1ab_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.9.0-pyhd8ed1ab_0.conda @@ -888,8 +890,8 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-1.3.2-h25fd6f3_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda - pypi: https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl - - pypi: git+https://github.com/OpenSourceEconomics/dags.git?rev=cf59c04#cf59c04c6ba07b7c54ca763dc155deea3341a480 - - pypi: https://files.pythonhosted.org/packages/43/f5/ee39c6e92acc742c052f137b47c210cd0a1b72dcd3f98495528bb4d27761/flatten_dict-0.4.2-py2.py3-none-any.whl + - pypi: git+https://github.com/OpenSourceEconomics/dags.git?branch=main#87ff051077f37470514808a3121e7c90429f6383 + - pypi: https://files.pythonhosted.org/packages/72/9f/485516087cd8c44183aaf9ab850247a28e2e4a42a4d62eab77c21f673450/flatten_dict-0.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/70/aa/dfac6d72cc35bc07e7587115b6946e333ef4ccb2e6cd26ecf639438c5d26/jax-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/21/98/77f15d81fd0637da454e453c8456d4a2b5c8b2e66823b4237ee8689152cf/jax_cuda13_pjrt-0.10.0-py3-none-manylinux_2_27_x86_64.whl - pypi: https://files.pythonhosted.org/packages/8f/2b/5c63c29d155afdf1d7827f8c04efe8cac47fc6783d8c53959e43de879dcc/jax_cuda13_plugin-0.10.0-cp314-cp314-manylinux_2_27_x86_64.whl @@ -897,14 +899,14 @@ environments: - pypi: https://files.pythonhosted.org/packages/94/05/3e39d416fb92b2738a76e8265e6bfc5d10542f90a7c32ad1eb831eea3fa3/jaxtyping-0.3.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c6/bb/82c7dcf38070b46172a517e2334e665c5bf374a262f99a283ea454bece7c/ml_dtypes-0.5.4-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3d/ec/c9b2998aebe3149dee2769e501257e048c8701de51263925f4dff76ddedc/nvidia_cublas-13.4.0.1-py3-none-manylinux_2_27_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/f8/79/0cefdaa1d9e45018a227bac64a79b92d2733cde28a8fd09c65362de08622/nvidia_cublas-13.4.1.1-py3-none-manylinux_2_27_x86_64.whl - pypi: https://files.pythonhosted.org/packages/92/87/d23db8276b76b4a7e4a702eebdc0a70e3b56c17b4dcd980ecb0f68b022e1/nvidia_cuda_cccl-13.2.75-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - pypi: https://files.pythonhosted.org/packages/ea/78/501eee5cce9202fba2f3476529e296a7f6d003261d80b52ab0abfa09ddd6/nvidia_cuda_crt-13.2.78-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - pypi: https://files.pythonhosted.org/packages/b7/2d/cbf8f6288259c502165282fdaa2b733daae98434e3f2aee2b7952ba87c6f/nvidia_cuda_cupti-13.2.75-py3-none-manylinux_2_25_x86_64.whl - pypi: https://files.pythonhosted.org/packages/65/0f/c7c7d538c61794130e759ad74710ab5aa8cab1f700ee1754381f8c665605/nvidia_cuda_nvcc-13.2.78-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - pypi: https://files.pythonhosted.org/packages/5f/96/237b40b171e06eb65905375c4ad5c96f78c2f861ac6e8ae7f650d95e1dfd/nvidia_cuda_nvrtc-13.2.78-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl - pypi: https://files.pythonhosted.org/packages/dc/74/f1493b0774c6eaf0234512bb650e1ab90ce8f61fecf0b4aaf1fb416f571e/nvidia_cuda_runtime-13.2.75-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/85/be/68e659d798aaad24817f10acbad952491b43df668376b704618296887f62/nvidia_cudnn_cu13-9.21.1.3-py3-none-manylinux_2_27_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/57/96/ce2cb84b5e8bb94dd55f554e3454b91e9ecd6708aa27d4a7b12f287613bc/nvidia_cudnn_cu13-9.22.0.52-py3-none-manylinux_2_27_x86_64.whl - pypi: https://files.pythonhosted.org/packages/36/3e/8d717a6e1f6e27b85b64650b1104dbcf6108c9dc7e27e9e26a0d8e936cc5/nvidia_cufft-12.2.0.46-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - pypi: https://files.pythonhosted.org/packages/6b/97/a3c41eac54c89f6aac788d2b3ccd6642b32aa6b79650af3dedb8ee7c2bfa/nvidia_cusolver-12.2.0.1-py3-none-manylinux_2_27_x86_64.whl - pypi: https://files.pythonhosted.org/packages/b7/bd/bad43b37bcf13167637bef26399693d517b95092d742e8749eda5f4a85f3/nvidia_cusparse-12.7.10.1-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl @@ -913,11 +915,11 @@ environments: - pypi: https://files.pythonhosted.org/packages/5d/7b/2ab033584a3339552472ac8d79543c503a0e06dd0d082448b06697e7f716/nvidia_nvshmem_cu13-3.6.5-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - pypi: https://files.pythonhosted.org/packages/e8/1f/930d63ccc8adcdf27bfc051a24e3e4da2cf6ef987848d6d1d642e29d704b/nvidia_nvvm-13.2.78-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl - pypi: https://files.pythonhosted.org/packages/23/cd/066e86230ae37ed0be70aae89aabf03ca8d9f39c8aea0dec8029455b5540/opt_einsum-3.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/15/88/3cdd54fa279341afa10acf8d2b503556b1375245dccc9315659f795dd2e9/pandas-3.0.2-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/58/3b/1cdec6772bdbaf7b25dab360c59f03cadf05492dd724c6540af905389b07/pandas-3.0.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/51/fe/53ac0cd932db5dcaf55961bc7cb7afdca8d80d8cc7406ed661f0c7dc111a/pdbp-1.8.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/da/9e/21c4088d1d433cfbf0578f138a6df59527dbd9e9d67355ee31b17e6dc774/portion-2.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/65/44/bb509c3d2c0b5a87e7a5af1d5917a402a32ff026f777a6d7cb6990746cbb/tabcompleter-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cb/fc/8c82be70b8f96d09943360f34cfb2ecdd3035294c51bce4131eeabe56645/tabcompleter-1.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8d/96/04e7b441807b26b794da5b11e59ed7f83b2cf8af202bd7eba8ad2fa6046e/wadler_lindig-0.1.7-py3-none-any.whl - pypi: ./ default: @@ -940,26 +942,26 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/asv_runner-0.2.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/async-lru-2.3.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-26.1.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-auth-0.10.1-h2d2dd48_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-auth-0.10.1-ha62d5e7_3.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-cal-0.9.13-h2c9d079_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-common-0.12.6-hb03c661_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-compression-0.3.2-h8b1a151_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-event-stream-0.6.0-h9b893ba_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-http-0.10.12-h4bacb7b_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-io-0.26.3-hc87160b_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-mqtt-0.15.2-he9ea9c5_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-s3-0.11.5-h6d69fc9_5.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-event-stream-0.7.0-h9b893ba_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-http-0.10.13-h4bacb7b_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-io-0.26.3-h692f434_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-mqtt-0.15.2-hc1936db_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-s3-0.12.2-he6ee468_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-sdkutils-0.2.4-h8b1a151_4.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-checksums-0.2.10-h8b1a151_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-crt-cpp-0.37.4-h4c8aef7_3.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-sdk-cpp-1.11.747-hc3785e1_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-crt-cpp-0.38.3-h745e52d_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-sdk-cpp-1.11.747-h41c0014_4.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/azure-core-cpp-1.16.2-h206d751_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/azure-identity-cpp-1.13.3-hed0cdb0_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/azure-storage-blobs-cpp-12.16.0-hdd73cc9_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/azure-storage-common-cpp-12.12.0-ha7a2c86_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/azure-storage-files-datalake-cpp-12.14.0-h52c5a47_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/azure-storage-blobs-cpp-12.16.0-hf824e48_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/azure-storage-common-cpp-12.13.0-ha7a2c86_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/azure-storage-files-datalake-cpp-12.14.0-h539c000_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.18.0-pyhcf101f3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/backports.zstd-1.3.0-py314h680f03e_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/backports.zstd-1.5.0-py314h680f03e_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.14.3-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-6.3.0-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-with-css-6.3.0-hbca2aae_1.conda @@ -976,7 +978,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.14.4-py314hd8ed1ab_100.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/debugpy-1.8.20-py314h42812f9_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.3.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/distlib-0.4.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda @@ -988,7 +990,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/h5py-3.16.0-nompi_py314hddf7a69_102.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/hdf5-2.1.0-nompi_hd4fcb43_104.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/hdf5-2.1.0-nompi_h87a9417_105.conda - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda @@ -997,7 +999,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.13-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.8.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-7.2.0-pyha191276_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.12.0-pyhecfbec7_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.13.0-pyh53cf698_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ipython_pygments_lexers-1.1.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/isoduration-20.11.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jedi-0.19.2-pyhd8ed1ab_1.conda @@ -1011,9 +1013,9 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_client-8.8.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_core-5.9.1-pyhc90fa1f_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_events-0.12.1-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.17.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.18.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server_terminals-0.5.4-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.5.6-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.5.7-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_pygments-0.3.0-pyhd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_server-2.28.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.3-hb9d3cd8_0.conda @@ -1022,48 +1024,48 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45.1-default_hbd61a6d_102.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libabseil-20260107.1-cxx17_h7b12aa8_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libaec-1.1.5-h088129d_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-24.0.0-ha7f89c6_0_cpu.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-acero-24.0.0-h635bf11_0_cpu.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-compute-24.0.0-h53684a4_0_cpu.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-dataset-24.0.0-h635bf11_0_cpu.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-substrait-24.0.0-hb4dd7c2_0_cpu.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.11.0-6_h4a7cf45_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-24.0.0-h0935d00_1_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-acero-24.0.0-h635bf11_1_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-compute-24.0.0-h53684a4_1_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-dataset-24.0.0-h635bf11_1_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-substrait-24.0.0-hb4dd7c2_1_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.11.0-7_h4a7cf45_openblas.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.2.0-hb03c661_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.2.0-hb03c661_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.2.0-hb03c661_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.11.0-6_h0358290_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.11.0-7_h0358290_openblas.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libcrc32c-1.1.2-h9c3ff4c_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcurl-8.19.0-hcf29cc6_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcurl-8.20.0-hcf29cc6_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20250104-pl5321h7949ede_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-hd590300_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libevent-2.1.12-hf998b51_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.5-hecca717_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.8.0-hecca717_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.5.2-h3435931_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-he0feb66_18.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.2.0-h69a702a_18.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-15.2.0-h69a702a_18.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.2.0-h68bc16d_18.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-he0feb66_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-he0feb66_19.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.2.0-h69a702a_19.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-15.2.0-h69a702a_19.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.2.0-h68bc16d_19.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-he0feb66_19.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgoogle-cloud-3.3.0-h25dbb67_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgoogle-cloud-storage-3.3.0-hdbdcf42_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgrpc-1.78.1-h1d1128b_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.18-h3b78370_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.11.0-6_h47877c9_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.11.0-7_h47877c9_openblas.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.3-hb03c661_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libmpdec-4.0.0-hb03c661_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.68.1-h877daf1_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.32-pthreads_h94d23a6_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.33-pthreads_h94d23a6_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libopentelemetry-cpp-1.26.0-h9692893_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libopentelemetry-cpp-headers-1.26.0-ha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libparquet-24.0.0-h7376487_0_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libparquet-24.0.0-h7376487_1_cpu.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libprotobuf-6.33.5-h2b00c02_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libre2-11-2025.11.05-h0dc7533_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libsodium-1.0.21-h280c20c_3.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.53.0-hf4e2dac_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.53.1-h0c1763c_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.11.1-hcf80075_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h934c35e_18.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.2.0-hdf11a46_18.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libthrift-0.22.0-h454ac66_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h934c35e_19.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.2.0-hdf11a46_19.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libthrift-0.22.0-h7d032f7_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libutf8proc-2.11.3-hfe17d71_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.42-h5347b49_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-16-2.15.3-hca6bf5a_0.conda @@ -1071,28 +1073,28 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.2-h25fd6f3_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.10.0-h5888daf_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.3-py314h67df5f8_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.2.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/memory_profiler-0.61.0-pyhcf101f3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.2.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/narwhals-2.20.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.2.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/narwhals-2.21.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbclient-0.10.4-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.17.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbformat-5.10.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.6-hdb14827_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nest-asyncio-1.6.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/nlohmann_json-3.12.0-h54a6638_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/notebook-shim-0.2.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.4.3-py314h2b28147_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.4.5-py314h2b28147_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.2-h35e630c_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/orc-2.3.0-h21090e2_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/overrides-7.7.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.1-pyhc364b38_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.2-pyhc364b38_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pandocfilters-1.5.0-pyhd8ed1ab_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.6-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.7-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pexpect-4.9.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.9.6-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/plotly-6.6.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/prek-0.3.10-hb17b654_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/prek-0.4.0-hb17b654_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/prometheus-cpp-1.3.0-ha5d0236_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.25.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.52-pyha770c72_0.conda @@ -1108,23 +1110,23 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.14.4-habeac84_100_cp314.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-discovery-1.2.2-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-discovery-1.3.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.21.2-pyhe01879c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.14.4-h4df99d1_100.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-2.0.7-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2026.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-3.2.1-pyh332efcf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2026.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.14-8_cp314.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.3-py314h67df5f8_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pyzmq-27.1.0-py312hda471dd_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/re2-2025.11.05-h5301d42_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.3-h853b02a_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.37.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.33.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.34.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3339-validator-0.1.4-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3986-validator-0.1.1-pyh9f0ad1d_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3987-syntax-1.1.0-pyhe01879c_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/rpds-py-0.30.0-py314h2e6c369_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/s2n-1.7.1-h1cbb8d7_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/s2n-1.7.2-hc5a330e_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/scipy-1.17.1-py314hf07bd8e_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-2.1.0-pyha191276_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-82.0.1-pyh332efcf_0.conda @@ -1140,15 +1142,15 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h366c992_103.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/tornado-6.5.5-py314h5bd0f2a_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.14.3-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.15.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing_utils-0.1.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/uri-template-1.3.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.6.3-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/virtualenv-21.2.4-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.6.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.7.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/virtualenv-21.3.3-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.7.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/webcolors-25.10.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/webencodings-0.5.1-pyhd8ed1ab_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.9.0-pyhd8ed1ab_0.conda @@ -1158,19 +1160,19 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-1.3.2-h25fd6f3_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda - pypi: https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl - - pypi: git+https://github.com/OpenSourceEconomics/dags.git?rev=cf59c04#cf59c04c6ba07b7c54ca763dc155deea3341a480 - - pypi: https://files.pythonhosted.org/packages/43/f5/ee39c6e92acc742c052f137b47c210cd0a1b72dcd3f98495528bb4d27761/flatten_dict-0.4.2-py2.py3-none-any.whl + - pypi: git+https://github.com/OpenSourceEconomics/dags.git?branch=main#87ff051077f37470514808a3121e7c90429f6383 + - pypi: https://files.pythonhosted.org/packages/72/9f/485516087cd8c44183aaf9ab850247a28e2e4a42a4d62eab77c21f673450/flatten_dict-0.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/70/aa/dfac6d72cc35bc07e7587115b6946e333ef4ccb2e6cd26ecf639438c5d26/jax-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a1/8e/b2a08ffc51c93842de71f7f988865cebfa7f43d6721957812dc8cc8b9d40/jaxlib-0.10.0-cp314-cp314-manylinux_2_27_x86_64.whl - pypi: https://files.pythonhosted.org/packages/94/05/3e39d416fb92b2738a76e8265e6bfc5d10542f90a7c32ad1eb831eea3fa3/jaxtyping-0.3.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c6/bb/82c7dcf38070b46172a517e2334e665c5bf374a262f99a283ea454bece7c/ml_dtypes-0.5.4-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/23/cd/066e86230ae37ed0be70aae89aabf03ca8d9f39c8aea0dec8029455b5540/opt_einsum-3.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/15/88/3cdd54fa279341afa10acf8d2b503556b1375245dccc9315659f795dd2e9/pandas-3.0.2-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/58/3b/1cdec6772bdbaf7b25dab360c59f03cadf05492dd724c6540af905389b07/pandas-3.0.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/51/fe/53ac0cd932db5dcaf55961bc7cb7afdca8d80d8cc7406ed661f0c7dc111a/pdbp-1.8.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/da/9e/21c4088d1d433cfbf0578f138a6df59527dbd9e9d67355ee31b17e6dc774/portion-2.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/65/44/bb509c3d2c0b5a87e7a5af1d5917a402a32ff026f777a6d7cb6990746cbb/tabcompleter-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cb/fc/8c82be70b8f96d09943360f34cfb2ecdd3035294c51bce4131eeabe56645/tabcompleter-1.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8d/96/04e7b441807b26b794da5b11e59ed7f83b2cf8af202bd7eba8ad2fa6046e/wadler_lindig-0.1.7-py3-none-any.whl - pypi: ./ osx-arm64: @@ -1186,26 +1188,26 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/asv_runner-0.2.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/async-lru-2.3.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-26.1.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-auth-0.10.1-hcb83491_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-auth-0.10.1-ha7d4cc1_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-cal-0.9.13-h6ee9776_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-common-0.12.6-hc919400_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-compression-0.3.2-h3e7f9b5_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-event-stream-0.6.0-h351c84d_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-http-0.10.12-h95cdebe_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-io-0.26.3-h4137820_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-mqtt-0.15.2-h69e7467_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-s3-0.11.5-ha5d16b2_5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-event-stream-0.7.0-h351c84d_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-http-0.10.13-h95cdebe_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-io-0.26.3-h4137820_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-mqtt-0.15.2-h8860bc9_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-s3-0.12.2-h07b101a_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-sdkutils-0.2.4-h16f91aa_4.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-checksums-0.2.10-h3e7f9b5_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-crt-cpp-0.37.4-h5505c15_3.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-sdk-cpp-1.11.747-had22720_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-crt-cpp-0.38.3-hba17502_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-sdk-cpp-1.11.747-h30a6df1_4.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/azure-core-cpp-1.16.2-he5ae378_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/azure-identity-cpp-1.13.3-h810541e_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/azure-storage-blobs-cpp-12.16.0-hc57151b_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/azure-storage-common-cpp-12.12.0-he467506_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/azure-storage-files-datalake-cpp-12.14.0-hf8a9d22_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/azure-storage-blobs-cpp-12.16.0-h5446563_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/azure-storage-common-cpp-12.13.0-he467506_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/azure-storage-files-datalake-cpp-12.14.0-hdc9d693_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.18.0-pyhcf101f3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/backports.zstd-1.3.0-py314h680f03e_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/backports.zstd-1.5.0-py314h680f03e_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.14.3-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-6.3.0-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-with-css-6.3.0-hbca2aae_1.conda @@ -1222,7 +1224,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.14.4-py314hd8ed1ab_100.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/debugpy-1.8.20-py314he609de1_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.3.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/distlib-0.4.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda @@ -1234,7 +1236,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/h5py-3.16.0-nompi_py314h658a3ac_102.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/hdf5-2.1.0-nompi_hc95e3eb_104.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/hdf5-2.1.0-nompi_he586413_105.conda - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda @@ -1242,7 +1244,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.13-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.8.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-7.2.0-pyh5552912_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.12.0-pyhecfbec7_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.13.0-pyh53cf698_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ipython_pygments_lexers-1.1.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/isoduration-20.11.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jedi-0.19.2-pyhd8ed1ab_1.conda @@ -1256,83 +1258,83 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_client-8.8.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_core-5.9.1-pyhc90fa1f_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_events-0.12.1-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.17.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.18.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server_terminals-0.5.4-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.5.6-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.5.7-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_pygments-0.3.0-pyhd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_server-2.28.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/krb5-1.22.2-h385eeb1_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/lark-1.3.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libabseil-20260107.1-cxx17_h2062a1b_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libaec-1.1.5-h8664d51_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libarrow-24.0.0-h2124f06_0_cpu.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libarrow-acero-24.0.0-hee8fe31_0_cpu.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libarrow-compute-24.0.0-h3b6a98a_0_cpu.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libarrow-dataset-24.0.0-hee8fe31_0_cpu.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libarrow-substrait-24.0.0-h05be00f_0_cpu.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libblas-3.11.0-6_h51639a9_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libarrow-24.0.0-h37fbca7_1_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libarrow-acero-24.0.0-hee8fe31_1_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libarrow-compute-24.0.0-h3b6a98a_1_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libarrow-dataset-24.0.0-hee8fe31_1_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libarrow-substrait-24.0.0-h05be00f_1_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libblas-3.11.0-7_h51639a9_openblas.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlicommon-1.2.0-hc919400_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlidec-1.2.0-hc919400_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlienc-1.2.0-hc919400_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcblas-3.11.0-6_hb0561ab_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcblas-3.11.0-7_hb0561ab_openblas.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcrc32c-1.1.2-hbdafb3b_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcurl-8.19.0-hd5a2499_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-22.1.4-h55c6f16_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcurl-8.20.0-hd5a2499_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-22.1.5-h55c6f16_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libedit-3.1.20250104-pl5321hafb1f1b_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libev-4.33-h93a5062_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libevent-2.1.12-h2757513_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.7.5-hf6b4638_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.8.0-hf6b4638_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.5.2-hcf2aa1b_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgcc-15.2.0-hcbb3090_18.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran-15.2.0-h07b0088_18.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran5-15.2.0-hdae7583_18.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgcc-15.2.0-hcbb3090_19.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran-15.2.0-h07b0088_19.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran5-15.2.0-hdae7583_19.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgoogle-cloud-3.3.0-he41eb1d_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgoogle-cloud-storage-3.3.0-ha114238_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgrpc-1.78.1-h3e3f78d_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libiconv-1.18-h23cfdf5_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblapack-3.11.0-6_hd9741b5_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblapack-3.11.0-7_hd9741b5_openblas.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblzma-5.8.3-h8088a28_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libmpdec-4.0.0-h84a0fba_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libnghttp2-1.68.1-h8f3e76b_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libopenblas-0.3.32-openmp_he657e61_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libopenblas-0.3.33-openmp_he657e61_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libopentelemetry-cpp-1.26.0-h08d5cc3_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libopentelemetry-cpp-headers-1.26.0-hce30654_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libparquet-24.0.0-h16c0493_0_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libparquet-24.0.0-h16c0493_1_cpu.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libprotobuf-6.33.5-h4a5acfd_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libre2-11-2025.11.05-h4c27e2a_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsodium-1.0.21-h1a92334_3.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.53.0-h1b79a29_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.53.1-h1b79a29_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libssh2-1.11.1-h1590b86_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libthrift-0.22.0-h14a376c_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libthrift-0.22.0-h1fb9c8a_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libutf8proc-2.11.3-h2431656_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libxml2-16-2.15.3-h6967ea9_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libxml2-2.15.3-heed7d32_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzlib-1.3.2-h8088a28_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/llvm-openmp-22.1.4-hc7d1edf_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/llvm-openmp-22.1.5-hc7d1edf_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/lz4-c-1.10.0-h286801f_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/markupsafe-3.0.3-py314h6e9b3f0_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.2.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/memory_profiler-0.61.0-pyhcf101f3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.2.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/narwhals-2.20.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.2.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/narwhals-2.21.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbclient-0.10.4-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.17.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbformat-5.10.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ncurses-6.5-h5e97a16_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ncurses-6.6-h1d4f5a5_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nest-asyncio-1.6.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/nlohmann_json-3.12.0-h784d473_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/notebook-shim-0.2.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/numpy-2.4.3-py314h1569ea8_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/numpy-2.4.5-py314hb79c6fa_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.6.2-hd24854e_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/orc-2.3.0-hd11884d_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/overrides-7.7.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.1-pyhc364b38_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.2-pyhc364b38_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pandocfilters-1.5.0-pyhd8ed1ab_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.6-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.7-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pexpect-4.9.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.9.6-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/plotly-6.6.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/prek-0.3.10-h6fdd925_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/prek-0.4.0-h6fdd925_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/prometheus-cpp-1.3.0-h0967b3e_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.25.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.52-pyha770c72_0.conda @@ -1350,18 +1352,18 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.14.4-h4c637c5_100_cp314.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-discovery-1.2.2-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-discovery-1.3.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.21.2-pyhe01879c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.14.4-h4df99d1_100.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-2.0.7-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2026.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-3.2.1-pyh332efcf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2026.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.14-8_cp314.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyyaml-6.0.3-py314h6e9b3f0_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyzmq-27.1.0-py312h022ad19_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/re2-2025.11.05-ha480c28_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/readline-8.3-h46df422_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.37.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.33.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.34.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3339-validator-0.1.4-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3986-validator-0.1.1-pyh9f0ad1d_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3987-syntax-1.1.0-pyhe01879c_1.conda @@ -1381,15 +1383,15 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-h010d191_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tornado-6.5.5-py314h6c2aa35_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.14.3-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.15.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing_utils-0.1.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/uri-template-1.3.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.6.3-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/virtualenv-21.2.4-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.6.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.7.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/virtualenv-21.3.3-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.7.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/webcolors-25.10.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/webencodings-0.5.1-pyhd8ed1ab_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.9.0-pyhd8ed1ab_0.conda @@ -1399,19 +1401,19 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zlib-1.3.2-h8088a28_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda - pypi: https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl - - pypi: git+https://github.com/OpenSourceEconomics/dags.git?rev=cf59c04#cf59c04c6ba07b7c54ca763dc155deea3341a480 - - pypi: https://files.pythonhosted.org/packages/43/f5/ee39c6e92acc742c052f137b47c210cd0a1b72dcd3f98495528bb4d27761/flatten_dict-0.4.2-py2.py3-none-any.whl + - pypi: git+https://github.com/OpenSourceEconomics/dags.git?branch=main#87ff051077f37470514808a3121e7c90429f6383 + - pypi: https://files.pythonhosted.org/packages/72/9f/485516087cd8c44183aaf9ab850247a28e2e4a42a4d62eab77c21f673450/flatten_dict-0.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/70/aa/dfac6d72cc35bc07e7587115b6946e333ef4ccb2e6cd26ecf639438c5d26/jax-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a7/25/e1e52a21786b321fb6a2edf9ef9971aa70f06bb2738aef9afd6d8f46a441/jaxlib-0.10.0-cp314-cp314-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/94/05/3e39d416fb92b2738a76e8265e6bfc5d10542f90a7c32ad1eb831eea3fa3/jaxtyping-0.3.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/72/4e/1339dc6e2557a344f5ba5590872e80346f76f6cb2ac3dd16e4666e88818c/ml_dtypes-0.5.4-cp314-cp314-macosx_10_13_universal2.whl - pypi: https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/23/cd/066e86230ae37ed0be70aae89aabf03ca8d9f39c8aea0dec8029455b5540/opt_einsum-3.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/95/25/bdb9326c3b5455f8d4d3549fce7abcf967259de146fe2cf7a82368141948/pandas-3.0.2-cp314-cp314-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/68/10/bf2d6738d72748b961a3751ab89522d58c54efc36a8e1a12161216cd45cf/pandas-3.0.3-cp314-cp314-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/51/fe/53ac0cd932db5dcaf55961bc7cb7afdca8d80d8cc7406ed661f0c7dc111a/pdbp-1.8.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/da/9e/21c4088d1d433cfbf0578f138a6df59527dbd9e9d67355ee31b17e6dc774/portion-2.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/65/44/bb509c3d2c0b5a87e7a5af1d5917a402a32ff026f777a6d7cb6990746cbb/tabcompleter-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cb/fc/8c82be70b8f96d09943360f34cfb2ecdd3035294c51bce4131eeabe56645/tabcompleter-1.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8d/96/04e7b441807b26b794da5b11e59ed7f83b2cf8af202bd7eba8ad2fa6046e/wadler_lindig-0.1.7-py3-none-any.whl - pypi: ./ win-64: @@ -1425,26 +1427,26 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/asv_runner-0.2.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/async-lru-2.3.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-26.1.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-auth-0.10.1-h5d51246_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-auth-0.10.1-h8b39d88_3.conda - conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-cal-0.9.13-h46f3b43_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-common-0.12.6-hfd05255_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-compression-0.3.2-hcb3a2da_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-event-stream-0.6.0-h87b2689_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-http-0.10.12-h612f3e8_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-io-0.26.3-h0d5b9f9_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-mqtt-0.15.2-h904b250_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-s3-0.11.5-h87bd87b_5.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-event-stream-0.7.0-h87b2689_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-http-0.10.13-h612f3e8_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-io-0.26.3-h0d5b9f9_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-mqtt-0.15.2-h82175e6_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-s3-0.12.2-h61b906f_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-sdkutils-0.2.4-hcb3a2da_4.conda - conda: https://conda.anaconda.org/conda-forge/win-64/aws-checksums-0.2.10-hcb3a2da_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/aws-crt-cpp-0.37.4-h4f72eff_3.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/aws-sdk-cpp-1.11.747-hd55a107_3.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/aws-crt-cpp-0.38.3-h1db8845_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/aws-sdk-cpp-1.11.747-hd63e0c5_4.conda - conda: https://conda.anaconda.org/conda-forge/win-64/azure-core-cpp-1.16.2-h49e36cd_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/azure-identity-cpp-1.13.3-h5ffce34_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/azure-storage-blobs-cpp-12.16.0-hcd625b1_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/azure-storage-common-cpp-12.12.0-h5ffce34_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/azure-storage-files-datalake-cpp-12.14.0-h1678c0b_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/azure-storage-blobs-cpp-12.16.0-h81bf7d1_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/azure-storage-common-cpp-12.13.0-h5ffce34_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/azure-storage-files-datalake-cpp-12.14.0-hab49af2_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.18.0-pyhcf101f3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/backports.zstd-1.3.0-py314h680f03e_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/backports.zstd-1.5.0-py314h680f03e_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.14.3-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-6.3.0-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-with-css-6.3.0-hbca2aae_1.conda @@ -1462,7 +1464,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.14.4-py314hd8ed1ab_100.conda - conda: https://conda.anaconda.org/conda-forge/win-64/debugpy-1.8.20-py314hb98de8c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.3.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/distlib-0.4.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda @@ -1472,7 +1474,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/h5py-3.16.0-nompi_py314h02517ec_102.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/hdf5-2.1.0-nompi_hd96b29f_104.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/hdf5-2.1.0-nompi_h0a39f1e_105.conda - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda @@ -1480,7 +1482,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.13-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.8.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-7.2.0-pyh6dadd2b_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.12.0-pyhccfa634_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.13.0-pyhe2676ad_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ipython_pygments_lexers-1.1.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/isoduration-20.11.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jedi-0.19.2-pyhd8ed1ab_1.conda @@ -1494,77 +1496,77 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_client-8.8.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_core-5.9.1-pyh6dadd2b_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_events-0.12.1-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.17.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.18.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server_terminals-0.5.4-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.5.6-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.5.7-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_pygments-0.3.0-pyhd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_server-2.28.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/krb5-1.22.2-h0ea6238_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/lark-1.3.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libabseil-20260107.1-cxx17_h0eb2380_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libaec-1.1.5-haf901d7_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libarrow-24.0.0-hc74aee5_0_cpu.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libarrow-acero-24.0.0-h7d8d6a5_0_cpu.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libarrow-compute-24.0.0-h081cd8e_0_cpu.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libarrow-dataset-24.0.0-h7d8d6a5_0_cpu.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libarrow-substrait-24.0.0-h524e9bd_0_cpu.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libblas-3.11.0-6_hf2e6a31_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libarrow-24.0.0-h37f918f_1_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libarrow-acero-24.0.0-h7d8d6a5_1_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libarrow-compute-24.0.0-h081cd8e_1_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libarrow-dataset-24.0.0-h7d8d6a5_1_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libarrow-substrait-24.0.0-h524e9bd_1_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libblas-3.11.0-7_h8455456_mkl.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libbrotlicommon-1.2.0-hfd05255_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libbrotlidec-1.2.0-hfd05255_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libbrotlienc-1.2.0-hfd05255_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libcblas-3.11.0-6_h2a3cdd5_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libcblas-3.11.0-7_h2a3cdd5_mkl.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libcrc32c-1.1.2-h0e60522_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/win-64/libcurl-8.19.0-h8206538_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libcurl-8.20.0-h8206538_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libevent-2.1.12-h3671451_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libexpat-2.7.5-hac47afa_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libexpat-2.8.0-hac47afa_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libffi-3.5.2-h3d046cb_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libgoogle-cloud-3.3.0-h2b231ac_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libgoogle-cloud-storage-3.3.0-he04ea4c_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libgrpc-1.78.1-h9ff2b3e_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libhwloc-2.12.2-default_h4379cf1_1000.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libhwloc-2.13.0-default_h049141e_1000.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libiconv-1.18-hc1393d2_2.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/liblapack-3.11.0-6_hf9ab0e9_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/liblapack-3.11.0-7_hf9ab0e9_mkl.conda - conda: https://conda.anaconda.org/conda-forge/win-64/liblzma-5.8.3-hfd05255_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libmpdec-4.0.0-hfd05255_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libopentelemetry-cpp-1.26.0-hc88f397_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libopentelemetry-cpp-headers-1.26.0-h57928b3_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libparquet-24.0.0-h7051d1f_0_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libparquet-24.0.0-h7051d1f_1_cpu.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libprotobuf-6.33.5-h61fc761_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libre2-11-2025.11.05-h04e5de1_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libsodium-1.0.21-h6a83c73_3.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libsqlite-3.53.0-hf5d6505_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libsqlite-3.53.1-hf5d6505_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libssh2-1.11.1-h9aa295b_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libthrift-0.22.0-h23985f6_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libthrift-0.22.0-h2e43b2f_2.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libutf8proc-2.11.3-hb980946_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libwinpthread-12.0.0.r4.gg4f2fc60ca-h57928b3_10.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-16-2.15.3-h692994f_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-2.15.3-hbc0d294_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libzlib-1.3.2-hfd05255_2.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/llvm-openmp-22.1.4-h4fa8253_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/llvm-openmp-22.1.5-h4fa8253_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/lz4-c-1.10.0-h2466b09_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/markupsafe-3.0.3-py314h2359020_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.2.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/memory_profiler-0.61.0-pyhcf101f3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.2.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/mkl-2025.3.1-hac47afa_12.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/narwhals-2.20.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.2.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/mkl-2026.0.0-hac47afa_906.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/narwhals-2.21.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbclient-0.10.4-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.17.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbformat-5.10.4-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nest-asyncio-1.6.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/nlohmann_json-3.12.0-h5112557_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/notebook-shim-0.2.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/numpy-2.4.3-py314h02f10f6_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/onemkl-license-2025.3.1-h57928b3_12.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/numpy-2.4.5-py314h02f10f6_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/onemkl-license-2026.0.0-h57928b3_906.conda - conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.6.2-hf411b9b_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/orc-2.3.0-h8fc0eb6_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/overrides-7.7.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.1-pyhc364b38_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.2-pyhc364b38_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pandocfilters-1.5.0-pyhd8ed1ab_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.6-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.7-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.9.6-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/plotly-6.6.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/prek-0.3.10-h18a1a76_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/prek-0.4.0-h18a1a76_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/prometheus-cpp-1.3.0-hcea2f5d_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.25.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.52-pyha770c72_0.conda @@ -1579,11 +1581,11 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyh09c184e_7.conda - conda: https://conda.anaconda.org/conda-forge/win-64/python-3.14.4-h4b44e0e_100_cp314.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-discovery-1.2.2-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-discovery-1.3.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.21.2-pyhe01879c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.14.4-h4df99d1_100.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-2.0.7-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2026.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-3.2.1-pyh332efcf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2026.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.14-8_cp314.conda - conda: https://conda.anaconda.org/conda-forge/win-64/pywin32-311-py314h8f8f202_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/pywinpty-2.0.15-py314h51f0985_1.conda @@ -1591,7 +1593,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/pyzmq-27.1.0-py312h343a6d4_2.conda - conda: https://conda.anaconda.org/conda-forge/win-64/re2-2025.11.05-ha104f34_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.37.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.33.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.34.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3339-validator-0.1.4-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3986-validator-0.1.1-pyh9f0ad1d_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3987-syntax-1.1.0-pyhe01879c_1.conda @@ -1606,26 +1608,26 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.8.3-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/stack_data-0.6.3-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tabulate-0.10.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/tbb-2022.3.0-h3155e25_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/tbb-2023.0.0-hd3d4ead_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/terminado-0.18.1-pyh6dadd2b_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tinycss2-1.4.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/tk-8.6.13-h6ed50ae_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/tornado-6.5.5-py314h5a2d7ad_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.14.3-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.15.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing_utils-0.1.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/ucrt-10.0.26100.0-h57928b3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/uri-template-1.3.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.6.3-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/vc-14.3-h41ae7f8_34.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.44.35208-h818238b_34.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/vcomp14-14.44.35208-h818238b_34.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/virtualenv-21.2.4-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/vs2015_runtime-14.44.35208-h38c0c73_34.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.6.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.7.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vc-14.5-h1b7c187_36.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.51.36231-h1b9f54f_36.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vcomp14-14.51.36231-h1b9f54f_36.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/virtualenv-21.3.3-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vs2015_runtime-14.51.36231-h84cd919_36.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.7.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/webcolors-25.10.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/webencodings-0.5.1-pyhd8ed1ab_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.9.0-pyhd8ed1ab_0.conda @@ -1637,20 +1639,20 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/zlib-1.3.2-hfd05255_2.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_6.conda - pypi: https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl - - pypi: git+https://github.com/OpenSourceEconomics/dags.git?rev=cf59c04#cf59c04c6ba07b7c54ca763dc155deea3341a480 - - pypi: https://files.pythonhosted.org/packages/43/f5/ee39c6e92acc742c052f137b47c210cd0a1b72dcd3f98495528bb4d27761/flatten_dict-0.4.2-py2.py3-none-any.whl + - pypi: git+https://github.com/OpenSourceEconomics/dags.git?branch=main#87ff051077f37470514808a3121e7c90429f6383 + - pypi: https://files.pythonhosted.org/packages/72/9f/485516087cd8c44183aaf9ab850247a28e2e4a42a4d62eab77c21f673450/flatten_dict-0.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/70/aa/dfac6d72cc35bc07e7587115b6946e333ef4ccb2e6cd26ecf639438c5d26/jax-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/24/08/26e6a3ecf0a95f1ec0dcd7a668d5c9a72e581c40fe4ae51e102ca63174c5/jaxlib-0.10.0-cp314-cp314-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/94/05/3e39d416fb92b2738a76e8265e6bfc5d10542f90a7c32ad1eb831eea3fa3/jaxtyping-0.3.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e9/93/2bfed22d2498c468f6bcd0d9f56b033eaa19f33320389314c19ef6766413/ml_dtypes-0.5.4-cp314-cp314-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/23/cd/066e86230ae37ed0be70aae89aabf03ca8d9f39c8aea0dec8029455b5540/opt_einsum-3.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/db/60/aba6a38de456e7341285102bede27514795c1eaa353bc0e7638b6b785356/pandas-3.0.2-cp314-cp314-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/38/55/792619469bab9882d8bbd5865d45a72f6478762d04a9af4bf0d08c503e95/pandas-3.0.3-cp314-cp314-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/51/fe/53ac0cd932db5dcaf55961bc7cb7afdca8d80d8cc7406ed661f0c7dc111a/pdbp-1.8.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/da/9e/21c4088d1d433cfbf0578f138a6df59527dbd9e9d67355ee31b17e6dc774/portion-2.6.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/5a/dc/491b7661614ab97483abf2056be1deee4dc2490ecbf7bff9ab5cdbac86e1/pyreadline3-3.5.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f7/5e/35c856e186b74678c24927847ad9895a51f1bc02a0c6126477a6c6040064/pyreadline3-3.5.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/65/44/bb509c3d2c0b5a87e7a5af1d5917a402a32ff026f777a6d7cb6990746cbb/tabcompleter-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cb/fc/8c82be70b8f96d09943360f34cfb2ecdd3035294c51bce4131eeabe56645/tabcompleter-1.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8d/96/04e7b441807b26b794da5b11e59ed7f83b2cf8af202bd7eba8ad2fa6046e/wadler_lindig-0.1.7-py3-none-any.whl - pypi: ./ docs: @@ -1673,26 +1675,26 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/asv_runner-0.2.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/async-lru-2.3.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-26.1.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-auth-0.10.1-h2d2dd48_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-auth-0.10.1-ha62d5e7_3.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-cal-0.9.13-h2c9d079_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-common-0.12.6-hb03c661_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-compression-0.3.2-h8b1a151_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-event-stream-0.6.0-h9b893ba_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-http-0.10.12-h4bacb7b_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-io-0.26.3-hc87160b_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-mqtt-0.15.2-he9ea9c5_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-s3-0.11.5-h6d69fc9_5.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-event-stream-0.7.0-h9b893ba_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-http-0.10.13-h4bacb7b_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-io-0.26.3-h692f434_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-mqtt-0.15.2-hc1936db_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-s3-0.12.2-he6ee468_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-sdkutils-0.2.4-h8b1a151_4.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-checksums-0.2.10-h8b1a151_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-crt-cpp-0.37.4-h4c8aef7_3.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-sdk-cpp-1.11.747-hc3785e1_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-crt-cpp-0.38.3-h745e52d_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-sdk-cpp-1.11.747-h41c0014_4.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/azure-core-cpp-1.16.2-h206d751_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/azure-identity-cpp-1.13.3-hed0cdb0_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/azure-storage-blobs-cpp-12.16.0-hdd73cc9_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/azure-storage-common-cpp-12.12.0-ha7a2c86_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/azure-storage-files-datalake-cpp-12.14.0-h52c5a47_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/azure-storage-blobs-cpp-12.16.0-hf824e48_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/azure-storage-common-cpp-12.13.0-ha7a2c86_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/azure-storage-files-datalake-cpp-12.14.0-h539c000_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.18.0-pyhcf101f3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/backports.zstd-1.3.0-py314h680f03e_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/backports.zstd-1.5.0-py314h680f03e_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.14.3-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-6.3.0-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-with-css-6.3.0-hbca2aae_1.conda @@ -1709,7 +1711,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.14.4-py314hd8ed1ab_100.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/debugpy-1.8.20-py314h42812f9_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.3.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/distlib-0.4.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda @@ -1721,7 +1723,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/h5py-3.16.0-nompi_py314hddf7a69_102.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/hdf5-2.1.0-nompi_hd4fcb43_104.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/hdf5-2.1.0-nompi_h87a9417_105.conda - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda @@ -1730,7 +1732,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.13-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.8.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-7.2.0-pyha191276_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.12.0-pyhecfbec7_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.13.0-pyh53cf698_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ipython_pygments_lexers-1.1.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/isoduration-20.11.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jedi-0.19.2-pyhd8ed1ab_1.conda @@ -1740,14 +1742,14 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-4.26.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-specifications-2025.9.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-with-format-nongpl-4.26.0-hcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter-book-2.1.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter-book-2.1.5-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter-lsp-2.3.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_client-8.8.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_core-5.9.1-pyhc90fa1f_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_events-0.12.1-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.17.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.18.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server_terminals-0.5.4-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.5.6-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.5.7-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_pygments-0.3.0-pyhd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_server-2.28.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.3-hb9d3cd8_0.conda @@ -1756,48 +1758,48 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45.1-default_hbd61a6d_102.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libabseil-20260107.1-cxx17_h7b12aa8_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libaec-1.1.5-h088129d_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-24.0.0-ha7f89c6_0_cpu.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-acero-24.0.0-h635bf11_0_cpu.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-compute-24.0.0-h53684a4_0_cpu.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-dataset-24.0.0-h635bf11_0_cpu.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-substrait-24.0.0-hb4dd7c2_0_cpu.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.11.0-6_h4a7cf45_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-24.0.0-h0935d00_1_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-acero-24.0.0-h635bf11_1_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-compute-24.0.0-h53684a4_1_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-dataset-24.0.0-h635bf11_1_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-substrait-24.0.0-hb4dd7c2_1_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.11.0-7_h4a7cf45_openblas.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.2.0-hb03c661_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.2.0-hb03c661_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.2.0-hb03c661_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.11.0-6_h0358290_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.11.0-7_h0358290_openblas.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libcrc32c-1.1.2-h9c3ff4c_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcurl-8.19.0-hcf29cc6_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcurl-8.20.0-hcf29cc6_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20250104-pl5321h7949ede_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-hd590300_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libevent-2.1.12-hf998b51_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.5-hecca717_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.8.0-hecca717_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.5.2-h3435931_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-he0feb66_18.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.2.0-h69a702a_18.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-15.2.0-h69a702a_18.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.2.0-h68bc16d_18.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-he0feb66_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-he0feb66_19.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.2.0-h69a702a_19.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-15.2.0-h69a702a_19.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.2.0-h68bc16d_19.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-he0feb66_19.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgoogle-cloud-3.3.0-h25dbb67_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgoogle-cloud-storage-3.3.0-hdbdcf42_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgrpc-1.78.1-h1d1128b_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.18-h3b78370_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.11.0-6_h47877c9_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.11.0-7_h47877c9_openblas.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.3-hb03c661_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libmpdec-4.0.0-hb03c661_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.68.1-h877daf1_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.32-pthreads_h94d23a6_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.33-pthreads_h94d23a6_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libopentelemetry-cpp-1.26.0-h9692893_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libopentelemetry-cpp-headers-1.26.0-ha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libparquet-24.0.0-h7376487_0_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libparquet-24.0.0-h7376487_1_cpu.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libprotobuf-6.33.5-h2b00c02_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libre2-11-2025.11.05-h0dc7533_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libsodium-1.0.21-h280c20c_3.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.53.0-hf4e2dac_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.53.1-h0c1763c_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.11.1-hcf80075_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h934c35e_18.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.2.0-hdf11a46_18.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libthrift-0.22.0-h454ac66_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h934c35e_19.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.2.0-hdf11a46_19.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libthrift-0.22.0-h7d032f7_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libutf8proc-2.11.3-hfe17d71_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.42-h5347b49_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libuv-1.51.0-hb03c661_1.conda @@ -1806,30 +1808,30 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.2-h25fd6f3_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.10.0-h5888daf_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.3-py314h67df5f8_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.2.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/memory_profiler-0.61.0-pyhcf101f3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.2.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/mystmd-1.8.3-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/narwhals-2.20.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.2.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/mystmd-1.9.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/narwhals-2.21.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbclient-0.10.4-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.17.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbformat-5.10.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.6-hdb14827_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nest-asyncio-1.6.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/nlohmann_json-3.12.0-h54a6638_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/nodejs-25.8.2-he4ff34a_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/notebook-shim-0.2.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.4.3-py314h2b28147_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.4.5-py314h2b28147_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.2-h35e630c_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/orc-2.3.0-h21090e2_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/overrides-7.7.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.1-pyhc364b38_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.2-pyhc364b38_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pandocfilters-1.5.0-pyhd8ed1ab_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.6-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.7-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pexpect-4.9.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.9.6-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/plotly-6.6.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/prek-0.3.10-hb17b654_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/prek-0.4.0-hb17b654_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/prometheus-cpp-1.3.0-ha5d0236_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.25.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.52-pyha770c72_0.conda @@ -1845,23 +1847,23 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.14.4-habeac84_100_cp314.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-discovery-1.2.2-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-discovery-1.3.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.21.2-pyhe01879c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.14.4-h4df99d1_100.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-2.0.7-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2026.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-3.2.1-pyh332efcf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2026.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.14-8_cp314.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.3-py314h67df5f8_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pyzmq-27.1.0-py312hda471dd_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/re2-2025.11.05-h5301d42_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.3-h853b02a_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.37.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.33.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.34.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3339-validator-0.1.4-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3986-validator-0.1.1-pyh9f0ad1d_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3987-syntax-1.1.0-pyhe01879c_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/rpds-py-0.30.0-py314h2e6c369_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/s2n-1.7.1-h1cbb8d7_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/s2n-1.7.2-hc5a330e_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/scipy-1.17.1-py314hf07bd8e_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-2.1.0-pyha191276_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-82.0.1-pyh332efcf_0.conda @@ -1877,15 +1879,15 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h366c992_103.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/tornado-6.5.5-py314h5bd0f2a_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.14.3-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.15.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing_utils-0.1.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/uri-template-1.3.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.6.3-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/virtualenv-21.2.4-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.6.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.7.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/virtualenv-21.3.3-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.7.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/webcolors-25.10.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/webencodings-0.5.1-pyhd8ed1ab_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.9.0-pyhd8ed1ab_0.conda @@ -1895,8 +1897,8 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-1.3.2-h25fd6f3_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda - pypi: https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl - - pypi: git+https://github.com/OpenSourceEconomics/dags.git?rev=cf59c04#cf59c04c6ba07b7c54ca763dc155deea3341a480 - - pypi: https://files.pythonhosted.org/packages/43/f5/ee39c6e92acc742c052f137b47c210cd0a1b72dcd3f98495528bb4d27761/flatten_dict-0.4.2-py2.py3-none-any.whl + - pypi: git+https://github.com/OpenSourceEconomics/dags.git?branch=main#87ff051077f37470514808a3121e7c90429f6383 + - pypi: https://files.pythonhosted.org/packages/72/9f/485516087cd8c44183aaf9ab850247a28e2e4a42a4d62eab77c21f673450/flatten_dict-0.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/70/aa/dfac6d72cc35bc07e7587115b6946e333ef4ccb2e6cd26ecf639438c5d26/jax-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a1/8e/b2a08ffc51c93842de71f7f988865cebfa7f43d6721957812dc8cc8b9d40/jaxlib-0.10.0-cp314-cp314-manylinux_2_27_x86_64.whl - pypi: https://files.pythonhosted.org/packages/94/05/3e39d416fb92b2738a76e8265e6bfc5d10542f90a7c32ad1eb831eea3fa3/jaxtyping-0.3.9-py3-none-any.whl @@ -1904,15 +1906,15 @@ environments: - pypi: https://files.pythonhosted.org/packages/c6/bb/82c7dcf38070b46172a517e2334e665c5bf374a262f99a283ea454bece7c/ml_dtypes-0.5.4-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/24/8d/e12d6ff4b9119db3cbf7b2db1ce257576441bd3c76388c786dea74f20b02/numba-0.65.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/87/96/f3eb235fafa82a34e2ab5dd7dc9ffff998ebf5f0bbc23fa56a96aeb44da6/numba-0.65.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - pypi: https://files.pythonhosted.org/packages/23/cd/066e86230ae37ed0be70aae89aabf03ca8d9f39c8aea0dec8029455b5540/opt_einsum-3.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/15/88/3cdd54fa279341afa10acf8d2b503556b1375245dccc9315659f795dd2e9/pandas-3.0.2-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/58/3b/1cdec6772bdbaf7b25dab360c59f03cadf05492dd724c6540af905389b07/pandas-3.0.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/51/fe/53ac0cd932db5dcaf55961bc7cb7afdca8d80d8cc7406ed661f0c7dc111a/pdbp-1.8.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/da/9e/21c4088d1d433cfbf0578f138a6df59527dbd9e9d67355ee31b17e6dc774/portion-2.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/3b/ef57eeb4c6f188fbe756b602dc6afa9d66eb0c2e77f02a3d4d51bfec2aaa/quantecon-0.11.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/65/44/bb509c3d2c0b5a87e7a5af1d5917a402a32ff026f777a6d7cb6990746cbb/tabcompleter-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cb/fc/8c82be70b8f96d09943360f34cfb2ecdd3035294c51bce4131eeabe56645/tabcompleter-1.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8d/96/04e7b441807b26b794da5b11e59ed7f83b2cf8af202bd7eba8ad2fa6046e/wadler_lindig-0.1.7-py3-none-any.whl - pypi: ./ osx-arm64: @@ -1928,26 +1930,26 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/asv_runner-0.2.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/async-lru-2.3.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-26.1.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-auth-0.10.1-hcb83491_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-auth-0.10.1-ha7d4cc1_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-cal-0.9.13-h6ee9776_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-common-0.12.6-hc919400_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-compression-0.3.2-h3e7f9b5_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-event-stream-0.6.0-h351c84d_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-http-0.10.12-h95cdebe_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-io-0.26.3-h4137820_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-mqtt-0.15.2-h69e7467_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-s3-0.11.5-ha5d16b2_5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-event-stream-0.7.0-h351c84d_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-http-0.10.13-h95cdebe_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-io-0.26.3-h4137820_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-mqtt-0.15.2-h8860bc9_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-s3-0.12.2-h07b101a_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-sdkutils-0.2.4-h16f91aa_4.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-checksums-0.2.10-h3e7f9b5_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-crt-cpp-0.37.4-h5505c15_3.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-sdk-cpp-1.11.747-had22720_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-crt-cpp-0.38.3-hba17502_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-sdk-cpp-1.11.747-h30a6df1_4.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/azure-core-cpp-1.16.2-he5ae378_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/azure-identity-cpp-1.13.3-h810541e_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/azure-storage-blobs-cpp-12.16.0-hc57151b_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/azure-storage-common-cpp-12.12.0-he467506_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/azure-storage-files-datalake-cpp-12.14.0-hf8a9d22_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/azure-storage-blobs-cpp-12.16.0-h5446563_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/azure-storage-common-cpp-12.13.0-he467506_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/azure-storage-files-datalake-cpp-12.14.0-hdc9d693_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.18.0-pyhcf101f3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/backports.zstd-1.3.0-py314h680f03e_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/backports.zstd-1.5.0-py314h680f03e_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.14.3-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-6.3.0-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-with-css-6.3.0-hbca2aae_1.conda @@ -1964,7 +1966,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.14.4-py314hd8ed1ab_100.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/debugpy-1.8.20-py314he609de1_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.3.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/distlib-0.4.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda @@ -1976,7 +1978,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/h5py-3.16.0-nompi_py314h658a3ac_102.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/hdf5-2.1.0-nompi_hc95e3eb_104.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/hdf5-2.1.0-nompi_he586413_105.conda - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda @@ -1985,7 +1987,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.13-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.8.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-7.2.0-pyh5552912_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.12.0-pyhecfbec7_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.13.0-pyh53cf698_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ipython_pygments_lexers-1.1.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/isoduration-20.11.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jedi-0.19.2-pyhd8ed1ab_1.conda @@ -1995,91 +1997,91 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-4.26.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-specifications-2025.9.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-with-format-nongpl-4.26.0-hcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter-book-2.1.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter-book-2.1.5-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter-lsp-2.3.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_client-8.8.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_core-5.9.1-pyhc90fa1f_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_events-0.12.1-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.17.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.18.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server_terminals-0.5.4-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.5.6-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.5.7-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_pygments-0.3.0-pyhd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_server-2.28.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/krb5-1.22.2-h385eeb1_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/lark-1.3.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libabseil-20260107.1-cxx17_h2062a1b_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libaec-1.1.5-h8664d51_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libarrow-24.0.0-h2124f06_0_cpu.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libarrow-acero-24.0.0-hee8fe31_0_cpu.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libarrow-compute-24.0.0-h3b6a98a_0_cpu.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libarrow-dataset-24.0.0-hee8fe31_0_cpu.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libarrow-substrait-24.0.0-h05be00f_0_cpu.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libblas-3.11.0-6_h51639a9_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libarrow-24.0.0-h37fbca7_1_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libarrow-acero-24.0.0-hee8fe31_1_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libarrow-compute-24.0.0-h3b6a98a_1_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libarrow-dataset-24.0.0-hee8fe31_1_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libarrow-substrait-24.0.0-h05be00f_1_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libblas-3.11.0-7_h51639a9_openblas.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlicommon-1.2.0-hc919400_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlidec-1.2.0-hc919400_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlienc-1.2.0-hc919400_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcblas-3.11.0-6_hb0561ab_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcblas-3.11.0-7_hb0561ab_openblas.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcrc32c-1.1.2-hbdafb3b_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcurl-8.19.0-hd5a2499_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-22.1.4-h55c6f16_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcurl-8.20.0-hd5a2499_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-22.1.5-h55c6f16_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libedit-3.1.20250104-pl5321hafb1f1b_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libev-4.33-h93a5062_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libevent-2.1.12-h2757513_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.7.5-hf6b4638_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.8.0-hf6b4638_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.5.2-hcf2aa1b_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgcc-15.2.0-hcbb3090_18.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran-15.2.0-h07b0088_18.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran5-15.2.0-hdae7583_18.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgcc-15.2.0-hcbb3090_19.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran-15.2.0-h07b0088_19.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran5-15.2.0-hdae7583_19.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgoogle-cloud-3.3.0-he41eb1d_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgoogle-cloud-storage-3.3.0-ha114238_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgrpc-1.78.1-h3e3f78d_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libiconv-1.18-h23cfdf5_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblapack-3.11.0-6_hd9741b5_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblapack-3.11.0-7_hd9741b5_openblas.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblzma-5.8.3-h8088a28_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libmpdec-4.0.0-h84a0fba_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libnghttp2-1.68.1-h8f3e76b_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libopenblas-0.3.32-openmp_he657e61_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libopenblas-0.3.33-openmp_he657e61_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libopentelemetry-cpp-1.26.0-h08d5cc3_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libopentelemetry-cpp-headers-1.26.0-hce30654_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libparquet-24.0.0-h16c0493_0_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libparquet-24.0.0-h16c0493_1_cpu.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libprotobuf-6.33.5-h4a5acfd_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libre2-11-2025.11.05-h4c27e2a_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsodium-1.0.21-h1a92334_3.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.53.0-h1b79a29_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.53.1-h1b79a29_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libssh2-1.11.1-h1590b86_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libthrift-0.22.0-h14a376c_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libthrift-0.22.0-h1fb9c8a_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libutf8proc-2.11.3-h2431656_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libuv-1.51.0-h6caf38d_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libxml2-16-2.15.3-h5ef1a60_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libxml2-2.15.3-h5654f7c_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzlib-1.3.2-h8088a28_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/llvm-openmp-22.1.4-hc7d1edf_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/llvm-openmp-22.1.5-hc7d1edf_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/lz4-c-1.10.0-h286801f_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/markupsafe-3.0.3-py314h6e9b3f0_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.2.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/memory_profiler-0.61.0-pyhcf101f3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.2.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/mystmd-1.8.3-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/narwhals-2.20.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.2.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/mystmd-1.9.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/narwhals-2.21.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbclient-0.10.4-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.17.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbformat-5.10.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ncurses-6.5-h5e97a16_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ncurses-6.6-h1d4f5a5_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nest-asyncio-1.6.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/nlohmann_json-3.12.0-h784d473_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/nodejs-25.8.2-h7039424_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/notebook-shim-0.2.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/numpy-2.4.3-py314h1569ea8_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/numpy-2.4.5-py314hb79c6fa_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.6.2-hd24854e_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/orc-2.3.0-hd11884d_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/overrides-7.7.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.1-pyhc364b38_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.2-pyhc364b38_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pandocfilters-1.5.0-pyhd8ed1ab_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.6-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.7-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pexpect-4.9.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.9.6-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/plotly-6.6.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/prek-0.3.10-h6fdd925_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/prek-0.4.0-h6fdd925_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/prometheus-cpp-1.3.0-h0967b3e_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.25.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.52-pyha770c72_0.conda @@ -2097,18 +2099,18 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.14.4-h4c637c5_100_cp314.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-discovery-1.2.2-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-discovery-1.3.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.21.2-pyhe01879c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.14.4-h4df99d1_100.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-2.0.7-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2026.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-3.2.1-pyh332efcf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2026.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.14-8_cp314.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyyaml-6.0.3-py314h6e9b3f0_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyzmq-27.1.0-py312h022ad19_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/re2-2025.11.05-ha480c28_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/readline-8.3-h46df422_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.37.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.33.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.34.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3339-validator-0.1.4-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3986-validator-0.1.1-pyh9f0ad1d_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3987-syntax-1.1.0-pyhe01879c_1.conda @@ -2128,15 +2130,15 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-h010d191_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tornado-6.5.5-py314h6c2aa35_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.14.3-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.15.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing_utils-0.1.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/uri-template-1.3.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.6.3-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/virtualenv-21.2.4-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.6.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.7.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/virtualenv-21.3.3-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.7.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/webcolors-25.10.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/webencodings-0.5.1-pyhd8ed1ab_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.9.0-pyhd8ed1ab_0.conda @@ -2146,8 +2148,8 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zlib-1.3.2-h8088a28_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda - pypi: https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl - - pypi: git+https://github.com/OpenSourceEconomics/dags.git?rev=cf59c04#cf59c04c6ba07b7c54ca763dc155deea3341a480 - - pypi: https://files.pythonhosted.org/packages/43/f5/ee39c6e92acc742c052f137b47c210cd0a1b72dcd3f98495528bb4d27761/flatten_dict-0.4.2-py2.py3-none-any.whl + - pypi: git+https://github.com/OpenSourceEconomics/dags.git?branch=main#87ff051077f37470514808a3121e7c90429f6383 + - pypi: https://files.pythonhosted.org/packages/72/9f/485516087cd8c44183aaf9ab850247a28e2e4a42a4d62eab77c21f673450/flatten_dict-0.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/70/aa/dfac6d72cc35bc07e7587115b6946e333ef4ccb2e6cd26ecf639438c5d26/jax-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a7/25/e1e52a21786b321fb6a2edf9ef9971aa70f06bb2738aef9afd6d8f46a441/jaxlib-0.10.0-cp314-cp314-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/94/05/3e39d416fb92b2738a76e8265e6bfc5d10542f90a7c32ad1eb831eea3fa3/jaxtyping-0.3.9-py3-none-any.whl @@ -2155,15 +2157,15 @@ environments: - pypi: https://files.pythonhosted.org/packages/72/4e/1339dc6e2557a344f5ba5590872e80346f76f6cb2ac3dd16e4666e88818c/ml_dtypes-0.5.4-cp314-cp314-macosx_10_13_universal2.whl - pypi: https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/56/a4/90edb01e9176053578e343d7a7276bc28356741ee67059aed8ed2c1a4e59/numba-0.65.0-cp314-cp314-macosx_12_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/4f/2e/8aed9b726d9ba5f11ad287645fd479e88278db3060a25cb1225d730eb2b7/numba-0.65.1-cp314-cp314-macosx_12_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/23/cd/066e86230ae37ed0be70aae89aabf03ca8d9f39c8aea0dec8029455b5540/opt_einsum-3.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/95/25/bdb9326c3b5455f8d4d3549fce7abcf967259de146fe2cf7a82368141948/pandas-3.0.2-cp314-cp314-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/68/10/bf2d6738d72748b961a3751ab89522d58c54efc36a8e1a12161216cd45cf/pandas-3.0.3-cp314-cp314-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/51/fe/53ac0cd932db5dcaf55961bc7cb7afdca8d80d8cc7406ed661f0c7dc111a/pdbp-1.8.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/da/9e/21c4088d1d433cfbf0578f138a6df59527dbd9e9d67355ee31b17e6dc774/portion-2.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/3b/ef57eeb4c6f188fbe756b602dc6afa9d66eb0c2e77f02a3d4d51bfec2aaa/quantecon-0.11.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/65/44/bb509c3d2c0b5a87e7a5af1d5917a402a32ff026f777a6d7cb6990746cbb/tabcompleter-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cb/fc/8c82be70b8f96d09943360f34cfb2ecdd3035294c51bce4131eeabe56645/tabcompleter-1.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8d/96/04e7b441807b26b794da5b11e59ed7f83b2cf8af202bd7eba8ad2fa6046e/wadler_lindig-0.1.7-py3-none-any.whl - pypi: ./ win-64: @@ -2177,26 +2179,26 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/asv_runner-0.2.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/async-lru-2.3.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-26.1.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-auth-0.10.1-h5d51246_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-auth-0.10.1-h8b39d88_3.conda - conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-cal-0.9.13-h46f3b43_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-common-0.12.6-hfd05255_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-compression-0.3.2-hcb3a2da_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-event-stream-0.6.0-h87b2689_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-http-0.10.12-h612f3e8_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-io-0.26.3-h0d5b9f9_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-mqtt-0.15.2-h904b250_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-s3-0.11.5-h87bd87b_5.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-event-stream-0.7.0-h87b2689_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-http-0.10.13-h612f3e8_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-io-0.26.3-h0d5b9f9_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-mqtt-0.15.2-h82175e6_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-s3-0.12.2-h61b906f_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-sdkutils-0.2.4-hcb3a2da_4.conda - conda: https://conda.anaconda.org/conda-forge/win-64/aws-checksums-0.2.10-hcb3a2da_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/aws-crt-cpp-0.37.4-h4f72eff_3.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/aws-sdk-cpp-1.11.747-hd55a107_3.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/aws-crt-cpp-0.38.3-h1db8845_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/aws-sdk-cpp-1.11.747-hd63e0c5_4.conda - conda: https://conda.anaconda.org/conda-forge/win-64/azure-core-cpp-1.16.2-h49e36cd_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/azure-identity-cpp-1.13.3-h5ffce34_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/azure-storage-blobs-cpp-12.16.0-hcd625b1_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/azure-storage-common-cpp-12.12.0-h5ffce34_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/azure-storage-files-datalake-cpp-12.14.0-h1678c0b_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/azure-storage-blobs-cpp-12.16.0-h81bf7d1_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/azure-storage-common-cpp-12.13.0-h5ffce34_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/azure-storage-files-datalake-cpp-12.14.0-hab49af2_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.18.0-pyhcf101f3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/backports.zstd-1.3.0-py314h680f03e_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/backports.zstd-1.5.0-py314h680f03e_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.14.3-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-6.3.0-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-with-css-6.3.0-hbca2aae_1.conda @@ -2214,7 +2216,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.14.4-py314hd8ed1ab_100.conda - conda: https://conda.anaconda.org/conda-forge/win-64/debugpy-1.8.20-py314hb98de8c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.3.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/distlib-0.4.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda @@ -2224,7 +2226,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/h5py-3.16.0-nompi_py314h02517ec_102.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/hdf5-2.1.0-nompi_hd96b29f_104.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/hdf5-2.1.0-nompi_h0a39f1e_105.conda - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda @@ -2233,7 +2235,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.13-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.8.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-7.2.0-pyh6dadd2b_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.12.0-pyhccfa634_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.13.0-pyhe2676ad_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ipython_pygments_lexers-1.1.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/isoduration-20.11.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jedi-0.19.2-pyhd8ed1ab_1.conda @@ -2243,66 +2245,66 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-4.26.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-specifications-2025.9.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-with-format-nongpl-4.26.0-hcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter-book-2.1.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter-book-2.1.5-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter-lsp-2.3.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_client-8.8.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_core-5.9.1-pyh6dadd2b_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_events-0.12.1-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.17.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.18.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server_terminals-0.5.4-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.5.6-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.5.7-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_pygments-0.3.0-pyhd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_server-2.28.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/krb5-1.22.2-h0ea6238_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/lark-1.3.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libabseil-20260107.1-cxx17_h0eb2380_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libaec-1.1.5-haf901d7_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libarrow-24.0.0-hc74aee5_0_cpu.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libarrow-acero-24.0.0-h7d8d6a5_0_cpu.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libarrow-compute-24.0.0-h081cd8e_0_cpu.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libarrow-dataset-24.0.0-h7d8d6a5_0_cpu.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libarrow-substrait-24.0.0-h524e9bd_0_cpu.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libblas-3.11.0-6_hf2e6a31_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libarrow-24.0.0-h37f918f_1_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libarrow-acero-24.0.0-h7d8d6a5_1_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libarrow-compute-24.0.0-h081cd8e_1_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libarrow-dataset-24.0.0-h7d8d6a5_1_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libarrow-substrait-24.0.0-h524e9bd_1_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libblas-3.11.0-7_h8455456_mkl.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libbrotlicommon-1.2.0-hfd05255_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libbrotlidec-1.2.0-hfd05255_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libbrotlienc-1.2.0-hfd05255_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libcblas-3.11.0-6_h2a3cdd5_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libcblas-3.11.0-7_h2a3cdd5_mkl.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libcrc32c-1.1.2-h0e60522_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/win-64/libcurl-8.19.0-h8206538_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libcurl-8.20.0-h8206538_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libevent-2.1.12-h3671451_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libexpat-2.7.5-hac47afa_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libexpat-2.8.0-hac47afa_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libffi-3.5.2-h3d046cb_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libgoogle-cloud-3.3.0-h2b231ac_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libgoogle-cloud-storage-3.3.0-he04ea4c_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libgrpc-1.78.1-h9ff2b3e_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libhwloc-2.12.2-default_h4379cf1_1000.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libhwloc-2.13.0-default_h049141e_1000.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libiconv-1.18-hc1393d2_2.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/liblapack-3.11.0-6_hf9ab0e9_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/liblapack-3.11.0-7_hf9ab0e9_mkl.conda - conda: https://conda.anaconda.org/conda-forge/win-64/liblzma-5.8.3-hfd05255_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libmpdec-4.0.0-hfd05255_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libopentelemetry-cpp-1.26.0-hc88f397_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libopentelemetry-cpp-headers-1.26.0-h57928b3_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libparquet-24.0.0-h7051d1f_0_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libparquet-24.0.0-h7051d1f_1_cpu.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libprotobuf-6.33.5-h61fc761_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libre2-11-2025.11.05-h04e5de1_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libsodium-1.0.21-h6a83c73_3.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libsqlite-3.53.0-hf5d6505_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libsqlite-3.53.1-hf5d6505_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libssh2-1.11.1-h9aa295b_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libthrift-0.22.0-h23985f6_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libthrift-0.22.0-h2e43b2f_2.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libutf8proc-2.11.3-hb980946_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libwinpthread-12.0.0.r4.gg4f2fc60ca-h57928b3_10.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-16-2.15.3-h3cfd58e_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-2.15.3-h8ef44ab_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libzlib-1.3.2-hfd05255_2.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/llvm-openmp-22.1.4-h4fa8253_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/llvm-openmp-22.1.5-h4fa8253_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/lz4-c-1.10.0-h2466b09_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/markupsafe-3.0.3-py314h2359020_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.2.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/memory_profiler-0.61.0-pyhcf101f3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.2.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/mkl-2025.3.1-hac47afa_12.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/mystmd-1.8.3-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/narwhals-2.20.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.2.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/mkl-2026.0.0-hac47afa_906.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/mystmd-1.9.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/narwhals-2.21.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbclient-0.10.4-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.17.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbformat-5.10.4-pyhd8ed1ab_1.conda @@ -2310,17 +2312,17 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/nlohmann_json-3.12.0-h5112557_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/nodejs-25.8.2-h80d1838_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/notebook-shim-0.2.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/numpy-2.4.3-py314h02f10f6_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/onemkl-license-2025.3.1-h57928b3_12.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/numpy-2.4.5-py314h02f10f6_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/onemkl-license-2026.0.0-h57928b3_906.conda - conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.6.2-hf411b9b_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/orc-2.3.0-h8fc0eb6_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/overrides-7.7.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.1-pyhc364b38_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.2-pyhc364b38_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pandocfilters-1.5.0-pyhd8ed1ab_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.6-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.7-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.9.6-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/plotly-6.6.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/prek-0.3.10-h18a1a76_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/prek-0.4.0-h18a1a76_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/prometheus-cpp-1.3.0-hcea2f5d_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.25.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.52-pyha770c72_0.conda @@ -2335,11 +2337,11 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyh09c184e_7.conda - conda: https://conda.anaconda.org/conda-forge/win-64/python-3.14.4-h4b44e0e_100_cp314.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-discovery-1.2.2-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-discovery-1.3.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.21.2-pyhe01879c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.14.4-h4df99d1_100.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-2.0.7-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2026.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-3.2.1-pyh332efcf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2026.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.14-8_cp314.conda - conda: https://conda.anaconda.org/conda-forge/win-64/pywin32-311-py314h8f8f202_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/pywinpty-2.0.15-py314h51f0985_1.conda @@ -2347,7 +2349,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/pyzmq-27.1.0-py312h343a6d4_2.conda - conda: https://conda.anaconda.org/conda-forge/win-64/re2-2025.11.05-ha104f34_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.37.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.33.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.34.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3339-validator-0.1.4-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3986-validator-0.1.1-pyh9f0ad1d_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3987-syntax-1.1.0-pyhe01879c_1.conda @@ -2362,26 +2364,26 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.8.3-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/stack_data-0.6.3-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tabulate-0.10.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/tbb-2022.3.0-h3155e25_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/tbb-2023.0.0-hd3d4ead_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/terminado-0.18.1-pyh6dadd2b_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tinycss2-1.4.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/tk-8.6.13-h6ed50ae_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/tornado-6.5.5-py314h5a2d7ad_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.14.3-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.15.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing_utils-0.1.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/ucrt-10.0.26100.0-h57928b3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/uri-template-1.3.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.6.3-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/vc-14.3-h41ae7f8_34.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.44.35208-h818238b_34.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/vcomp14-14.44.35208-h818238b_34.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/virtualenv-21.2.4-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/vs2015_runtime-14.44.35208-h38c0c73_34.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.6.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.7.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vc-14.5-h1b7c187_36.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.51.36231-h1b9f54f_36.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vcomp14-14.51.36231-h1b9f54f_36.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/virtualenv-21.3.3-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vs2015_runtime-14.51.36231-h84cd919_36.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.7.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/webcolors-25.10.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/webencodings-0.5.1-pyhd8ed1ab_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.9.0-pyhd8ed1ab_0.conda @@ -2393,8 +2395,8 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/zlib-1.3.2-hfd05255_2.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_6.conda - pypi: https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl - - pypi: git+https://github.com/OpenSourceEconomics/dags.git?rev=cf59c04#cf59c04c6ba07b7c54ca763dc155deea3341a480 - - pypi: https://files.pythonhosted.org/packages/43/f5/ee39c6e92acc742c052f137b47c210cd0a1b72dcd3f98495528bb4d27761/flatten_dict-0.4.2-py2.py3-none-any.whl + - pypi: git+https://github.com/OpenSourceEconomics/dags.git?branch=main#87ff051077f37470514808a3121e7c90429f6383 + - pypi: https://files.pythonhosted.org/packages/72/9f/485516087cd8c44183aaf9ab850247a28e2e4a42a4d62eab77c21f673450/flatten_dict-0.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/70/aa/dfac6d72cc35bc07e7587115b6946e333ef4ccb2e6cd26ecf639438c5d26/jax-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/24/08/26e6a3ecf0a95f1ec0dcd7a668d5c9a72e581c40fe4ae51e102ca63174c5/jaxlib-0.10.0-cp314-cp314-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/94/05/3e39d416fb92b2738a76e8265e6bfc5d10542f90a7c32ad1eb831eea3fa3/jaxtyping-0.3.9-py3-none-any.whl @@ -2402,16 +2404,16 @@ environments: - pypi: https://files.pythonhosted.org/packages/e9/93/2bfed22d2498c468f6bcd0d9f56b033eaa19f33320389314c19ef6766413/ml_dtypes-0.5.4-cp314-cp314-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/73/5b/fbce55ce3d933afbc7ade04df826853e4a846aaa47d58d2fbb669b8f2d08/numba-0.65.0-cp314-cp314-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/56/46/3f7fc04fb853559e74b210e0b62c19974ec844cefec611f9e535f4da3761/numba-0.65.1-cp314-cp314-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/23/cd/066e86230ae37ed0be70aae89aabf03ca8d9f39c8aea0dec8029455b5540/opt_einsum-3.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/db/60/aba6a38de456e7341285102bede27514795c1eaa353bc0e7638b6b785356/pandas-3.0.2-cp314-cp314-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/38/55/792619469bab9882d8bbd5865d45a72f6478762d04a9af4bf0d08c503e95/pandas-3.0.3-cp314-cp314-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/51/fe/53ac0cd932db5dcaf55961bc7cb7afdca8d80d8cc7406ed661f0c7dc111a/pdbp-1.8.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/da/9e/21c4088d1d433cfbf0578f138a6df59527dbd9e9d67355ee31b17e6dc774/portion-2.6.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/5a/dc/491b7661614ab97483abf2056be1deee4dc2490ecbf7bff9ab5cdbac86e1/pyreadline3-3.5.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f7/5e/35c856e186b74678c24927847ad9895a51f1bc02a0c6126477a6c6040064/pyreadline3-3.5.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/3b/ef57eeb4c6f188fbe756b602dc6afa9d66eb0c2e77f02a3d4d51bfec2aaa/quantecon-0.11.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/65/44/bb509c3d2c0b5a87e7a5af1d5917a402a32ff026f777a6d7cb6990746cbb/tabcompleter-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cb/fc/8c82be70b8f96d09943360f34cfb2ecdd3035294c51bce4131eeabe56645/tabcompleter-1.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8d/96/04e7b441807b26b794da5b11e59ed7f83b2cf8af202bd7eba8ad2fa6046e/wadler_lindig-0.1.7-py3-none-any.whl - pypi: ./ metal: @@ -2435,26 +2437,26 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/asv_runner-0.2.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/async-lru-2.3.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-26.1.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-auth-0.10.1-hcb83491_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-auth-0.10.1-ha7d4cc1_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-cal-0.9.13-h6ee9776_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-common-0.12.6-hc919400_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-compression-0.3.2-h3e7f9b5_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-event-stream-0.6.0-h351c84d_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-http-0.10.12-h95cdebe_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-io-0.26.3-h4137820_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-mqtt-0.15.2-h69e7467_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-s3-0.11.5-ha5d16b2_5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-event-stream-0.7.0-h351c84d_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-http-0.10.13-h95cdebe_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-io-0.26.3-h4137820_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-mqtt-0.15.2-h8860bc9_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-s3-0.12.2-h07b101a_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-sdkutils-0.2.4-h16f91aa_4.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-checksums-0.2.10-h3e7f9b5_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-crt-cpp-0.37.4-h5505c15_3.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-sdk-cpp-1.11.747-had22720_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-crt-cpp-0.38.3-hba17502_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-sdk-cpp-1.11.747-h30a6df1_4.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/azure-core-cpp-1.16.2-he5ae378_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/azure-identity-cpp-1.13.3-h810541e_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/azure-storage-blobs-cpp-12.16.0-hc57151b_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/azure-storage-common-cpp-12.12.0-he467506_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/azure-storage-files-datalake-cpp-12.14.0-hf8a9d22_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/azure-storage-blobs-cpp-12.16.0-h5446563_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/azure-storage-common-cpp-12.13.0-he467506_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/azure-storage-files-datalake-cpp-12.14.0-hdc9d693_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.18.0-pyhcf101f3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/backports.zstd-1.3.0-py314h680f03e_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/backports.zstd-1.5.0-py314h680f03e_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.14.3-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-6.3.0-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-with-css-6.3.0-hbca2aae_1.conda @@ -2471,7 +2473,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.14.4-py314hd8ed1ab_100.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/debugpy-1.8.20-py314he609de1_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.3.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/distlib-0.4.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda @@ -2483,7 +2485,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/h5py-3.16.0-nompi_py314h658a3ac_102.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/hdf5-2.1.0-nompi_hc95e3eb_104.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/hdf5-2.1.0-nompi_he586413_105.conda - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda @@ -2491,7 +2493,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.13-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.8.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-7.2.0-pyh5552912_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.12.0-pyhecfbec7_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.13.0-pyh53cf698_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ipython_pygments_lexers-1.1.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/isoduration-20.11.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jedi-0.19.2-pyhd8ed1ab_1.conda @@ -2505,83 +2507,83 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_client-8.8.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_core-5.9.1-pyhc90fa1f_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_events-0.12.1-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.17.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.18.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server_terminals-0.5.4-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.5.6-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.5.7-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_pygments-0.3.0-pyhd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_server-2.28.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/krb5-1.22.2-h385eeb1_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/lark-1.3.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libabseil-20260107.1-cxx17_h2062a1b_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libaec-1.1.5-h8664d51_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libarrow-24.0.0-h2124f06_0_cpu.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libarrow-acero-24.0.0-hee8fe31_0_cpu.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libarrow-compute-24.0.0-h3b6a98a_0_cpu.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libarrow-dataset-24.0.0-hee8fe31_0_cpu.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libarrow-substrait-24.0.0-h05be00f_0_cpu.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libblas-3.11.0-6_h51639a9_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libarrow-24.0.0-h37fbca7_1_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libarrow-acero-24.0.0-hee8fe31_1_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libarrow-compute-24.0.0-h3b6a98a_1_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libarrow-dataset-24.0.0-hee8fe31_1_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libarrow-substrait-24.0.0-h05be00f_1_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libblas-3.11.0-7_h51639a9_openblas.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlicommon-1.2.0-hc919400_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlidec-1.2.0-hc919400_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlienc-1.2.0-hc919400_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcblas-3.11.0-6_hb0561ab_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcblas-3.11.0-7_hb0561ab_openblas.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcrc32c-1.1.2-hbdafb3b_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcurl-8.19.0-hd5a2499_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-22.1.4-h55c6f16_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcurl-8.20.0-hd5a2499_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-22.1.5-h55c6f16_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libedit-3.1.20250104-pl5321hafb1f1b_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libev-4.33-h93a5062_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libevent-2.1.12-h2757513_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.7.5-hf6b4638_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.8.0-hf6b4638_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.5.2-hcf2aa1b_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgcc-15.2.0-hcbb3090_18.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran-15.2.0-h07b0088_18.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran5-15.2.0-hdae7583_18.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgcc-15.2.0-hcbb3090_19.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran-15.2.0-h07b0088_19.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran5-15.2.0-hdae7583_19.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgoogle-cloud-3.3.0-he41eb1d_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgoogle-cloud-storage-3.3.0-ha114238_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgrpc-1.78.1-h3e3f78d_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libiconv-1.18-h23cfdf5_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblapack-3.11.0-6_hd9741b5_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblapack-3.11.0-7_hd9741b5_openblas.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblzma-5.8.3-h8088a28_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libmpdec-4.0.0-h84a0fba_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libnghttp2-1.68.1-h8f3e76b_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libopenblas-0.3.32-openmp_he657e61_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libopenblas-0.3.33-openmp_he657e61_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libopentelemetry-cpp-1.26.0-h08d5cc3_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libopentelemetry-cpp-headers-1.26.0-hce30654_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libparquet-24.0.0-h16c0493_0_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libparquet-24.0.0-h16c0493_1_cpu.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libprotobuf-6.33.5-h4a5acfd_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libre2-11-2025.11.05-h4c27e2a_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsodium-1.0.21-h1a92334_3.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.53.0-h1b79a29_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.53.1-h1b79a29_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libssh2-1.11.1-h1590b86_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libthrift-0.22.0-h14a376c_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libthrift-0.22.0-h1fb9c8a_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libutf8proc-2.11.3-h2431656_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libxml2-16-2.15.3-h6967ea9_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libxml2-2.15.3-heed7d32_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzlib-1.3.2-h8088a28_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/llvm-openmp-22.1.4-hc7d1edf_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/llvm-openmp-22.1.5-hc7d1edf_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/lz4-c-1.10.0-h286801f_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/markupsafe-3.0.3-py314h6e9b3f0_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.2.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/memory_profiler-0.61.0-pyhcf101f3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.2.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/narwhals-2.20.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.2.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/narwhals-2.21.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbclient-0.10.4-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.17.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbformat-5.10.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ncurses-6.5-h5e97a16_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ncurses-6.6-h1d4f5a5_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nest-asyncio-1.6.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/nlohmann_json-3.12.0-h784d473_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/notebook-shim-0.2.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/numpy-2.4.3-py314h1569ea8_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/numpy-2.4.5-py314hb79c6fa_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.6.2-hd24854e_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/orc-2.3.0-hd11884d_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/overrides-7.7.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.1-pyhc364b38_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.2-pyhc364b38_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pandocfilters-1.5.0-pyhd8ed1ab_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.6-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.7-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pexpect-4.9.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.9.6-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/plotly-6.6.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/prek-0.3.10-h6fdd925_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/prek-0.4.0-h6fdd925_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/prometheus-cpp-1.3.0-h0967b3e_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.25.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.52-pyha770c72_0.conda @@ -2599,18 +2601,18 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.14.4-h4c637c5_100_cp314.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-discovery-1.2.2-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-discovery-1.3.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.21.2-pyhe01879c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.14.4-h4df99d1_100.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-2.0.7-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2026.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-3.2.1-pyh332efcf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2026.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.14-8_cp314.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyyaml-6.0.3-py314h6e9b3f0_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyzmq-27.1.0-py312h022ad19_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/re2-2025.11.05-ha480c28_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/readline-8.3-h46df422_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.37.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.33.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.34.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3339-validator-0.1.4-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3986-validator-0.1.1-pyh9f0ad1d_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3987-syntax-1.1.0-pyhe01879c_1.conda @@ -2630,15 +2632,15 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-h010d191_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tornado-6.5.5-py314h6c2aa35_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.14.3-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.15.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing_utils-0.1.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/uri-template-1.3.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.6.3-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/virtualenv-21.2.4-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.6.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.7.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/virtualenv-21.3.3-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.7.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/webcolors-25.10.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/webencodings-0.5.1-pyhd8ed1ab_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.9.0-pyhd8ed1ab_0.conda @@ -2648,8 +2650,8 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zlib-1.3.2-h8088a28_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda - pypi: https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl - - pypi: git+https://github.com/OpenSourceEconomics/dags.git?rev=cf59c04#cf59c04c6ba07b7c54ca763dc155deea3341a480 - - pypi: https://files.pythonhosted.org/packages/43/f5/ee39c6e92acc742c052f137b47c210cd0a1b72dcd3f98495528bb4d27761/flatten_dict-0.4.2-py2.py3-none-any.whl + - pypi: git+https://github.com/OpenSourceEconomics/dags.git?branch=main#87ff051077f37470514808a3121e7c90429f6383 + - pypi: https://files.pythonhosted.org/packages/72/9f/485516087cd8c44183aaf9ab850247a28e2e4a42a4d62eab77c21f673450/flatten_dict-0.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/70/aa/dfac6d72cc35bc07e7587115b6946e333ef4ccb2e6cd26ecf639438c5d26/jax-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/09/dc/6d8fbfc29d902251cf333414cf7dcfaf4b252a9920c881354584ed36270d/jax_metal-0.1.1-py3-none-macosx_13_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/a7/25/e1e52a21786b321fb6a2edf9ef9971aa70f06bb2738aef9afd6d8f46a441/jaxlib-0.10.0-cp314-cp314-macosx_11_0_arm64.whl @@ -2657,11 +2659,11 @@ environments: - pypi: https://files.pythonhosted.org/packages/72/4e/1339dc6e2557a344f5ba5590872e80346f76f6cb2ac3dd16e4666e88818c/ml_dtypes-0.5.4-cp314-cp314-macosx_10_13_universal2.whl - pypi: https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/23/cd/066e86230ae37ed0be70aae89aabf03ca8d9f39c8aea0dec8029455b5540/opt_einsum-3.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/95/25/bdb9326c3b5455f8d4d3549fce7abcf967259de146fe2cf7a82368141948/pandas-3.0.2-cp314-cp314-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/68/10/bf2d6738d72748b961a3751ab89522d58c54efc36a8e1a12161216cd45cf/pandas-3.0.3-cp314-cp314-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/51/fe/53ac0cd932db5dcaf55961bc7cb7afdca8d80d8cc7406ed661f0c7dc111a/pdbp-1.8.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/da/9e/21c4088d1d433cfbf0578f138a6df59527dbd9e9d67355ee31b17e6dc774/portion-2.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/65/44/bb509c3d2c0b5a87e7a5af1d5917a402a32ff026f777a6d7cb6990746cbb/tabcompleter-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cb/fc/8c82be70b8f96d09943360f34cfb2ecdd3035294c51bce4131eeabe56645/tabcompleter-1.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8d/96/04e7b441807b26b794da5b11e59ed7f83b2cf8af202bd7eba8ad2fa6046e/wadler_lindig-0.1.7-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/87/1b/9e33c09813d65e248f7f773119148a612516a4bea93e9c6f545f78455b7c/wheel-0.47.0-py3-none-any.whl - pypi: ./ @@ -2685,26 +2687,26 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/asv_runner-0.2.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/async-lru-2.3.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-26.1.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-auth-0.10.1-h2d2dd48_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-auth-0.10.1-ha62d5e7_3.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-cal-0.9.13-h2c9d079_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-common-0.12.6-hb03c661_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-compression-0.3.2-h8b1a151_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-event-stream-0.6.0-h9b893ba_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-http-0.10.12-h4bacb7b_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-io-0.26.3-hc87160b_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-mqtt-0.15.2-he9ea9c5_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-s3-0.11.5-h6d69fc9_5.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-event-stream-0.7.0-h9b893ba_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-http-0.10.13-h4bacb7b_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-io-0.26.3-h692f434_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-mqtt-0.15.2-hc1936db_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-s3-0.12.2-he6ee468_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-sdkutils-0.2.4-h8b1a151_4.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-checksums-0.2.10-h8b1a151_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-crt-cpp-0.37.4-h4c8aef7_3.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-sdk-cpp-1.11.747-hc3785e1_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-crt-cpp-0.38.3-h745e52d_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-sdk-cpp-1.11.747-h41c0014_4.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/azure-core-cpp-1.16.2-h206d751_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/azure-identity-cpp-1.13.3-hed0cdb0_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/azure-storage-blobs-cpp-12.16.0-hdd73cc9_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/azure-storage-common-cpp-12.12.0-ha7a2c86_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/azure-storage-files-datalake-cpp-12.14.0-h52c5a47_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/azure-storage-blobs-cpp-12.16.0-hf824e48_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/azure-storage-common-cpp-12.13.0-ha7a2c86_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/azure-storage-files-datalake-cpp-12.14.0-h539c000_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.18.0-pyhcf101f3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/backports.zstd-1.3.0-py314h680f03e_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/backports.zstd-1.5.0-py314h680f03e_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.14.3-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-6.3.0-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-with-css-6.3.0-hbca2aae_1.conda @@ -2720,10 +2722,10 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/cloudpickle-3.1.2-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/coverage-7.13.5-py314h67df5f8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/coverage-7.14.0-py314h67df5f8_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.14.4-py314hd8ed1ab_100.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/debugpy-1.8.20-py314h42812f9_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.3.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/distlib-0.4.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda @@ -2736,7 +2738,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/h5py-3.16.0-nompi_py314hddf7a69_102.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/hdf5-2.1.0-nompi_hd4fcb43_104.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/hdf5-2.1.0-nompi_h87a9417_105.conda - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda @@ -2746,7 +2748,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.8.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.3.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-7.2.0-pyha191276_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.12.0-pyhecfbec7_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.13.0-pyh53cf698_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ipython_pygments_lexers-1.1.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/isoduration-20.11.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jedi-0.19.2-pyhd8ed1ab_1.conda @@ -2760,9 +2762,9 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_client-8.8.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_core-5.9.1-pyhc90fa1f_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_events-0.12.1-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.17.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.18.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server_terminals-0.5.4-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.5.6-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.5.7-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_pygments-0.3.0-pyhd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_server-2.28.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.3-hb9d3cd8_0.conda @@ -2771,48 +2773,48 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45.1-default_hbd61a6d_102.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libabseil-20260107.1-cxx17_h7b12aa8_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libaec-1.1.5-h088129d_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-24.0.0-ha7f89c6_0_cpu.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-acero-24.0.0-h635bf11_0_cpu.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-compute-24.0.0-h53684a4_0_cpu.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-dataset-24.0.0-h635bf11_0_cpu.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-substrait-24.0.0-hb4dd7c2_0_cpu.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.11.0-6_h4a7cf45_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-24.0.0-h0935d00_1_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-acero-24.0.0-h635bf11_1_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-compute-24.0.0-h53684a4_1_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-dataset-24.0.0-h635bf11_1_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-substrait-24.0.0-hb4dd7c2_1_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.11.0-7_h4a7cf45_openblas.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.2.0-hb03c661_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.2.0-hb03c661_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.2.0-hb03c661_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.11.0-6_h0358290_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.11.0-7_h0358290_openblas.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libcrc32c-1.1.2-h9c3ff4c_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcurl-8.19.0-hcf29cc6_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcurl-8.20.0-hcf29cc6_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20250104-pl5321h7949ede_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-hd590300_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libevent-2.1.12-hf998b51_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.5-hecca717_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.8.0-hecca717_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.5.2-h3435931_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-he0feb66_18.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.2.0-h69a702a_18.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-15.2.0-h69a702a_18.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.2.0-h68bc16d_18.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-he0feb66_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-he0feb66_19.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.2.0-h69a702a_19.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-15.2.0-h69a702a_19.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.2.0-h68bc16d_19.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-he0feb66_19.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgoogle-cloud-3.3.0-h25dbb67_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgoogle-cloud-storage-3.3.0-hdbdcf42_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgrpc-1.78.1-h1d1128b_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.18-h3b78370_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.11.0-6_h47877c9_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.11.0-7_h47877c9_openblas.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.3-hb03c661_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libmpdec-4.0.0-hb03c661_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.68.1-h877daf1_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.32-pthreads_h94d23a6_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.33-pthreads_h94d23a6_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libopentelemetry-cpp-1.26.0-h9692893_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libopentelemetry-cpp-headers-1.26.0-ha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libparquet-24.0.0-h7376487_0_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libparquet-24.0.0-h7376487_1_cpu.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libprotobuf-6.33.5-h2b00c02_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libre2-11-2025.11.05-h0dc7533_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libsodium-1.0.21-h280c20c_3.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.53.0-hf4e2dac_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.53.1-h0c1763c_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.11.1-hcf80075_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h934c35e_18.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.2.0-hdf11a46_18.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libthrift-0.22.0-h454ac66_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h934c35e_19.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.2.0-hdf11a46_19.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libthrift-0.22.0-h7d032f7_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libutf8proc-2.11.3-hfe17d71_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.42-h5347b49_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-16-2.15.3-hca6bf5a_0.conda @@ -2820,29 +2822,29 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.2-h25fd6f3_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.10.0-h5888daf_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.3-py314h67df5f8_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.2.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/memory_profiler-0.61.0-pyhcf101f3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.2.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/narwhals-2.20.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.2.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/narwhals-2.21.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbclient-0.10.4-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.17.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbformat-5.10.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.6-hdb14827_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nest-asyncio-1.6.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/nlohmann_json-3.12.0-h54a6638_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/notebook-shim-0.2.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.4.3-py314h2b28147_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.4.5-py314h2b28147_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.2-h35e630c_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/orc-2.3.0-h21090e2_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/overrides-7.7.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.1-pyhc364b38_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.2-pyhc364b38_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pandocfilters-1.5.0-pyhd8ed1ab_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.6-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.7-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pexpect-4.9.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.9.6-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/plotly-6.6.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.6.0-pyhf9edf01_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/prek-0.3.10-hb17b654_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/prek-0.4.0-hb17b654_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/prometheus-cpp-1.3.0-ha5d0236_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.25.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.52-pyha770c72_0.conda @@ -2861,23 +2863,23 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-3.8.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.14.4-habeac84_100_cp314.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-discovery-1.2.2-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-discovery-1.3.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.21.2-pyhe01879c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.14.4-h4df99d1_100.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-2.0.7-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2026.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-3.2.1-pyh332efcf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2026.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.14-8_cp314.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.3-py314h67df5f8_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pyzmq-27.1.0-py312hda471dd_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/re2-2025.11.05-h5301d42_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.3-h853b02a_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.37.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.33.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.34.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3339-validator-0.1.4-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3986-validator-0.1.1-pyh9f0ad1d_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3987-syntax-1.1.0-pyhe01879c_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/rpds-py-0.30.0-py314h2e6c369_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/s2n-1.7.1-h1cbb8d7_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/s2n-1.7.2-hc5a330e_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/scipy-1.17.1-py314hf07bd8e_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-2.1.0-pyha191276_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-82.0.1-pyh332efcf_0.conda @@ -2893,15 +2895,15 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h366c992_103.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/tornado-6.5.5-py314h5bd0f2a_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.14.3-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.15.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing_utils-0.1.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/uri-template-1.3.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.6.3-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/virtualenv-21.2.4-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.6.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.7.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/virtualenv-21.3.3-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.7.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/webcolors-25.10.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/webencodings-0.5.1-pyhd8ed1ab_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.9.0-pyhd8ed1ab_0.conda @@ -2911,8 +2913,8 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-1.3.2-h25fd6f3_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda - pypi: https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl - - pypi: git+https://github.com/OpenSourceEconomics/dags.git?rev=cf59c04#cf59c04c6ba07b7c54ca763dc155deea3341a480 - - pypi: https://files.pythonhosted.org/packages/43/f5/ee39c6e92acc742c052f137b47c210cd0a1b72dcd3f98495528bb4d27761/flatten_dict-0.4.2-py2.py3-none-any.whl + - pypi: git+https://github.com/OpenSourceEconomics/dags.git?branch=main#87ff051077f37470514808a3121e7c90429f6383 + - pypi: https://files.pythonhosted.org/packages/72/9f/485516087cd8c44183aaf9ab850247a28e2e4a42a4d62eab77c21f673450/flatten_dict-0.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/70/aa/dfac6d72cc35bc07e7587115b6946e333ef4ccb2e6cd26ecf639438c5d26/jax-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a1/8e/b2a08ffc51c93842de71f7f988865cebfa7f43d6721957812dc8cc8b9d40/jaxlib-0.10.0-cp314-cp314-manylinux_2_27_x86_64.whl - pypi: https://files.pythonhosted.org/packages/94/05/3e39d416fb92b2738a76e8265e6bfc5d10542f90a7c32ad1eb831eea3fa3/jaxtyping-0.3.9-py3-none-any.whl @@ -2920,15 +2922,15 @@ environments: - pypi: https://files.pythonhosted.org/packages/c6/bb/82c7dcf38070b46172a517e2334e665c5bf374a262f99a283ea454bece7c/ml_dtypes-0.5.4-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/24/8d/e12d6ff4b9119db3cbf7b2db1ce257576441bd3c76388c786dea74f20b02/numba-0.65.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/87/96/f3eb235fafa82a34e2ab5dd7dc9ffff998ebf5f0bbc23fa56a96aeb44da6/numba-0.65.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - pypi: https://files.pythonhosted.org/packages/23/cd/066e86230ae37ed0be70aae89aabf03ca8d9f39c8aea0dec8029455b5540/opt_einsum-3.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/15/88/3cdd54fa279341afa10acf8d2b503556b1375245dccc9315659f795dd2e9/pandas-3.0.2-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/58/3b/1cdec6772bdbaf7b25dab360c59f03cadf05492dd724c6540af905389b07/pandas-3.0.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/51/fe/53ac0cd932db5dcaf55961bc7cb7afdca8d80d8cc7406ed661f0c7dc111a/pdbp-1.8.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/da/9e/21c4088d1d433cfbf0578f138a6df59527dbd9e9d67355ee31b17e6dc774/portion-2.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/3b/ef57eeb4c6f188fbe756b602dc6afa9d66eb0c2e77f02a3d4d51bfec2aaa/quantecon-0.11.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/65/44/bb509c3d2c0b5a87e7a5af1d5917a402a32ff026f777a6d7cb6990746cbb/tabcompleter-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cb/fc/8c82be70b8f96d09943360f34cfb2ecdd3035294c51bce4131eeabe56645/tabcompleter-1.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8d/96/04e7b441807b26b794da5b11e59ed7f83b2cf8af202bd7eba8ad2fa6046e/wadler_lindig-0.1.7-py3-none-any.whl - pypi: ./ osx-arm64: @@ -2944,26 +2946,26 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/asv_runner-0.2.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/async-lru-2.3.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-26.1.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-auth-0.10.1-hcb83491_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-auth-0.10.1-ha7d4cc1_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-cal-0.9.13-h6ee9776_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-common-0.12.6-hc919400_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-compression-0.3.2-h3e7f9b5_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-event-stream-0.6.0-h351c84d_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-http-0.10.12-h95cdebe_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-io-0.26.3-h4137820_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-mqtt-0.15.2-h69e7467_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-s3-0.11.5-ha5d16b2_5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-event-stream-0.7.0-h351c84d_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-http-0.10.13-h95cdebe_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-io-0.26.3-h4137820_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-mqtt-0.15.2-h8860bc9_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-s3-0.12.2-h07b101a_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-sdkutils-0.2.4-h16f91aa_4.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-checksums-0.2.10-h3e7f9b5_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-crt-cpp-0.37.4-h5505c15_3.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-sdk-cpp-1.11.747-had22720_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-crt-cpp-0.38.3-hba17502_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-sdk-cpp-1.11.747-h30a6df1_4.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/azure-core-cpp-1.16.2-he5ae378_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/azure-identity-cpp-1.13.3-h810541e_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/azure-storage-blobs-cpp-12.16.0-hc57151b_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/azure-storage-common-cpp-12.12.0-he467506_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/azure-storage-files-datalake-cpp-12.14.0-hf8a9d22_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/azure-storage-blobs-cpp-12.16.0-h5446563_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/azure-storage-common-cpp-12.13.0-he467506_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/azure-storage-files-datalake-cpp-12.14.0-hdc9d693_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.18.0-pyhcf101f3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/backports.zstd-1.3.0-py314h680f03e_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/backports.zstd-1.5.0-py314h680f03e_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.14.3-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-6.3.0-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-with-css-6.3.0-hbca2aae_1.conda @@ -2979,10 +2981,10 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/cloudpickle-3.1.2-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/coverage-7.13.5-py314h6e9b3f0_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/coverage-7.14.0-py314h6e9b3f0_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.14.4-py314hd8ed1ab_100.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/debugpy-1.8.20-py314he609de1_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.3.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/distlib-0.4.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda @@ -2995,7 +2997,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/h5py-3.16.0-nompi_py314h658a3ac_102.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/hdf5-2.1.0-nompi_hc95e3eb_104.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/hdf5-2.1.0-nompi_he586413_105.conda - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda @@ -3005,7 +3007,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.8.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.3.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-7.2.0-pyh5552912_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.12.0-pyhecfbec7_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.13.0-pyh53cf698_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ipython_pygments_lexers-1.1.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/isoduration-20.11.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jedi-0.19.2-pyhd8ed1ab_1.conda @@ -3019,84 +3021,84 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_client-8.8.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_core-5.9.1-pyhc90fa1f_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_events-0.12.1-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.17.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.18.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server_terminals-0.5.4-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.5.6-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.5.7-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_pygments-0.3.0-pyhd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_server-2.28.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/krb5-1.22.2-h385eeb1_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/lark-1.3.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libabseil-20260107.1-cxx17_h2062a1b_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libaec-1.1.5-h8664d51_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libarrow-24.0.0-h2124f06_0_cpu.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libarrow-acero-24.0.0-hee8fe31_0_cpu.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libarrow-compute-24.0.0-h3b6a98a_0_cpu.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libarrow-dataset-24.0.0-hee8fe31_0_cpu.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libarrow-substrait-24.0.0-h05be00f_0_cpu.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libblas-3.11.0-6_h51639a9_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libarrow-24.0.0-h37fbca7_1_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libarrow-acero-24.0.0-hee8fe31_1_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libarrow-compute-24.0.0-h3b6a98a_1_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libarrow-dataset-24.0.0-hee8fe31_1_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libarrow-substrait-24.0.0-h05be00f_1_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libblas-3.11.0-7_h51639a9_openblas.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlicommon-1.2.0-hc919400_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlidec-1.2.0-hc919400_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlienc-1.2.0-hc919400_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcblas-3.11.0-6_hb0561ab_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcblas-3.11.0-7_hb0561ab_openblas.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcrc32c-1.1.2-hbdafb3b_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcurl-8.19.0-hd5a2499_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-22.1.4-h55c6f16_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcurl-8.20.0-hd5a2499_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-22.1.5-h55c6f16_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libedit-3.1.20250104-pl5321hafb1f1b_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libev-4.33-h93a5062_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libevent-2.1.12-h2757513_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.7.5-hf6b4638_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.8.0-hf6b4638_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.5.2-hcf2aa1b_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgcc-15.2.0-hcbb3090_18.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran-15.2.0-h07b0088_18.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran5-15.2.0-hdae7583_18.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgcc-15.2.0-hcbb3090_19.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran-15.2.0-h07b0088_19.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran5-15.2.0-hdae7583_19.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgoogle-cloud-3.3.0-he41eb1d_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgoogle-cloud-storage-3.3.0-ha114238_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgrpc-1.78.1-h3e3f78d_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libiconv-1.18-h23cfdf5_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblapack-3.11.0-6_hd9741b5_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblapack-3.11.0-7_hd9741b5_openblas.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblzma-5.8.3-h8088a28_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libmpdec-4.0.0-h84a0fba_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libnghttp2-1.68.1-h8f3e76b_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libopenblas-0.3.32-openmp_he657e61_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libopenblas-0.3.33-openmp_he657e61_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libopentelemetry-cpp-1.26.0-h08d5cc3_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libopentelemetry-cpp-headers-1.26.0-hce30654_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libparquet-24.0.0-h16c0493_0_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libparquet-24.0.0-h16c0493_1_cpu.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libprotobuf-6.33.5-h4a5acfd_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libre2-11-2025.11.05-h4c27e2a_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsodium-1.0.21-h1a92334_3.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.53.0-h1b79a29_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.53.1-h1b79a29_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libssh2-1.11.1-h1590b86_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libthrift-0.22.0-h14a376c_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libthrift-0.22.0-h1fb9c8a_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libutf8proc-2.11.3-h2431656_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libxml2-16-2.15.3-h5ef1a60_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libxml2-2.15.3-h5654f7c_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzlib-1.3.2-h8088a28_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/llvm-openmp-22.1.4-hc7d1edf_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/llvm-openmp-22.1.5-hc7d1edf_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/lz4-c-1.10.0-h286801f_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/markupsafe-3.0.3-py314h6e9b3f0_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.2.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/memory_profiler-0.61.0-pyhcf101f3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.2.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/narwhals-2.20.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.2.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/narwhals-2.21.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbclient-0.10.4-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.17.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbformat-5.10.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ncurses-6.5-h5e97a16_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ncurses-6.6-h1d4f5a5_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nest-asyncio-1.6.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/nlohmann_json-3.12.0-h784d473_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/notebook-shim-0.2.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/numpy-2.4.3-py314h1569ea8_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/numpy-2.4.5-py314hb79c6fa_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.6.2-hd24854e_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/orc-2.3.0-hd11884d_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/overrides-7.7.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.1-pyhc364b38_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.2-pyhc364b38_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pandocfilters-1.5.0-pyhd8ed1ab_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.6-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.7-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pexpect-4.9.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.9.6-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/plotly-6.6.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.6.0-pyhf9edf01_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/prek-0.3.10-h6fdd925_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/prek-0.4.0-h6fdd925_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/prometheus-cpp-1.3.0-h0967b3e_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.25.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.52-pyha770c72_0.conda @@ -3117,18 +3119,18 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-3.8.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.14.4-h4c637c5_100_cp314.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-discovery-1.2.2-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-discovery-1.3.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.21.2-pyhe01879c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.14.4-h4df99d1_100.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-2.0.7-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2026.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-3.2.1-pyh332efcf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2026.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.14-8_cp314.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyyaml-6.0.3-py314h6e9b3f0_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyzmq-27.1.0-py312h022ad19_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/re2-2025.11.05-ha480c28_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/readline-8.3-h46df422_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.37.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.33.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.34.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3339-validator-0.1.4-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3986-validator-0.1.1-pyh9f0ad1d_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3987-syntax-1.1.0-pyhe01879c_1.conda @@ -3148,15 +3150,15 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-h010d191_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tornado-6.5.5-py314h6c2aa35_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.14.3-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.15.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing_utils-0.1.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/uri-template-1.3.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.6.3-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/virtualenv-21.2.4-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.6.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.7.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/virtualenv-21.3.3-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.7.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/webcolors-25.10.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/webencodings-0.5.1-pyhd8ed1ab_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.9.0-pyhd8ed1ab_0.conda @@ -3166,8 +3168,8 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zlib-1.3.2-h8088a28_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda - pypi: https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl - - pypi: git+https://github.com/OpenSourceEconomics/dags.git?rev=cf59c04#cf59c04c6ba07b7c54ca763dc155deea3341a480 - - pypi: https://files.pythonhosted.org/packages/43/f5/ee39c6e92acc742c052f137b47c210cd0a1b72dcd3f98495528bb4d27761/flatten_dict-0.4.2-py2.py3-none-any.whl + - pypi: git+https://github.com/OpenSourceEconomics/dags.git?branch=main#87ff051077f37470514808a3121e7c90429f6383 + - pypi: https://files.pythonhosted.org/packages/72/9f/485516087cd8c44183aaf9ab850247a28e2e4a42a4d62eab77c21f673450/flatten_dict-0.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/70/aa/dfac6d72cc35bc07e7587115b6946e333ef4ccb2e6cd26ecf639438c5d26/jax-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a7/25/e1e52a21786b321fb6a2edf9ef9971aa70f06bb2738aef9afd6d8f46a441/jaxlib-0.10.0-cp314-cp314-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/94/05/3e39d416fb92b2738a76e8265e6bfc5d10542f90a7c32ad1eb831eea3fa3/jaxtyping-0.3.9-py3-none-any.whl @@ -3175,15 +3177,15 @@ environments: - pypi: https://files.pythonhosted.org/packages/72/4e/1339dc6e2557a344f5ba5590872e80346f76f6cb2ac3dd16e4666e88818c/ml_dtypes-0.5.4-cp314-cp314-macosx_10_13_universal2.whl - pypi: https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/56/a4/90edb01e9176053578e343d7a7276bc28356741ee67059aed8ed2c1a4e59/numba-0.65.0-cp314-cp314-macosx_12_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/4f/2e/8aed9b726d9ba5f11ad287645fd479e88278db3060a25cb1225d730eb2b7/numba-0.65.1-cp314-cp314-macosx_12_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/23/cd/066e86230ae37ed0be70aae89aabf03ca8d9f39c8aea0dec8029455b5540/opt_einsum-3.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/95/25/bdb9326c3b5455f8d4d3549fce7abcf967259de146fe2cf7a82368141948/pandas-3.0.2-cp314-cp314-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/68/10/bf2d6738d72748b961a3751ab89522d58c54efc36a8e1a12161216cd45cf/pandas-3.0.3-cp314-cp314-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/51/fe/53ac0cd932db5dcaf55961bc7cb7afdca8d80d8cc7406ed661f0c7dc111a/pdbp-1.8.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/da/9e/21c4088d1d433cfbf0578f138a6df59527dbd9e9d67355ee31b17e6dc774/portion-2.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/3b/ef57eeb4c6f188fbe756b602dc6afa9d66eb0c2e77f02a3d4d51bfec2aaa/quantecon-0.11.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/65/44/bb509c3d2c0b5a87e7a5af1d5917a402a32ff026f777a6d7cb6990746cbb/tabcompleter-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cb/fc/8c82be70b8f96d09943360f34cfb2ecdd3035294c51bce4131eeabe56645/tabcompleter-1.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8d/96/04e7b441807b26b794da5b11e59ed7f83b2cf8af202bd7eba8ad2fa6046e/wadler_lindig-0.1.7-py3-none-any.whl - pypi: ./ win-64: @@ -3197,26 +3199,26 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/asv_runner-0.2.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/async-lru-2.3.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-26.1.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-auth-0.10.1-h5d51246_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-auth-0.10.1-h8b39d88_3.conda - conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-cal-0.9.13-h46f3b43_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-common-0.12.6-hfd05255_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-compression-0.3.2-hcb3a2da_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-event-stream-0.6.0-h87b2689_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-http-0.10.12-h612f3e8_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-io-0.26.3-h0d5b9f9_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-mqtt-0.15.2-h904b250_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-s3-0.11.5-h87bd87b_5.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-event-stream-0.7.0-h87b2689_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-http-0.10.13-h612f3e8_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-io-0.26.3-h0d5b9f9_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-mqtt-0.15.2-h82175e6_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-s3-0.12.2-h61b906f_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-sdkutils-0.2.4-hcb3a2da_4.conda - conda: https://conda.anaconda.org/conda-forge/win-64/aws-checksums-0.2.10-hcb3a2da_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/aws-crt-cpp-0.37.4-h4f72eff_3.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/aws-sdk-cpp-1.11.747-hd55a107_3.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/aws-crt-cpp-0.38.3-h1db8845_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/aws-sdk-cpp-1.11.747-hd63e0c5_4.conda - conda: https://conda.anaconda.org/conda-forge/win-64/azure-core-cpp-1.16.2-h49e36cd_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/azure-identity-cpp-1.13.3-h5ffce34_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/azure-storage-blobs-cpp-12.16.0-hcd625b1_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/azure-storage-common-cpp-12.12.0-h5ffce34_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/azure-storage-files-datalake-cpp-12.14.0-h1678c0b_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/azure-storage-blobs-cpp-12.16.0-h81bf7d1_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/azure-storage-common-cpp-12.13.0-h5ffce34_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/azure-storage-files-datalake-cpp-12.14.0-hab49af2_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.18.0-pyhcf101f3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/backports.zstd-1.3.0-py314h680f03e_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/backports.zstd-1.5.0-py314h680f03e_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.14.3-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-6.3.0-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-with-css-6.3.0-hbca2aae_1.conda @@ -3232,10 +3234,10 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/cloudpickle-3.1.2-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/coverage-7.13.5-py314h2359020_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/coverage-7.14.0-py314h2359020_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.14.4-py314hd8ed1ab_100.conda - conda: https://conda.anaconda.org/conda-forge/win-64/debugpy-1.8.20-py314hb98de8c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.3.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/distlib-0.4.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda @@ -3246,7 +3248,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/h5py-3.16.0-nompi_py314h02517ec_102.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/hdf5-2.1.0-nompi_hd96b29f_104.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/hdf5-2.1.0-nompi_h0a39f1e_105.conda - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda @@ -3256,7 +3258,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.8.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.3.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-7.2.0-pyh6dadd2b_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.12.0-pyhccfa634_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.13.0-pyhe2676ad_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ipython_pygments_lexers-1.1.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/isoduration-20.11.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jedi-0.19.2-pyhd8ed1ab_1.conda @@ -3270,78 +3272,78 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_client-8.8.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_core-5.9.1-pyh6dadd2b_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_events-0.12.1-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.17.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.18.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server_terminals-0.5.4-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.5.6-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.5.7-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_pygments-0.3.0-pyhd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_server-2.28.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/krb5-1.22.2-h0ea6238_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/lark-1.3.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libabseil-20260107.1-cxx17_h0eb2380_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libaec-1.1.5-haf901d7_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libarrow-24.0.0-hc74aee5_0_cpu.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libarrow-acero-24.0.0-h7d8d6a5_0_cpu.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libarrow-compute-24.0.0-h081cd8e_0_cpu.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libarrow-dataset-24.0.0-h7d8d6a5_0_cpu.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libarrow-substrait-24.0.0-h524e9bd_0_cpu.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libblas-3.11.0-6_hf2e6a31_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libarrow-24.0.0-h37f918f_1_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libarrow-acero-24.0.0-h7d8d6a5_1_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libarrow-compute-24.0.0-h081cd8e_1_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libarrow-dataset-24.0.0-h7d8d6a5_1_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libarrow-substrait-24.0.0-h524e9bd_1_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libblas-3.11.0-7_h8455456_mkl.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libbrotlicommon-1.2.0-hfd05255_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libbrotlidec-1.2.0-hfd05255_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libbrotlienc-1.2.0-hfd05255_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libcblas-3.11.0-6_h2a3cdd5_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libcblas-3.11.0-7_h2a3cdd5_mkl.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libcrc32c-1.1.2-h0e60522_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/win-64/libcurl-8.19.0-h8206538_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libcurl-8.20.0-h8206538_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libevent-2.1.12-h3671451_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libexpat-2.7.5-hac47afa_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libexpat-2.8.0-hac47afa_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libffi-3.5.2-h3d046cb_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libgoogle-cloud-3.3.0-h2b231ac_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libgoogle-cloud-storage-3.3.0-he04ea4c_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libgrpc-1.78.1-h9ff2b3e_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libhwloc-2.12.2-default_h4379cf1_1000.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libhwloc-2.13.0-default_h049141e_1000.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libiconv-1.18-hc1393d2_2.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/liblapack-3.11.0-6_hf9ab0e9_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/liblapack-3.11.0-7_hf9ab0e9_mkl.conda - conda: https://conda.anaconda.org/conda-forge/win-64/liblzma-5.8.3-hfd05255_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libmpdec-4.0.0-hfd05255_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libopentelemetry-cpp-1.26.0-hc88f397_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libopentelemetry-cpp-headers-1.26.0-h57928b3_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libparquet-24.0.0-h7051d1f_0_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libparquet-24.0.0-h7051d1f_1_cpu.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libprotobuf-6.33.5-h61fc761_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libre2-11-2025.11.05-h04e5de1_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libsodium-1.0.21-h6a83c73_3.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libsqlite-3.53.0-hf5d6505_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libsqlite-3.53.1-hf5d6505_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libssh2-1.11.1-h9aa295b_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libthrift-0.22.0-h23985f6_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libthrift-0.22.0-h2e43b2f_2.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libutf8proc-2.11.3-hb980946_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libwinpthread-12.0.0.r4.gg4f2fc60ca-h57928b3_10.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-16-2.15.3-h3cfd58e_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-2.15.3-h8ef44ab_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libzlib-1.3.2-hfd05255_2.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/llvm-openmp-22.1.4-h4fa8253_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/llvm-openmp-22.1.5-h4fa8253_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/lz4-c-1.10.0-h2466b09_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/markupsafe-3.0.3-py314h2359020_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.2.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/memory_profiler-0.61.0-pyhcf101f3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.2.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/mkl-2025.3.1-hac47afa_12.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/narwhals-2.20.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.2.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/mkl-2026.0.0-hac47afa_906.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/narwhals-2.21.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbclient-0.10.4-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.17.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbformat-5.10.4-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nest-asyncio-1.6.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/nlohmann_json-3.12.0-h5112557_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/notebook-shim-0.2.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/numpy-2.4.3-py314h02f10f6_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/onemkl-license-2025.3.1-h57928b3_12.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/numpy-2.4.5-py314h02f10f6_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/onemkl-license-2026.0.0-h57928b3_906.conda - conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.6.2-hf411b9b_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/orc-2.3.0-h8fc0eb6_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/overrides-7.7.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.1-pyhc364b38_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.2-pyhc364b38_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pandocfilters-1.5.0-pyhd8ed1ab_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.6-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.7-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.9.6-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/plotly-6.6.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.6.0-pyhf9edf01_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/prek-0.3.10-h18a1a76_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/prek-0.4.0-h18a1a76_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/prometheus-cpp-1.3.0-hcea2f5d_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.25.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.52-pyha770c72_0.conda @@ -3359,11 +3361,11 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-3.8.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/python-3.14.4-h4b44e0e_100_cp314.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-discovery-1.2.2-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-discovery-1.3.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.21.2-pyhe01879c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.14.4-h4df99d1_100.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-2.0.7-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2026.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-3.2.1-pyh332efcf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2026.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.14-8_cp314.conda - conda: https://conda.anaconda.org/conda-forge/win-64/pywin32-311-py314h8f8f202_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/pywinpty-2.0.15-py314h51f0985_1.conda @@ -3371,7 +3373,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/pyzmq-27.1.0-py312h343a6d4_2.conda - conda: https://conda.anaconda.org/conda-forge/win-64/re2-2025.11.05-ha104f34_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.37.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.33.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.34.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3339-validator-0.1.4-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3986-validator-0.1.1-pyh9f0ad1d_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3987-syntax-1.1.0-pyhe01879c_1.conda @@ -3386,26 +3388,26 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.8.3-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/stack_data-0.6.3-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tabulate-0.10.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/tbb-2022.3.0-h3155e25_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/tbb-2023.0.0-hd3d4ead_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/terminado-0.18.1-pyh6dadd2b_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tinycss2-1.4.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/tk-8.6.13-h6ed50ae_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/tornado-6.5.5-py314h5a2d7ad_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.14.3-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.15.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing_utils-0.1.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/ucrt-10.0.26100.0-h57928b3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/uri-template-1.3.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.6.3-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/vc-14.3-h41ae7f8_34.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.44.35208-h818238b_34.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/vcomp14-14.44.35208-h818238b_34.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/virtualenv-21.2.4-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/vs2015_runtime-14.44.35208-h38c0c73_34.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.6.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.7.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vc-14.5-h1b7c187_36.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.51.36231-h1b9f54f_36.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vcomp14-14.51.36231-h1b9f54f_36.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/virtualenv-21.3.3-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vs2015_runtime-14.51.36231-h84cd919_36.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.7.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/webcolors-25.10.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/webencodings-0.5.1-pyhd8ed1ab_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.9.0-pyhd8ed1ab_0.conda @@ -3417,8 +3419,8 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/zlib-1.3.2-hfd05255_2.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_6.conda - pypi: https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl - - pypi: git+https://github.com/OpenSourceEconomics/dags.git?rev=cf59c04#cf59c04c6ba07b7c54ca763dc155deea3341a480 - - pypi: https://files.pythonhosted.org/packages/43/f5/ee39c6e92acc742c052f137b47c210cd0a1b72dcd3f98495528bb4d27761/flatten_dict-0.4.2-py2.py3-none-any.whl + - pypi: git+https://github.com/OpenSourceEconomics/dags.git?branch=main#87ff051077f37470514808a3121e7c90429f6383 + - pypi: https://files.pythonhosted.org/packages/72/9f/485516087cd8c44183aaf9ab850247a28e2e4a42a4d62eab77c21f673450/flatten_dict-0.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/70/aa/dfac6d72cc35bc07e7587115b6946e333ef4ccb2e6cd26ecf639438c5d26/jax-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/24/08/26e6a3ecf0a95f1ec0dcd7a668d5c9a72e581c40fe4ae51e102ca63174c5/jaxlib-0.10.0-cp314-cp314-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/94/05/3e39d416fb92b2738a76e8265e6bfc5d10542f90a7c32ad1eb831eea3fa3/jaxtyping-0.3.9-py3-none-any.whl @@ -3426,16 +3428,16 @@ environments: - pypi: https://files.pythonhosted.org/packages/e9/93/2bfed22d2498c468f6bcd0d9f56b033eaa19f33320389314c19ef6766413/ml_dtypes-0.5.4-cp314-cp314-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/73/5b/fbce55ce3d933afbc7ade04df826853e4a846aaa47d58d2fbb669b8f2d08/numba-0.65.0-cp314-cp314-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/56/46/3f7fc04fb853559e74b210e0b62c19974ec844cefec611f9e535f4da3761/numba-0.65.1-cp314-cp314-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/23/cd/066e86230ae37ed0be70aae89aabf03ca8d9f39c8aea0dec8029455b5540/opt_einsum-3.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/db/60/aba6a38de456e7341285102bede27514795c1eaa353bc0e7638b6b785356/pandas-3.0.2-cp314-cp314-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/38/55/792619469bab9882d8bbd5865d45a72f6478762d04a9af4bf0d08c503e95/pandas-3.0.3-cp314-cp314-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/51/fe/53ac0cd932db5dcaf55961bc7cb7afdca8d80d8cc7406ed661f0c7dc111a/pdbp-1.8.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/da/9e/21c4088d1d433cfbf0578f138a6df59527dbd9e9d67355ee31b17e6dc774/portion-2.6.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/5a/dc/491b7661614ab97483abf2056be1deee4dc2490ecbf7bff9ab5cdbac86e1/pyreadline3-3.5.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f7/5e/35c856e186b74678c24927847ad9895a51f1bc02a0c6126477a6c6040064/pyreadline3-3.5.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/3b/ef57eeb4c6f188fbe756b602dc6afa9d66eb0c2e77f02a3d4d51bfec2aaa/quantecon-0.11.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/65/44/bb509c3d2c0b5a87e7a5af1d5917a402a32ff026f777a6d7cb6990746cbb/tabcompleter-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cb/fc/8c82be70b8f96d09943360f34cfb2ecdd3035294c51bce4131eeabe56645/tabcompleter-1.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8d/96/04e7b441807b26b794da5b11e59ed7f83b2cf8af202bd7eba8ad2fa6046e/wadler_lindig-0.1.7-py3-none-any.whl - pypi: ./ tests-cuda12: @@ -3458,26 +3460,26 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/asv_runner-0.2.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/async-lru-2.3.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-26.1.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-auth-0.10.1-h2d2dd48_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-auth-0.10.1-ha62d5e7_3.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-cal-0.9.13-h2c9d079_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-common-0.12.6-hb03c661_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-compression-0.3.2-h8b1a151_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-event-stream-0.6.0-h9b893ba_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-http-0.10.12-h4bacb7b_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-io-0.26.3-hc87160b_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-mqtt-0.15.2-he9ea9c5_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-s3-0.11.5-h6d69fc9_5.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-event-stream-0.7.0-h9b893ba_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-http-0.10.13-h4bacb7b_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-io-0.26.3-h692f434_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-mqtt-0.15.2-hc1936db_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-s3-0.12.2-he6ee468_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-sdkutils-0.2.4-h8b1a151_4.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-checksums-0.2.10-h8b1a151_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-crt-cpp-0.37.4-h4c8aef7_3.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-sdk-cpp-1.11.747-hc3785e1_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-crt-cpp-0.38.3-h745e52d_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-sdk-cpp-1.11.747-h41c0014_4.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/azure-core-cpp-1.16.2-h206d751_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/azure-identity-cpp-1.13.3-hed0cdb0_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/azure-storage-blobs-cpp-12.16.0-hdd73cc9_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/azure-storage-common-cpp-12.12.0-ha7a2c86_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/azure-storage-files-datalake-cpp-12.14.0-h52c5a47_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/azure-storage-blobs-cpp-12.16.0-hf824e48_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/azure-storage-common-cpp-12.13.0-ha7a2c86_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/azure-storage-files-datalake-cpp-12.14.0-h539c000_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.18.0-pyhcf101f3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/backports.zstd-1.3.0-py314h680f03e_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/backports.zstd-1.5.0-py314h680f03e_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.14.3-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils_impl_linux-64-2.45.1-default_hfdba357_102.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils_linux-64-2.45.1-default_h4852527_102.conda @@ -3495,7 +3497,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/cloudpickle-3.1.2-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/coverage-7.13.5-py314h67df5f8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/coverage-7.14.0-py314h67df5f8_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.14.4-py314hd8ed1ab_100.conda - conda: https://conda.anaconda.org/conda-forge/noarch/cuda-cccl_linux-64-12.9.27-ha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/cuda-crt-dev_linux-64-12.9.86-ha770c72_2.conda @@ -3517,7 +3519,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-nvvm-tools-12.9.86-h4bc722e_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/cuda-version-12.9-h4f385c5_3.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/debugpy-1.8.20-py314h42812f9_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.3.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/distlib-0.4.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda @@ -3525,16 +3527,16 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.2.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.29.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/fqdn-1.5.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_impl_linux-64-14.3.0-hbdf3cc3_18.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_linux-64-14.3.0-h298d278_23.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_impl_linux-64-14.3.0-h235f0fe_19.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_linux-64-14.3.0-h50e9bb6_24.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/gflags-2.2.2-h5888daf_1005.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/glog-0.7.1-hbabe93e_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_impl_linux-64-14.3.0-h2185e75_18.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_linux-64-14.3.0-h91b0f8e_23.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_impl_linux-64-14.3.0-h2185e75_19.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_linux-64-14.3.0-h8a413ad_24.conda - conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/h5py-3.16.0-nompi_py314hddf7a69_102.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/hdf5-2.1.0-nompi_hd4fcb43_104.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/hdf5-2.1.0-nompi_h87a9417_105.conda - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda @@ -3544,7 +3546,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.8.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.3.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-7.2.0-pyha191276_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.12.0-pyhecfbec7_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.13.0-pyh53cf698_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ipython_pygments_lexers-1.1.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/isoduration-20.11.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jedi-0.19.2-pyhd8ed1ab_1.conda @@ -3558,9 +3560,9 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_client-8.8.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_core-5.9.1-pyhc90fa1f_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_events-0.12.1-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.17.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.18.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server_terminals-0.5.4-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.5.6-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.5.7-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_pygments-0.3.0-pyhd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_server-2.28.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/kernel-headers_linux-64-4.18.0-he073ed8_9.conda @@ -3570,53 +3572,53 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45.1-default_hbd61a6d_102.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libabseil-20260107.1-cxx17_h7b12aa8_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libaec-1.1.5-h088129d_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-24.0.0-ha7f89c6_0_cpu.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-acero-24.0.0-h635bf11_0_cpu.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-compute-24.0.0-h53684a4_0_cpu.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-dataset-24.0.0-h635bf11_0_cpu.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-substrait-24.0.0-hb4dd7c2_0_cpu.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.11.0-6_h4a7cf45_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-24.0.0-h0935d00_1_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-acero-24.0.0-h635bf11_1_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-compute-24.0.0-h53684a4_1_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-dataset-24.0.0-h635bf11_1_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-substrait-24.0.0-hb4dd7c2_1_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.11.0-7_h4a7cf45_openblas.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.2.0-hb03c661_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.2.0-hb03c661_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.2.0-hb03c661_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.11.0-6_h0358290_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.11.0-7_h0358290_openblas.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libcrc32c-1.1.2-h9c3ff4c_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcurl-8.19.0-hcf29cc6_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcurl-8.20.0-hcf29cc6_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20250104-pl5321h7949ede_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-hd590300_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libevent-2.1.12-hf998b51_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.5-hecca717_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.8.0-hecca717_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.5.2-h3435931_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-he0feb66_18.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/libgcc-devel_linux-64-14.3.0-hf649bbc_118.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.2.0-h69a702a_18.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-15.2.0-h69a702a_18.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.2.0-h68bc16d_18.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-he0feb66_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-he0feb66_19.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/libgcc-devel_linux-64-14.3.0-hf649bbc_119.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.2.0-h69a702a_19.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-15.2.0-h69a702a_19.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.2.0-h68bc16d_19.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-he0feb66_19.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgoogle-cloud-3.3.0-h25dbb67_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgoogle-cloud-storage-3.3.0-hdbdcf42_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgrpc-1.78.1-h1d1128b_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.18-h3b78370_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.11.0-6_h47877c9_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.11.0-7_h47877c9_openblas.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.3-hb03c661_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libmpdec-4.0.0-hb03c661_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.68.1-h877daf1_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libnvptxcompiler-dev-12.9.86-ha770c72_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/libnvptxcompiler-dev_linux-64-12.9.86-ha770c72_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.32-pthreads_h94d23a6_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.33-pthreads_h94d23a6_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libopentelemetry-cpp-1.26.0-h9692893_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libopentelemetry-cpp-headers-1.26.0-ha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libparquet-24.0.0-h7376487_0_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libparquet-24.0.0-h7376487_1_cpu.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libprotobuf-6.33.5-h2b00c02_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libre2-11-2025.11.05-h0dc7533_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libsanitizer-14.3.0-h8f1669f_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsanitizer-14.3.0-h8f1669f_19.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libsodium-1.0.21-h280c20c_3.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.53.0-hf4e2dac_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.53.1-h0c1763c_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.11.1-hcf80075_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h934c35e_18.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/libstdcxx-devel_linux-64-14.3.0-h9f08a49_118.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.2.0-hdf11a46_18.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libthrift-0.22.0-h454ac66_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h934c35e_19.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/libstdcxx-devel_linux-64-14.3.0-h9f08a49_119.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.2.0-hdf11a46_19.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libthrift-0.22.0-h7d032f7_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libutf8proc-2.11.3-hfe17d71_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.42-h5347b49_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-16-2.15.3-hca6bf5a_0.conda @@ -3624,29 +3626,29 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.2-h25fd6f3_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.10.0-h5888daf_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.3-py314h67df5f8_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.2.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/memory_profiler-0.61.0-pyhcf101f3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.2.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/narwhals-2.20.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.2.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/narwhals-2.21.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbclient-0.10.4-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.17.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbformat-5.10.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.6-hdb14827_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nest-asyncio-1.6.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/nlohmann_json-3.12.0-h54a6638_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/notebook-shim-0.2.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.4.3-py314h2b28147_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.4.5-py314h2b28147_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.2-h35e630c_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/orc-2.3.0-h21090e2_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/overrides-7.7.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.1-pyhc364b38_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.2-pyhc364b38_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pandocfilters-1.5.0-pyhd8ed1ab_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.6-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.7-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pexpect-4.9.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.9.6-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/plotly-6.6.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.6.0-pyhf9edf01_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/prek-0.3.10-hb17b654_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/prek-0.4.0-hb17b654_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/prometheus-cpp-1.3.0-ha5d0236_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.25.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.52-pyha770c72_0.conda @@ -3665,23 +3667,23 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-3.8.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.14.4-habeac84_100_cp314.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-discovery-1.2.2-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-discovery-1.3.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.21.2-pyhe01879c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.14.4-h4df99d1_100.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-2.0.7-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2026.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-3.2.1-pyh332efcf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2026.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.14-8_cp314.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.3-py314h67df5f8_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pyzmq-27.1.0-py312hda471dd_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/re2-2025.11.05-h5301d42_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.3-h853b02a_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.37.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.33.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.34.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3339-validator-0.1.4-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3986-validator-0.1.1-pyh9f0ad1d_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3987-syntax-1.1.0-pyhe01879c_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/rpds-py-0.30.0-py314h2e6c369_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/s2n-1.7.1-h1cbb8d7_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/s2n-1.7.2-hc5a330e_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/scipy-1.17.1-py314hf07bd8e_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-2.1.0-pyha191276_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-82.0.1-pyh332efcf_0.conda @@ -3698,15 +3700,15 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h366c992_103.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/tornado-6.5.5-py314h5bd0f2a_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.14.3-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.15.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing_utils-0.1.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/uri-template-1.3.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.6.3-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/virtualenv-21.2.4-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.6.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.7.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/virtualenv-21.3.3-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.7.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/webcolors-25.10.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/webencodings-0.5.1-pyhd8ed1ab_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.9.0-pyhd8ed1ab_0.conda @@ -3716,8 +3718,8 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-1.3.2-h25fd6f3_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda - pypi: https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl - - pypi: git+https://github.com/OpenSourceEconomics/dags.git?rev=cf59c04#cf59c04c6ba07b7c54ca763dc155deea3341a480 - - pypi: https://files.pythonhosted.org/packages/43/f5/ee39c6e92acc742c052f137b47c210cd0a1b72dcd3f98495528bb4d27761/flatten_dict-0.4.2-py2.py3-none-any.whl + - pypi: git+https://github.com/OpenSourceEconomics/dags.git?branch=main#87ff051077f37470514808a3121e7c90429f6383 + - pypi: https://files.pythonhosted.org/packages/72/9f/485516087cd8c44183aaf9ab850247a28e2e4a42a4d62eab77c21f673450/flatten_dict-0.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/70/aa/dfac6d72cc35bc07e7587115b6946e333ef4ccb2e6cd26ecf639438c5d26/jax-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5f/78/a3d9ceda0793f4fb43daa292af7b801932611a1aed442636ddfc93d58c7a/jax_cuda12_pjrt-0.10.0-py3-none-manylinux_2_27_x86_64.whl - pypi: https://files.pythonhosted.org/packages/71/ec/9ba9f450f8a61c8388a3c8b74fe07d76230961aa6af65e7ed8d1bf9073fa/jax_cuda12_plugin-0.10.0-cp314-cp314-manylinux_2_27_x86_64.whl @@ -3727,14 +3729,14 @@ environments: - pypi: https://files.pythonhosted.org/packages/c6/bb/82c7dcf38070b46172a517e2334e665c5bf374a262f99a283ea454bece7c/ml_dtypes-0.5.4-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/24/8d/e12d6ff4b9119db3cbf7b2db1ce257576441bd3c76388c786dea74f20b02/numba-0.65.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/87/96/f3eb235fafa82a34e2ab5dd7dc9ffff998ebf5f0bbc23fa56a96aeb44da6/numba-0.65.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - pypi: https://files.pythonhosted.org/packages/cb/c0/0a517bfe63ccd3b92eb254d264e28fca3c7cab75d07daea315250fb1bf73/nvidia_cublas_cu12-12.9.2.10-py3-none-manylinux_2_27_x86_64.whl - pypi: https://files.pythonhosted.org/packages/18/2a/d4cd8506d2044e082f8cd921be57392e6a9b5ccd3ffdf050362430a3d5d5/nvidia_cuda_cccl_cu12-12.9.27-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - pypi: https://files.pythonhosted.org/packages/c1/2e/b84e32197e33f39907b455b83395a017e697c07a449a2b15fd07fc1c9981/nvidia_cuda_cupti_cu12-12.9.79-py3-none-manylinux_2_25_x86_64.whl - pypi: https://files.pythonhosted.org/packages/25/48/b54a06168a2190572a312bfe4ce443687773eb61367ced31e064953dd2f7/nvidia_cuda_nvcc_cu12-12.9.86-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl - pypi: https://files.pythonhosted.org/packages/b8/85/e4af82cc9202023862090bfca4ea827d533329e925c758f0cde964cb54b7/nvidia_cuda_nvrtc_cu12-12.9.86-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl - pypi: https://files.pythonhosted.org/packages/bc/46/a92db19b8309581092a3add7e6fceb4c301a3fd233969856a8cbf042cd3c/nvidia_cuda_runtime_cu12-12.9.79-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/3c/8e/0c3aeef5f8a9507ad0b2d6fb3f28f38997cda1c7e7f614adc00ceb64a901/nvidia_cudnn_cu12-9.21.1.3-py3-none-manylinux_2_27_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/a0/8f/2ede6b758b7524608472010f632bdd3370ea271d715d1d66044614b84cdc/nvidia_cudnn_cu12-9.22.0.52-py3-none-manylinux_2_27_x86_64.whl - pypi: https://files.pythonhosted.org/packages/95/f4/61e6996dd20481ee834f57a8e9dca28b1869366a135e0d42e2aa8493bdd4/nvidia_cufft_cu12-11.4.1.4-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - pypi: https://files.pythonhosted.org/packages/33/40/79b0c64d44d6c166c0964ec1d803d067f4a145cca23e23925fd351d0e642/nvidia_cusolver_cu12-11.7.5.82-py3-none-manylinux_2_27_x86_64.whl - pypi: https://files.pythonhosted.org/packages/12/46/b0fd4b04f86577921feb97d8e2cf028afe04f614d17fb5013de9282c9216/nvidia_cusparse_cu12-12.5.10.65-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl @@ -3742,13 +3744,13 @@ environments: - pypi: https://files.pythonhosted.org/packages/46/0c/c75bbfb967457a0b7670b8ad267bfc4fffdf341c074e0a80db06c24ccfd4/nvidia_nvjitlink_cu12-12.9.86-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl - pypi: https://files.pythonhosted.org/packages/9e/da/36fa8307cc40889307fed415d70b67d35ec330ffce889a9c03cf8f616cfa/nvidia_nvshmem_cu12-3.6.5-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - pypi: https://files.pythonhosted.org/packages/23/cd/066e86230ae37ed0be70aae89aabf03ca8d9f39c8aea0dec8029455b5540/opt_einsum-3.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/15/88/3cdd54fa279341afa10acf8d2b503556b1375245dccc9315659f795dd2e9/pandas-3.0.2-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/58/3b/1cdec6772bdbaf7b25dab360c59f03cadf05492dd724c6540af905389b07/pandas-3.0.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/51/fe/53ac0cd932db5dcaf55961bc7cb7afdca8d80d8cc7406ed661f0c7dc111a/pdbp-1.8.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/da/9e/21c4088d1d433cfbf0578f138a6df59527dbd9e9d67355ee31b17e6dc774/portion-2.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/3b/ef57eeb4c6f188fbe756b602dc6afa9d66eb0c2e77f02a3d4d51bfec2aaa/quantecon-0.11.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/65/44/bb509c3d2c0b5a87e7a5af1d5917a402a32ff026f777a6d7cb6990746cbb/tabcompleter-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cb/fc/8c82be70b8f96d09943360f34cfb2ecdd3035294c51bce4131eeabe56645/tabcompleter-1.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8d/96/04e7b441807b26b794da5b11e59ed7f83b2cf8af202bd7eba8ad2fa6046e/wadler_lindig-0.1.7-py3-none-any.whl - pypi: ./ tests-cuda13: @@ -3771,26 +3773,26 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/asv_runner-0.2.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/async-lru-2.3.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-26.1.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-auth-0.10.1-h2d2dd48_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-auth-0.10.1-ha62d5e7_3.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-cal-0.9.13-h2c9d079_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-common-0.12.6-hb03c661_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-compression-0.3.2-h8b1a151_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-event-stream-0.6.0-h9b893ba_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-http-0.10.12-h4bacb7b_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-io-0.26.3-hc87160b_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-mqtt-0.15.2-he9ea9c5_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-s3-0.11.5-h6d69fc9_5.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-event-stream-0.7.0-h9b893ba_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-http-0.10.13-h4bacb7b_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-io-0.26.3-h692f434_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-mqtt-0.15.2-hc1936db_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-s3-0.12.2-he6ee468_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-sdkutils-0.2.4-h8b1a151_4.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-checksums-0.2.10-h8b1a151_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-crt-cpp-0.37.4-h4c8aef7_3.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-sdk-cpp-1.11.747-hc3785e1_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-crt-cpp-0.38.3-h745e52d_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-sdk-cpp-1.11.747-h41c0014_4.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/azure-core-cpp-1.16.2-h206d751_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/azure-identity-cpp-1.13.3-hed0cdb0_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/azure-storage-blobs-cpp-12.16.0-hdd73cc9_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/azure-storage-common-cpp-12.12.0-ha7a2c86_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/azure-storage-files-datalake-cpp-12.14.0-h52c5a47_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/azure-storage-blobs-cpp-12.16.0-hf824e48_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/azure-storage-common-cpp-12.13.0-ha7a2c86_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/azure-storage-files-datalake-cpp-12.14.0-h539c000_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.18.0-pyhcf101f3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/backports.zstd-1.3.0-py314h680f03e_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/backports.zstd-1.5.0-py314h680f03e_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.14.3-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils_impl_linux-64-2.45.1-default_hfdba357_102.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils_linux-64-2.45.1-default_h4852527_102.conda @@ -3808,7 +3810,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/cloudpickle-3.1.2-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/coverage-7.13.5-py314h67df5f8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/coverage-7.14.0-py314h67df5f8_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.14.4-py314hd8ed1ab_100.conda - conda: https://conda.anaconda.org/conda-forge/noarch/cuda-cccl_linux-64-13.2.75-ha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/cuda-crt-dev_linux-64-13.2.78-ha770c72_0.conda @@ -3830,7 +3832,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-nvvm-tools-13.2.78-h4bc722e_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/cuda-version-13.2-he2cc418_3.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/debugpy-1.8.20-py314h42812f9_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.3.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/distlib-0.4.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda @@ -3838,16 +3840,16 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.2.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.29.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/fqdn-1.5.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_impl_linux-64-15.2.0-he420e7e_18.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_linux-64-15.2.0-h862fb80_23.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_impl_linux-64-15.2.0-he0086c7_19.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_linux-64-15.2.0-h7be306e_24.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/gflags-2.2.2-h5888daf_1005.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/glog-0.7.1-hbabe93e_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_impl_linux-64-15.2.0-hda75c37_18.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_linux-64-15.2.0-h98b7566_23.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_impl_linux-64-15.2.0-hda75c37_19.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_linux-64-15.2.0-he30e93d_24.conda - conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/h5py-3.16.0-nompi_py314hddf7a69_102.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/hdf5-2.1.0-nompi_hd4fcb43_104.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/hdf5-2.1.0-nompi_h87a9417_105.conda - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda @@ -3857,7 +3859,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.8.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.3.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-7.2.0-pyha191276_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.12.0-pyhecfbec7_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.13.0-pyh53cf698_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ipython_pygments_lexers-1.1.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/isoduration-20.11.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jedi-0.19.2-pyhd8ed1ab_1.conda @@ -3871,9 +3873,9 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_client-8.8.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_core-5.9.1-pyhc90fa1f_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_events-0.12.1-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.17.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.18.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server_terminals-0.5.4-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.5.6-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.5.7-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_pygments-0.3.0-pyhd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_server-2.28.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/kernel-headers_linux-64-4.18.0-he073ed8_9.conda @@ -3883,53 +3885,53 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45.1-default_hbd61a6d_102.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libabseil-20260107.1-cxx17_h7b12aa8_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libaec-1.1.5-h088129d_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-24.0.0-ha7f89c6_0_cpu.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-acero-24.0.0-h635bf11_0_cpu.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-compute-24.0.0-h53684a4_0_cpu.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-dataset-24.0.0-h635bf11_0_cpu.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-substrait-24.0.0-hb4dd7c2_0_cpu.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.11.0-6_h4a7cf45_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-24.0.0-h0935d00_1_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-acero-24.0.0-h635bf11_1_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-compute-24.0.0-h53684a4_1_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-dataset-24.0.0-h635bf11_1_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-substrait-24.0.0-hb4dd7c2_1_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.11.0-7_h4a7cf45_openblas.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.2.0-hb03c661_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.2.0-hb03c661_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.2.0-hb03c661_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.11.0-6_h0358290_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.11.0-7_h0358290_openblas.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libcrc32c-1.1.2-h9c3ff4c_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcurl-8.19.0-hcf29cc6_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcurl-8.20.0-hcf29cc6_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20250104-pl5321h7949ede_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-hd590300_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libevent-2.1.12-hf998b51_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.5-hecca717_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.8.0-hecca717_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.5.2-h3435931_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-he0feb66_18.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/libgcc-devel_linux-64-15.2.0-hcc6f6b0_118.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.2.0-h69a702a_18.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-15.2.0-h69a702a_18.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.2.0-h68bc16d_18.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-he0feb66_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-he0feb66_19.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/libgcc-devel_linux-64-15.2.0-hcc6f6b0_119.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.2.0-h69a702a_19.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-15.2.0-h69a702a_19.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.2.0-h68bc16d_19.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-he0feb66_19.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgoogle-cloud-3.3.0-h25dbb67_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgoogle-cloud-storage-3.3.0-hdbdcf42_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgrpc-1.78.1-h1d1128b_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.18-h3b78370_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.11.0-6_h47877c9_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.11.0-7_h47877c9_openblas.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.3-hb03c661_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libmpdec-4.0.0-hb03c661_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.68.1-h877daf1_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libnvptxcompiler-dev-13.2.78-ha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/libnvptxcompiler-dev_linux-64-13.2.78-ha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.32-pthreads_h94d23a6_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.33-pthreads_h94d23a6_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libopentelemetry-cpp-1.26.0-h9692893_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libopentelemetry-cpp-headers-1.26.0-ha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libparquet-24.0.0-h7376487_0_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libparquet-24.0.0-h7376487_1_cpu.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libprotobuf-6.33.5-h2b00c02_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libre2-11-2025.11.05-h0dc7533_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libsanitizer-15.2.0-h90f66d4_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsanitizer-15.2.0-h90f66d4_19.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libsodium-1.0.21-h280c20c_3.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.53.0-hf4e2dac_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.53.1-h0c1763c_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.11.1-hcf80075_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h934c35e_18.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/libstdcxx-devel_linux-64-15.2.0-hd446a21_118.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.2.0-hdf11a46_18.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libthrift-0.22.0-h454ac66_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h934c35e_19.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/libstdcxx-devel_linux-64-15.2.0-hd446a21_119.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.2.0-hdf11a46_19.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libthrift-0.22.0-h7d032f7_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libutf8proc-2.11.3-hfe17d71_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.42-h5347b49_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-16-2.15.3-hca6bf5a_0.conda @@ -3937,29 +3939,29 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.2-h25fd6f3_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.10.0-h5888daf_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.3-py314h67df5f8_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.2.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/memory_profiler-0.61.0-pyhcf101f3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.2.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/narwhals-2.20.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.2.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/narwhals-2.21.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbclient-0.10.4-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.17.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbformat-5.10.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.6-hdb14827_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nest-asyncio-1.6.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/nlohmann_json-3.12.0-h54a6638_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/notebook-shim-0.2.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.4.3-py314h2b28147_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.4.5-py314h2b28147_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.2-h35e630c_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/orc-2.3.0-h21090e2_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/overrides-7.7.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.1-pyhc364b38_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.2-pyhc364b38_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pandocfilters-1.5.0-pyhd8ed1ab_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.6-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.7-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pexpect-4.9.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.9.6-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/plotly-6.6.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.6.0-pyhf9edf01_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/prek-0.3.10-hb17b654_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/prek-0.4.0-hb17b654_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/prometheus-cpp-1.3.0-ha5d0236_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.25.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.52-pyha770c72_0.conda @@ -3978,23 +3980,23 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-3.8.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.14.4-habeac84_100_cp314.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-discovery-1.2.2-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-discovery-1.3.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.21.2-pyhe01879c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.14.4-h4df99d1_100.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-2.0.7-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2026.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-3.2.1-pyh332efcf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2026.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.14-8_cp314.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.3-py314h67df5f8_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pyzmq-27.1.0-py312hda471dd_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/re2-2025.11.05-h5301d42_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.3-h853b02a_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.37.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.33.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.34.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3339-validator-0.1.4-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3986-validator-0.1.1-pyh9f0ad1d_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3987-syntax-1.1.0-pyhe01879c_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/rpds-py-0.30.0-py314h2e6c369_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/s2n-1.7.1-h1cbb8d7_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/s2n-1.7.2-hc5a330e_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/scipy-1.17.1-py314hf07bd8e_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-2.1.0-pyha191276_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-82.0.1-pyh332efcf_0.conda @@ -4011,15 +4013,15 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h366c992_103.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/tornado-6.5.5-py314h5bd0f2a_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.14.3-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.15.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing_utils-0.1.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/uri-template-1.3.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.6.3-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/virtualenv-21.2.4-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.6.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.7.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/virtualenv-21.3.3-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.7.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/webcolors-25.10.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/webencodings-0.5.1-pyhd8ed1ab_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.9.0-pyhd8ed1ab_0.conda @@ -4029,8 +4031,8 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-1.3.2-h25fd6f3_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda - pypi: https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl - - pypi: git+https://github.com/OpenSourceEconomics/dags.git?rev=cf59c04#cf59c04c6ba07b7c54ca763dc155deea3341a480 - - pypi: https://files.pythonhosted.org/packages/43/f5/ee39c6e92acc742c052f137b47c210cd0a1b72dcd3f98495528bb4d27761/flatten_dict-0.4.2-py2.py3-none-any.whl + - pypi: git+https://github.com/OpenSourceEconomics/dags.git?branch=main#87ff051077f37470514808a3121e7c90429f6383 + - pypi: https://files.pythonhosted.org/packages/72/9f/485516087cd8c44183aaf9ab850247a28e2e4a42a4d62eab77c21f673450/flatten_dict-0.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/70/aa/dfac6d72cc35bc07e7587115b6946e333ef4ccb2e6cd26ecf639438c5d26/jax-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/21/98/77f15d81fd0637da454e453c8456d4a2b5c8b2e66823b4237ee8689152cf/jax_cuda13_pjrt-0.10.0-py3-none-manylinux_2_27_x86_64.whl - pypi: https://files.pythonhosted.org/packages/8f/2b/5c63c29d155afdf1d7827f8c04efe8cac47fc6783d8c53959e43de879dcc/jax_cuda13_plugin-0.10.0-cp314-cp314-manylinux_2_27_x86_64.whl @@ -4040,15 +4042,15 @@ environments: - pypi: https://files.pythonhosted.org/packages/c6/bb/82c7dcf38070b46172a517e2334e665c5bf374a262f99a283ea454bece7c/ml_dtypes-0.5.4-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/24/8d/e12d6ff4b9119db3cbf7b2db1ce257576441bd3c76388c786dea74f20b02/numba-0.65.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/3d/ec/c9b2998aebe3149dee2769e501257e048c8701de51263925f4dff76ddedc/nvidia_cublas-13.4.0.1-py3-none-manylinux_2_27_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/87/96/f3eb235fafa82a34e2ab5dd7dc9ffff998ebf5f0bbc23fa56a96aeb44da6/numba-0.65.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/f8/79/0cefdaa1d9e45018a227bac64a79b92d2733cde28a8fd09c65362de08622/nvidia_cublas-13.4.1.1-py3-none-manylinux_2_27_x86_64.whl - pypi: https://files.pythonhosted.org/packages/92/87/d23db8276b76b4a7e4a702eebdc0a70e3b56c17b4dcd980ecb0f68b022e1/nvidia_cuda_cccl-13.2.75-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - pypi: https://files.pythonhosted.org/packages/ea/78/501eee5cce9202fba2f3476529e296a7f6d003261d80b52ab0abfa09ddd6/nvidia_cuda_crt-13.2.78-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - pypi: https://files.pythonhosted.org/packages/b7/2d/cbf8f6288259c502165282fdaa2b733daae98434e3f2aee2b7952ba87c6f/nvidia_cuda_cupti-13.2.75-py3-none-manylinux_2_25_x86_64.whl - pypi: https://files.pythonhosted.org/packages/65/0f/c7c7d538c61794130e759ad74710ab5aa8cab1f700ee1754381f8c665605/nvidia_cuda_nvcc-13.2.78-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - pypi: https://files.pythonhosted.org/packages/5f/96/237b40b171e06eb65905375c4ad5c96f78c2f861ac6e8ae7f650d95e1dfd/nvidia_cuda_nvrtc-13.2.78-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl - pypi: https://files.pythonhosted.org/packages/dc/74/f1493b0774c6eaf0234512bb650e1ab90ce8f61fecf0b4aaf1fb416f571e/nvidia_cuda_runtime-13.2.75-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/85/be/68e659d798aaad24817f10acbad952491b43df668376b704618296887f62/nvidia_cudnn_cu13-9.21.1.3-py3-none-manylinux_2_27_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/57/96/ce2cb84b5e8bb94dd55f554e3454b91e9ecd6708aa27d4a7b12f287613bc/nvidia_cudnn_cu13-9.22.0.52-py3-none-manylinux_2_27_x86_64.whl - pypi: https://files.pythonhosted.org/packages/36/3e/8d717a6e1f6e27b85b64650b1104dbcf6108c9dc7e27e9e26a0d8e936cc5/nvidia_cufft-12.2.0.46-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - pypi: https://files.pythonhosted.org/packages/6b/97/a3c41eac54c89f6aac788d2b3ccd6642b32aa6b79650af3dedb8ee7c2bfa/nvidia_cusolver-12.2.0.1-py3-none-manylinux_2_27_x86_64.whl - pypi: https://files.pythonhosted.org/packages/b7/bd/bad43b37bcf13167637bef26399693d517b95092d742e8749eda5f4a85f3/nvidia_cusparse-12.7.10.1-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl @@ -4057,13 +4059,13 @@ environments: - pypi: https://files.pythonhosted.org/packages/5d/7b/2ab033584a3339552472ac8d79543c503a0e06dd0d082448b06697e7f716/nvidia_nvshmem_cu13-3.6.5-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - pypi: https://files.pythonhosted.org/packages/e8/1f/930d63ccc8adcdf27bfc051a24e3e4da2cf6ef987848d6d1d642e29d704b/nvidia_nvvm-13.2.78-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl - pypi: https://files.pythonhosted.org/packages/23/cd/066e86230ae37ed0be70aae89aabf03ca8d9f39c8aea0dec8029455b5540/opt_einsum-3.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/15/88/3cdd54fa279341afa10acf8d2b503556b1375245dccc9315659f795dd2e9/pandas-3.0.2-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/58/3b/1cdec6772bdbaf7b25dab360c59f03cadf05492dd724c6540af905389b07/pandas-3.0.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/51/fe/53ac0cd932db5dcaf55961bc7cb7afdca8d80d8cc7406ed661f0c7dc111a/pdbp-1.8.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/da/9e/21c4088d1d433cfbf0578f138a6df59527dbd9e9d67355ee31b17e6dc774/portion-2.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/3b/ef57eeb4c6f188fbe756b602dc6afa9d66eb0c2e77f02a3d4d51bfec2aaa/quantecon-0.11.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/65/44/bb509c3d2c0b5a87e7a5af1d5917a402a32ff026f777a6d7cb6990746cbb/tabcompleter-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cb/fc/8c82be70b8f96d09943360f34cfb2ecdd3035294c51bce4131eeabe56645/tabcompleter-1.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8d/96/04e7b441807b26b794da5b11e59ed7f83b2cf8af202bd7eba8ad2fa6046e/wadler_lindig-0.1.7-py3-none-any.whl - pypi: ./ tests-metal: @@ -4087,26 +4089,26 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/asv_runner-0.2.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/async-lru-2.3.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-26.1.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-auth-0.10.1-hcb83491_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-auth-0.10.1-ha7d4cc1_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-cal-0.9.13-h6ee9776_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-common-0.12.6-hc919400_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-compression-0.3.2-h3e7f9b5_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-event-stream-0.6.0-h351c84d_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-http-0.10.12-h95cdebe_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-io-0.26.3-h4137820_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-mqtt-0.15.2-h69e7467_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-s3-0.11.5-ha5d16b2_5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-event-stream-0.7.0-h351c84d_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-http-0.10.13-h95cdebe_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-io-0.26.3-h4137820_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-mqtt-0.15.2-h8860bc9_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-s3-0.12.2-h07b101a_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-sdkutils-0.2.4-h16f91aa_4.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-checksums-0.2.10-h3e7f9b5_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-crt-cpp-0.37.4-h5505c15_3.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-sdk-cpp-1.11.747-had22720_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-crt-cpp-0.38.3-hba17502_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-sdk-cpp-1.11.747-h30a6df1_4.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/azure-core-cpp-1.16.2-he5ae378_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/azure-identity-cpp-1.13.3-h810541e_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/azure-storage-blobs-cpp-12.16.0-hc57151b_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/azure-storage-common-cpp-12.12.0-he467506_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/azure-storage-files-datalake-cpp-12.14.0-hf8a9d22_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/azure-storage-blobs-cpp-12.16.0-h5446563_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/azure-storage-common-cpp-12.13.0-he467506_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/azure-storage-files-datalake-cpp-12.14.0-hdc9d693_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.18.0-pyhcf101f3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/backports.zstd-1.3.0-py314h680f03e_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/backports.zstd-1.5.0-py314h680f03e_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.14.3-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-6.3.0-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-with-css-6.3.0-hbca2aae_1.conda @@ -4122,10 +4124,10 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/cloudpickle-3.1.2-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/coverage-7.13.5-py314h6e9b3f0_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/coverage-7.14.0-py314h6e9b3f0_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.14.4-py314hd8ed1ab_100.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/debugpy-1.8.20-py314he609de1_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.3.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/distlib-0.4.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda @@ -4138,7 +4140,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/h5py-3.16.0-nompi_py314h658a3ac_102.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/hdf5-2.1.0-nompi_hc95e3eb_104.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/hdf5-2.1.0-nompi_he586413_105.conda - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda @@ -4147,7 +4149,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.8.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.3.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-7.2.0-pyh5552912_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.12.0-pyhecfbec7_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.13.0-pyh53cf698_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ipython_pygments_lexers-1.1.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/isoduration-20.11.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jedi-0.19.2-pyhd8ed1ab_1.conda @@ -4161,84 +4163,84 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_client-8.8.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_core-5.9.1-pyhc90fa1f_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_events-0.12.1-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.17.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.18.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server_terminals-0.5.4-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.5.6-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.5.7-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_pygments-0.3.0-pyhd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_server-2.28.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/krb5-1.22.2-h385eeb1_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/lark-1.3.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libabseil-20260107.1-cxx17_h2062a1b_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libaec-1.1.5-h8664d51_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libarrow-24.0.0-h2124f06_0_cpu.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libarrow-acero-24.0.0-hee8fe31_0_cpu.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libarrow-compute-24.0.0-h3b6a98a_0_cpu.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libarrow-dataset-24.0.0-hee8fe31_0_cpu.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libarrow-substrait-24.0.0-h05be00f_0_cpu.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libblas-3.11.0-6_h51639a9_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libarrow-24.0.0-h37fbca7_1_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libarrow-acero-24.0.0-hee8fe31_1_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libarrow-compute-24.0.0-h3b6a98a_1_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libarrow-dataset-24.0.0-hee8fe31_1_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libarrow-substrait-24.0.0-h05be00f_1_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libblas-3.11.0-7_h51639a9_openblas.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlicommon-1.2.0-hc919400_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlidec-1.2.0-hc919400_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlienc-1.2.0-hc919400_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcblas-3.11.0-6_hb0561ab_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcblas-3.11.0-7_hb0561ab_openblas.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcrc32c-1.1.2-hbdafb3b_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcurl-8.19.0-hd5a2499_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-22.1.4-h55c6f16_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcurl-8.20.0-hd5a2499_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-22.1.5-h55c6f16_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libedit-3.1.20250104-pl5321hafb1f1b_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libev-4.33-h93a5062_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libevent-2.1.12-h2757513_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.7.5-hf6b4638_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.8.0-hf6b4638_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.5.2-hcf2aa1b_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgcc-15.2.0-hcbb3090_18.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran-15.2.0-h07b0088_18.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran5-15.2.0-hdae7583_18.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgcc-15.2.0-hcbb3090_19.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran-15.2.0-h07b0088_19.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran5-15.2.0-hdae7583_19.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgoogle-cloud-3.3.0-he41eb1d_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgoogle-cloud-storage-3.3.0-ha114238_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgrpc-1.78.1-h3e3f78d_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libiconv-1.18-h23cfdf5_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblapack-3.11.0-6_hd9741b5_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblapack-3.11.0-7_hd9741b5_openblas.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblzma-5.8.3-h8088a28_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libmpdec-4.0.0-h84a0fba_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libnghttp2-1.68.1-h8f3e76b_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libopenblas-0.3.32-openmp_he657e61_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libopenblas-0.3.33-openmp_he657e61_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libopentelemetry-cpp-1.26.0-h08d5cc3_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libopentelemetry-cpp-headers-1.26.0-hce30654_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libparquet-24.0.0-h16c0493_0_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libparquet-24.0.0-h16c0493_1_cpu.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libprotobuf-6.33.5-h4a5acfd_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libre2-11-2025.11.05-h4c27e2a_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsodium-1.0.21-h1a92334_3.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.53.0-h1b79a29_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.53.1-h1b79a29_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libssh2-1.11.1-h1590b86_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libthrift-0.22.0-h14a376c_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libthrift-0.22.0-h1fb9c8a_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libutf8proc-2.11.3-h2431656_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libxml2-16-2.15.3-h6967ea9_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libxml2-2.15.3-heed7d32_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzlib-1.3.2-h8088a28_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/llvm-openmp-22.1.4-hc7d1edf_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/llvm-openmp-22.1.5-hc7d1edf_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/lz4-c-1.10.0-h286801f_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/markupsafe-3.0.3-py314h6e9b3f0_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.2.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/memory_profiler-0.61.0-pyhcf101f3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.2.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/narwhals-2.20.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.2.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/narwhals-2.21.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbclient-0.10.4-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.17.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbformat-5.10.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ncurses-6.5-h5e97a16_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ncurses-6.6-h1d4f5a5_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nest-asyncio-1.6.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/nlohmann_json-3.12.0-h784d473_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/notebook-shim-0.2.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/numpy-2.4.3-py314h1569ea8_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/numpy-2.4.5-py314hb79c6fa_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.6.2-hd24854e_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/orc-2.3.0-hd11884d_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/overrides-7.7.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.1-pyhc364b38_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.2-pyhc364b38_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pandocfilters-1.5.0-pyhd8ed1ab_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.6-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.7-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pexpect-4.9.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.9.6-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/plotly-6.6.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.6.0-pyhf9edf01_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/prek-0.3.10-h6fdd925_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/prek-0.4.0-h6fdd925_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/prometheus-cpp-1.3.0-h0967b3e_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.25.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.52-pyha770c72_0.conda @@ -4259,18 +4261,18 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-3.8.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.14.4-h4c637c5_100_cp314.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-discovery-1.2.2-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-discovery-1.3.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.21.2-pyhe01879c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.14.4-h4df99d1_100.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-2.0.7-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2026.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-3.2.1-pyh332efcf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2026.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.14-8_cp314.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyyaml-6.0.3-py314h6e9b3f0_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyzmq-27.1.0-py312h022ad19_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/re2-2025.11.05-ha480c28_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/readline-8.3-h46df422_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.37.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.33.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.34.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3339-validator-0.1.4-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3986-validator-0.1.1-pyh9f0ad1d_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3987-syntax-1.1.0-pyhe01879c_1.conda @@ -4290,15 +4292,15 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-h010d191_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tornado-6.5.5-py314h6c2aa35_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.14.3-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.15.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing_utils-0.1.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/uri-template-1.3.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.6.3-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/virtualenv-21.2.4-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.6.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.7.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/virtualenv-21.3.3-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.7.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/webcolors-25.10.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/webencodings-0.5.1-pyhd8ed1ab_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.9.0-pyhd8ed1ab_0.conda @@ -4308,8 +4310,8 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zlib-1.3.2-h8088a28_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda - pypi: https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl - - pypi: git+https://github.com/OpenSourceEconomics/dags.git?rev=cf59c04#cf59c04c6ba07b7c54ca763dc155deea3341a480 - - pypi: https://files.pythonhosted.org/packages/43/f5/ee39c6e92acc742c052f137b47c210cd0a1b72dcd3f98495528bb4d27761/flatten_dict-0.4.2-py2.py3-none-any.whl + - pypi: git+https://github.com/OpenSourceEconomics/dags.git?branch=main#87ff051077f37470514808a3121e7c90429f6383 + - pypi: https://files.pythonhosted.org/packages/72/9f/485516087cd8c44183aaf9ab850247a28e2e4a42a4d62eab77c21f673450/flatten_dict-0.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/70/aa/dfac6d72cc35bc07e7587115b6946e333ef4ccb2e6cd26ecf639438c5d26/jax-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/09/dc/6d8fbfc29d902251cf333414cf7dcfaf4b252a9920c881354584ed36270d/jax_metal-0.1.1-py3-none-macosx_13_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/a7/25/e1e52a21786b321fb6a2edf9ef9971aa70f06bb2738aef9afd6d8f46a441/jaxlib-0.10.0-cp314-cp314-macosx_11_0_arm64.whl @@ -4318,15 +4320,15 @@ environments: - pypi: https://files.pythonhosted.org/packages/72/4e/1339dc6e2557a344f5ba5590872e80346f76f6cb2ac3dd16e4666e88818c/ml_dtypes-0.5.4-cp314-cp314-macosx_10_13_universal2.whl - pypi: https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/56/a4/90edb01e9176053578e343d7a7276bc28356741ee67059aed8ed2c1a4e59/numba-0.65.0-cp314-cp314-macosx_12_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/4f/2e/8aed9b726d9ba5f11ad287645fd479e88278db3060a25cb1225d730eb2b7/numba-0.65.1-cp314-cp314-macosx_12_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/23/cd/066e86230ae37ed0be70aae89aabf03ca8d9f39c8aea0dec8029455b5540/opt_einsum-3.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/95/25/bdb9326c3b5455f8d4d3549fce7abcf967259de146fe2cf7a82368141948/pandas-3.0.2-cp314-cp314-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/68/10/bf2d6738d72748b961a3751ab89522d58c54efc36a8e1a12161216cd45cf/pandas-3.0.3-cp314-cp314-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/51/fe/53ac0cd932db5dcaf55961bc7cb7afdca8d80d8cc7406ed661f0c7dc111a/pdbp-1.8.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/da/9e/21c4088d1d433cfbf0578f138a6df59527dbd9e9d67355ee31b17e6dc774/portion-2.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/3b/ef57eeb4c6f188fbe756b602dc6afa9d66eb0c2e77f02a3d4d51bfec2aaa/quantecon-0.11.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/65/44/bb509c3d2c0b5a87e7a5af1d5917a402a32ff026f777a6d7cb6990746cbb/tabcompleter-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cb/fc/8c82be70b8f96d09943360f34cfb2ecdd3035294c51bce4131eeabe56645/tabcompleter-1.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8d/96/04e7b441807b26b794da5b11e59ed7f83b2cf8af202bd7eba8ad2fa6046e/wadler_lindig-0.1.7-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/87/1b/9e33c09813d65e248f7f773119148a612516a4bea93e9c6f545f78455b7c/wheel-0.47.0-py3-none-any.whl - pypi: ./ @@ -4351,26 +4353,26 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/asv_runner-0.2.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/async-lru-2.3.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-26.1.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-auth-0.10.1-h2d2dd48_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-auth-0.10.1-ha62d5e7_3.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-cal-0.9.13-h2c9d079_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-common-0.12.6-hb03c661_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-compression-0.3.2-h8b1a151_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-event-stream-0.6.0-h9b893ba_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-http-0.10.12-h4bacb7b_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-io-0.26.3-hc87160b_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-mqtt-0.15.2-he9ea9c5_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-s3-0.11.5-h6d69fc9_5.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-event-stream-0.7.0-h9b893ba_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-http-0.10.13-h4bacb7b_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-io-0.26.3-h692f434_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-mqtt-0.15.2-hc1936db_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-s3-0.12.2-he6ee468_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-sdkutils-0.2.4-h8b1a151_4.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-checksums-0.2.10-h8b1a151_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-crt-cpp-0.37.4-h4c8aef7_3.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-sdk-cpp-1.11.747-hc3785e1_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-crt-cpp-0.38.3-h745e52d_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-sdk-cpp-1.11.747-h41c0014_4.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/azure-core-cpp-1.16.2-h206d751_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/azure-identity-cpp-1.13.3-hed0cdb0_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/azure-storage-blobs-cpp-12.16.0-hdd73cc9_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/azure-storage-common-cpp-12.12.0-ha7a2c86_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/azure-storage-files-datalake-cpp-12.14.0-h52c5a47_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/azure-storage-blobs-cpp-12.16.0-hf824e48_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/azure-storage-common-cpp-12.13.0-ha7a2c86_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/azure-storage-files-datalake-cpp-12.14.0-h539c000_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.18.0-pyhcf101f3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/backports.zstd-1.3.0-py314h680f03e_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/backports.zstd-1.5.0-py314h680f03e_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.14.3-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-6.3.0-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-with-css-6.3.0-hbca2aae_1.conda @@ -4390,13 +4392,13 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/contourpy-1.3.3-py314h97ea11e_4.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/coverage-7.13.5-py314h67df5f8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/coverage-7.14.0-py314h67df5f8_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.14.4-py314hd8ed1ab_100.conda - conda: https://conda.anaconda.org/conda-forge/noarch/cycler-0.12.1-pyhcf101f3_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/cyrus-sasl-2.1.28-hac629b4_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/dbus-1.16.2-h24cb091_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/debugpy-1.8.20-py314h42812f9_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.3.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/distlib-0.4.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/double-conversion-3.4.0-hecca717_0.conda @@ -4411,7 +4413,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.17.1-h27c8c51_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-ecosystem-1-0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-hc364b38_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/fonttools-4.62.1-pyh7db6752_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/fonttools-4.63.0-pyh7db6752_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/fqdn-1.5.1-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/freetype-2.14.3-ha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/gflags-2.2.2-h5888daf_1005.conda @@ -4421,7 +4423,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/h5py-3.16.0-nompi_py314hddf7a69_102.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-14.2.0-h6083320_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/hdf5-2.1.0-nompi_hd4fcb43_104.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/hdf5-2.1.0-nompi_h87a9417_105.conda - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda @@ -4431,7 +4433,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.8.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.3.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-7.2.0-pyha191276_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.12.0-pyhecfbec7_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.13.0-pyh53cf698_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ipython_pygments_lexers-1.1.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/isoduration-20.11.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jedi-0.19.2-pyhd8ed1ab_1.conda @@ -4445,83 +4447,83 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_client-8.8.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_core-5.9.1-pyhc90fa1f_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_events-0.12.1-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.17.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.18.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server_terminals-0.5.4-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.5.6-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.5.7-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_pygments-0.3.0-pyhd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_server-2.28.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.3-hb9d3cd8_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.5.0-py314h97ea11e_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/krb5-1.22.2-ha1258a1_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/lark-1.3.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.18-h0c24ade_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.19.1-h0c24ade_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45.1-default_hbd61a6d_102.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/lerc-4.1.0-hdb68285_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libabseil-20260107.1-cxx17_h7b12aa8_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libaec-1.1.5-h088129d_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-24.0.0-ha7f89c6_0_cpu.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-acero-24.0.0-h635bf11_0_cpu.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-compute-24.0.0-h53684a4_0_cpu.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-dataset-24.0.0-h635bf11_0_cpu.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-substrait-24.0.0-hb4dd7c2_0_cpu.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.11.0-6_h4a7cf45_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-24.0.0-h0935d00_1_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-acero-24.0.0-h635bf11_1_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-compute-24.0.0-h53684a4_1_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-dataset-24.0.0-h635bf11_1_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-substrait-24.0.0-hb4dd7c2_1_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.11.0-7_h4a7cf45_openblas.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.2.0-hb03c661_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.2.0-hb03c661_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.2.0-hb03c661_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.11.0-6_h0358290_openblas.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libclang13-22.1.4-default_h746c552_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.11.0-7_h0358290_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libclang13-22.1.5-default_h746c552_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libcrc32c-1.1.2-h9c3ff4c_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/linux-64/libcups-2.3.3-h7a8fb5f_6.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcurl-8.19.0-hcf29cc6_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcurl-8.20.0-hcf29cc6_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.25-h17f619e_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libdrm-2.4.125-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libdrm-2.4.127-hb03c661_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20250104-pl5321h7949ede_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libegl-1.7.0-ha4b6fd6_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libegl-devel-1.7.0-ha4b6fd6_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-hd590300_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libevent-2.1.12-hf998b51_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.5-hecca717_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.8.0-hecca717_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.5.2-h3435931_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libfreetype-2.14.3-ha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libfreetype6-2.14.3-h73754d4_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-he0feb66_18.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.2.0-h69a702a_18.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-15.2.0-h69a702a_18.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.2.0-h68bc16d_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-he0feb66_19.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.2.0-h69a702a_19.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-15.2.0-h69a702a_19.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.2.0-h68bc16d_19.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgl-1.7.0-ha4b6fd6_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgl-devel-1.7.0-ha4b6fd6_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libglib-2.86.4-h6548e54_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libglib-2.88.1-h0d30a3d_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libglvnd-1.7.0-ha4b6fd6_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libglx-1.7.0-ha4b6fd6_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libglx-devel-1.7.0-ha4b6fd6_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-he0feb66_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-he0feb66_19.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgoogle-cloud-3.3.0-h25dbb67_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgoogle-cloud-storage-3.3.0-hdbdcf42_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgrpc-1.78.1-h1d1128b_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.18-h3b78370_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libjpeg-turbo-3.1.4.1-hb03c661_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.11.0-6_h47877c9_openblas.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libllvm22-22.1.4-hf7376ad_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.11.0-7_h47877c9_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libllvm22-22.1.5-hf7376ad_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.3-hb03c661_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libmpdec-4.0.0-hb03c661_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.68.1-h877daf1_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libntlm-1.8-hb9d3cd8_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.32-pthreads_h94d23a6_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.33-pthreads_h94d23a6_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libopengl-1.7.0-ha4b6fd6_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libopentelemetry-cpp-1.26.0-h9692893_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libopentelemetry-cpp-headers-1.26.0-ha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libparquet-24.0.0-h7376487_0_cpu.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libpciaccess-0.18-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libparquet-24.0.0-h7376487_1_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libpciaccess-0.19-hb03c661_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.58-h421ea60_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libpq-18.3-h9abb657_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libpq-18.4-hd5a49e9_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libprotobuf-6.33.5-h2b00c02_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libre2-11-2025.11.05-h0dc7533_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libsodium-1.0.21-h280c20c_3.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.53.0-hf4e2dac_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.53.1-h0c1763c_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.11.1-hcf80075_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h934c35e_18.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.2.0-hdf11a46_18.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libthrift-0.22.0-h454ac66_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h934c35e_19.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.2.0-hdf11a46_19.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libthrift-0.22.0-h7d032f7_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.7.1-h9d88235_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libutf8proc-2.11.3-hfe17d71_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.42-h5347b49_0.conda @@ -4536,33 +4538,33 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.2-h25fd6f3_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.10.0-h5888daf_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.3-py314h67df5f8_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.10.8-py314hdafbbf9_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.10.8-py314h1194b4b_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.10.9-py314hdafbbf9_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.10.9-py314h1194b4b_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.2.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/memory_profiler-0.61.0-pyhcf101f3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.2.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.2.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/narwhals-2.20.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/narwhals-2.21.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbclient-0.10.4-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.17.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbformat-5.10.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.6-hdb14827_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nest-asyncio-1.6.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/nlohmann_json-3.12.0-h54a6638_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/notebook-shim-0.2.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.4.3-py314h2b28147_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.4.5-py314h2b28147_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/numpy-typing-compat-20251206.2.4-pyhd6139ff_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.5.4-h55fea9a_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/openldap-2.6.13-hbde042b_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.2-h35e630c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/optype-0.17.0-pyhc364b38_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/optype-numpy-0.17.0-pyhada4073_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/optype-0.17.1-pyhc364b38_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/optype-numpy-0.17.1-pyh3cfb546_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/orc-2.3.0-h21090e2_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/overrides-7.7.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.1-pyhc364b38_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.2-pyhc364b38_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pandas-stubs-3.0.0.260204-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pandocfilters-1.5.0-pyhd8ed1ab_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.6-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.7-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pcre2-10.47-haa7fec5_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pexpect-4.9.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pillow-12.2.0-py314h8ec4b1a_0.conda @@ -4570,7 +4572,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.9.6-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/plotly-6.6.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.6.0-pyhf9edf01_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/prek-0.3.10-hb17b654_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/prek-0.4.0-hb17b654_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/prometheus-cpp-1.3.0-ha5d0236_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.25.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.52-pyha770c72_0.conda @@ -4585,32 +4587,32 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.20.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pympler-1.1-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.3.2-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/pyside6-6.11.0-py314h3987850_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pyside6-6.11.1-py314h3987850_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-9.0.3-pyhc364b38_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-cov-7.1.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-3.8.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.14.4-habeac84_100_cp314.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-discovery-1.2.2-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-discovery-1.3.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.21.2-pyhe01879c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.14.4-h4df99d1_100.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-2.0.7-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2026.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-3.2.1-pyh332efcf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2026.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.14-8_cp314.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.3-py314h67df5f8_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pyzmq-27.1.0-py312hda471dd_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/qhull-2020.2-h434a139_5.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/qt6-main-6.11.0-pl5321h16c4a6b_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/qt6-main-6.11.1-pl5321h16c4a6b_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/re2-2025.11.05-h5301d42_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.3-h853b02a_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.37.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.33.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.34.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3339-validator-0.1.4-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3986-validator-0.1.1-pyh9f0ad1d_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3987-syntax-1.1.0-pyhe01879c_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/rpds-py-0.30.0-py314h2e6c369_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/s2n-1.7.1-h1cbb8d7_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/s2n-1.7.2-hc5a330e_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/scipy-1.17.1-py314hf07bd8e_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/scipy-stubs-1.17.1.4-pyhc364b38_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-2.1.0-pyha191276_1.conda @@ -4627,19 +4629,19 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h366c992_103.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/tornado-6.5.5-py314h5bd0f2a_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.14.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ty-0.0.32-h4e94fc0_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/types-pytz-2026.1.1.20260408-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.15.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ty-0.0.37-h4e94fc0_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/types-pytz-2026.2.0.20260506-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing_utils-0.1.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-17.0.1-py314h5bd0f2a_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/uri-template-1.3.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.6.3-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/virtualenv-21.2.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.7.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/virtualenv-21.3.3-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/wayland-1.25.0-hd6090a7_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.6.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.7.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/webcolors-25.10.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/webencodings-0.5.1-pyhd8ed1ab_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.9.0-pyhd8ed1ab_0.conda @@ -4673,8 +4675,8 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-ng-2.3.3-hceb46e0_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda - pypi: https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl - - pypi: git+https://github.com/OpenSourceEconomics/dags.git?rev=cf59c04#cf59c04c6ba07b7c54ca763dc155deea3341a480 - - pypi: https://files.pythonhosted.org/packages/43/f5/ee39c6e92acc742c052f137b47c210cd0a1b72dcd3f98495528bb4d27761/flatten_dict-0.4.2-py2.py3-none-any.whl + - pypi: git+https://github.com/OpenSourceEconomics/dags.git?branch=main#87ff051077f37470514808a3121e7c90429f6383 + - pypi: https://files.pythonhosted.org/packages/72/9f/485516087cd8c44183aaf9ab850247a28e2e4a42a4d62eab77c21f673450/flatten_dict-0.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/70/aa/dfac6d72cc35bc07e7587115b6946e333ef4ccb2e6cd26ecf639438c5d26/jax-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a1/8e/b2a08ffc51c93842de71f7f988865cebfa7f43d6721957812dc8cc8b9d40/jaxlib-0.10.0-cp314-cp314-manylinux_2_27_x86_64.whl - pypi: https://files.pythonhosted.org/packages/94/05/3e39d416fb92b2738a76e8265e6bfc5d10542f90a7c32ad1eb831eea3fa3/jaxtyping-0.3.9-py3-none-any.whl @@ -4682,15 +4684,15 @@ environments: - pypi: https://files.pythonhosted.org/packages/c6/bb/82c7dcf38070b46172a517e2334e665c5bf374a262f99a283ea454bece7c/ml_dtypes-0.5.4-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/24/8d/e12d6ff4b9119db3cbf7b2db1ce257576441bd3c76388c786dea74f20b02/numba-0.65.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/87/96/f3eb235fafa82a34e2ab5dd7dc9ffff998ebf5f0bbc23fa56a96aeb44da6/numba-0.65.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - pypi: https://files.pythonhosted.org/packages/23/cd/066e86230ae37ed0be70aae89aabf03ca8d9f39c8aea0dec8029455b5540/opt_einsum-3.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/15/88/3cdd54fa279341afa10acf8d2b503556b1375245dccc9315659f795dd2e9/pandas-3.0.2-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/58/3b/1cdec6772bdbaf7b25dab360c59f03cadf05492dd724c6540af905389b07/pandas-3.0.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/51/fe/53ac0cd932db5dcaf55961bc7cb7afdca8d80d8cc7406ed661f0c7dc111a/pdbp-1.8.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/da/9e/21c4088d1d433cfbf0578f138a6df59527dbd9e9d67355ee31b17e6dc774/portion-2.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/3b/ef57eeb4c6f188fbe756b602dc6afa9d66eb0c2e77f02a3d4d51bfec2aaa/quantecon-0.11.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/65/44/bb509c3d2c0b5a87e7a5af1d5917a402a32ff026f777a6d7cb6990746cbb/tabcompleter-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cb/fc/8c82be70b8f96d09943360f34cfb2ecdd3035294c51bce4131eeabe56645/tabcompleter-1.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8d/96/04e7b441807b26b794da5b11e59ed7f83b2cf8af202bd7eba8ad2fa6046e/wadler_lindig-0.1.7-py3-none-any.whl - pypi: ./ osx-arm64: @@ -4706,26 +4708,26 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/asv_runner-0.2.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/async-lru-2.3.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-26.1.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-auth-0.10.1-hcb83491_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-auth-0.10.1-ha7d4cc1_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-cal-0.9.13-h6ee9776_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-common-0.12.6-hc919400_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-compression-0.3.2-h3e7f9b5_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-event-stream-0.6.0-h351c84d_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-http-0.10.12-h95cdebe_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-io-0.26.3-h4137820_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-mqtt-0.15.2-h69e7467_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-s3-0.11.5-ha5d16b2_5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-event-stream-0.7.0-h351c84d_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-http-0.10.13-h95cdebe_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-io-0.26.3-h4137820_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-mqtt-0.15.2-h8860bc9_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-s3-0.12.2-h07b101a_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-sdkutils-0.2.4-h16f91aa_4.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-checksums-0.2.10-h3e7f9b5_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-crt-cpp-0.37.4-h5505c15_3.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-sdk-cpp-1.11.747-had22720_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-crt-cpp-0.38.3-hba17502_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-sdk-cpp-1.11.747-h30a6df1_4.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/azure-core-cpp-1.16.2-he5ae378_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/azure-identity-cpp-1.13.3-h810541e_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/azure-storage-blobs-cpp-12.16.0-hc57151b_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/azure-storage-common-cpp-12.12.0-he467506_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/azure-storage-files-datalake-cpp-12.14.0-hf8a9d22_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/azure-storage-blobs-cpp-12.16.0-h5446563_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/azure-storage-common-cpp-12.13.0-he467506_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/azure-storage-files-datalake-cpp-12.14.0-hdc9d693_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.18.0-pyhcf101f3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/backports.zstd-1.3.0-py314h680f03e_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/backports.zstd-1.5.0-py314h680f03e_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.14.3-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-6.3.0-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-with-css-6.3.0-hbca2aae_1.conda @@ -4744,18 +4746,18 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/contourpy-1.3.3-py314hf8a3a22_4.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/coverage-7.13.5-py314h6e9b3f0_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/coverage-7.14.0-py314h6e9b3f0_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.14.4-py314hd8ed1ab_100.conda - conda: https://conda.anaconda.org/conda-forge/noarch/cycler-0.12.1-pyhcf101f3_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/debugpy-1.8.20-py314he609de1_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.3.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/distlib-0.4.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/execnet-2.1.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.2.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.29.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/fonttools-4.62.1-pyh7db6752_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/fonttools-4.63.0-pyh7db6752_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/fqdn-1.5.1-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/freetype-2.14.3-hce30654_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/gflags-2.2.2-hf9b8971_1005.conda @@ -4763,7 +4765,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/h5py-3.16.0-nompi_py314h658a3ac_102.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/hdf5-2.1.0-nompi_hc95e3eb_104.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/hdf5-2.1.0-nompi_he586413_105.conda - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda @@ -4773,7 +4775,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.8.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.3.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-7.2.0-pyh5552912_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.12.0-pyhecfbec7_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.13.0-pyh53cf698_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ipython_pygments_lexers-1.1.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/isoduration-20.11.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jedi-0.19.2-pyhd8ed1ab_1.conda @@ -4787,62 +4789,62 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_client-8.8.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_core-5.9.1-pyhc90fa1f_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_events-0.12.1-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.17.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.18.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server_terminals-0.5.4-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.5.6-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.5.7-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_pygments-0.3.0-pyhd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_server-2.28.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/kiwisolver-1.5.0-py314hf8a3a22_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/krb5-1.22.2-h385eeb1_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/lark-1.3.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/lcms2-2.18-hdfa7624_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/lcms2-2.19.1-hdfa7624_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/lerc-4.1.0-h1eee2c3_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libabseil-20260107.1-cxx17_h2062a1b_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libaec-1.1.5-h8664d51_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libarrow-24.0.0-h2124f06_0_cpu.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libarrow-acero-24.0.0-hee8fe31_0_cpu.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libarrow-compute-24.0.0-h3b6a98a_0_cpu.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libarrow-dataset-24.0.0-hee8fe31_0_cpu.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libarrow-substrait-24.0.0-h05be00f_0_cpu.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libblas-3.11.0-6_h51639a9_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libarrow-24.0.0-h37fbca7_1_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libarrow-acero-24.0.0-hee8fe31_1_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libarrow-compute-24.0.0-h3b6a98a_1_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libarrow-dataset-24.0.0-hee8fe31_1_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libarrow-substrait-24.0.0-h05be00f_1_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libblas-3.11.0-7_h51639a9_openblas.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlicommon-1.2.0-hc919400_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlidec-1.2.0-hc919400_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlienc-1.2.0-hc919400_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcblas-3.11.0-6_hb0561ab_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcblas-3.11.0-7_hb0561ab_openblas.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcrc32c-1.1.2-hbdafb3b_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcurl-8.19.0-hd5a2499_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-22.1.4-h55c6f16_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcurl-8.20.0-hd5a2499_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-22.1.5-h55c6f16_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libdeflate-1.25-hc11a715_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libedit-3.1.20250104-pl5321hafb1f1b_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libev-4.33-h93a5062_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libevent-2.1.12-h2757513_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.7.5-hf6b4638_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.8.0-hf6b4638_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.5.2-hcf2aa1b_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libfreetype-2.14.3-hce30654_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libfreetype6-2.14.3-hdfa99f5_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgcc-15.2.0-hcbb3090_18.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran-15.2.0-h07b0088_18.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran5-15.2.0-hdae7583_18.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgcc-15.2.0-hcbb3090_19.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran-15.2.0-h07b0088_19.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran5-15.2.0-hdae7583_19.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgoogle-cloud-3.3.0-he41eb1d_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgoogle-cloud-storage-3.3.0-ha114238_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgrpc-1.78.1-h3e3f78d_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libiconv-1.18-h23cfdf5_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libjpeg-turbo-3.1.4.1-h84a0fba_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblapack-3.11.0-6_hd9741b5_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblapack-3.11.0-7_hd9741b5_openblas.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblzma-5.8.3-h8088a28_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libmpdec-4.0.0-h84a0fba_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libnghttp2-1.68.1-h8f3e76b_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libopenblas-0.3.32-openmp_he657e61_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libopenblas-0.3.33-openmp_he657e61_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libopentelemetry-cpp-1.26.0-h08d5cc3_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libopentelemetry-cpp-headers-1.26.0-hce30654_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libparquet-24.0.0-h16c0493_0_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libparquet-24.0.0-h16c0493_1_cpu.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libpng-1.6.58-h132b30e_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libprotobuf-6.33.5-h4a5acfd_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libre2-11-2025.11.05-h4c27e2a_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsodium-1.0.21-h1a92334_3.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.53.0-h1b79a29_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.53.1-h1b79a29_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libssh2-1.11.1-h1590b86_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libthrift-0.22.0-h14a376c_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libthrift-0.22.0-h1fb9c8a_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libtiff-4.7.1-h4030677_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libutf8proc-2.11.3-h2431656_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libwebp-base-1.6.0-h07db88b_0.conda @@ -4850,41 +4852,41 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libxml2-16-2.15.3-h5ef1a60_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libxml2-2.15.3-h5654f7c_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzlib-1.3.2-h8088a28_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/llvm-openmp-22.1.4-hc7d1edf_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/llvm-openmp-22.1.5-hc7d1edf_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/lz4-c-1.10.0-h286801f_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/markupsafe-3.0.3-py314h6e9b3f0_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/matplotlib-3.10.8-py314he55896b_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/matplotlib-base-3.10.8-py314hd63e3f0_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/matplotlib-3.10.9-py314he55896b_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/matplotlib-base-3.10.9-py314hc042b31_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.2.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/memory_profiler-0.61.0-pyhcf101f3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.2.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.2.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/narwhals-2.20.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/narwhals-2.21.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbclient-0.10.4-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.17.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbformat-5.10.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ncurses-6.5-h5e97a16_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ncurses-6.6-h1d4f5a5_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nest-asyncio-1.6.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/nlohmann_json-3.12.0-h784d473_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/notebook-shim-0.2.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/numpy-2.4.3-py314h1569ea8_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/numpy-2.4.5-py314hb79c6fa_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/numpy-typing-compat-20251206.2.4-pyhd6139ff_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openjpeg-2.5.4-hd9e9057_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.6.2-hd24854e_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/optype-0.17.0-pyhc364b38_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/optype-numpy-0.17.0-pyhada4073_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/optype-0.17.1-pyhc364b38_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/optype-numpy-0.17.1-pyh3cfb546_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/orc-2.3.0-hd11884d_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/overrides-7.7.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.1-pyhc364b38_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.2-pyhc364b38_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pandas-stubs-3.0.0.260204-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pandocfilters-1.5.0-pyhd8ed1ab_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.6-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.7-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pexpect-4.9.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pillow-12.2.0-py314hab283cf_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.9.6-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/plotly-6.6.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.6.0-pyhf9edf01_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/prek-0.3.10-h6fdd925_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/prek-0.4.0-h6fdd925_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/prometheus-cpp-1.3.0-h0967b3e_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.25.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.52-pyha770c72_0.conda @@ -4907,11 +4909,11 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-3.8.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.14.4-h4c637c5_100_cp314.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-discovery-1.2.2-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-discovery-1.3.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.21.2-pyhe01879c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.14.4-h4df99d1_100.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-2.0.7-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2026.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-3.2.1-pyh332efcf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2026.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.14-8_cp314.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyyaml-6.0.3-py314h6e9b3f0_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyzmq-27.1.0-py312h022ad19_2.conda @@ -4919,7 +4921,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/re2-2025.11.05-ha480c28_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/readline-8.3-h46df422_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.37.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.33.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.34.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3339-validator-0.1.4-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3986-validator-0.1.1-pyh9f0ad1d_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3987-syntax-1.1.0-pyhe01879c_1.conda @@ -4940,18 +4942,18 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-h010d191_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tornado-6.5.5-py314h6c2aa35_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.14.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ty-0.0.32-hdfcc030_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/types-pytz-2026.1.1.20260408-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.15.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ty-0.0.37-hdfcc030_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/types-pytz-2026.2.0.20260506-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing_utils-0.1.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/unicodedata2-17.0.1-py314h6c2aa35_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/uri-template-1.3.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.6.3-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/virtualenv-21.2.4-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.6.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.7.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/virtualenv-21.3.3-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.7.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/webcolors-25.10.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/webencodings-0.5.1-pyhd8ed1ab_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.9.0-pyhd8ed1ab_0.conda @@ -4964,8 +4966,8 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zlib-ng-2.3.3-hed4e4f5_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda - pypi: https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl - - pypi: git+https://github.com/OpenSourceEconomics/dags.git?rev=cf59c04#cf59c04c6ba07b7c54ca763dc155deea3341a480 - - pypi: https://files.pythonhosted.org/packages/43/f5/ee39c6e92acc742c052f137b47c210cd0a1b72dcd3f98495528bb4d27761/flatten_dict-0.4.2-py2.py3-none-any.whl + - pypi: git+https://github.com/OpenSourceEconomics/dags.git?branch=main#87ff051077f37470514808a3121e7c90429f6383 + - pypi: https://files.pythonhosted.org/packages/72/9f/485516087cd8c44183aaf9ab850247a28e2e4a42a4d62eab77c21f673450/flatten_dict-0.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/70/aa/dfac6d72cc35bc07e7587115b6946e333ef4ccb2e6cd26ecf639438c5d26/jax-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a7/25/e1e52a21786b321fb6a2edf9ef9971aa70f06bb2738aef9afd6d8f46a441/jaxlib-0.10.0-cp314-cp314-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/94/05/3e39d416fb92b2738a76e8265e6bfc5d10542f90a7c32ad1eb831eea3fa3/jaxtyping-0.3.9-py3-none-any.whl @@ -4973,15 +4975,15 @@ environments: - pypi: https://files.pythonhosted.org/packages/72/4e/1339dc6e2557a344f5ba5590872e80346f76f6cb2ac3dd16e4666e88818c/ml_dtypes-0.5.4-cp314-cp314-macosx_10_13_universal2.whl - pypi: https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/56/a4/90edb01e9176053578e343d7a7276bc28356741ee67059aed8ed2c1a4e59/numba-0.65.0-cp314-cp314-macosx_12_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/4f/2e/8aed9b726d9ba5f11ad287645fd479e88278db3060a25cb1225d730eb2b7/numba-0.65.1-cp314-cp314-macosx_12_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/23/cd/066e86230ae37ed0be70aae89aabf03ca8d9f39c8aea0dec8029455b5540/opt_einsum-3.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/95/25/bdb9326c3b5455f8d4d3549fce7abcf967259de146fe2cf7a82368141948/pandas-3.0.2-cp314-cp314-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/68/10/bf2d6738d72748b961a3751ab89522d58c54efc36a8e1a12161216cd45cf/pandas-3.0.3-cp314-cp314-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/51/fe/53ac0cd932db5dcaf55961bc7cb7afdca8d80d8cc7406ed661f0c7dc111a/pdbp-1.8.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/da/9e/21c4088d1d433cfbf0578f138a6df59527dbd9e9d67355ee31b17e6dc774/portion-2.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/3b/ef57eeb4c6f188fbe756b602dc6afa9d66eb0c2e77f02a3d4d51bfec2aaa/quantecon-0.11.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/65/44/bb509c3d2c0b5a87e7a5af1d5917a402a32ff026f777a6d7cb6990746cbb/tabcompleter-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cb/fc/8c82be70b8f96d09943360f34cfb2ecdd3035294c51bce4131eeabe56645/tabcompleter-1.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8d/96/04e7b441807b26b794da5b11e59ed7f83b2cf8af202bd7eba8ad2fa6046e/wadler_lindig-0.1.7-py3-none-any.whl - pypi: ./ win-64: @@ -4996,26 +4998,26 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/asv_runner-0.2.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/async-lru-2.3.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-26.1.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-auth-0.10.1-h5d51246_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-auth-0.10.1-h8b39d88_3.conda - conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-cal-0.9.13-h46f3b43_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-common-0.12.6-hfd05255_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-compression-0.3.2-hcb3a2da_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-event-stream-0.6.0-h87b2689_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-http-0.10.12-h612f3e8_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-io-0.26.3-h0d5b9f9_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-mqtt-0.15.2-h904b250_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-s3-0.11.5-h87bd87b_5.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-event-stream-0.7.0-h87b2689_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-http-0.10.13-h612f3e8_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-io-0.26.3-h0d5b9f9_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-mqtt-0.15.2-h82175e6_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-s3-0.12.2-h61b906f_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-sdkutils-0.2.4-hcb3a2da_4.conda - conda: https://conda.anaconda.org/conda-forge/win-64/aws-checksums-0.2.10-hcb3a2da_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/aws-crt-cpp-0.37.4-h4f72eff_3.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/aws-sdk-cpp-1.11.747-hd55a107_3.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/aws-crt-cpp-0.38.3-h1db8845_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/aws-sdk-cpp-1.11.747-hd63e0c5_4.conda - conda: https://conda.anaconda.org/conda-forge/win-64/azure-core-cpp-1.16.2-h49e36cd_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/azure-identity-cpp-1.13.3-h5ffce34_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/azure-storage-blobs-cpp-12.16.0-hcd625b1_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/azure-storage-common-cpp-12.12.0-h5ffce34_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/azure-storage-files-datalake-cpp-12.14.0-h1678c0b_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/azure-storage-blobs-cpp-12.16.0-h81bf7d1_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/azure-storage-common-cpp-12.13.0-h5ffce34_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/azure-storage-files-datalake-cpp-12.14.0-hab49af2_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.18.0-pyhcf101f3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/backports.zstd-1.3.0-py314h680f03e_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/backports.zstd-1.5.0-py314h680f03e_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.14.3-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-6.3.0-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-with-css-6.3.0-hbca2aae_1.conda @@ -5035,11 +5037,11 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/contourpy-1.3.3-py314hf309875_4.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/coverage-7.13.5-py314h2359020_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/coverage-7.14.0-py314h2359020_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.14.4-py314hd8ed1ab_100.conda - conda: https://conda.anaconda.org/conda-forge/noarch/cycler-0.12.1-pyhcf101f3_2.conda - conda: https://conda.anaconda.org/conda-forge/win-64/debugpy-1.8.20-py314hb98de8c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.3.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/distlib-0.4.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/double-conversion-3.4.0-hac47afa_0.conda @@ -5054,7 +5056,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/fontconfig-2.17.1-hd47e2ca_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-ecosystem-1-0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-hc364b38_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/fonttools-4.62.1-pyh7db6752_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/fonttools-4.63.0-pyh7db6752_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/fqdn-1.5.1-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/freetype-2.14.3-h57928b3_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/graphite2-1.3.14-hac47afa_2.conda @@ -5062,7 +5064,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/h5py-3.16.0-nompi_py314h02517ec_102.conda - conda: https://conda.anaconda.org/conda-forge/win-64/harfbuzz-14.2.0-h5a1b470_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/hdf5-2.1.0-nompi_hd96b29f_104.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/hdf5-2.1.0-nompi_h0a39f1e_105.conda - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda @@ -5072,7 +5074,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.8.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.3.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-7.2.0-pyh6dadd2b_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.12.0-pyhccfa634_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.13.0-pyhe2676ad_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ipython_pygments_lexers-1.1.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/isoduration-20.11.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jedi-0.19.2-pyhd8ed1ab_1.conda @@ -5086,60 +5088,60 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_client-8.8.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_core-5.9.1-pyh6dadd2b_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_events-0.12.1-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.17.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.18.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server_terminals-0.5.4-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.5.6-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.5.7-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_pygments-0.3.0-pyhd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_server-2.28.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/kiwisolver-1.5.0-py314hf309875_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/krb5-1.22.2-h0ea6238_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/lark-1.3.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/lcms2-2.18-hf2c6c5f_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/lcms2-2.19.1-hf2c6c5f_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/lerc-4.1.0-hd936e49_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libabseil-20260107.1-cxx17_h0eb2380_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libaec-1.1.5-haf901d7_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libarrow-24.0.0-hc74aee5_0_cpu.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libarrow-acero-24.0.0-h7d8d6a5_0_cpu.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libarrow-compute-24.0.0-h081cd8e_0_cpu.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libarrow-dataset-24.0.0-h7d8d6a5_0_cpu.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libarrow-substrait-24.0.0-h524e9bd_0_cpu.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libblas-3.11.0-6_hf2e6a31_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libarrow-24.0.0-h37f918f_1_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libarrow-acero-24.0.0-h7d8d6a5_1_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libarrow-compute-24.0.0-h081cd8e_1_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libarrow-dataset-24.0.0-h7d8d6a5_1_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libarrow-substrait-24.0.0-h524e9bd_1_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libblas-3.11.0-7_h8455456_mkl.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libbrotlicommon-1.2.0-hfd05255_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libbrotlidec-1.2.0-hfd05255_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libbrotlienc-1.2.0-hfd05255_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libcblas-3.11.0-6_h2a3cdd5_mkl.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libclang13-22.1.4-default_ha2db4b5_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libcblas-3.11.0-7_h2a3cdd5_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libclang13-22.1.5-default_ha2db4b5_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libcrc32c-1.1.2-h0e60522_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/win-64/libcurl-8.19.0-h8206538_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libcurl-8.20.0-h8206538_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libdeflate-1.25-h51727cc_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libevent-2.1.12-h3671451_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libexpat-2.7.5-hac47afa_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libexpat-2.8.0-hac47afa_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libffi-3.5.2-h3d046cb_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libfreetype-2.14.3-h57928b3_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libfreetype6-2.14.3-hdbac1cb_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libgcc-15.2.0-h8ee18e1_18.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libglib-2.86.4-h0c9aed9_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libgomp-15.2.0-h8ee18e1_18.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libgcc-15.2.0-h8ee18e1_19.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libglib-2.88.1-h7ce1215_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libgomp-15.2.0-h8ee18e1_19.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libgoogle-cloud-3.3.0-h2b231ac_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libgoogle-cloud-storage-3.3.0-he04ea4c_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libgrpc-1.78.1-h9ff2b3e_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libhwloc-2.12.2-default_h4379cf1_1000.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libhwloc-2.13.0-default_h049141e_1000.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libiconv-1.18-hc1393d2_2.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libintl-0.22.5-h5728263_3.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libjpeg-turbo-3.1.4.1-hfd05255_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/liblapack-3.11.0-6_hf9ab0e9_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/liblapack-3.11.0-7_hf9ab0e9_mkl.conda - conda: https://conda.anaconda.org/conda-forge/win-64/liblzma-5.8.3-hfd05255_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libmpdec-4.0.0-hfd05255_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libopentelemetry-cpp-1.26.0-hc88f397_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libopentelemetry-cpp-headers-1.26.0-h57928b3_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libparquet-24.0.0-h7051d1f_0_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libparquet-24.0.0-h7051d1f_1_cpu.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libpng-1.6.58-h7351971_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libprotobuf-6.33.5-h61fc761_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libre2-11-2025.11.05-h04e5de1_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libsodium-1.0.21-h6a83c73_3.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libsqlite-3.53.0-hf5d6505_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libsqlite-3.53.1-hf5d6505_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libssh2-1.11.1-h9aa295b_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libthrift-0.22.0-h23985f6_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libthrift-0.22.0-h2e43b2f_2.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libtiff-4.7.1-h8f73337_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libutf8proc-2.11.3-hb980946_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libvulkan-loader-1.4.341.0-h477610d_0.conda @@ -5150,43 +5152,43 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-2.15.3-h8ef44ab_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libxslt-1.1.43-h0fbe4c1_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libzlib-1.3.2-hfd05255_2.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/llvm-openmp-22.1.4-h4fa8253_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/llvm-openmp-22.1.5-h4fa8253_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/lz4-c-1.10.0-h2466b09_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/markupsafe-3.0.3-py314h2359020_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/matplotlib-3.10.8-py314h86ab7b2_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/matplotlib-base-3.10.8-py314hfa45d96_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/matplotlib-3.10.9-py314h86ab7b2_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/matplotlib-base-3.10.9-py314hfa45d96_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.2.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/memory_profiler-0.61.0-pyhcf101f3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.2.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/mkl-2025.3.1-hac47afa_12.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.2.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/mkl-2026.0.0-hac47afa_906.conda - conda: https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/narwhals-2.20.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/narwhals-2.21.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbclient-0.10.4-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.17.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbformat-5.10.4-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nest-asyncio-1.6.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/nlohmann_json-3.12.0-h5112557_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/notebook-shim-0.2.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/numpy-2.4.3-py314h02f10f6_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/numpy-2.4.5-py314h02f10f6_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/numpy-typing-compat-20251206.2.4-pyhd6139ff_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/onemkl-license-2025.3.1-h57928b3_12.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/onemkl-license-2026.0.0-h57928b3_906.conda - conda: https://conda.anaconda.org/conda-forge/win-64/openjpeg-2.5.4-h0e57b4f_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.6.2-hf411b9b_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/optype-0.17.0-pyhc364b38_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/optype-numpy-0.17.0-pyhada4073_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/optype-0.17.1-pyhc364b38_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/optype-numpy-0.17.1-pyh3cfb546_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/orc-2.3.0-h8fc0eb6_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/overrides-7.7.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.1-pyhc364b38_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.2-pyhc364b38_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pandas-stubs-3.0.0.260204-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pandocfilters-1.5.0-pyhd8ed1ab_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.6-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.7-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/pcre2-10.47-hd2b5f0e_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/pillow-12.2.0-py314h61b30b5_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/pixman-0.46.4-h5112557_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.9.6-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/plotly-6.6.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.6.0-pyhf9edf01_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/prek-0.3.10-h18a1a76_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/prek-0.4.0-h18a1a76_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/prometheus-cpp-1.3.0-hcea2f5d_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.25.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.52-pyha770c72_0.conda @@ -5200,28 +5202,28 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.20.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pympler-1.1-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.3.2-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/pyside6-6.11.0-py314h447aaf0_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/pyside6-6.11.1-py314h447aaf0_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyh09c184e_7.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-9.0.3-pyhc364b38_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-cov-7.1.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-3.8.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/python-3.14.4-h4b44e0e_100_cp314.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-discovery-1.2.2-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-discovery-1.3.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.21.2-pyhe01879c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.14.4-h4df99d1_100.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-2.0.7-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2026.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-3.2.1-pyh332efcf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2026.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.14-8_cp314.conda - conda: https://conda.anaconda.org/conda-forge/win-64/pywin32-311-py314h8f8f202_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/pywinpty-2.0.15-py314h51f0985_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/pyyaml-6.0.3-py314h2359020_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/pyzmq-27.1.0-py312h343a6d4_2.conda - conda: https://conda.anaconda.org/conda-forge/win-64/qhull-2020.2-hc790b64_5.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/qt6-main-6.11.0-pl5321hfcac499_4.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/qt6-main-6.11.1-pl5321hfcac499_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/re2-2025.11.05-ha104f34_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.37.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.33.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.34.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3339-validator-0.1.4-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3986-validator-0.1.1-pyh9f0ad1d_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3987-syntax-1.1.0-pyhe01879c_1.conda @@ -5237,15 +5239,15 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.8.3-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/stack_data-0.6.3-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tabulate-0.10.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/tbb-2022.3.0-h3155e25_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/tbb-2023.0.0-hd3d4ead_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/terminado-0.18.1-pyh6dadd2b_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tinycss2-1.4.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/tk-8.6.13-h6ed50ae_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/tornado-6.5.5-py314h5a2d7ad_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.14.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/ty-0.0.32-hc21aad4_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/types-pytz-2026.1.1.20260408-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.15.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/ty-0.0.37-hc21aad4_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/types-pytz-2026.2.0.20260506-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing_utils-0.1.0-pyhd8ed1ab_1.conda @@ -5253,13 +5255,13 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/ucrt-10.0.26100.0-h57928b3_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/unicodedata2-17.0.1-py314h5a2d7ad_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/uri-template-1.3.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.6.3-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/vc-14.3-h41ae7f8_34.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.44.35208-h818238b_34.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/vcomp14-14.44.35208-h818238b_34.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/virtualenv-21.2.4-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/vs2015_runtime-14.44.35208-h38c0c73_34.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.6.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.7.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vc-14.5-h1b7c187_36.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.51.36231-h1b9f54f_36.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vcomp14-14.51.36231-h1b9f54f_36.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/virtualenv-21.3.3-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vs2015_runtime-14.51.36231-h84cd919_36.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.7.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/webcolors-25.10.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/webencodings-0.5.1-pyhd8ed1ab_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.9.0-pyhd8ed1ab_0.conda @@ -5274,8 +5276,8 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/zlib-ng-2.3.3-h0261ad2_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_6.conda - pypi: https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl - - pypi: git+https://github.com/OpenSourceEconomics/dags.git?rev=cf59c04#cf59c04c6ba07b7c54ca763dc155deea3341a480 - - pypi: https://files.pythonhosted.org/packages/43/f5/ee39c6e92acc742c052f137b47c210cd0a1b72dcd3f98495528bb4d27761/flatten_dict-0.4.2-py2.py3-none-any.whl + - pypi: git+https://github.com/OpenSourceEconomics/dags.git?branch=main#87ff051077f37470514808a3121e7c90429f6383 + - pypi: https://files.pythonhosted.org/packages/72/9f/485516087cd8c44183aaf9ab850247a28e2e4a42a4d62eab77c21f673450/flatten_dict-0.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/70/aa/dfac6d72cc35bc07e7587115b6946e333ef4ccb2e6cd26ecf639438c5d26/jax-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/24/08/26e6a3ecf0a95f1ec0dcd7a668d5c9a72e581c40fe4ae51e102ca63174c5/jaxlib-0.10.0-cp314-cp314-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/94/05/3e39d416fb92b2738a76e8265e6bfc5d10542f90a7c32ad1eb831eea3fa3/jaxtyping-0.3.9-py3-none-any.whl @@ -5283,16 +5285,16 @@ environments: - pypi: https://files.pythonhosted.org/packages/e9/93/2bfed22d2498c468f6bcd0d9f56b033eaa19f33320389314c19ef6766413/ml_dtypes-0.5.4-cp314-cp314-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/73/5b/fbce55ce3d933afbc7ade04df826853e4a846aaa47d58d2fbb669b8f2d08/numba-0.65.0-cp314-cp314-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/56/46/3f7fc04fb853559e74b210e0b62c19974ec844cefec611f9e535f4da3761/numba-0.65.1-cp314-cp314-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/23/cd/066e86230ae37ed0be70aae89aabf03ca8d9f39c8aea0dec8029455b5540/opt_einsum-3.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/db/60/aba6a38de456e7341285102bede27514795c1eaa353bc0e7638b6b785356/pandas-3.0.2-cp314-cp314-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/38/55/792619469bab9882d8bbd5865d45a72f6478762d04a9af4bf0d08c503e95/pandas-3.0.3-cp314-cp314-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/51/fe/53ac0cd932db5dcaf55961bc7cb7afdca8d80d8cc7406ed661f0c7dc111a/pdbp-1.8.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/da/9e/21c4088d1d433cfbf0578f138a6df59527dbd9e9d67355ee31b17e6dc774/portion-2.6.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/5a/dc/491b7661614ab97483abf2056be1deee4dc2490ecbf7bff9ab5cdbac86e1/pyreadline3-3.5.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f7/5e/35c856e186b74678c24927847ad9895a51f1bc02a0c6126477a6c6040064/pyreadline3-3.5.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/3b/ef57eeb4c6f188fbe756b602dc6afa9d66eb0c2e77f02a3d4d51bfec2aaa/quantecon-0.11.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/65/44/bb509c3d2c0b5a87e7a5af1d5917a402a32ff026f777a6d7cb6990746cbb/tabcompleter-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cb/fc/8c82be70b8f96d09943360f34cfb2ecdd3035294c51bce4131eeabe56645/tabcompleter-1.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8d/96/04e7b441807b26b794da5b11e59ed7f83b2cf8af202bd7eba8ad2fa6046e/wadler_lindig-0.1.7-py3-none-any.whl - pypi: ./ packages: @@ -5602,57 +5604,57 @@ packages: license: MIT license_family: MIT purls: - - pkg:pypi/attrs?source=compressed-mapping + - pkg:pypi/attrs?source=hash-mapping size: 64927 timestamp: 1773935801332 -- conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-auth-0.10.1-h2d2dd48_2.conda - sha256: 292aa18fe6ab5351710e6416fbd683eaef3aa5b1b7396da9350ff08efc660e4f - md5: 675ea6d90900350b1dcfa8231a5ea2dd +- conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-auth-0.10.1-ha62d5e7_3.conda + sha256: ccbf2cc4bea4aab6e071d67ecc2743197759f6df855787e7a5f57f7973f913a2 + md5: 55eaf7066da1299d217ab32baedc7fa8 depends: - libgcc >=14 - __glibc >=2.17,<3.0.a0 - - aws-c-common >=0.12.6,<0.12.7.0a0 + - aws-c-io >=0.26.3,<0.26.4.0a0 - aws-c-sdkutils >=0.2.4,<0.2.5.0a0 + - aws-c-common >=0.12.6,<0.12.7.0a0 + - aws-c-http >=0.10.13,<0.10.14.0a0 - aws-c-cal >=0.9.13,<0.9.14.0a0 - - aws-c-http >=0.10.12,<0.10.13.0a0 - - aws-c-io >=0.26.3,<0.26.4.0a0 license: Apache-2.0 license_family: APACHE purls: [] - size: 134426 - timestamp: 1774274932726 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-auth-0.10.1-hcb83491_2.conda - sha256: aba942578ad57e7b584434ed4e39c5ff7ed4ad3f326ac3eda26913ca343ea255 - md5: 1c701edc28f543a0e040325b223d5ca0 + size: 134427 + timestamp: 1777489423676 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-auth-0.10.1-ha7d4cc1_3.conda + sha256: ce023981f49a96074bc84d6249c7097b71c27e8d3bd750fc07c520579159521c + md5: 4fbd86a4d1efeb954b0d559d6717bd2b depends: - __osx >=11.0 + - aws-c-http >=0.10.13,<0.10.14.0a0 - aws-c-io >=0.26.3,<0.26.4.0a0 - - aws-c-sdkutils >=0.2.4,<0.2.5.0a0 - aws-c-common >=0.12.6,<0.12.7.0a0 + - aws-c-sdkutils >=0.2.4,<0.2.5.0a0 - aws-c-cal >=0.9.13,<0.9.14.0a0 - - aws-c-http >=0.10.12,<0.10.13.0a0 license: Apache-2.0 license_family: APACHE purls: [] - size: 116820 - timestamp: 1774275057443 -- conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-auth-0.10.1-h5d51246_2.conda - sha256: f937d40f01493c4799a673f56d70434d6cddb2ec967cf642a39e0e04282a9a1e - md5: 908d5d8755564e2c3f3770fca7ff0736 + size: 116717 + timestamp: 1777489477698 +- conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-auth-0.10.1-h8b39d88_3.conda + sha256: ffa66e862ddcd8a825c3d44e83404daec7b8d36b7313650e09aa39443c312f5e + md5: 9f25944ccae498b7afbc81ce24f4c37a depends: - vc >=14.3,<15 - vc14_runtime >=14.44.35208 - ucrt >=10.0.20348.0 - - aws-c-common >=0.12.6,<0.12.7.0a0 - aws-c-io >=0.26.3,<0.26.4.0a0 - - aws-c-http >=0.10.12,<0.10.13.0a0 - - aws-c-cal >=0.9.13,<0.9.14.0a0 - aws-c-sdkutils >=0.2.4,<0.2.5.0a0 + - aws-c-http >=0.10.13,<0.10.14.0a0 + - aws-c-cal >=0.9.13,<0.9.14.0a0 + - aws-c-common >=0.12.6,<0.12.7.0a0 license: Apache-2.0 license_family: APACHE purls: [] - size: 127421 - timestamp: 1774275018076 + size: 127435 + timestamp: 1777489461908 - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-cal-0.9.13-h2c9d079_1.conda sha256: f21d648349a318f4ae457ea5403d542ba6c0e0343b8642038523dd612b2a5064 md5: 3c3d02681058c3d206b562b2e3bc337f @@ -5759,124 +5761,124 @@ packages: purls: [] size: 23087 timestamp: 1767790877990 -- conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-event-stream-0.6.0-h9b893ba_1.conda - sha256: 4a1a060ab40cb4fa39d24418758ca9faa1f51df6918f05143118e79bb11b4350 - md5: cd4946050ecfcb3c6fd09106ae6a261e +- conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-event-stream-0.7.0-h9b893ba_0.conda + sha256: 9692edaeaf90f7710b7ec49c7ca42961c59344dafa6fadbaec8c283b0606ca68 + md5: 60076118b1579967748f0c9a2912de7c depends: - - libstdcxx >=14 - libgcc >=14 - __glibc >=2.17,<3.0.a0 - - aws-c-io >=0.26.3,<0.26.4.0a0 - - aws-checksums >=0.2.10,<0.2.11.0a0 + - libstdcxx >=14 - aws-c-common >=0.12.6,<0.12.7.0a0 + - aws-checksums >=0.2.10,<0.2.11.0a0 + - aws-c-io >=0.26.3,<0.26.4.0a0 license: Apache-2.0 license_family: APACHE purls: [] - size: 58989 - timestamp: 1774270004533 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-event-stream-0.6.0-h351c84d_1.conda - sha256: 8927fac75ad4cc4a2fbece5dbcc666cd6672a8ad87370cb183ff4d4f3e11f371 - md5: 228fe528ff814e420d8e13757f3c381e + size: 59054 + timestamp: 1774479894768 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-event-stream-0.7.0-h351c84d_0.conda + sha256: 454c02593d88246ac0fd4fc5e306147dd4f6c4866931c436ddeccdb37a70250f + md5: cb6d3b9905ffa47de2628e1ba9666c33 depends: - - libcxx >=19 - __osx >=11.0 - - aws-c-common >=0.12.6,<0.12.7.0a0 + - libcxx >=19 - aws-c-io >=0.26.3,<0.26.4.0a0 + - aws-c-common >=0.12.6,<0.12.7.0a0 - aws-checksums >=0.2.10,<0.2.11.0a0 license: Apache-2.0 license_family: APACHE purls: [] - size: 53641 - timestamp: 1774270084862 -- conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-event-stream-0.6.0-h87b2689_1.conda - sha256: 63b7a1d3bfcfabeb5d4819c2577ff9fa93e28814ab63a5419740adf9b13a0f3a - md5: d2edd57e91a743151d816920cad61e54 + size: 53822 + timestamp: 1774480046539 +- conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-event-stream-0.7.0-h87b2689_0.conda + sha256: e826611d4ec8e9dc97fbf8ad7b6b54dc15ebd64b3a236be7e6bf8b898806d811 + md5: e116eed7dbaa4fcfae0f51c962440a81 depends: - vc >=14.3,<15 - vc14_runtime >=14.44.35208 - ucrt >=10.0.20348.0 - - aws-checksums >=0.2.10,<0.2.11.0a0 - aws-c-common >=0.12.6,<0.12.7.0a0 + - aws-checksums >=0.2.10,<0.2.11.0a0 - aws-c-io >=0.26.3,<0.26.4.0a0 license: Apache-2.0 license_family: APACHE purls: [] - size: 57598 - timestamp: 1774270085349 -- conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-http-0.10.12-h4bacb7b_1.conda - sha256: c6f910d400ef9034493988e8cd37bd4712e42d85921122bcda4ba68d4614b131 - md5: 7bc920933e5fb225aba86a788164a8f1 + size: 57651 + timestamp: 1774479982094 +- conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-http-0.10.13-h4bacb7b_0.conda + sha256: 38cfc8894db6729770ac18f900296c3f7c20f349a5586a8d8e1a62571fce61d5 + md5: 77f70a9ab785a146dbf66fba00131403 depends: - - libgcc >=14 - __glibc >=2.17,<3.0.a0 - - aws-c-io >=0.26.3,<0.26.4.0a0 - - aws-c-cal >=0.9.13,<0.9.14.0a0 + - libgcc >=14 - aws-c-compression >=0.3.2,<0.3.3.0a0 - aws-c-common >=0.12.6,<0.12.7.0a0 + - aws-c-cal >=0.9.13,<0.9.14.0a0 + - aws-c-io >=0.26.3,<0.26.4.0a0 license: Apache-2.0 license_family: APACHE purls: [] - size: 225868 - timestamp: 1774270031584 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-http-0.10.12-h95cdebe_1.conda - sha256: b25380b43c2c5733dcaac88b075fa286893af1c147ca40d50286df150ace5fb8 - md5: 806ff124512457583d675c62336b1392 + size: 225826 + timestamp: 1774488399486 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-http-0.10.13-h95cdebe_0.conda + sha256: 7a008a5a2f76256e841a8565b373a7dcfd2a439e5d9dbec320322468637f69e5 + md5: fc4478bc51e76c5d26ea2c4f1e3ba366 depends: - __osx >=11.0 - - aws-c-common >=0.12.6,<0.12.7.0a0 - - aws-c-io >=0.26.3,<0.26.4.0a0 - - aws-c-compression >=0.3.2,<0.3.3.0a0 - aws-c-cal >=0.9.13,<0.9.14.0a0 + - aws-c-compression >=0.3.2,<0.3.3.0a0 + - aws-c-io >=0.26.3,<0.26.4.0a0 + - aws-c-common >=0.12.6,<0.12.7.0a0 license: Apache-2.0 license_family: APACHE purls: [] - size: 172940 - timestamp: 1774270153001 -- conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-http-0.10.12-h612f3e8_1.conda - sha256: dc297fbce04335f5f80b30bcdee1925ed4a0d95e7a2382523870c6b4981ca1b2 - md5: 26af0e9d7853d27e909ce01c287692b4 + size: 173575 + timestamp: 1774488444724 +- conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-http-0.10.13-h612f3e8_0.conda + sha256: cf939d4a0849bc41421b4c380b2bbbc0beb1fd9b375bb9627b98d9415ec9ea69 + md5: 88626be3c14ac87c09629dcbf65e6279 depends: - vc >=14.3,<15 - vc14_runtime >=14.44.35208 - ucrt >=10.0.20348.0 - - aws-c-cal >=0.9.13,<0.9.14.0a0 - aws-c-common >=0.12.6,<0.12.7.0a0 - aws-c-compression >=0.3.2,<0.3.3.0a0 + - aws-c-cal >=0.9.13,<0.9.14.0a0 - aws-c-io >=0.26.3,<0.26.4.0a0 license: Apache-2.0 license_family: APACHE purls: [] - size: 207778 - timestamp: 1774270109581 -- conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-io-0.26.3-hc87160b_0.conda - sha256: c66ebb7815949db72bab7c86bf477197e4bc6937c381cf32248bdd1ce496db00 - md5: dde6a3e4fe6bb2ecd2a7050dd1e701fb + size: 208426 + timestamp: 1774488477105 +- conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-io-0.26.3-h692f434_1.conda + sha256: e3e33031d641864128ab11f9b8585ad5beb82fa988fe833bb0767dd01878a371 + md5: 14260392d0b491c537b5e26e9a506fff depends: - __glibc >=2.17,<3.0.a0 - libgcc >=14 - - aws-c-cal >=0.9.13,<0.9.14.0a0 - - s2n >=1.7.1,<1.7.2.0a0 - aws-c-common >=0.12.6,<0.12.7.0a0 + - s2n >=1.7.2,<1.7.3.0a0 + - aws-c-cal >=0.9.13,<0.9.14.0a0 license: Apache-2.0 license_family: APACHE purls: [] - size: 181624 - timestamp: 1773868304737 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-io-0.26.3-h4137820_0.conda - sha256: 0e6ba2c8f250f466b9d671d3970e1f7c149c925b79c10fa7778708192a2a7833 - md5: 730d1cbd0973bd7ac150e181d3b572f3 + size: 181583 + timestamp: 1777471132287 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-io-0.26.3-h4137820_1.conda + sha256: e03ce71af986541d79fae88f7636d7a252b215d4560b47f005050dc9e1dc3c11 + md5: af3d15f053619ca43ea0943de01d368b depends: - __osx >=11.0 - - aws-c-cal >=0.9.13,<0.9.14.0a0 - aws-c-common >=0.12.6,<0.12.7.0a0 + - aws-c-cal >=0.9.13,<0.9.14.0a0 license: Apache-2.0 license_family: APACHE purls: [] - size: 177072 - timestamp: 1773868341204 -- conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-io-0.26.3-h0d5b9f9_0.conda - sha256: 3c9d50fb7895df4edd72d177299551608c24d8b0b82db0cf34c8e2bf6644979c - md5: ce36c60ed6b15c8dbb7ccddec4ebf57f + size: 176967 + timestamp: 1777471210683 +- conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-io-0.26.3-h0d5b9f9_1.conda + sha256: 77a9e9cbbea1ed76d02605955a8cf098d3793a8dc871b31b4617a8054f151639 + md5: d6091ef6857cee4f541716790de07b48 depends: - vc >=14.3,<15 - vc14_runtime >=14.44.35208 @@ -5886,102 +5888,102 @@ packages: license: Apache-2.0 license_family: APACHE purls: [] - size: 182296 - timestamp: 1773868342627 -- conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-mqtt-0.15.2-he9ea9c5_1.conda - sha256: 3fc68793c0ca2c36524ae67abac696ce6b066a8be6b2b980cbdc40ae1310041a - md5: 8e77514673f5773b40ff8953583938b6 + size: 182289 + timestamp: 1777471159132 +- conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-mqtt-0.15.2-hc1936db_2.conda + sha256: 236ab4138ff4600f95903d2da94125df78577055f6687afa8806db0f6ed2e1a8 + md5: 9120bc47b6f837f3cea90928c3e9a8fa depends: - - __glibc >=2.17,<3.0.a0 - libgcc >=14 - - aws-c-io >=0.26.3,<0.26.4.0a0 + - __glibc >=2.17,<3.0.a0 + - aws-c-http >=0.10.13,<0.10.14.0a0 - aws-c-common >=0.12.6,<0.12.7.0a0 - - aws-c-http >=0.10.12,<0.10.13.0a0 + - aws-c-io >=0.26.3,<0.26.4.0a0 license: Apache-2.0 license_family: APACHE purls: [] - size: 221711 - timestamp: 1774275485771 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-mqtt-0.15.2-h69e7467_1.conda - sha256: 69a12dfccdeb1497e3fbcaedea77c7adab854b482558aaa4ce5dea3a80d08c76 - md5: 1f4f6b9a183bea3ddf9af5ebcda0933d + size: 221638 + timestamp: 1777488145895 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-mqtt-0.15.2-h8860bc9_2.conda + sha256: 19a97f5d06ef994d7f48e77de3f998cc0a72d750d9363f2ba3894234c7bc799e + md5: 826c667323e95b2af0223641c69f327c depends: - __osx >=11.0 - aws-c-io >=0.26.3,<0.26.4.0a0 - - aws-c-http >=0.10.12,<0.10.13.0a0 + - aws-c-http >=0.10.13,<0.10.14.0a0 - aws-c-common >=0.12.6,<0.12.7.0a0 license: Apache-2.0 license_family: APACHE purls: [] - size: 156423 - timestamp: 1774275623505 -- conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-mqtt-0.15.2-h904b250_1.conda - sha256: f99bf60673f0d5a143450009c9454087c9bca01be74ae08394f8fc47789fa56a - md5: fbccf4b054995b97bf98c38f0989a9a3 + size: 156329 + timestamp: 1777488187414 +- conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-mqtt-0.15.2-h82175e6_2.conda + sha256: 96bf32162c9b4771a5193b27b93f5ef9aeb4c4d5ac5ea634de196e04e631f025 + md5: f0ef94911aacba679e072fb6bec80015 depends: - vc >=14.3,<15 - vc14_runtime >=14.44.35208 - ucrt >=10.0.20348.0 - aws-c-io >=0.26.3,<0.26.4.0a0 - aws-c-common >=0.12.6,<0.12.7.0a0 - - aws-c-http >=0.10.12,<0.10.13.0a0 + - aws-c-http >=0.10.13,<0.10.14.0a0 license: Apache-2.0 license_family: APACHE purls: [] - size: 212290 - timestamp: 1774275592614 -- conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-s3-0.11.5-h6d69fc9_5.conda - sha256: c15869656f5fbebe27cc5aa58b23831f75d85502d324fedd7ee7e552c79b495d - md5: 4c5c16bf1133dcfe100f33dd4470998e + size: 212275 + timestamp: 1777488175661 +- conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-s3-0.12.2-he6ee468_1.conda + sha256: 4cecb4d595b7cf558087c37b8131cae5204b2c64d75f6b951dc3731d3f872bb8 + md5: 50ae8372984b8b98e056ac8f6b70ab29 depends: - - __glibc >=2.17,<3.0.a0 - libgcc >=14 - - aws-c-io >=0.26.3,<0.26.4.0a0 - - aws-checksums >=0.2.10,<0.2.11.0a0 + - __glibc >=2.17,<3.0.a0 - aws-c-common >=0.12.6,<0.12.7.0a0 - - aws-c-http >=0.10.12,<0.10.13.0a0 - - openssl >=3.5.5,<4.0a0 - - aws-c-auth >=0.10.1,<0.10.2.0a0 + - aws-checksums >=0.2.10,<0.2.11.0a0 - aws-c-cal >=0.9.13,<0.9.14.0a0 + - aws-c-io >=0.26.3,<0.26.4.0a0 + - openssl >=3.5.6,<4.0a0 + - aws-c-http >=0.10.13,<0.10.14.0a0 + - aws-c-auth >=0.10.1,<0.10.2.0a0 license: Apache-2.0 license_family: APACHE purls: [] - size: 151340 - timestamp: 1774282148690 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-s3-0.11.5-ha5d16b2_5.conda - sha256: bd8f4ffb8346dd02bda2bc1ae9993ebdb131298b1308cb9e6b1e771b530d9dd5 - md5: f33735fd60f9c4a21c51a0283eb8afc1 + size: 152657 + timestamp: 1777824812393 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-s3-0.12.2-h07b101a_1.conda + sha256: 236b4acfc8b0757e427087b53ebaf090190d106a7e73b6b1e8f80388988e89ac + md5: 7a520ebd6ae9efe641cb207b650d004c depends: - __osx >=11.0 - aws-c-common >=0.12.6,<0.12.7.0a0 - - aws-c-http >=0.10.12,<0.10.13.0a0 - - aws-c-auth >=0.10.1,<0.10.2.0a0 + - aws-c-http >=0.10.13,<0.10.14.0a0 - aws-c-cal >=0.9.13,<0.9.14.0a0 - - aws-checksums >=0.2.10,<0.2.11.0a0 + - aws-c-auth >=0.10.1,<0.10.2.0a0 - aws-c-io >=0.26.3,<0.26.4.0a0 + - aws-checksums >=0.2.10,<0.2.11.0a0 license: Apache-2.0 license_family: APACHE purls: [] - size: 129783 - timestamp: 1774282252139 -- conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-s3-0.11.5-h87bd87b_5.conda - sha256: 62367b6d4d8aa1b43fb63e51d779bb829dfdd53d908c1b6700efa23255dd38db - md5: 2d90128559ec4b3c78d1b889b8b13b50 + size: 131374 + timestamp: 1777824889044 +- conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-s3-0.12.2-h61b906f_1.conda + sha256: 8d9c747d71c493e6d5e5a125a267c6ac51baba1e4b89c01c2a4084239267b8e1 + md5: 2c4cd5a0bb004c9975a4d7257a55c34a depends: - vc >=14.3,<15 - vc14_runtime >=14.44.35208 - ucrt >=10.0.20348.0 - - aws-c-http >=0.10.12,<0.10.13.0a0 - aws-c-io >=0.26.3,<0.26.4.0a0 - - aws-c-common >=0.12.6,<0.12.7.0a0 - - aws-c-auth >=0.10.1,<0.10.2.0a0 - - aws-c-cal >=0.9.13,<0.9.14.0a0 - aws-checksums >=0.2.10,<0.2.11.0a0 + - aws-c-cal >=0.9.13,<0.9.14.0a0 + - aws-c-auth >=0.10.1,<0.10.2.0a0 + - aws-c-http >=0.10.13,<0.10.14.0a0 + - aws-c-common >=0.12.6,<0.12.7.0a0 license: Apache-2.0 license_family: APACHE purls: [] - size: 141733 - timestamp: 1774282227215 + size: 143057 + timestamp: 1777824834454 - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-sdkutils-0.2.4-h8b1a151_4.conda sha256: 9d62c5029f6f8219368a8665f0a549da572dc777f52413b7d75609cacdbc02cc md5: c7e3e08b7b1b285524ab9d74162ce40b @@ -6054,117 +6056,117 @@ packages: purls: [] size: 116853 timestamp: 1771063509650 -- conda: https://conda.anaconda.org/conda-forge/linux-64/aws-crt-cpp-0.37.4-h4c8aef7_3.conda - sha256: b82d0bc6d4b716347e1aefb0acc6e4bff04b3f807537ada9b458a7e467beb2a0 - md5: 798a499cf76e530a992365d557ba5827 +- conda: https://conda.anaconda.org/conda-forge/linux-64/aws-crt-cpp-0.38.3-h745e52d_1.conda + sha256: 5616649034662ab7846b78b344891f49b895807cabd83918aebb3439aa9ca405 + md5: 6a65b3595a8933808c03ff065dfb7702 depends: - - libgcc >=14 - - libstdcxx >=14 - __glibc >=2.17,<3.0.a0 - - aws-c-event-stream >=0.6.0,<0.6.1.0a0 - - aws-c-mqtt >=0.15.2,<0.15.3.0a0 + - libstdcxx >=14 + - libgcc >=14 + - aws-c-auth >=0.10.1,<0.10.2.0a0 + - aws-c-cal >=0.9.13,<0.9.14.0a0 - aws-c-common >=0.12.6,<0.12.7.0a0 - - aws-c-s3 >=0.11.5,<0.11.6.0a0 - - aws-c-http >=0.10.12,<0.10.13.0a0 + - aws-c-s3 >=0.12.2,<0.12.3.0a0 + - aws-c-mqtt >=0.15.2,<0.15.3.0a0 - aws-c-io >=0.26.3,<0.26.4.0a0 - - aws-c-auth >=0.10.1,<0.10.2.0a0 + - aws-c-event-stream >=0.7.0,<0.7.1.0a0 + - aws-c-http >=0.10.13,<0.10.14.0a0 - aws-c-sdkutils >=0.2.4,<0.2.5.0a0 - - aws-c-cal >=0.9.13,<0.9.14.0a0 license: Apache-2.0 license_family: APACHE purls: [] - size: 410120 - timestamp: 1774286908570 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-crt-cpp-0.37.4-h5505c15_3.conda - sha256: bb9e0abbe22825810776e4c6929f4587567b795272126aaca7e55b30c91f2d29 - md5: a13b36ec511c0589632e3689cd34ccc0 + size: 412541 + timestamp: 1778019077033 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-crt-cpp-0.38.3-hba17502_1.conda + sha256: 917ca9bcd9271a55be1c39dc1b07ce2e6c268c1fd507750d86138d98f708724a + md5: 40aa7f64708aef33749bcedd53d04d2e depends: - libcxx >=19 - __osx >=11.0 - - aws-c-cal >=0.9.13,<0.9.14.0a0 - - aws-c-mqtt >=0.15.2,<0.15.3.0a0 - - aws-c-common >=0.12.6,<0.12.7.0a0 - - aws-c-sdkutils >=0.2.4,<0.2.5.0a0 - - aws-c-event-stream >=0.6.0,<0.6.1.0a0 - - aws-c-http >=0.10.12,<0.10.13.0a0 - aws-c-auth >=0.10.1,<0.10.2.0a0 - - aws-c-s3 >=0.11.5,<0.11.6.0a0 + - aws-c-event-stream >=0.7.0,<0.7.1.0a0 + - aws-c-sdkutils >=0.2.4,<0.2.5.0a0 + - aws-c-common >=0.12.6,<0.12.7.0a0 + - aws-c-cal >=0.9.13,<0.9.14.0a0 + - aws-c-http >=0.10.13,<0.10.14.0a0 - aws-c-io >=0.26.3,<0.26.4.0a0 + - aws-c-s3 >=0.12.2,<0.12.3.0a0 + - aws-c-mqtt >=0.15.2,<0.15.3.0a0 license: Apache-2.0 license_family: APACHE purls: [] - size: 269460 - timestamp: 1774286981607 -- conda: https://conda.anaconda.org/conda-forge/win-64/aws-crt-cpp-0.37.4-h4f72eff_3.conda - sha256: 4a072a69e8b0a6552269cdf32831dc2cfa429a61c58edc5353f94dde09a3002f - md5: 81e1ff78b80119ec772bf28b30216f00 + size: 271073 + timestamp: 1778019218424 +- conda: https://conda.anaconda.org/conda-forge/win-64/aws-crt-cpp-0.38.3-h1db8845_1.conda + sha256: f5b2630c36a2bcc9191f4e8507f33b952a8d1d95bab6e01f2ce8e91d10393c59 + md5: 11486baa17799f372477c636def4d51a depends: - vc >=14.3,<15 - vc14_runtime >=14.44.35208 - ucrt >=10.0.20348.0 - - aws-c-cal >=0.9.13,<0.9.14.0a0 - aws-c-sdkutils >=0.2.4,<0.2.5.0a0 - - aws-c-event-stream >=0.6.0,<0.6.1.0a0 - - aws-c-s3 >=0.11.5,<0.11.6.0a0 - - aws-c-common >=0.12.6,<0.12.7.0a0 - - aws-c-mqtt >=0.15.2,<0.15.3.0a0 + - aws-c-s3 >=0.12.2,<0.12.3.0a0 - aws-c-auth >=0.10.1,<0.10.2.0a0 - - aws-c-http >=0.10.12,<0.10.13.0a0 + - aws-c-mqtt >=0.15.2,<0.15.3.0a0 + - aws-c-http >=0.10.13,<0.10.14.0a0 + - aws-c-common >=0.12.6,<0.12.7.0a0 + - aws-c-event-stream >=0.7.0,<0.7.1.0a0 - aws-c-io >=0.26.3,<0.26.4.0a0 + - aws-c-cal >=0.9.13,<0.9.14.0a0 license: Apache-2.0 license_family: APACHE purls: [] - size: 304084 - timestamp: 1774286995597 -- conda: https://conda.anaconda.org/conda-forge/linux-64/aws-sdk-cpp-1.11.747-hc3785e1_3.conda - sha256: 62b7e565852fa061a26281b2890e432853fabefa8ea3dc22d00d39295a030805 - md5: cfffedbfd03d5a6bb74157c14b6f0cdf + size: 306413 + timestamp: 1778019104103 +- conda: https://conda.anaconda.org/conda-forge/linux-64/aws-sdk-cpp-1.11.747-h41c0014_4.conda + sha256: f17585991350e00084614faaa704166a07fdcf58e80c76003e35111093c6e5e9 + md5: 169a79ea1127077d8dc36dc963ff55ac depends: - - __glibc >=2.17,<3.0.a0 - libstdcxx >=14 - libgcc >=14 + - __glibc >=2.17,<3.0.a0 - aws-c-common >=0.12.6,<0.12.7.0a0 - - libzlib >=1.3.1,<2.0a0 - - aws-crt-cpp >=0.37.4,<0.37.5.0a0 - - aws-c-event-stream >=0.6.0,<0.6.1.0a0 - - libcurl >=8.19.0,<9.0a0 + - libzlib >=1.3.2,<2.0a0 + - aws-crt-cpp >=0.38.3,<0.38.4.0a0 + - libcurl >=8.20.0,<9.0a0 + - aws-c-event-stream >=0.7.0,<0.7.1.0a0 license: Apache-2.0 license_family: APACHE purls: [] - size: 3624521 - timestamp: 1773666645246 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-sdk-cpp-1.11.747-had22720_3.conda - sha256: b5ce4fafe17ab58980f944b9a45504ce45dda0423064591d51240eb8308589af - md5: 157ae2a6008d62f61107f5b78dce06d2 + size: 3624409 + timestamp: 1778156208464 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-sdk-cpp-1.11.747-h30a6df1_4.conda + sha256: 29c21176bc47051ed20db066dc4917b1c9d8e209f936a1737998755061ee133f + md5: 45bb0d0e776ed7cd12597710058c2d50 depends: - - libcxx >=19 - __osx >=11.0 + - libcxx >=19 + - aws-crt-cpp >=0.38.3,<0.38.4.0a0 + - libcurl >=8.20.0,<9.0a0 + - libzlib >=1.3.2,<2.0a0 + - aws-c-event-stream >=0.7.0,<0.7.1.0a0 - aws-c-common >=0.12.6,<0.12.7.0a0 - - aws-c-event-stream >=0.6.0,<0.6.1.0a0 - - libcurl >=8.19.0,<9.0a0 - - libzlib >=1.3.1,<2.0a0 - - aws-crt-cpp >=0.37.4,<0.37.5.0a0 license: Apache-2.0 license_family: APACHE purls: [] - size: 3260974 - timestamp: 1773666675518 -- conda: https://conda.anaconda.org/conda-forge/win-64/aws-sdk-cpp-1.11.747-hd55a107_3.conda - sha256: b2ca74995fecfc1029f95c6256dea6d7e035e24633870a52665a8d48f49331f8 - md5: 48efab184702deb479a3766b1462efec + size: 3261086 + timestamp: 1778156290937 +- conda: https://conda.anaconda.org/conda-forge/win-64/aws-sdk-cpp-1.11.747-hd63e0c5_4.conda + sha256: 1d8f02b1eaeba71654692d7f7d8eede738fc7be9208ed22e0fe8406d5e621e58 + md5: 38bb9c437f8c362641bc102a74f487e1 depends: - vc >=14.3,<15 - vc14_runtime >=14.44.35208 - ucrt >=10.0.20348.0 - - libzlib >=1.3.1,<2.0a0 - - aws-c-event-stream >=0.6.0,<0.6.1.0a0 + - libzlib >=1.3.2,<2.0a0 + - aws-c-event-stream >=0.7.0,<0.7.1.0a0 + - aws-crt-cpp >=0.38.3,<0.38.4.0a0 - aws-c-common >=0.12.6,<0.12.7.0a0 - - aws-crt-cpp >=0.37.4,<0.37.5.0a0 license: Apache-2.0 license_family: APACHE purls: [] - size: 23794273 - timestamp: 1773666686533 + size: 23790512 + timestamp: 1778158982457 - conda: https://conda.anaconda.org/conda-forge/linux-64/azure-core-cpp-1.16.2-h206d751_0.conda sha256: 321d1070905e467b6bc6f5067b97c1868d7345c272add82b82e08a0224e326f0 md5: 5492abf806c45298ae642831c670bba0 @@ -6244,50 +6246,50 @@ packages: purls: [] size: 424962 timestamp: 1770345047909 -- conda: https://conda.anaconda.org/conda-forge/linux-64/azure-storage-blobs-cpp-12.16.0-hdd73cc9_1.conda - sha256: cef75b91bdd5a65c560b501df78905437cc2090a64b4c5ecd7da9e08e9e9af7c - md5: 939d9ce324e51961c7c4c0046733dbb7 +- conda: https://conda.anaconda.org/conda-forge/linux-64/azure-storage-blobs-cpp-12.16.0-hf824e48_2.conda + sha256: ec278ffc9785cffeed097f57483fd0bc32c9083f56d7e6d95de46e560e4b49d1 + md5: 315c1c09f02a1efeb1b4d3dbcd2aa26a depends: - __glibc >=2.17,<3.0.a0 - azure-core-cpp >=1.16.2,<1.16.3.0a0 - - azure-storage-common-cpp >=12.12.0,<12.12.1.0a0 + - azure-storage-common-cpp >=12.13.0,<12.13.1.0a0 - libgcc >=14 - libstdcxx >=14 license: MIT license_family: MIT purls: [] - size: 579825 - timestamp: 1770321459546 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/azure-storage-blobs-cpp-12.16.0-hc57151b_1.conda - sha256: 9de2f050a49597e5b98b59bf90880e00bfdff79a3afbb18828565c3a645d62d6 - md5: f08b3b9d7333dc427b79897e6e3e7f29 + size: 580752 + timestamp: 1778727162545 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/azure-storage-blobs-cpp-12.16.0-h5446563_2.conda + sha256: 2ab2bc487d2cb985d2d45adbac7a6fe9a554bd78808268622566acb5e28fe5a2 + md5: 1ac96ad3d642a951b4576ea09ae502a3 depends: - __osx >=11.0 - azure-core-cpp >=1.16.2,<1.16.3.0a0 - - azure-storage-common-cpp >=12.12.0,<12.12.1.0a0 + - azure-storage-common-cpp >=12.13.0,<12.13.1.0a0 - libcxx >=19 license: MIT license_family: MIT purls: [] - size: 426735 - timestamp: 1770322058844 -- conda: https://conda.anaconda.org/conda-forge/win-64/azure-storage-blobs-cpp-12.16.0-hcd625b1_1.conda - sha256: 654fae004aee8616a8ed4935a6fa703d629e4d1686a9fe431ef2e689846c0016 - md5: bc419192d40ca1b4928f70519d54b96c + size: 426524 + timestamp: 1778727625073 +- conda: https://conda.anaconda.org/conda-forge/win-64/azure-storage-blobs-cpp-12.16.0-h81bf7d1_2.conda + sha256: 303e08ee92f09e98d23bfd8c566f9f46f50dc732792497833425b0f6f2a61fd1 + md5: cff26b4c1811a4cb84a8d3e5ff955650 depends: - azure-core-cpp >=1.16.2,<1.16.3.0a0 - - azure-storage-common-cpp >=12.12.0,<12.12.1.0a0 + - azure-storage-common-cpp >=12.13.0,<12.13.1.0a0 - ucrt >=10.0.20348.0 - vc >=14.3,<15 - vc14_runtime >=14.44.35208 license: MIT license_family: MIT purls: [] - size: 781612 - timestamp: 1770321543576 -- conda: https://conda.anaconda.org/conda-forge/linux-64/azure-storage-common-cpp-12.12.0-ha7a2c86_1.conda - sha256: ef7d1cae36910b21385d0816f8524a84dee1513e0306927e41a6bd32b5b9a0d0 - md5: 6400f73fe5ebe19fe7aca3616f1f1de7 + size: 782578 + timestamp: 1778727275165 +- conda: https://conda.anaconda.org/conda-forge/linux-64/azure-storage-common-cpp-12.13.0-ha7a2c86_0.conda + sha256: 67fa6937bc2f6400f5ff19727f5d926fdc68d7fce3aaeab4016f49bb93d89cbb + md5: a7e8cca395e0a1616b389749580b7804 depends: - __glibc >=2.17,<3.0.a0 - azure-core-cpp >=1.16.2,<1.16.3.0a0 @@ -6295,30 +6297,30 @@ packages: - libstdcxx >=14 - libxml2 - libxml2-16 >=2.14.6 - - openssl >=3.5.5,<4.0a0 + - openssl >=3.5.6,<4.0a0 license: MIT license_family: MIT purls: [] - size: 150405 - timestamp: 1770240307002 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/azure-storage-common-cpp-12.12.0-he467506_1.conda - sha256: 541be427e681d129c8722e81548d2e51c4b1a817f88333f3fbb3dcdef7eacafb - md5: b658a3fb0fc412b2a4d30da3fcec036f + size: 159140 + timestamp: 1778661935076 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/azure-storage-common-cpp-12.13.0-he467506_0.conda + sha256: bc73ce983d90baa732e6f64e4d8b4ddbb8e671c5d6e7b9475d33dbd118ddd5b6 + md5: 4cfc08976cf62fef7736a763652987cb depends: - __osx >=11.0 - azure-core-cpp >=1.16.2,<1.16.3.0a0 - libcxx >=19 - libxml2 - libxml2-16 >=2.14.6 - - openssl >=3.5.5,<4.0a0 + - openssl >=3.5.6,<4.0a0 license: MIT license_family: MIT purls: [] - size: 121500 - timestamp: 1770240531430 -- conda: https://conda.anaconda.org/conda-forge/win-64/azure-storage-common-cpp-12.12.0-h5ffce34_1.conda - sha256: 98dfdd2d86d34b93a39d04a73eb4ca26cc0986bf20892005a66db13077eb4b86 - md5: 716715d06097dfd791b0bab525839910 + size: 128808 + timestamp: 1778662321258 +- conda: https://conda.anaconda.org/conda-forge/win-64/azure-storage-common-cpp-12.13.0-h5ffce34_0.conda + sha256: a6c53ef367cfbee76793ea35160f902bd5d1ebebb579a7a53d6b4de3b2011b32 + md5: 40d5c4a1192882e4f6c4a59631f0d2d4 depends: - azure-core-cpp >=1.16.2,<1.16.3.0a0 - ucrt >=10.0.20348.0 @@ -6327,52 +6329,52 @@ packages: license: MIT license_family: MIT purls: [] - size: 246289 - timestamp: 1770240396492 -- conda: https://conda.anaconda.org/conda-forge/linux-64/azure-storage-files-datalake-cpp-12.14.0-h52c5a47_1.conda - sha256: 55aa8ad5217d358e0ccf4a715bd1f9bafef3cd1c2ea4021f0e916f174c20f8e3 - md5: 6d10339800840562b7dad7775f5d2c16 + size: 256294 + timestamp: 1778662025067 +- conda: https://conda.anaconda.org/conda-forge/linux-64/azure-storage-files-datalake-cpp-12.14.0-h539c000_2.conda + sha256: 7765e9082b544555f74473ec21e366d92bb7688635d42d200860798e8b792a25 + md5: 245b61f9baef23f8f6cf04ccda928521 depends: - __glibc >=2.17,<3.0.a0 - azure-core-cpp >=1.16.2,<1.16.3.0a0 - azure-storage-blobs-cpp >=12.16.0,<12.16.1.0a0 - - azure-storage-common-cpp >=12.12.0,<12.12.1.0a0 + - azure-storage-common-cpp >=12.13.0,<12.13.1.0a0 - libgcc >=14 - libstdcxx >=14 license: MIT license_family: MIT purls: [] - size: 302524 - timestamp: 1770384269834 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/azure-storage-files-datalake-cpp-12.14.0-hf8a9d22_1.conda - sha256: 1891df88b68768bc042ea766c1be279bff0fdaf471470bfa3fa599284dbd0975 - md5: 601ac4f945ba078955557edf743f1f78 + size: 302771 + timestamp: 1778763856084 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/azure-storage-files-datalake-cpp-12.14.0-hdc9d693_2.conda + sha256: 77dde85d2c3c4c2f2a0a0cf6ac7e2b2458d60fe9a633e8fe934f0c9bfcbae168 + md5: 4dbee4ea590bf017fb7b2fba71b16b24 depends: - __osx >=11.0 - azure-core-cpp >=1.16.2,<1.16.3.0a0 - azure-storage-blobs-cpp >=12.16.0,<12.16.1.0a0 - - azure-storage-common-cpp >=12.12.0,<12.12.1.0a0 + - azure-storage-common-cpp >=12.13.0,<12.13.1.0a0 - libcxx >=19 license: MIT license_family: MIT purls: [] - size: 198153 - timestamp: 1770384528646 -- conda: https://conda.anaconda.org/conda-forge/win-64/azure-storage-files-datalake-cpp-12.14.0-h1678c0b_1.conda - sha256: 9941733f0f4b3a2649f534c71195c8e7a92984e9e9f17c7eb6d84803e3cdccf1 - md5: 64afdd17c4a6f4cb1d97caaad1fdc191 + size: 198818 + timestamp: 1778764243281 +- conda: https://conda.anaconda.org/conda-forge/win-64/azure-storage-files-datalake-cpp-12.14.0-hab49af2_2.conda + sha256: 4ee09742ae920c02bf84926c63d339b9662785b5aec80963af709b1c139068f4 + md5: 8a63603563a73598ab7d632158be0aa1 depends: - azure-core-cpp >=1.16.2,<1.16.3.0a0 - azure-storage-blobs-cpp >=12.16.0,<12.16.1.0a0 - - azure-storage-common-cpp >=12.12.0,<12.12.1.0a0 + - azure-storage-common-cpp >=12.13.0,<12.13.1.0a0 - ucrt >=10.0.20348.0 - vc >=14.3,<15 - vc14_runtime >=14.44.35208 license: MIT license_family: MIT purls: [] - size: 438910 - timestamp: 1770384369008 + size: 439535 + timestamp: 1778763967002 - conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.18.0-pyhcf101f3_1.conda sha256: a14a9ad02101aab25570543a59c5193043b73dc311a25650134ed9e6cb691770 md5: f1976ce927373500cc19d3c0b2c85177 @@ -6384,19 +6386,20 @@ packages: license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/babel?source=compressed-mapping + - pkg:pypi/babel?source=hash-mapping size: 7684321 timestamp: 1772555330347 -- conda: https://conda.anaconda.org/conda-forge/noarch/backports.zstd-1.3.0-py314h680f03e_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/backports.zstd-1.5.0-py314h680f03e_0.conda noarch: generic - sha256: c31ab719d256bc6f89926131e88ecd0f0c5d003fe8481852c6424f4ec6c7eb29 - md5: a2ac7763a9ac75055b68f325d3255265 + sha256: a1c97297e867776760489537bc5ae36fa83a154be30e3b79385a39ca4cb058fe + md5: 1133126d840e75287d83947be3fc3e71 depends: - python >=3.14 license: BSD-3-Clause AND MIT AND EPL-2.0 - purls: [] - size: 7514 - timestamp: 1767044983590 + purls: + - pkg:pypi/backports-zstd?source=compressed-mapping + size: 7533 + timestamp: 1778594057496 - pypi: https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl name: beartype version: 0.22.9 @@ -6854,7 +6857,7 @@ packages: - python >=3.10 license: ISC purls: - - pkg:pypi/certifi?source=compressed-mapping + - pkg:pypi/certifi?source=hash-mapping size: 135656 timestamp: 1776866680878 - conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-2.0.0-py314h4a8dc5f_1.conda @@ -6913,13 +6916,13 @@ packages: license: MIT license_family: MIT purls: - - pkg:pypi/charset-normalizer?source=compressed-mapping + - pkg:pypi/charset-normalizer?source=hash-mapping size: 58872 timestamp: 1775127203018 -- pypi: https://files.pythonhosted.org/packages/ae/44/c1221527f6a71a01ec6fbad7fa78f1d50dfa02217385cf0fa3eec7087d59/click-8.3.3-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/ee/ae/8e92f8058baf87f6c7d86ee7e457668690195cc77efedb8d3797a06e3940/click-8.4.0-py3-none-any.whl name: click - version: 8.3.3 - sha256: a2bf429bb3033c89fa4936ffb35d5cb471e3719e1f3c8a7c3fff0b8314305613 + version: 8.4.0 + sha256: 40c50b7c6c6adac2823d411041ec84f3f103f1b280d5e9ce0d7f998995832f81 requires_dist: - colorama ; sys_platform == 'win32' requires_python: '>=3.10' @@ -7014,9 +7017,9 @@ packages: - pkg:pypi/contourpy?source=hash-mapping size: 247437 timestamp: 1769155978556 -- conda: https://conda.anaconda.org/conda-forge/linux-64/coverage-7.13.5-py314h67df5f8_0.conda - sha256: cf5f98a291c3a5489cb299bae38711d5dc21b88a00df981f3b1528781e18c909 - md5: 78f547b78ace7541c4f54c4268ac9d2e +- conda: https://conda.anaconda.org/conda-forge/linux-64/coverage-7.14.0-py314h67df5f8_0.conda + sha256: 572f6f6527f35e214eb26d4d3d92b53af11b080de6958876f02b9288e518dfdf + md5: 7f8715a1928f6f126323320a4c5ada3a depends: - __glibc >=2.17,<3.0.a0 - libgcc >=14 @@ -7027,11 +7030,11 @@ packages: license_family: APACHE purls: - pkg:pypi/coverage?source=hash-mapping - size: 411308 - timestamp: 1773761119353 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/coverage-7.13.5-py314h6e9b3f0_0.conda - sha256: 808ebcb57027251f379f84e53a3755d2851918f78bdd512d131afe40ca64a041 - md5: cdbafe4a3e605024e7372c9580f9d734 + size: 414377 + timestamp: 1778445024489 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/coverage-7.14.0-py314h6e9b3f0_0.conda + sha256: 9b03482f61e8006dd2e113458727e7319932dea78cedbfea8a89df5d7a46d1d2 + md5: 70cf43e2d03269a3dfb33c284ce05dff depends: - __osx >=11.0 - python >=3.14,<3.15.0a0 @@ -7042,11 +7045,11 @@ packages: license_family: APACHE purls: - pkg:pypi/coverage?source=hash-mapping - size: 412458 - timestamp: 1773761280047 -- conda: https://conda.anaconda.org/conda-forge/win-64/coverage-7.13.5-py314h2359020_0.conda - sha256: 80a6a7be7eef784b8314a4cb563563c654e2180a0b2b31b232f79b2e7334aaf2 - md5: 849f0bd5b83d4fd59b41202b21bb3ca2 + size: 412814 + timestamp: 1778445420201 +- conda: https://conda.anaconda.org/conda-forge/win-64/coverage-7.14.0-py314h2359020_0.conda + sha256: 340e6af5a19c091dabc334f9906e6ba2c5667721c90897ea878e05ee9f09fcf3 + md5: 25ce440d36bee2f53d5636a392dbe6f6 depends: - python >=3.14,<3.15.0a0 - python_abi 3.14.* *_cp314 @@ -7058,8 +7061,8 @@ packages: license_family: APACHE purls: - pkg:pypi/coverage?source=hash-mapping - size: 438927 - timestamp: 1773760993379 + size: 437794 + timestamp: 1778444996415 - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.14.4-py314hd8ed1ab_100.conda noarch: generic sha256: 40dc224f2b718e5f034efd2332bc315a719063235f63673468d26a24770094ee @@ -7543,9 +7546,9 @@ packages: purls: [] size: 210103 timestamp: 1771943128249 -- pypi: git+https://github.com/OpenSourceEconomics/dags.git?rev=cf59c04#cf59c04c6ba07b7c54ca763dc155deea3341a480 +- pypi: git+https://github.com/OpenSourceEconomics/dags.git?branch=main#87ff051077f37470514808a3121e7c90429f6383 name: dags - version: 0.5.2.dev6+gcf59c04c6 + version: 0.5.2.dev1+g87ff05107 requires_dist: - flatten-dict - networkx>=3.6 @@ -7609,17 +7612,16 @@ packages: - pkg:pypi/debugpy?source=hash-mapping size: 4026404 timestamp: 1769745008861 -- conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda - sha256: c17c6b9937c08ad63cb20a26f403a3234088e57d4455600974a0ce865cb14017 - md5: 9ce473d1d1be1cc3810856a48b3fab32 +- conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.3.0-pyhd8ed1ab_0.conda + sha256: 78df51c83107eced84a6d07643fa9d8da2e93384208cc1941bad0da2c9cf8b07 + md5: ed5e04c929dc7c8bd57684aa14fa5693 depends: - - python >=3.9 + - python >=3.10 license: BSD-2-Clause - license_family: BSD purls: - - pkg:pypi/decorator?source=hash-mapping - size: 14129 - timestamp: 1740385067843 + - pkg:pypi/decorator?source=compressed-mapping + size: 16435 + timestamp: 1779011007912 - conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 sha256: 9717a059677553562a8f38ff07f3b9f61727bd614f505658b0a5ecbcf8df89be md5: 961b3a227b437d82ad7054484cfa71b2 @@ -7722,18 +7724,14 @@ packages: - python >=3.10 license: Unlicense purls: - - pkg:pypi/filelock?source=compressed-mapping + - pkg:pypi/filelock?source=hash-mapping size: 34211 timestamp: 1776621506566 -- pypi: https://files.pythonhosted.org/packages/43/f5/ee39c6e92acc742c052f137b47c210cd0a1b72dcd3f98495528bb4d27761/flatten_dict-0.4.2-py2.py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/72/9f/485516087cd8c44183aaf9ab850247a28e2e4a42a4d62eab77c21f673450/flatten_dict-0.5.0-py3-none-any.whl name: flatten-dict - version: 0.4.2 - sha256: 7e245b20c4c718981212210eec4284a330c9f713e632e98765560e05421e48ad - requires_dist: - - importlib-metadata ; python_full_version < '3.8' - - pathlib2>=2.3,<3.0 ; python_full_version < '3.4' - - six>=1.12,<2.0 - requires_python: '>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*' + version: 0.5.0 + sha256: c4bd2010052e4d33241433720d054322403fa7ad914fdc5cb1b31a713d4c561e + requires_python: '>=3.10,<4.0' - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab24e00_0.tar.bz2 sha256: 58d7f40d2940dd0a8aa28651239adbf5613254df0f75789919c4e6762054403b md5: 0c96522c6bdaed4b1566d11387caaf45 @@ -7822,9 +7820,9 @@ packages: purls: [] size: 4059 timestamp: 1762351264405 -- conda: https://conda.anaconda.org/conda-forge/noarch/fonttools-4.62.1-pyh7db6752_0.conda - sha256: fa77109df37580ce0933d4e6c5a44b2f0c192af2f8e503bfdbfb3b49a8b8e538 - md5: 14cf1ac7a1e29553c6918f7860aab6d8 +- conda: https://conda.anaconda.org/conda-forge/noarch/fonttools-4.63.0-pyh7db6752_0.conda + sha256: c9752235f1ff7061d834e5e4a3d0adf71ebeeff2b3fad82dab607edce7f70c91 + md5: 0509ee74d95e5b98eb6fe2a47760e399 depends: - brotli - munkres @@ -7836,8 +7834,8 @@ packages: license_family: MIT purls: - pkg:pypi/fonttools?source=compressed-mapping - size: 840293 - timestamp: 1776708212291 + size: 846038 + timestamp: 1778770337113 - conda: https://conda.anaconda.org/conda-forge/noarch/fqdn-1.5.1-pyhd8ed1ab_1.conda sha256: 2509992ec2fd38ab27c7cdb42cf6cadc566a1cc0d1021a2673475d9fa87c6276 md5: d3549fd50d450b6d9e7dddff25dd2110 @@ -7880,10 +7878,10 @@ packages: purls: [] size: 185640 timestamp: 1774300487600 -- pypi: https://files.pythonhosted.org/packages/d5/1f/5f4a3cd9e4440e9d9bc78ad0a91a1c8d46b4d429d5239ebe6793c9fe5c41/fsspec-2026.3.0-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/d5/0c/043d5e551459da400957a1395e0febbf771446ff34291afcbe3d8be2a279/fsspec-2026.4.0-py3-none-any.whl name: fsspec - version: 2026.3.0 - sha256: d2ceafaad1b3457968ed14efa28798162f1638dbb5d2a6868a2db002a5ee39a4 + version: 2026.4.0 + sha256: 11ef7bb35dab8a394fde6e608221d5cf3e8499401c249bebaeaad760a1a8dec2 requires_dist: - adlfs ; extra == 'abfs' - adlfs ; extra == 'adl' @@ -7988,43 +7986,43 @@ packages: - zstandard ; python_full_version < '3.14' and extra == 'test-full' - tqdm ; extra == 'tqdm' requires_python: '>=3.10' -- conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_impl_linux-64-14.3.0-hbdf3cc3_18.conda - sha256: 3b31a273b806c6851e16e9cf63ef87cae28d19be0df148433f3948e7da795592 - md5: 30bb690150536f622873758b0e8d6712 +- conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_impl_linux-64-14.3.0-h235f0fe_19.conda + sha256: 1e2500ca976d4831c953d1c6db7b238d2e6806910b930e3eb631b79ba5c3ba41 + md5: 99936dc616b7ce97b0468759b8a7c64e depends: - binutils_impl_linux-64 >=2.45 - libgcc >=14.3.0 - - libgcc-devel_linux-64 14.3.0 hf649bbc_118 + - libgcc-devel_linux-64 14.3.0 hf649bbc_119 - libgomp >=14.3.0 - - libsanitizer 14.3.0 h8f1669f_18 + - libsanitizer 14.3.0 h8f1669f_19 - libstdcxx >=14.3.0 - - libstdcxx-devel_linux-64 14.3.0 h9f08a49_118 + - libstdcxx-devel_linux-64 14.3.0 h9f08a49_119 - sysroot_linux-64 license: GPL-3.0-only WITH GCC-exception-3.1 license_family: GPL purls: [] - size: 76302378 - timestamp: 1771378056505 -- conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_impl_linux-64-15.2.0-he420e7e_18.conda - sha256: a088cfd3ae6fa83815faa8703bc9d21cc915f17bd1b51aac9c16ddf678da21e4 - md5: cf56b6d74f580b91fd527e10d9a2e324 + size: 77667192 + timestamp: 1778268558509 +- conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_impl_linux-64-15.2.0-he0086c7_19.conda + sha256: a48400ec4b73369c1c59babe4ad35821b63a88bba0ec40a80cea5f8c53a26b83 + md5: e3be72048d3c4a78b8e27ec48ba06252 depends: - binutils_impl_linux-64 >=2.45 - libgcc >=15.2.0 - - libgcc-devel_linux-64 15.2.0 hcc6f6b0_118 + - libgcc-devel_linux-64 15.2.0 hcc6f6b0_119 - libgomp >=15.2.0 - - libsanitizer 15.2.0 h90f66d4_18 + - libsanitizer 15.2.0 h90f66d4_19 - libstdcxx >=15.2.0 - - libstdcxx-devel_linux-64 15.2.0 hd446a21_118 + - libstdcxx-devel_linux-64 15.2.0 hd446a21_119 - sysroot_linux-64 license: GPL-3.0-only WITH GCC-exception-3.1 license_family: GPL purls: [] - size: 81814135 - timestamp: 1771378369317 -- conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_linux-64-14.3.0-h298d278_23.conda - sha256: b535da55f53ed0e44a366295dad325b242958fb3d91ba84b0173bfae28b39793 - md5: b6090b005c6e1947e897c926caac1286 + size: 81180457 + timestamp: 1778269124617 +- conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_linux-64-14.3.0-h50e9bb6_24.conda + sha256: 5f73bc0ce1729466f99d072678fb1bc13d5424d03a34cb2e69fbafbfd5e27ab2 + md5: 91b0f19212d79a1a4dca034aac729e4f depends: - gcc_impl_linux-64 14.3.0.* - binutils_linux-64 @@ -8032,11 +8030,11 @@ packages: license: BSD-3-Clause license_family: BSD purls: [] - size: 28912 - timestamp: 1775508892545 -- conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_linux-64-15.2.0-h862fb80_23.conda - sha256: 15baa12c7c1f9540525b1cee4e6d42bc26c632aed7088eecf89b9d22b8b94d81 - md5: 3251d2b55a91af721d3d6924c45d6bb1 + size: 29073 + timestamp: 1777144725126 +- conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_linux-64-15.2.0-h7be306e_24.conda + sha256: 7e1a77123819f9e6c15439df9a987c66235c53e4c6d12a9ab3cea883258214df + md5: 81f96ca8673107e2da4a6b9e3807cf74 depends: - gcc_impl_linux-64 15.2.0.* - binutils_linux-64 @@ -8044,8 +8042,8 @@ packages: license: BSD-3-Clause license_family: BSD purls: [] - size: 28920 - timestamp: 1775508945710 + size: 29081 + timestamp: 1777144726741 - conda: https://conda.anaconda.org/conda-forge/linux-64/gflags-2.2.2-h5888daf_1005.conda sha256: 6c33bf0c4d8f418546ba9c250db4e4221040936aef8956353bc764d4877bc39a md5: d411fc29e338efb48c5fd4576d71d881 @@ -8117,10 +8115,10 @@ packages: purls: [] size: 96336 timestamp: 1755102441729 -- pypi: https://files.pythonhosted.org/packages/07/49/d4cad6e5381a50947bb973d2f6cf6592621451b09368b8c20d9b8af49c5b/greenlet-3.4.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/a3/59/1bd6d7428d6ed9106efbb8c52310c60fd04f6672490f452aeaa3829aa436/greenlet-3.5.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl name: greenlet - version: 3.4.0 - sha256: 4df3b0b2289ec686d3c821a5fee44259c05cfe824dd5e6e12c8e5f5df23085cf + version: 3.5.0 + sha256: 8f52a464e4ed91780bdfbbdd2b97197f3accaa629b98c200f4dffada759f3ae7 requires_dist: - sphinx ; extra == 'docs' - furo ; extra == 'docs' @@ -8128,58 +8126,58 @@ packages: - psutil ; extra == 'test' - setuptools ; extra == 'test' requires_python: '>=3.10' -- conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_impl_linux-64-14.3.0-h2185e75_18.conda - sha256: 38ffca57cc9c264d461ac2ce9464a9d605e0f606d92d831de9075cb0d95fc68a - md5: 6514b3a10e84b6a849e1b15d3753eb22 +- conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_impl_linux-64-14.3.0-h2185e75_19.conda + sha256: a31694c26d6a525d44f81130ebf7b9abe18771b7eaecb2cf93630c0b8b8fb936 + md5: 8b867d053ed89743eeac52c3a50f112d depends: - - gcc_impl_linux-64 14.3.0 hbdf3cc3_18 - - libstdcxx-devel_linux-64 14.3.0 h9f08a49_118 + - gcc_impl_linux-64 14.3.0 h235f0fe_19 + - libstdcxx-devel_linux-64 14.3.0 h9f08a49_119 - sysroot_linux-64 - tzdata license: GPL-3.0-only WITH GCC-exception-3.1 license_family: GPL purls: [] - size: 14566100 - timestamp: 1771378271421 -- conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_impl_linux-64-15.2.0-hda75c37_18.conda - sha256: 48946f1f43d699b68123fb39329ef5acf3d9cbf8f96bdb8fb14b6197f5402825 - md5: e39123ab71f2e4cf989aa6aa5fafdaaf + size: 15235650 + timestamp: 1778268773535 +- conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_impl_linux-64-15.2.0-hda75c37_19.conda + sha256: 3f5288346b9fe233352443b3c2e31f1fde845e39d3e96475fc05ec2e782af158 + md5: 9d41f3899b512199af0a4bb939b83e21 depends: - - gcc_impl_linux-64 15.2.0 he420e7e_18 - - libstdcxx-devel_linux-64 15.2.0 hd446a21_118 + - gcc_impl_linux-64 15.2.0 he0086c7_19 + - libstdcxx-devel_linux-64 15.2.0 hd446a21_119 - sysroot_linux-64 - tzdata license: GPL-3.0-only WITH GCC-exception-3.1 license_family: GPL purls: [] - size: 15587873 - timestamp: 1771378609722 -- conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_linux-64-14.3.0-h91b0f8e_23.conda - sha256: 8a6a78d354fd259906b2f01f5c29c4f9e42878fa870eadc20f7251d4554a4445 - md5: 12d093c7df954a01b396a748442bd5cb + size: 16356816 + timestamp: 1778269332159 +- conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_linux-64-14.3.0-h8a413ad_24.conda + sha256: 66e357fad69998624d24ed52d7e1550f8159dc78418fff044377790f29e0fee3 + md5: ea3921760f33250a1c12926fce1660eb depends: - gxx_impl_linux-64 14.3.0.* - - gcc_linux-64 ==14.3.0 h298d278_23 + - gcc_linux-64 ==14.3.0 h50e9bb6_24 - binutils_linux-64 - sysroot_linux-64 license: BSD-3-Clause license_family: BSD purls: [] - size: 27479 - timestamp: 1775508892545 -- conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_linux-64-15.2.0-h98b7566_23.conda - sha256: b42e47b9e3a549be01606e09bd070d18d24a7cc5437e51ea52d39759c1417764 - md5: 249806b139b4f6178475d39938182875 + size: 27606 + timestamp: 1777144725126 +- conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_linux-64-15.2.0-he30e93d_24.conda + sha256: 9b40af502e2471ceff9a04a860165d8a6fac659c07dc115ed8357e1a77e2cbe7 + md5: 0787df5104bd63d2186dd3902244e7c3 depends: - gxx_impl_linux-64 15.2.0.* - - gcc_linux-64 ==15.2.0 h862fb80_23 + - gcc_linux-64 ==15.2.0 h7be306e_24 - binutils_linux-64 - sysroot_linux-64 license: BSD-3-Clause license_family: BSD purls: [] - size: 27474 - timestamp: 1775508945710 + size: 27602 + timestamp: 1777144726741 - conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhcf101f3_1.conda sha256: 96cac6573fd35ae151f4d6979bab6fbc90cb6b1fb99054ba19eb075da9822fcb md5: b8993c19b0c32a2f7b66cbb58ca27069 @@ -8190,7 +8188,7 @@ packages: license: MIT license_family: MIT purls: - - pkg:pypi/h11?source=compressed-mapping + - pkg:pypi/h11?source=hash-mapping size: 39069 timestamp: 1767729720872 - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda @@ -8275,6 +8273,7 @@ packages: - libstdcxx >=14 - libzlib >=1.3.2,<2.0a0 license: MIT + license_family: MIT purls: [] size: 2333599 timestamp: 1776778392713 @@ -8294,78 +8293,79 @@ packages: - vc >=14.2,<15 - vc14_runtime >=14.29.30139 license: MIT + license_family: MIT purls: [] size: 1322557 timestamp: 1776778816190 -- conda: https://conda.anaconda.org/conda-forge/linux-64/hdf5-2.1.0-nompi_hd4fcb43_104.conda - sha256: c6ff674a4a5a237fcf748fed8f64e79df54b42189986e705f35ba64dc6603235 - md5: 1d92558abd05cea0577f83a5eca38733 +- conda: https://conda.anaconda.org/conda-forge/linux-64/hdf5-2.1.0-nompi_h87a9417_105.conda + sha256: beb8a2fb18924ca7b5b82cfb50f008f882f577daef2c00ed88022abea35fec76 + md5: 0d0595612fa229dddb5fc565c260a11f depends: - __glibc >=2.17,<3.0.a0 - aws-c-auth >=0.10.1,<0.10.2.0a0 - aws-c-common >=0.12.6,<0.12.7.0a0 - - aws-c-http >=0.10.12,<0.10.13.0a0 + - aws-c-http >=0.10.13,<0.10.14.0a0 - aws-c-io >=0.26.3,<0.26.4.0a0 - - aws-c-s3 >=0.11.5,<0.11.6.0a0 + - aws-c-s3 >=0.12.2,<0.12.3.0a0 - aws-c-sdkutils >=0.2.4,<0.2.5.0a0 - libaec >=1.1.5,<2.0a0 - - libcurl >=8.19.0,<9.0a0 + - libcurl >=8.20.0,<9.0a0 - libgcc >=14 - libgfortran - libgfortran5 >=14.3.0 - libstdcxx >=14 - libzlib >=1.3.2,<2.0a0 - - openssl >=3.5.5,<4.0a0 + - openssl >=3.5.6,<4.0a0 license: BSD-3-Clause license_family: BSD purls: [] - size: 4138489 - timestamp: 1775243967708 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/hdf5-2.1.0-nompi_hc95e3eb_104.conda - sha256: 5b96accf983be97718fbfaddd6706591d7ef6511b4ccdac8a09f6b9899d1b284 - md5: e5390fd4a3b964a3ed619480df918294 + size: 4713397 + timestamp: 1777861887131 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/hdf5-2.1.0-nompi_he586413_105.conda + sha256: 518237c7e1e1f1f2d98f0283a483571b2d62c5c71b455a0ad0f0cc40087bb939 + md5: deb297adb6083474bb8b75b92172fb95 depends: - __osx >=11.0 - aws-c-auth >=0.10.1,<0.10.2.0a0 - aws-c-common >=0.12.6,<0.12.7.0a0 - - aws-c-http >=0.10.12,<0.10.13.0a0 + - aws-c-http >=0.10.13,<0.10.14.0a0 - aws-c-io >=0.26.3,<0.26.4.0a0 - - aws-c-s3 >=0.11.5,<0.11.6.0a0 + - aws-c-s3 >=0.12.2,<0.12.3.0a0 - aws-c-sdkutils >=0.2.4,<0.2.5.0a0 - libaec >=1.1.5,<2.0a0 - - libcurl >=8.19.0,<9.0a0 + - libcurl >=8.20.0,<9.0a0 - libcxx >=19 - libgfortran - libgfortran5 >=14.3.0 - libzlib >=1.3.2,<2.0a0 - - openssl >=3.5.5,<4.0a0 + - openssl >=3.5.6,<4.0a0 license: BSD-3-Clause license_family: BSD purls: [] - size: 3418702 - timestamp: 1775244340092 -- conda: https://conda.anaconda.org/conda-forge/win-64/hdf5-2.1.0-nompi_hd96b29f_104.conda - sha256: ad660bf000e2a905ebdc8c297d9b3851ac48834284b673e655adda490425f652 - md5: 37c1890c40a1514fa92ba13e27d5b1c3 + size: 3319915 + timestamp: 1777861894583 +- conda: https://conda.anaconda.org/conda-forge/win-64/hdf5-2.1.0-nompi_h0a39f1e_105.conda + sha256: 2f2d49ccf163a4bdf556662fb2949bdf408940e2db67a2d15be2d8be247b6e43 + md5: d5850b9e97b9a577441067628fb8d573 depends: - aws-c-auth >=0.10.1,<0.10.2.0a0 - aws-c-common >=0.12.6,<0.12.7.0a0 - - aws-c-http >=0.10.12,<0.10.13.0a0 + - aws-c-http >=0.10.13,<0.10.14.0a0 - aws-c-io >=0.26.3,<0.26.4.0a0 - - aws-c-s3 >=0.11.5,<0.11.6.0a0 + - aws-c-s3 >=0.12.2,<0.12.3.0a0 - aws-c-sdkutils >=0.2.4,<0.2.5.0a0 - libaec >=1.1.5,<2.0a0 - - libcurl >=8.19.0,<9.0a0 + - libcurl >=8.20.0,<9.0a0 - libzlib >=1.3.2,<2.0a0 - - openssl >=3.5.5,<4.0a0 + - openssl >=3.5.6,<4.0a0 - ucrt >=10.0.20348.0 - vc >=14.3,<15 - vc14_runtime >=14.44.35208 license: BSD-3-Clause license_family: BSD purls: [] - size: 2564561 - timestamp: 1775244102272 + size: 2599543 + timestamp: 1777861984545 - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda sha256: 6ad78a180576c706aabeb5b4c8ceb97c0cb25f1e112d76495bff23e3779948ba md5: 0a802cb9888dd14eeefc611f05c40b6e @@ -8461,8 +8461,9 @@ packages: - python >=3.10 - python license: BSD-3-Clause + license_family: BSD purls: - - pkg:pypi/idna?source=compressed-mapping + - pkg:pypi/idna?source=hash-mapping size: 59038 timestamp: 1776947141407 - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.8.0-pyhcf101f3_0.conda @@ -8475,7 +8476,7 @@ packages: license: Apache-2.0 license_family: APACHE purls: - - pkg:pypi/importlib-metadata?source=compressed-mapping + - pkg:pypi/importlib-metadata?source=hash-mapping size: 34387 timestamp: 1773931568510 - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.3.0-pyhd8ed1ab_0.conda @@ -8486,7 +8487,7 @@ packages: license: MIT license_family: MIT purls: - - pkg:pypi/iniconfig?source=compressed-mapping + - pkg:pypi/iniconfig?source=hash-mapping size: 13387 timestamp: 1760831448842 - conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-7.2.0-pyh5552912_1.conda @@ -8571,50 +8572,54 @@ packages: - pkg:pypi/ipykernel?source=hash-mapping size: 133644 timestamp: 1770566133040 -- conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.12.0-pyhccfa634_0.conda - sha256: a0d3e4c8e4d7b3801377a03de32951f68d77dd1bfe25082c7915f4e6b0aaa463 - md5: 3734e3b6618ea6e04ad08678d8ed7a45 +- conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.13.0-pyh53cf698_0.conda + sha256: a0af49948a1842dfd15a0b0b2fd56c94ddbd07e07a6c8b4bc70d43015eafaff0 + md5: 73e9657cd19605740d21efb14d8d0cb9 depends: - - __win + - __unix - decorator >=5.1.0 - ipython_pygments_lexers >=1.0.0 - jedi >=0.18.2 - matplotlib-inline >=0.1.6 - prompt-toolkit >=3.0.41,<3.1.0 + - psutil >=7 - pygments >=2.14.0 - - python >=3.12 + - python >=3.11 - stack_data >=0.6.0 - traitlets >=5.13.0 - - colorama >=0.4.4 + - typing_extensions >=4.6 + - pexpect >4.6 - python license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/ipython?source=compressed-mapping - size: 648954 - timestamp: 1774610078420 -- conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.12.0-pyhecfbec7_0.conda - sha256: 932044bd893f7adce6c9b384b96a72fd3804cc381e76789398c2fae900f21df7 - md5: b293210beb192c3024683bf6a998a0b8 + - pkg:pypi/ipython?source=hash-mapping + size: 651632 + timestamp: 1777038396606 +- conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.13.0-pyhe2676ad_0.conda + sha256: f252ec33597115ff21cbb31051f6f9be34ca36cbbbf3d266b597660d8d8edde9 + md5: 5631ab99e902463d9dd4221e5b4eab6d depends: - - __unix + - __win - decorator >=5.1.0 - ipython_pygments_lexers >=1.0.0 - jedi >=0.18.2 - matplotlib-inline >=0.1.6 - prompt-toolkit >=3.0.41,<3.1.0 + - psutil >=7 - pygments >=2.14.0 - - python >=3.12 + - python >=3.11 - stack_data >=0.6.0 - traitlets >=5.13.0 - - pexpect >4.6 + - typing_extensions >=4.6 + - colorama >=0.4.4 - python license: BSD-3-Clause license_family: BSD purls: - pkg:pypi/ipython?source=hash-mapping - size: 649967 - timestamp: 1774609994657 + size: 650593 + timestamp: 1777038425499 - conda: https://conda.anaconda.org/conda-forge/noarch/ipython_pygments_lexers-1.1.1-pyhd8ed1ab_0.conda sha256: 894682a42a7d659ae12878dbcb274516a7031bbea9104e92f8e88c1f2765a104 md5: bd80ba060603cc228d9d81c257093119 @@ -8801,7 +8806,7 @@ packages: license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/jinja2?source=compressed-mapping + - pkg:pypi/jinja2?source=hash-mapping size: 120685 timestamp: 1764517220861 - pypi: https://files.pythonhosted.org/packages/7b/91/984aca2ec129e2757d1e4e3c81c3fcda9d0f85b74670a094cc443d9ee949/joblib-1.5.3-py3-none-any.whl @@ -8817,7 +8822,7 @@ packages: license: Apache-2.0 license_family: APACHE purls: - - pkg:pypi/json5?source=compressed-mapping + - pkg:pypi/json5?source=hash-mapping size: 34731 timestamp: 1774655440045 - conda: https://conda.anaconda.org/conda-forge/noarch/jsonpointer-3.1.1-pyhcf101f3_0.conda @@ -8880,9 +8885,9 @@ packages: purls: [] size: 4740 timestamp: 1767839954258 -- conda: https://conda.anaconda.org/conda-forge/noarch/jupyter-book-2.1.4-pyhcf101f3_0.conda - sha256: 19933f74b3dd88aaafb4ee0ee2051c9c1b2db9c77862731a381bc59c800596b4 - md5: 6466d205c69ad4f33ac9100a93af55b5 +- conda: https://conda.anaconda.org/conda-forge/noarch/jupyter-book-2.1.5-pyhcf101f3_0.conda + sha256: 398ebf0ec7a445ff4a423d4cd322d8307167adbe304bc7012cf4fb5a351daf68 + md5: 59d77abab8772f3f677bf71ad0af0ddc depends: - ipykernel - jupyter_core @@ -8895,8 +8900,8 @@ packages: license_family: BSD purls: - pkg:pypi/jupyter-book?source=hash-mapping - size: 2179593 - timestamp: 1775073480141 + size: 2179874 + timestamp: 1777706555599 - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter-lsp-2.3.1-pyhcf101f3_0.conda sha256: 3766e2ae59641c172cec8a821528bfa6bf9543ffaaeb8b358bfd5259dcf18e4e md5: 0c3b465ceee138b9c39279cc02e5c4a0 @@ -8925,7 +8930,7 @@ packages: license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/jupyter-client?source=compressed-mapping + - pkg:pypi/jupyter-client?source=hash-mapping size: 112785 timestamp: 1767954655912 - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_core-5.9.1-pyh6dadd2b_0.conda @@ -8979,13 +8984,14 @@ packages: - traitlets >=5.3 - python license: BSD-3-Clause + license_family: BSD purls: - - pkg:pypi/jupyter-events?source=compressed-mapping + - pkg:pypi/jupyter-events?source=hash-mapping size: 24002 timestamp: 1776861872237 -- conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.17.0-pyhcf101f3_0.conda - sha256: 74c4e642be97c538dae1895f7052599dfd740d8bd251f727bce6453ce8d6cd9a - md5: d79a87dcfa726bcea8e61275feed6f83 +- conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.18.2-pyhcf101f3_0.conda + sha256: 04fb8ea7749f67abaf76df6257bf86688e1389ceed55eb4fb0176fd2e882dbd6 + md5: 5ee7945accf0f215ddd6055d25d7cd83 depends: - anyio >=3.1.0 - argon2-cffi >=21.1 @@ -9011,8 +9017,8 @@ packages: license_family: BSD purls: - pkg:pypi/jupyter-server?source=hash-mapping - size: 347094 - timestamp: 1755870522134 + size: 360522 + timestamp: 1778060967727 - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server_terminals-0.5.4-pyhcf101f3_0.conda sha256: 5eda79ed9f53f590031d29346abd183051263227dd9ee667b5ca1133ce297654 md5: 7b8bace4943e0dc345fc45938826f2b8 @@ -9026,9 +9032,9 @@ packages: - pkg:pypi/jupyter-server-terminals?source=hash-mapping size: 22052 timestamp: 1768574057200 -- conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.5.6-pyhd8ed1ab_0.conda - sha256: 436a70259a9b4e13ce8b15faa8b37342835954d77a0a74d21dd24547e0871088 - md5: bcbb401d6fa84e0cee34d4926b0e9e93 +- conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.5.7-pyhd8ed1ab_0.conda + sha256: b85befad5ba1f50c0cc042a2ffb26441d13ffc2f18572dc20d3541476da0c7b9 + md5: 2ffe77234070324e763a6eddabb5f467 depends: - async-lru >=1.0.0 - httpx >=0.25.0,<1 @@ -9049,8 +9055,8 @@ packages: license_family: BSD purls: - pkg:pypi/jupyterlab?source=hash-mapping - size: 8245973 - timestamp: 1773240966438 + size: 8861204 + timestamp: 1777483115382 - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_pygments-0.3.0-pyhd8ed1ab_2.conda sha256: dc24b900742fdaf1e077d9a3458fd865711de80bca95fe3c6d46610c532c6ef0 md5: fd312693df06da3578383232528c468d @@ -9203,36 +9209,36 @@ packages: - pkg:pypi/lark?source=hash-mapping size: 94312 timestamp: 1761596921009 -- conda: https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.18-h0c24ade_0.conda - sha256: 836ec4b895352110335b9fdcfa83a8dcdbe6c5fb7c06c4929130600caea91c0a - md5: 6f2e2c8f58160147c4d1c6f4c14cbac4 +- conda: https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.19.1-h0c24ade_0.conda + sha256: eb89c6c39f2f6a93db55723dbb2f6bba8c8e63e6312bf1abf13e6e9ff45849c8 + md5: f92f984b558e6e6204014b16d212b271 depends: - __glibc >=2.17,<3.0.a0 - libgcc >=14 - - libjpeg-turbo >=3.1.2,<4.0a0 + - libjpeg-turbo >=3.1.4.1,<4.0a0 - libtiff >=4.7.1,<4.8.0a0 license: MIT license_family: MIT purls: [] - size: 249959 - timestamp: 1768184673131 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/lcms2-2.18-hdfa7624_0.conda - sha256: d768da024ab74a4b30642401877fa914a68bdc238667f16b1ec2e0e98b2451a6 - md5: 6631a7bd2335bb9699b1dbc234b19784 + size: 251086 + timestamp: 1778079286384 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/lcms2-2.19.1-hdfa7624_0.conda + sha256: d589ff5294e42576563b22bdea0860cb80b0cbe0f3852836eddaadedf6eec4ef + md5: e5ba982008c0ac1a1c0154617371bab5 depends: - __osx >=11.0 - - libjpeg-turbo >=3.1.2,<4.0a0 + - libjpeg-turbo >=3.1.4.1,<4.0a0 - libtiff >=4.7.1,<4.8.0a0 license: MIT license_family: MIT purls: [] - size: 211756 - timestamp: 1768184994800 -- conda: https://conda.anaconda.org/conda-forge/win-64/lcms2-2.18-hf2c6c5f_0.conda - sha256: 7eeb18c5c86db146b62da66d9e8b0e753a52987f9134a494309588bbeceddf28 - md5: b6c68d6b829b044cd17a41e0a8a23ca1 + size: 212998 + timestamp: 1778079809873 +- conda: https://conda.anaconda.org/conda-forge/win-64/lcms2-2.19.1-hf2c6c5f_0.conda + sha256: 57ecd32470a2607db238e631cda6d160ad65451715065fc4449acb11fe48fe28 + md5: 29f2c366a0da954bafd69a0d549c0ab3 depends: - - libjpeg-turbo >=3.1.2,<4.0a0 + - libjpeg-turbo >=3.1.4.1,<4.0a0 - libtiff >=4.7.1,<4.8.0a0 - ucrt >=10.0.20348.0 - vc >=14.3,<15 @@ -9240,8 +9246,8 @@ packages: license: MIT license_family: MIT purls: [] - size: 522238 - timestamp: 1768184858107 + size: 523813 + timestamp: 1778079433472 - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45.1-default_hbd61a6d_102.conda sha256: 3d584956604909ff5df353767f3a2a2f60e07d070b328d109f30ac40cd62df6c md5: 18335a698559cdbcd86150a48bf54ba6 @@ -9369,12 +9375,13 @@ packages: purls: [] size: 34463 timestamp: 1769221960556 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-24.0.0-ha7f89c6_0_cpu.conda - sha256: 0117df14b22d795ee56d5fc08ceabc14e12fa9c93d64979e3616260225ed30ca - md5: 8aeb79715524b48267068fb0fd185956 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-24.0.0-h0935d00_1_cpu.conda + build_number: 1 + sha256: d2325979993c71580e571eaa470e0ca33b86acb23c653909fecaef688a1c44b4 + md5: aed984d45692d6211ebf013b62c9fa02 depends: - __glibc >=2.17,<3.0.a0 - - aws-crt-cpp >=0.37.4,<0.37.5.0a0 + - aws-crt-cpp >=0.38.3,<0.38.4.0a0 - aws-sdk-cpp >=1.11.747,<1.11.748.0a0 - azure-core-cpp >=1.16.2,<1.16.3.0a0 - azure-identity-cpp >=1.13.3,<1.13.4.0a0 @@ -9402,15 +9409,17 @@ packages: - arrow-cpp <0.0a0 - parquet-cpp <0.0a0 license: Apache-2.0 + license_family: APACHE purls: [] - size: 6496823 - timestamp: 1776815770895 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libarrow-24.0.0-h2124f06_0_cpu.conda - sha256: 58715feab991b36d659a3432faf41abaea6a3320e184b63b7d0961e86c6598fe - md5: 1b5a248fdad0e0744b512a5590bfb090 + size: 6508876 + timestamp: 1778175634414 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libarrow-24.0.0-h37fbca7_1_cpu.conda + build_number: 1 + sha256: 814e775718a3ccafcbcd704b11dc402374513ec6f66241780ff7ffbe7f2ffcda + md5: 9efaddf61a69aeb93cff572fed6baccc depends: - __osx >=11.0 - - aws-crt-cpp >=0.37.4,<0.37.5.0a0 + - aws-crt-cpp >=0.38.3,<0.38.4.0a0 - aws-sdk-cpp >=1.11.747,<1.11.748.0a0 - azure-core-cpp >=1.16.2,<1.16.3.0a0 - azure-identity-cpp >=1.13.3,<1.13.4.0a0 @@ -9433,18 +9442,20 @@ packages: - snappy >=1.2.2,<1.3.0a0 - zstd >=1.5.7,<1.6.0a0 constrains: - - arrow-cpp <0.0a0 - - apache-arrow-proc =*=cpu - parquet-cpp <0.0a0 + - apache-arrow-proc =*=cpu + - arrow-cpp <0.0a0 license: Apache-2.0 + license_family: APACHE purls: [] - size: 4267842 - timestamp: 1776815214765 -- conda: https://conda.anaconda.org/conda-forge/win-64/libarrow-24.0.0-hc74aee5_0_cpu.conda - sha256: 40391ae880c7bb101b0774fbaba62410dec5984d9f3527fe40798114c3de37a5 - md5: b5a964c5cafa1663a84bec0cccbd03db + size: 4239511 + timestamp: 1778174861358 +- conda: https://conda.anaconda.org/conda-forge/win-64/libarrow-24.0.0-h37f918f_1_cpu.conda + build_number: 1 + sha256: 35b64339ff3f2c8cfe19424dafbd23071c48ddf76dbab1ee2fb52f76437f9333 + md5: db9503de7e87d1e986a2ca1b1f7179b1 depends: - - aws-crt-cpp >=0.37.4,<0.37.5.0a0 + - aws-crt-cpp >=0.38.3,<0.38.4.0a0 - aws-sdk-cpp >=1.11.747,<1.11.748.0a0 - azure-core-cpp >=1.16.2,<1.16.3.0a0 - azure-identity-cpp >=1.13.3,<1.13.4.0a0 @@ -9456,7 +9467,7 @@ packages: - libbrotlidec >=1.2.0,<1.3.0a0 - libbrotlienc >=1.2.0,<1.3.0a0 - libcrc32c >=1.1.2,<1.2.0a0 - - libcurl >=8.19.0,<9.0a0 + - libcurl >=8.20.0,<9.0a0 - libgoogle-cloud >=3.3.0,<3.4.0a0 - libgoogle-cloud-storage >=3.3.0,<3.4.0a0 - libprotobuf >=6.33.5,<6.33.6.0a0 @@ -9473,74 +9484,84 @@ packages: - apache-arrow-proc =*=cpu - arrow-cpp <0.0a0 license: Apache-2.0 + license_family: APACHE purls: [] - size: 4339700 - timestamp: 1776819462924 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-acero-24.0.0-h635bf11_0_cpu.conda - sha256: a46a6264bea99b9980dfd469922a43a6ad8ed579b5d58c5236811b2893082ba7 - md5: 178d7e3f5c392e606ccd0aaff4331019 + size: 4294275 + timestamp: 1778179333251 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-acero-24.0.0-h635bf11_1_cpu.conda + build_number: 1 + sha256: d5e2ca1557393490eb0b921d0dabe6203c685da97c0cf8c5b15c21c2d69b517a + md5: fa76d2ed4b435617a0fe5b8e7b9ae9c1 depends: - __glibc >=2.17,<3.0.a0 - - libarrow 24.0.0 ha7f89c6_0_cpu - - libarrow-compute 24.0.0 h53684a4_0_cpu + - libarrow 24.0.0 h0935d00_1_cpu + - libarrow-compute 24.0.0 h53684a4_1_cpu - libgcc >=14 - libstdcxx >=14 license: Apache-2.0 + license_family: APACHE purls: [] - size: 592885 - timestamp: 1776816000408 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libarrow-acero-24.0.0-hee8fe31_0_cpu.conda - sha256: 5a84b718bab4f4a494731a29ccc888f5d6f65b5fe9c6f720c0f1931c4af7c431 - md5: 330879d2d6e847716d7dcaa748a915fb + size: 591773 + timestamp: 1778175876713 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libarrow-acero-24.0.0-hee8fe31_1_cpu.conda + build_number: 1 + sha256: 4d0f25e14c02a08a9843bf7cb9af8fe00772151ac95a93809482aa7e1002c0ea + md5: 4c849c657fd2f7676c6935a17d9a6c04 depends: - __osx >=11.0 - libabseil * cxx17* - libabseil >=20260107.1,<20260108.0a0 - - libarrow 24.0.0 h2124f06_0_cpu - - libarrow-compute 24.0.0 h3b6a98a_0_cpu + - libarrow 24.0.0 h37fbca7_1_cpu + - libarrow-compute 24.0.0 h3b6a98a_1_cpu - libcxx >=21 - libopentelemetry-cpp >=1.26.0,<1.27.0a0 - libprotobuf >=6.33.5,<6.33.6.0a0 license: Apache-2.0 + license_family: APACHE purls: [] - size: 520298 - timestamp: 1776815770472 -- conda: https://conda.anaconda.org/conda-forge/win-64/libarrow-acero-24.0.0-h7d8d6a5_0_cpu.conda - sha256: e07761db31c034dd1e80007d59e22859615ef4a263f2bf987f38aabbea77a17c - md5: 15b51e64c379f24f69dd93cace3525e8 + size: 519410 + timestamp: 1778175198375 +- conda: https://conda.anaconda.org/conda-forge/win-64/libarrow-acero-24.0.0-h7d8d6a5_1_cpu.conda + build_number: 1 + sha256: f0651a93459453ad646db770a5739517e3da31d5ceff6fb6f9adc20da9e3e15d + md5: bf58b68d1923ed120e181a2b3529c638 depends: - - libarrow 24.0.0 hc74aee5_0_cpu - - libarrow-compute 24.0.0 h081cd8e_0_cpu + - libarrow 24.0.0 h37f918f_1_cpu + - libarrow-compute 24.0.0 h081cd8e_1_cpu - ucrt >=10.0.20348.0 - vc >=14.3,<15 - vc14_runtime >=14.44.35208 license: Apache-2.0 + license_family: APACHE purls: [] - size: 447020 - timestamp: 1776819781866 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-compute-24.0.0-h53684a4_0_cpu.conda - sha256: 24886c26b36267d706be56c06bd89b1692e3da0e3099bdc6bd5cecd042cca606 - md5: 73e0aeaa603ff40128e75435a3c5ac77 + size: 447154 + timestamp: 1778179585769 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-compute-24.0.0-h53684a4_1_cpu.conda + build_number: 1 + sha256: e4ca855698a8005508114a8c197ae7fe48ea37430064253a2055dbb0122f8a39 + md5: 0aac1926c3b2f8c35570af6be677f8ad depends: - __glibc >=2.17,<3.0.a0 - - libarrow 24.0.0 ha7f89c6_0_cpu + - libarrow 24.0.0 h0935d00_1_cpu - libgcc >=14 - libre2-11 >=2025.11.5 - libstdcxx >=14 - libutf8proc >=2.11.3,<2.12.0a0 - re2 license: Apache-2.0 + license_family: APACHE purls: [] - size: 2992194 - timestamp: 1776815851047 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libarrow-compute-24.0.0-h3b6a98a_0_cpu.conda - sha256: 47062ecfaa022f038623a25e69d3ced60aafbd46c4997ae718cb1e3aa478311f - md5: beced1053d628ad812ddcc975fa8a929 + size: 2992759 + timestamp: 1778175759450 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libarrow-compute-24.0.0-h3b6a98a_1_cpu.conda + build_number: 1 + sha256: 04076544c1797753b4ba145a68727bf68827591de9870867bac5e4e7ca39a829 + md5: 548f34b1374e772de97cdba8774c5f58 depends: - __osx >=11.0 - libabseil * cxx17* - libabseil >=20260107.1,<20260108.0a0 - - libarrow 24.0.0 h2124f06_0_cpu + - libarrow 24.0.0 h37fbca7_1_cpu - libcxx >=21 - libopentelemetry-cpp >=1.26.0,<1.27.0a0 - libprotobuf >=6.33.5,<6.33.6.0a0 @@ -9548,14 +9569,16 @@ packages: - libutf8proc >=2.11.3,<2.12.0a0 - re2 license: Apache-2.0 + license_family: APACHE purls: [] - size: 2241493 - timestamp: 1776815413056 -- conda: https://conda.anaconda.org/conda-forge/win-64/libarrow-compute-24.0.0-h081cd8e_0_cpu.conda - sha256: 93171fdab7cf3c8b52ef1d3b897713bb8b820130bd3a8250c9bd5e4e3ecf70bb - md5: bd6eafa1d7f8b0cc288f007cf8de0555 + size: 2243159 + timestamp: 1778174967068 +- conda: https://conda.anaconda.org/conda-forge/win-64/libarrow-compute-24.0.0-h081cd8e_1_cpu.conda + build_number: 1 + sha256: 5d6821e62bd747f660ec9814d72aa6b47096e50f5f45334dba0eee037cd41695 + md5: 783540ebc553149b52eee6a5a363fd07 depends: - - libarrow 24.0.0 hc74aee5_0_cpu + - libarrow 24.0.0 h37f918f_1_cpu - libre2-11 >=2025.11.5 - libutf8proc >=2.11.3,<2.12.0a0 - re2 @@ -9563,159 +9586,172 @@ packages: - vc >=14.3,<15 - vc14_runtime >=14.44.35208 license: Apache-2.0 + license_family: APACHE purls: [] - size: 1753471 - timestamp: 1776819564791 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-dataset-24.0.0-h635bf11_0_cpu.conda - sha256: 37293ee47f819072805e7e23d3ed15e7f1baccfacb2080ed360a96e208d930b2 - md5: dc226b80ae51753ce2bd8193dcc42a88 + size: 1757536 + timestamp: 1778179415666 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-dataset-24.0.0-h635bf11_1_cpu.conda + build_number: 1 + sha256: 38ce55721b4d2cc776d0e34078ccba63dfd5c141f1f49bb41cd6ae4da10c21e4 + md5: 021214e64486a6ba4df95d64b703f1fb depends: - __glibc >=2.17,<3.0.a0 - - libarrow 24.0.0 ha7f89c6_0_cpu - - libarrow-acero 24.0.0 h635bf11_0_cpu - - libarrow-compute 24.0.0 h53684a4_0_cpu + - libarrow 24.0.0 h0935d00_1_cpu + - libarrow-acero 24.0.0 h635bf11_1_cpu + - libarrow-compute 24.0.0 h53684a4_1_cpu - libgcc >=14 - - libparquet 24.0.0 h7376487_0_cpu + - libparquet 24.0.0 h7376487_1_cpu - libstdcxx >=14 license: Apache-2.0 + license_family: APACHE purls: [] - size: 591654 - timestamp: 1776816104463 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libarrow-dataset-24.0.0-hee8fe31_0_cpu.conda - sha256: 228d9d7c05e0c9c9d94f192649d8345dbea77680f6674d0db25f22645e7f0d77 - md5: f75c7393899ad279c62c94089aacf760 + size: 591861 + timestamp: 1778175957189 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libarrow-dataset-24.0.0-hee8fe31_1_cpu.conda + build_number: 1 + sha256: 2557536377f7e3ae986e47b3584b53ca148d91f6d1b836f3371ff56b15082379 + md5: bfcfc8dc98740cd7577cc25933ce6a62 depends: - __osx >=11.0 - libabseil * cxx17* - libabseil >=20260107.1,<20260108.0a0 - - libarrow 24.0.0 h2124f06_0_cpu - - libarrow-acero 24.0.0 hee8fe31_0_cpu - - libarrow-compute 24.0.0 h3b6a98a_0_cpu + - libarrow 24.0.0 h37fbca7_1_cpu + - libarrow-acero 24.0.0 hee8fe31_1_cpu + - libarrow-compute 24.0.0 h3b6a98a_1_cpu - libcxx >=21 - libopentelemetry-cpp >=1.26.0,<1.27.0a0 - - libparquet 24.0.0 h16c0493_0_cpu + - libparquet 24.0.0 h16c0493_1_cpu - libprotobuf >=6.33.5,<6.33.6.0a0 license: Apache-2.0 + license_family: APACHE purls: [] - size: 520380 - timestamp: 1776816021240 -- conda: https://conda.anaconda.org/conda-forge/win-64/libarrow-dataset-24.0.0-h7d8d6a5_0_cpu.conda - sha256: 40ca580f478140a9d2a73e0d7307ce2b9e8666b5131bd053ded411cd6b575aea - md5: 72c2b0d37627da3386a0e7446b79008f + size: 519773 + timestamp: 1778175399688 +- conda: https://conda.anaconda.org/conda-forge/win-64/libarrow-dataset-24.0.0-h7d8d6a5_1_cpu.conda + build_number: 1 + sha256: c2556c2695ab5f38a8d0732a5f70b72611305aea175553efcb0563a58a913b6d + md5: 85086a57de14a61242ee23f6527c1a71 depends: - - libarrow 24.0.0 hc74aee5_0_cpu - - libarrow-acero 24.0.0 h7d8d6a5_0_cpu - - libarrow-compute 24.0.0 h081cd8e_0_cpu - - libparquet 24.0.0 h7051d1f_0_cpu + - libarrow 24.0.0 h37f918f_1_cpu + - libarrow-acero 24.0.0 h7d8d6a5_1_cpu + - libarrow-compute 24.0.0 h081cd8e_1_cpu + - libparquet 24.0.0 h7051d1f_1_cpu - ucrt >=10.0.20348.0 - vc >=14.3,<15 - vc14_runtime >=14.44.35208 license: Apache-2.0 + license_family: APACHE purls: [] - size: 428602 - timestamp: 1776819923347 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-substrait-24.0.0-hb4dd7c2_0_cpu.conda - sha256: ea628348819cbccf73b0440071c437e33db05e01fd9c7d4f6a0268c0217c12fc - md5: cb5a9557a2ffa1b18b4e05621354c6bd + size: 429162 + timestamp: 1778179693804 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-substrait-24.0.0-hb4dd7c2_1_cpu.conda + build_number: 1 + sha256: 9479a863e61e5cc3e3ef395267f23dc1475199f53a96c0d04a168f4b0c97db2a + md5: e3e42803a838c2177759e6aef1363512 depends: - __glibc >=2.17,<3.0.a0 - libabseil * cxx17* - libabseil >=20260107.1,<20260108.0a0 - - libarrow 24.0.0 ha7f89c6_0_cpu - - libarrow-acero 24.0.0 h635bf11_0_cpu - - libarrow-dataset 24.0.0 h635bf11_0_cpu + - libarrow 24.0.0 h0935d00_1_cpu + - libarrow-acero 24.0.0 h635bf11_1_cpu + - libarrow-dataset 24.0.0 h635bf11_1_cpu - libgcc >=14 - libprotobuf >=6.33.5,<6.33.6.0a0 - libstdcxx >=14 license: Apache-2.0 + license_family: APACHE purls: [] - size: 502184 - timestamp: 1776816139283 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libarrow-substrait-24.0.0-h05be00f_0_cpu.conda - sha256: a25d817ddb317ac5c2bb352f48c5f38a5de2a9b4e148047641de3e285280337b - md5: 7e1ca98358e41e10f187a8b125523525 + size: 501879 + timestamp: 1778175984173 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libarrow-substrait-24.0.0-h05be00f_1_cpu.conda + build_number: 1 + sha256: 9ba4cd38cfb7d2830b0e6905a2bfa6dd6c435d81ec3f923dc664b45b91cc742b + md5: e9d4414f2487505ea94cbb0852aa0c51 depends: - __osx >=11.0 - libabseil * cxx17* - libabseil >=20260107.1,<20260108.0a0 - - libarrow 24.0.0 h2124f06_0_cpu - - libarrow-acero 24.0.0 hee8fe31_0_cpu - - libarrow-dataset 24.0.0 hee8fe31_0_cpu + - libarrow 24.0.0 h37fbca7_1_cpu + - libarrow-acero 24.0.0 hee8fe31_1_cpu + - libarrow-dataset 24.0.0 hee8fe31_1_cpu - libcxx >=21 - libprotobuf >=6.33.5,<6.33.6.0a0 license: Apache-2.0 + license_family: APACHE purls: [] - size: 454733 - timestamp: 1776816138758 -- conda: https://conda.anaconda.org/conda-forge/win-64/libarrow-substrait-24.0.0-h524e9bd_0_cpu.conda - sha256: 569f9e5853bdf3cd1660d121595662e4852e6b157f02b73a3057ac6fe2208000 - md5: 913460f9f04cc084d326473c52f4df2d + size: 455365 + timestamp: 1778175475107 +- conda: https://conda.anaconda.org/conda-forge/win-64/libarrow-substrait-24.0.0-h524e9bd_1_cpu.conda + build_number: 1 + sha256: e5a7a0d8ea0735a96f0927343d668258db64934ae0eb321078f751803cf660ed + md5: e4baea828629bbc187e06305f76ca938 depends: - libabseil * cxx17* - libabseil >=20260107.1,<20260108.0a0 - - libarrow 24.0.0 hc74aee5_0_cpu - - libarrow-acero 24.0.0 h7d8d6a5_0_cpu - - libarrow-dataset 24.0.0 h7d8d6a5_0_cpu + - libarrow 24.0.0 h37f918f_1_cpu + - libarrow-acero 24.0.0 h7d8d6a5_1_cpu + - libarrow-dataset 24.0.0 h7d8d6a5_1_cpu - libprotobuf >=6.33.5,<6.33.6.0a0 - ucrt >=10.0.20348.0 - vc >=14.3,<15 - vc14_runtime >=14.44.35208 license: Apache-2.0 + license_family: APACHE purls: [] - size: 362008 - timestamp: 1776819969211 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.11.0-6_h4a7cf45_openblas.conda - build_number: 6 - sha256: 7bfe936dbb5db04820cf300a9cc1f5ee8d5302fc896c2d66e30f1ee2f20fbfd6 - md5: 6d6d225559bfa6e2f3c90ee9c03d4e2e + size: 362015 + timestamp: 1778179727388 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.11.0-7_h4a7cf45_openblas.conda + build_number: 7 + sha256: 081c850f99bc355821fac9c6e3727d40b3f8ce3beb50a5437cf03726b611ff39 + md5: 955b44e8b00b7f7ef4ce0130cef12394 depends: - - libopenblas >=0.3.32,<0.3.33.0a0 - - libopenblas >=0.3.32,<1.0a0 + - libopenblas >=0.3.33,<0.3.34.0a0 + - libopenblas >=0.3.33,<1.0a0 constrains: - - blas 2.306 openblas - - liblapack 3.11.0 6*_openblas - - liblapacke 3.11.0 6*_openblas - - libcblas 3.11.0 6*_openblas - - mkl <2026 + - libcblas 3.11.0 7*_openblas + - blas 2.307 openblas + - liblapack 3.11.0 7*_openblas + - liblapacke 3.11.0 7*_openblas + - mkl <2027 license: BSD-3-Clause license_family: BSD purls: [] - size: 18621 - timestamp: 1774503034895 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libblas-3.11.0-6_h51639a9_openblas.conda - build_number: 6 - sha256: 979227fc03628925037ab2dfda008eb7b5592644d9c2c21dd285cefe8c42553d - md5: e551103471911260488a02155cef9c94 + size: 18716 + timestamp: 1778489854108 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libblas-3.11.0-7_h51639a9_openblas.conda + build_number: 7 + sha256: 662935bfb93d2d097e26e273a3a2f504a9f833f64aa6f9e295b577655478c39b + md5: ab6670d099d19fe70cb9efb88a1b5f78 depends: - - libopenblas >=0.3.32,<0.3.33.0a0 - - libopenblas >=0.3.32,<1.0a0 + - libopenblas >=0.3.33,<0.3.34.0a0 + - libopenblas >=0.3.33,<1.0a0 constrains: - - liblapacke 3.11.0 6*_openblas - - liblapack 3.11.0 6*_openblas - - blas 2.306 openblas - - libcblas 3.11.0 6*_openblas - - mkl <2026 + - libcblas 3.11.0 7*_openblas + - blas 2.307 openblas + - mkl <2027 + - liblapack 3.11.0 7*_openblas + - liblapacke 3.11.0 7*_openblas license: BSD-3-Clause license_family: BSD purls: [] - size: 18859 - timestamp: 1774504387211 -- conda: https://conda.anaconda.org/conda-forge/win-64/libblas-3.11.0-6_hf2e6a31_mkl.conda - build_number: 6 - sha256: 10c8054f007adca8c780cd8bb9335fa5d990f0494b825158d3157983a25b1ea2 - md5: 95543eec964b4a4a7ca3c4c9be481aa1 + size: 18783 + timestamp: 1778489983152 +- conda: https://conda.anaconda.org/conda-forge/win-64/libblas-3.11.0-7_h8455456_mkl.conda + build_number: 7 + sha256: 9eec27eee4300284e62a61cb2298089c80d31f6f9e924eeabc06e9788a00cffe + md5: 269e54b44974ff48846b4c31d0397041 depends: - - mkl >=2025.3.1,<2026.0a0 + - mkl >=2026.0.0,<2027.0a0 constrains: - - blas 2.306 mkl - - liblapacke 3.11.0 6*_mkl - - liblapack 3.11.0 6*_mkl - - libcblas 3.11.0 6*_mkl + - blas 2.307 mkl + - libcblas 3.11.0 7*_mkl + - liblapacke 3.11.0 7*_mkl + - liblapack 3.11.0 7*_mkl license: BSD-3-Clause license_family: BSD purls: [] - size: 68082 - timestamp: 1774503684284 + size: 68060 + timestamp: 1778490352569 - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.2.0-hb03c661_1.conda sha256: 318f36bd49ca8ad85e6478bd8506c88d82454cc008c1ac1c6bf00a3c42fa610e md5: 72c8fd1af66bd67bf580645b426513ed @@ -9821,67 +9857,67 @@ packages: purls: [] size: 252903 timestamp: 1764017901735 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.11.0-6_h0358290_openblas.conda - build_number: 6 - sha256: 57edafa7796f6fa3ebbd5367692dd4c7f552be42109c2dd1a7c89b55089bf374 - md5: 36ae340a916635b97ac8a0655ace2a35 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.11.0-7_h0358290_openblas.conda + build_number: 7 + sha256: 956ae0bb1ec8b0c3663d75b151aceb0521b54e513bf97f621a035f9c87037970 + md5: 0675639dc24cb0032f199e7ff68e4633 depends: - - libblas 3.11.0 6_h4a7cf45_openblas + - libblas 3.11.0 7_h4a7cf45_openblas constrains: - - blas 2.306 openblas - - liblapack 3.11.0 6*_openblas - - liblapacke 3.11.0 6*_openblas + - liblapacke 3.11.0 7*_openblas + - blas 2.307 openblas + - liblapack 3.11.0 7*_openblas license: BSD-3-Clause license_family: BSD purls: [] - size: 18622 - timestamp: 1774503050205 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcblas-3.11.0-6_hb0561ab_openblas.conda - build_number: 6 - sha256: 2e6b3e9b1ab672133b70fc6730e42290e952793f132cb5e72eee22835463eba0 - md5: 805c6d31c5621fd75e53dfcf21fb243a + size: 18675 + timestamp: 1778489861559 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcblas-3.11.0-7_hb0561ab_openblas.conda + build_number: 7 + sha256: 3ac3d27022b3ca8b1980c087e0ede250434f6ed90a4fdc78a8a5ed382bc75505 + md5: 189b373453ec3904095dcb16f502bace depends: - - libblas 3.11.0 6_h51639a9_openblas + - libblas 3.11.0 7_h51639a9_openblas constrains: - - liblapacke 3.11.0 6*_openblas - - blas 2.306 openblas - - liblapack 3.11.0 6*_openblas + - blas 2.307 openblas + - liblapack 3.11.0 7*_openblas + - liblapacke 3.11.0 7*_openblas license: BSD-3-Clause license_family: BSD purls: [] - size: 18863 - timestamp: 1774504433388 -- conda: https://conda.anaconda.org/conda-forge/win-64/libcblas-3.11.0-6_h2a3cdd5_mkl.conda - build_number: 6 - sha256: 02b2a2225f4899c6aaa1dc723e06b3f7a4903d2129988f91fc1527409b07b0a5 - md5: 9e4bf521c07f4d423cba9296b7927e3c + size: 18810 + timestamp: 1778489991330 +- conda: https://conda.anaconda.org/conda-forge/win-64/libcblas-3.11.0-7_h2a3cdd5_mkl.conda + build_number: 7 + sha256: 82da0f854831f783f9d3f1219c4255956e8167a474f3f526151128f02453871c + md5: 4700b7af6acefb26ff0127ba68230941 depends: - - libblas 3.11.0 6_hf2e6a31_mkl + - libblas 3.11.0 7_h8455456_mkl constrains: - - blas 2.306 mkl - - liblapacke 3.11.0 6*_mkl - - liblapack 3.11.0 6*_mkl + - blas 2.307 mkl + - liblapacke 3.11.0 7*_mkl + - liblapack 3.11.0 7*_mkl license: BSD-3-Clause license_family: BSD purls: [] - size: 68221 - timestamp: 1774503722413 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libclang13-22.1.4-default_h746c552_0.conda - sha256: 05830902772b73bb9f0806eb45d43e0c4250452e028069234632db60d7e84793 - md5: 1a39f14c89cf0a54aee5ef6f679f1030 + size: 68594 + timestamp: 1778490364980 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libclang13-22.1.5-default_h746c552_1.conda + sha256: 7f479f796ba05f22324fb484f53da6f9948395499d7558cc4ff5ec24e91448b1 + md5: af8c5fb71cb5aa4861365af2784fc9ce depends: - __glibc >=2.17,<3.0.a0 - libgcc >=14 - - libllvm22 >=22.1.4,<22.2.0a0 + - libllvm22 >=22.1.5,<22.2.0a0 - libstdcxx >=14 license: Apache-2.0 WITH LLVM-exception license_family: Apache purls: [] - size: 12815684 - timestamp: 1776922765965 -- conda: https://conda.anaconda.org/conda-forge/win-64/libclang13-22.1.4-default_ha2db4b5_0.conda - sha256: 24c916b71547377cd1c4fe949a8dd823be22a2e1a0698e56bd95a6fb7df5aaf4 - md5: 8759fc24d708cc4af95af5d86f5441d1 + size: 12827971 + timestamp: 1778479852868 +- conda: https://conda.anaconda.org/conda-forge/win-64/libclang13-22.1.5-default_ha2db4b5_0.conda + sha256: c10dcb835068ced8d0d45dfeb7126f5ff686258c0fbf2cd8ea6de5b629d0c52a + md5: 74229a56cbbfda28f75bed42ac5cacc7 depends: - libzlib >=1.3.2,<2.0a0 - ucrt >=10.0.20348.0 @@ -9891,8 +9927,8 @@ packages: license: Apache-2.0 WITH LLVM-exception license_family: Apache purls: [] - size: 30484843 - timestamp: 1776921661165 + size: 30485424 + timestamp: 1778465527873 - conda: https://conda.anaconda.org/conda-forge/linux-64/libcrc32c-1.1.2-h9c3ff4c_0.tar.bz2 sha256: fd1d153962764433fe6233f34a72cdeed5dcf8a883a85769e8295ce940b5b0c5 md5: c965a5aa0d5c1c37ffc62dff36e28400 @@ -9939,64 +9975,64 @@ packages: purls: [] size: 4518030 timestamp: 1770902209173 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libcurl-8.19.0-hcf29cc6_0.conda - sha256: a0390fd0536ebcd2244e243f5f00ab8e76ab62ed9aa214cd54470fe7496620f4 - md5: d50608c443a30c341c24277d28290f76 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libcurl-8.20.0-hcf29cc6_0.conda + sha256: 75963a5dd913311f59a35dbd307592f4fa754c4808aff9c33edb430c415e38eb + md5: c3cc2864f82a944bc90a7beb4d3b0e88 depends: - __glibc >=2.17,<3.0.a0 - krb5 >=1.22.2,<1.23.0a0 - libgcc >=14 - - libnghttp2 >=1.67.0,<2.0a0 + - libnghttp2 >=1.68.1,<2.0a0 - libssh2 >=1.11.1,<2.0a0 - - libzlib >=1.3.1,<2.0a0 - - openssl >=3.5.5,<4.0a0 + - libzlib >=1.3.2,<2.0a0 + - openssl >=3.5.6,<4.0a0 - zstd >=1.5.7,<1.6.0a0 license: curl license_family: MIT purls: [] - size: 466704 - timestamp: 1773218522665 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcurl-8.19.0-hd5a2499_0.conda - sha256: c4d581b067fa60f9dc0e1c5f18b756760ff094a03139e6b206eb98d185ae2bb1 - md5: 9fc7771fc8104abed9119113160be15a + size: 468706 + timestamp: 1777461492876 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcurl-8.20.0-hd5a2499_0.conda + sha256: 38c0bc634b61e542776e97cfd15d5d41edd304d4e47c333004d2d622439b2381 + md5: 2f57b7d0c6adda88957586b7afd78438 depends: - __osx >=11.0 - krb5 >=1.22.2,<1.23.0a0 - - libnghttp2 >=1.67.0,<2.0a0 + - libnghttp2 >=1.68.1,<2.0a0 - libssh2 >=1.11.1,<2.0a0 - - libzlib >=1.3.1,<2.0a0 - - openssl >=3.5.5,<4.0a0 + - libzlib >=1.3.2,<2.0a0 + - openssl >=3.5.6,<4.0a0 - zstd >=1.5.7,<1.6.0a0 license: curl license_family: MIT purls: [] - size: 399616 - timestamp: 1773219210246 -- conda: https://conda.anaconda.org/conda-forge/win-64/libcurl-8.19.0-h8206538_0.conda - sha256: 6b2143ba5454b399dab4471e9e1d07352a2f33b569975e6b8aedc2d9bf51cbb0 - md5: ed181e29a7ebf0f60b84b98d6140a340 + size: 400568 + timestamp: 1777462251987 +- conda: https://conda.anaconda.org/conda-forge/win-64/libcurl-8.20.0-h8206538_0.conda + sha256: f4ce5aa835a698532feaa368e804365a7e45a9edebe006a8e1c80505d893c24e + md5: 7bee27a8f0a295117ccb864f30d2d87e depends: - krb5 >=1.22.2,<1.23.0a0 - libssh2 >=1.11.1,<2.0a0 - - libzlib >=1.3.1,<2.0a0 + - libzlib >=1.3.2,<2.0a0 - ucrt >=10.0.20348.0 - vc >=14.3,<15 - vc14_runtime >=14.44.35208 license: curl license_family: MIT purls: [] - size: 392543 - timestamp: 1773218585056 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-22.1.4-h55c6f16_0.conda - sha256: 25a0d02148a39b665d9c2957676faf62a4d2a58494d53b201151199a197db4b0 - md5: 448a1af83a9205655ee1cf48d3875ca3 + size: 393114 + timestamp: 1777461635732 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-22.1.5-h55c6f16_1.conda + sha256: dddd01bd6b338221342a89530a1caffe6051a70cc8f8b1d8bb591d5447a3c603 + md5: ff484b683fecf1e875dfc7aa01d19796 depends: - __osx >=11.0 license: Apache-2.0 WITH LLVM-exception license_family: Apache purls: [] - size: 569927 - timestamp: 1776816293111 + size: 569359 + timestamp: 1778191546305 - conda: https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.25-h17f619e_0.conda sha256: aa8e8c4be9a2e81610ddf574e05b64ee131fab5e0e3693210c9d6d2fba32c680 md5: 6c77a605a7a689d17d4819c0f8ac9a00 @@ -10030,18 +10066,18 @@ packages: purls: [] size: 156818 timestamp: 1761979842440 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libdrm-2.4.125-hb03c661_1.conda - sha256: c076a213bd3676cc1ef22eeff91588826273513ccc6040d9bea68bccdc849501 - md5: 9314bc5a1fe7d1044dc9dfd3ef400535 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libdrm-2.4.127-hb03c661_0.conda + sha256: 7d3187c11b7ae66c5595a8afd5a7ce352a490527fdf6614cab129bc7f2c16ba3 + md5: d8d16b9b32a3c5df7e5b3350e2cbe058 depends: - __glibc >=2.17,<3.0.a0 - libgcc >=14 - - libpciaccess >=0.18,<0.19.0a0 + - libpciaccess >=0.19,<0.20.0a0 license: MIT license_family: MIT purls: [] - size: 310785 - timestamp: 1757212153962 + size: 311505 + timestamp: 1778975798004 - conda: https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20250104-pl5321h7949ede_0.conda sha256: d789471216e7aba3c184cd054ed61ce3f6dac6f87a50ec69291b9297f8c18724 md5: c277e0a4d549b03ac1e9d6cbbe3d017b @@ -10141,45 +10177,45 @@ packages: purls: [] size: 410555 timestamp: 1685726568668 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.5-hecca717_0.conda - sha256: e8c2b57f6aacabdf2f1b0924bd4831ce5071ba080baa4a9e8c0d720588b6794c - md5: 49f570f3bc4c874a06ea69b7225753af +- conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.8.0-hecca717_0.conda + sha256: ea33c40977ea7a2c3658c522230058395bc2ee0d89d99f0711390b6a1ee80d12 + md5: a3b390520c563d78cc58974de95a03e5 depends: - __glibc >=2.17,<3.0.a0 - libgcc >=14 constrains: - - expat 2.7.5.* + - expat 2.8.0.* license: MIT license_family: MIT purls: [] - size: 76624 - timestamp: 1774719175983 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.7.5-hf6b4638_0.conda - sha256: 06780dec91dd25770c8cf01e158e1062fbf7c576b1406427475ce69a8af75b7e - md5: a32123f93e168eaa4080d87b0fb5da8a + size: 77241 + timestamp: 1777846112704 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.8.0-hf6b4638_0.conda + sha256: f4b1cafc59afaede8fa0a2d9cf376840f1c553001acd72f6ead18bbc8ac8c49c + md5: 65466e82c09e888ca7560c11a97d5450 depends: - __osx >=11.0 constrains: - - expat 2.7.5.* + - expat 2.8.0.* license: MIT license_family: MIT purls: [] - size: 68192 - timestamp: 1774719211725 -- conda: https://conda.anaconda.org/conda-forge/win-64/libexpat-2.7.5-hac47afa_0.conda - sha256: 6850c3a4d5dc215b86f58518cfb8752998533d6569b08da8df1da72e7c68e571 - md5: bfb43f52f13b7c56e7677aa7a8efdf0c + size: 68789 + timestamp: 1777846180142 +- conda: https://conda.anaconda.org/conda-forge/win-64/libexpat-2.8.0-hac47afa_0.conda + sha256: 2d81d647c1f01108803457cac999b947456f44dd0a3c2325395677feacaeca67 + md5: 264e350e035092b5135a2147c238aec4 depends: - ucrt >=10.0.20348.0 - vc >=14.3,<15 - vc14_runtime >=14.44.35208 constrains: - - expat 2.7.5.* + - expat 2.8.0.* license: MIT license_family: MIT purls: [] - size: 70609 - timestamp: 1774719377850 + size: 71094 + timestamp: 1777846223617 - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.5.2-h3435931_0.conda sha256: 31f19b6a88ce40ebc0d5a992c131f57d919f73c0b92cd1617a5bec83f6e961e6 md5: a360c33a5abe61c07959e449fa1453eb @@ -10282,105 +10318,105 @@ packages: purls: [] size: 340180 timestamp: 1774300467879 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-he0feb66_18.conda - sha256: faf7d2017b4d718951e3a59d081eb09759152f93038479b768e3d612688f83f5 - md5: 0aa00f03f9e39fb9876085dee11a85d4 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-he0feb66_19.conda + sha256: 8e0a3b5e41272e5678499b5dfc4cddb673f9e935de01eb0767ce857001229f46 + md5: 57736f29cc2b0ec0b6c2952d3f101b6a depends: - __glibc >=2.17,<3.0.a0 - _openmp_mutex >=4.5 constrains: - - libgcc-ng ==15.2.0=*_18 - - libgomp 15.2.0 he0feb66_18 + - libgcc-ng ==15.2.0=*_19 + - libgomp 15.2.0 he0feb66_19 license: GPL-3.0-only WITH GCC-exception-3.1 license_family: GPL purls: [] - size: 1041788 - timestamp: 1771378212382 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgcc-15.2.0-hcbb3090_18.conda - sha256: 1d9c4f35586adb71bcd23e31b68b7f3e4c4ab89914c26bed5f2859290be5560e - md5: 92df6107310b1fff92c4cc84f0de247b + size: 1041084 + timestamp: 1778269013026 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgcc-15.2.0-hcbb3090_19.conda + sha256: 06644fa4d34d57c9e48f4d84b1256f9e5f654fdb37f43acc8a58a396952d42b7 + md5: 644058123986582db33aebd4ae2ca184 depends: - _openmp_mutex constrains: - - libgcc-ng ==15.2.0=*_18 - - libgomp 15.2.0 18 + - libgcc-ng ==15.2.0=*_19 + - libgomp 15.2.0 19 license: GPL-3.0-only WITH GCC-exception-3.1 license_family: GPL purls: [] - size: 401974 - timestamp: 1771378877463 -- conda: https://conda.anaconda.org/conda-forge/win-64/libgcc-15.2.0-h8ee18e1_18.conda - sha256: da2c96563c76b8c601746f03e03ac75d2b4640fa2ee017cb23d6c9fc31f1b2c6 - md5: b085746891cca3bd2704a450a7b4b5ce + size: 404080 + timestamp: 1778273064154 +- conda: https://conda.anaconda.org/conda-forge/win-64/libgcc-15.2.0-h8ee18e1_19.conda + sha256: 80e80ef5e31b00b12539db3c5aaecde60dab91381abfc1060e323d5c3b016dce + md5: cc5d690fc1c629038f13c68e88e65f44 depends: - _openmp_mutex >=4.5 - libwinpthread >=12.0.0.r4.gg4f2fc60ca constrains: - - libgcc-ng ==15.2.0=*_18 - msys2-conda-epoch <0.0a0 - - libgomp 15.2.0 h8ee18e1_18 + - libgcc-ng ==15.2.0=*_19 + - libgomp 15.2.0 h8ee18e1_19 license: GPL-3.0-only WITH GCC-exception-3.1 license_family: GPL purls: [] - size: 820022 - timestamp: 1771382190160 -- conda: https://conda.anaconda.org/conda-forge/noarch/libgcc-devel_linux-64-14.3.0-hf649bbc_118.conda - sha256: 1abc6a81ee66e8ac9ac09a26e2d6ad7bba23f0a0cc3a6118654f036f9c0e1854 - md5: 06901733131833f5edd68cf3d9679798 + size: 821854 + timestamp: 1778273037795 +- conda: https://conda.anaconda.org/conda-forge/noarch/libgcc-devel_linux-64-14.3.0-hf649bbc_119.conda + sha256: e1815bb11d5abe886979e95889d84310d83d078d36a3567ca67cbf57a3876d88 + md5: 7d517e32d656a8880d98c0e4fc8ddc2c depends: - __unix license: GPL-3.0-only WITH GCC-exception-3.1 license_family: GPL purls: [] - size: 3084533 - timestamp: 1771377786730 -- conda: https://conda.anaconda.org/conda-forge/noarch/libgcc-devel_linux-64-15.2.0-hcc6f6b0_118.conda - sha256: af69fc5852908d26e5b630b270982ac792506551dd6af1614bf0370dd5ab5746 - md5: 5d3a96d55f1be45fef88ee23155effd9 + size: 3091520 + timestamp: 1778268364856 +- conda: https://conda.anaconda.org/conda-forge/noarch/libgcc-devel_linux-64-15.2.0-hcc6f6b0_119.conda + sha256: 38a557eba305468ac1f90ac85e50d8defd76141cb0b8a43b2fc1aca71dd5d5f2 + md5: 683fcb168e1df9a21fa80d5aa2d9330b depends: - __unix license: GPL-3.0-only WITH GCC-exception-3.1 license_family: GPL purls: [] - size: 3085932 - timestamp: 1771378098166 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.2.0-h69a702a_18.conda - sha256: e318a711400f536c81123e753d4c797a821021fb38970cebfb3f454126016893 - md5: d5e96b1ed75ca01906b3d2469b4ce493 + size: 3095909 + timestamp: 1778268932148 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.2.0-h69a702a_19.conda + sha256: 9dcf54adfaa5e861123c2da4f2f0451a685464ea7e5a41ad91cf67b31d658d98 + md5: 331ee9b72b9dff570d56b1302c5ab37d depends: - - libgcc 15.2.0 he0feb66_18 + - libgcc 15.2.0 he0feb66_19 license: GPL-3.0-only WITH GCC-exception-3.1 license_family: GPL purls: [] - size: 27526 - timestamp: 1771378224552 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-15.2.0-h69a702a_18.conda - sha256: d2c9fad338fd85e4487424865da8e74006ab2e2475bd788f624d7a39b2a72aee - md5: 9063115da5bc35fdc3e1002e69b9ef6e + size: 27694 + timestamp: 1778269016987 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-15.2.0-h69a702a_19.conda + sha256: 561a42758ef25b9ce308c4e2cf56daee4f06138385a17e29a492cd928e00be6f + md5: 42bf7eca1a951735fa06c0e3c0d5c8e6 depends: - - libgfortran5 15.2.0 h68bc16d_18 + - libgfortran5 15.2.0 h68bc16d_19 constrains: - - libgfortran-ng ==15.2.0=*_18 + - libgfortran-ng ==15.2.0=*_19 license: GPL-3.0-only WITH GCC-exception-3.1 license_family: GPL purls: [] - size: 27523 - timestamp: 1771378269450 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran-15.2.0-h07b0088_18.conda - sha256: 63f89087c3f0c8621c5c89ecceec1e56e5e1c84f65fc9c5feca33a07c570a836 - md5: 26981599908ed2205366e8fc91b37fc6 + size: 27655 + timestamp: 1778269042954 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran-15.2.0-h07b0088_19.conda + sha256: d4837b3b9b30af3132d260225e91ab9dde83be04c59513f500cc81050fb37486 + md5: 1ea03f87cdb1078fbc0e2b2deb63752c depends: - - libgfortran5 15.2.0 hdae7583_18 + - libgfortran5 15.2.0 hdae7583_19 constrains: - - libgfortran-ng ==15.2.0=*_18 + - libgfortran-ng ==15.2.0=*_19 license: GPL-3.0-only WITH GCC-exception-3.1 license_family: GPL purls: [] - size: 138973 - timestamp: 1771379054939 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.2.0-h68bc16d_18.conda - sha256: 539b57cf50ec85509a94ba9949b7e30717839e4d694bc94f30d41c9d34de2d12 - md5: 646855f357199a12f02a87382d429b75 + size: 139675 + timestamp: 1778273280875 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.2.0-h68bc16d_19.conda + sha256: 057978bb69fea29ed715a9b98adf71015c31baecc4aeb2bfc20d4fd5d83579d4 + md5: 85072b0ad177c966294f129b7c04a2d5 depends: - __glibc >=2.17,<3.0.a0 - libgcc >=15.2.0 @@ -10389,11 +10425,11 @@ packages: license: GPL-3.0-only WITH GCC-exception-3.1 license_family: GPL purls: [] - size: 2482475 - timestamp: 1771378241063 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran5-15.2.0-hdae7583_18.conda - sha256: 91033978ba25e6a60fb86843cf7e1f7dc8ad513f9689f991c9ddabfaf0361e7e - md5: c4a6f7989cffb0544bfd9207b6789971 + size: 2483673 + timestamp: 1778269025089 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran5-15.2.0-hdae7583_19.conda + sha256: d0a68b7a121d115b80c169e24d1265dcc25a3fe58d107df1bbc430797e226d88 + md5: ba36d8c606a6a53fe0b8c12d47267b3d depends: - libgcc >=15.2.0 constrains: @@ -10401,8 +10437,8 @@ packages: license: GPL-3.0-only WITH GCC-exception-3.1 license_family: GPL purls: [] - size: 598634 - timestamp: 1771378886363 + size: 599691 + timestamp: 1778273075448 - conda: https://conda.anaconda.org/conda-forge/linux-64/libgl-1.7.0-ha4b6fd6_2.conda sha256: dc2752241fa3d9e40ce552c1942d0a4b5eeb93740c9723873f6fcf8d39ef8d2d md5: 928b8be80851f5d8ffb016f9c81dae7a @@ -10425,40 +10461,40 @@ packages: purls: [] size: 113911 timestamp: 1731331012126 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libglib-2.86.4-h6548e54_1.conda - sha256: a27e44168a1240b15659888ce0d9b938ed4bdb49e9ea68a7c1ff27bcea8b55ce - md5: bb26456332b07f68bf3b7622ed71c0da +- conda: https://conda.anaconda.org/conda-forge/linux-64/libglib-2.88.1-h0d30a3d_2.conda + sha256: 33eb5d5310a5c2c0a4707a0afa644801c2e08c8f70c45e1f62f354116dfe0970 + md5: 17d484ab9c8179c6a6e5b7dbb5065afc depends: - __glibc >=2.17,<3.0.a0 - - libffi >=3.5.2,<3.6.0a0 - libgcc >=14 - - libiconv >=1.18,<2.0a0 - - libzlib >=1.3.1,<2.0a0 + - libffi >=3.5.2,<3.6.0a0 - pcre2 >=10.47,<10.48.0a0 + - libzlib >=1.3.2,<2.0a0 + - libiconv >=1.18,<2.0a0 constrains: - - glib 2.86.4 *_1 + - glib >2.66 license: LGPL-2.1-or-later purls: [] - size: 4398701 - timestamp: 1771863239578 -- conda: https://conda.anaconda.org/conda-forge/win-64/libglib-2.86.4-h0c9aed9_1.conda - sha256: f035fb25f8858f201e0055c719ef91022e9465cd51fe803304b781863286fb10 - md5: 0329a7e92c8c8b61fcaaf7ad44642a96 + size: 4754097 + timestamp: 1778508800134 +- conda: https://conda.anaconda.org/conda-forge/win-64/libglib-2.88.1-h7ce1215_2.conda + sha256: f61277e224e9889c221bb2eac0f57d5aeeb82fc45d3dc326957d251c97444f7c + md5: 5fb838786a8317ebb38056bbe236d3ff depends: - - libffi >=3.5.2,<3.6.0a0 - - libiconv >=1.18,<2.0a0 - - libintl >=0.22.5,<1.0a0 - - libzlib >=1.3.1,<2.0a0 - - pcre2 >=10.47,<10.48.0a0 - - ucrt >=10.0.20348.0 - vc >=14.3,<15 - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - libiconv >=1.18,<2.0a0 + - libzlib >=1.3.2,<2.0a0 + - pcre2 >=10.47,<10.48.0a0 + - libintl >=0.22.5,<1.0a0 + - libffi >=3.5.2,<3.6.0a0 constrains: - - glib 2.86.4 *_1 + - glib >2.66 license: LGPL-2.1-or-later purls: [] - size: 4095369 - timestamp: 1771863229701 + size: 4522891 + timestamp: 1778508851933 - conda: https://conda.anaconda.org/conda-forge/linux-64/libglvnd-1.7.0-ha4b6fd6_2.conda sha256: 1175f8a7a0c68b7f81962699751bb6574e6f07db4c9f72825f978e3016f46850 md5: 434ca7e50e40f4918ab701e3facd59a0 @@ -10491,19 +10527,19 @@ packages: purls: [] size: 26388 timestamp: 1731331003255 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-he0feb66_18.conda - sha256: 21337ab58e5e0649d869ab168d4e609b033509de22521de1bfed0c031bfc5110 - md5: 239c5e9546c38a1e884d69effcf4c882 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-he0feb66_19.conda + sha256: 5abe4ab9d93f6c9757d654f1969ae2267d4505315c1f2f8fe705fd60af084f1b + md5: faac990cb7aedc7f3a2224f2c9b0c26c depends: - __glibc >=2.17,<3.0.a0 license: GPL-3.0-only WITH GCC-exception-3.1 license_family: GPL purls: [] - size: 603262 - timestamp: 1771378117851 -- conda: https://conda.anaconda.org/conda-forge/win-64/libgomp-15.2.0-h8ee18e1_18.conda - sha256: 94981bc2e42374c737750895c6fdcfc43b7126c4fc788cad0ecc7281745931da - md5: 939fb173e2a4d4e980ef689e99b35223 + size: 603817 + timestamp: 1778268942614 +- conda: https://conda.anaconda.org/conda-forge/win-64/libgomp-15.2.0-h8ee18e1_19.conda + sha256: 4dc958ced2fc7f42bc675b07e2c9abe3e150875ffdf62ca551d94fc6facf1fd7 + md5: f1147651e3fdd585e2f442c0c2fc8f2d depends: - libwinpthread >=12.0.0.r4.gg4f2fc60ca constrains: @@ -10511,8 +10547,8 @@ packages: license: GPL-3.0-only WITH GCC-exception-3.1 license_family: GPL purls: [] - size: 663864 - timestamp: 1771382118742 + size: 664640 + timestamp: 1778272979661 - conda: https://conda.anaconda.org/conda-forge/linux-64/libgoogle-cloud-3.3.0-h25dbb67_1.conda sha256: 17ea802cef3942b0a850b8e33b03fc575f79734f3c829cdd6a4e56e2dae60791 md5: b2baa4ce6a9d9472aaa602b88f8d40ac @@ -10691,9 +10727,9 @@ packages: purls: [] size: 11546515 timestamp: 1774013326223 -- conda: https://conda.anaconda.org/conda-forge/win-64/libhwloc-2.12.2-default_h4379cf1_1000.conda - sha256: 8cdf11333a81085468d9aa536ebb155abd74adc293576f6013fc0c85a7a90da3 - md5: 3b576f6860f838f950c570f4433b086e +- conda: https://conda.anaconda.org/conda-forge/win-64/libhwloc-2.13.0-default_h049141e_1000.conda + sha256: 2ee12e37223dfcd0acd050c80a91150c482b6e2899198521e1800dce66662467 + md5: 6a01c986e30292c715038d2788aa1385 depends: - libwinpthread >=12.0.0.r4.gg4f2fc60ca - libxml2 @@ -10704,8 +10740,8 @@ packages: license: BSD-3-Clause license_family: BSD purls: [] - size: 2411241 - timestamp: 1765104337762 + size: 2396128 + timestamp: 1770954127918 - conda: https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.18-h3b78370_2.conda sha256: c467851a7312765447155e071752d7bf9bf44d610a5687e32706f480aad2833f md5: 915f5995e94f60e9a4826e0b0920ee88 @@ -10781,54 +10817,54 @@ packages: purls: [] size: 842806 timestamp: 1775962811457 -- conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.11.0-6_h47877c9_openblas.conda - build_number: 6 - sha256: 371f517eb7010b21c6cc882c7606daccebb943307cb9a3bf2c70456a5c024f7d - md5: 881d801569b201c2e753f03c84b85e15 +- conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.11.0-7_h47877c9_openblas.conda + build_number: 7 + sha256: 96962084921f197c9ad13fb7f8b324f2351d50ff3d8d962148751ad532f54a01 + md5: 6569b4f273740e25dc0dc7e3232c2a6c depends: - - libblas 3.11.0 6_h4a7cf45_openblas + - libblas 3.11.0 7_h4a7cf45_openblas constrains: - - blas 2.306 openblas - - liblapacke 3.11.0 6*_openblas - - libcblas 3.11.0 6*_openblas + - liblapacke 3.11.0 7*_openblas + - libcblas 3.11.0 7*_openblas + - blas 2.307 openblas license: BSD-3-Clause license_family: BSD purls: [] - size: 18624 - timestamp: 1774503065378 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblapack-3.11.0-6_hd9741b5_openblas.conda - build_number: 6 - sha256: 21606b7346810559e259807497b86f438950cf19e71838e44ebaf4bd2b35b549 - md5: ee33d2d05a7c5ea1f67653b37eb74db1 + size: 18694 + timestamp: 1778489869038 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblapack-3.11.0-7_hd9741b5_openblas.conda + build_number: 7 + sha256: ff3018918ca8b22173dcb231842e819767fd05a08df61483eb5f3e9f2895d114 + md5: d1289ad41d5a78e2269eea3a2d7f0c7d depends: - - libblas 3.11.0 6_h51639a9_openblas + - libblas 3.11.0 7_h51639a9_openblas constrains: - - liblapacke 3.11.0 6*_openblas - - libcblas 3.11.0 6*_openblas - - blas 2.306 openblas + - libcblas 3.11.0 7*_openblas + - blas 2.307 openblas + - liblapacke 3.11.0 7*_openblas license: BSD-3-Clause license_family: BSD purls: [] - size: 18863 - timestamp: 1774504467905 -- conda: https://conda.anaconda.org/conda-forge/win-64/liblapack-3.11.0-6_hf9ab0e9_mkl.conda - build_number: 6 - sha256: 2e6ac39e456ba13ec8f02fc0787b8a22c89780e24bd5556eaf642177463ffb36 - md5: 7e9cdaf6f302142bc363bbab3b5e7074 + size: 18780 + timestamp: 1778490000843 +- conda: https://conda.anaconda.org/conda-forge/win-64/liblapack-3.11.0-7_hf9ab0e9_mkl.conda + build_number: 7 + sha256: cd20e15b893ef82612fa46db41ad677351feb0c42cf3c27172777a35bb99b421 + md5: 56de899eaa1209fd8769418b7bc7a60c depends: - - libblas 3.11.0 6_hf2e6a31_mkl + - libblas 3.11.0 7_h8455456_mkl constrains: - - blas 2.306 mkl - - liblapacke 3.11.0 6*_mkl - - libcblas 3.11.0 6*_mkl + - blas 2.307 mkl + - libcblas 3.11.0 7*_mkl + - liblapacke 3.11.0 7*_mkl license: BSD-3-Clause license_family: BSD purls: [] - size: 80571 - timestamp: 1774503757128 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libllvm22-22.1.4-hf7376ad_0.conda - sha256: 6cf83bb8084b4b305fd2d6da9e3ab42acc0a01b19d11c4d7e9766dad91afce49 - md5: 80a690c83cba58ba483b90a07e59e721 + size: 80578 + timestamp: 1778490377191 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libllvm22-22.1.5-hf7376ad_1.conda + sha256: 094198dc5c7fbd85e3719d192d5b77c3f0dccf657dfd9ba0c79e391f11f7ace2 + md5: 6adc0202fa7fcf0a5fce8c31ef2ed866 depends: - __glibc >=2.17,<3.0.a0 - libgcc >=14 @@ -10840,8 +10876,8 @@ packages: license: Apache-2.0 WITH LLVM-exception license_family: Apache purls: [] - size: 44242661 - timestamp: 1776821554393 + size: 44241856 + timestamp: 1778417624650 - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.3-hb03c661_0.conda sha256: ec30e52a3c1bf7d0425380a189d209a52baa03f22fb66dd3eb587acaa765bd6d md5: b88d90cad08e6bc8ad540cb310a761fb @@ -10992,36 +11028,36 @@ packages: purls: [] size: 15164138 timestamp: 1776121337288 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.32-pthreads_h94d23a6_0.conda - sha256: 6dc30b28f32737a1c52dada10c8f3a41bc9e021854215efca04a7f00487d09d9 - md5: 89d61bc91d3f39fda0ca10fcd3c68594 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.33-pthreads_h94d23a6_0.conda + sha256: 3d9aa85648e5e18a6d66db98b8c4317cc426721ad7a220aa86330d1ccedc8903 + md5: 2d3278b721e40468295ca755c3b84070 depends: - __glibc >=2.17,<3.0.a0 - libgcc >=14 - libgfortran - libgfortran5 >=14.3.0 constrains: - - openblas >=0.3.32,<0.3.33.0a0 + - openblas >=0.3.33,<0.3.34.0a0 license: BSD-3-Clause license_family: BSD purls: [] - size: 5928890 - timestamp: 1774471724897 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libopenblas-0.3.32-openmp_he657e61_0.conda - sha256: 713e453bde3531c22a660577e59bf91ef578dcdfd5edb1253a399fa23514949a - md5: 3a1111a4b6626abebe8b978bb5a323bf + size: 5931919 + timestamp: 1776993658641 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libopenblas-0.3.33-openmp_he657e61_0.conda + sha256: 9dd455b2d172aeedfa2058d324b5b5822b0bc1b7c1f32cd183d7078540d2f6eb + md5: 909e41855c29f0d52ae630198cd57135 depends: - __osx >=11.0 - libgfortran - libgfortran5 >=14.3.0 - llvm-openmp >=19.1.7 constrains: - - openblas >=0.3.32,<0.3.33.0a0 + - openblas >=0.3.33,<0.3.34.0a0 license: BSD-3-Clause license_family: BSD purls: [] - size: 4308797 - timestamp: 1774472508546 + size: 4304965 + timestamp: 1776995497368 - conda: https://conda.anaconda.org/conda-forge/linux-64/libopengl-1.7.0-ha4b6fd6_2.conda sha256: 215086c108d80349e96051ad14131b751d17af3ed2cb5a34edd62fa89bfe8ead md5: 7df50d44d4a14d6c31a2c54f2cd92157 @@ -11116,62 +11152,68 @@ packages: purls: [] size: 436562 timestamp: 1774001693139 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libparquet-24.0.0-h7376487_0_cpu.conda - sha256: 374a9c6222df047ab6d43c6efe96d380b7750b53ec9b84fc9ad8bf1c22df4522 - md5: c828cca50cd3a7c53d12ce8f0872c6ec +- conda: https://conda.anaconda.org/conda-forge/linux-64/libparquet-24.0.0-h7376487_1_cpu.conda + build_number: 1 + sha256: 65d6ba055d872911d30f55b8bf2880ff05f57f2a9cc59447be1dccce07f68109 + md5: 5e60f3c311d00d456f089177bb75ebaf depends: - __glibc >=2.17,<3.0.a0 - - libarrow 24.0.0 ha7f89c6_0_cpu + - libarrow 24.0.0 h0935d00_1_cpu - libgcc >=14 - libstdcxx >=14 - libthrift >=0.22.0,<0.22.1.0a0 - openssl >=3.5.6,<4.0a0 license: Apache-2.0 + license_family: APACHE purls: [] - size: 1428303 - timestamp: 1776815964191 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libparquet-24.0.0-h16c0493_0_cpu.conda - sha256: 992e7c26f555689ea9df19c534df691df148e96f5e4f0c1e5c71be8ea7b8b58c - md5: 09b65c6b00e5c9d9f4ca07427b492daf + size: 1426881 + timestamp: 1778175848424 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libparquet-24.0.0-h16c0493_1_cpu.conda + build_number: 1 + sha256: 72e5dc5747144cc4e8ea4c287509c69c6f8d1c72a678285e39e3df76867cef5b + md5: 5b32ce08a542383f4c72cdee323979e8 depends: - __osx >=11.0 - libabseil * cxx17* - libabseil >=20260107.1,<20260108.0a0 - - libarrow 24.0.0 h2124f06_0_cpu + - libarrow 24.0.0 h37fbca7_1_cpu - libcxx >=21 - libopentelemetry-cpp >=1.26.0,<1.27.0a0 - libprotobuf >=6.33.5,<6.33.6.0a0 - libthrift >=0.22.0,<0.22.1.0a0 - openssl >=3.5.6,<4.0a0 license: Apache-2.0 + license_family: APACHE purls: [] - size: 1097756 - timestamp: 1776815688915 -- conda: https://conda.anaconda.org/conda-forge/win-64/libparquet-24.0.0-h7051d1f_0_cpu.conda - sha256: 12eca40be87388d28afbcf78c9897cd2a762a64122b2bbc0e8016acb447c8252 - md5: 0b34d3ccc580180446b7f7225ddab6ab + size: 1098422 + timestamp: 1778175143698 +- conda: https://conda.anaconda.org/conda-forge/win-64/libparquet-24.0.0-h7051d1f_1_cpu.conda + build_number: 1 + sha256: b2afe937c690bfac4481340f4834ad7d00693e3f3f987bf0c3ad36e8412a7462 + md5: 7046be3427cdf836adef97415d55d37f depends: - - libarrow 24.0.0 hc74aee5_0_cpu + - libarrow 24.0.0 h37f918f_1_cpu - libthrift >=0.22.0,<0.22.1.0a0 - openssl >=3.5.6,<4.0a0 - ucrt >=10.0.20348.0 - vc >=14.3,<15 - vc14_runtime >=14.44.35208 license: Apache-2.0 + license_family: APACHE purls: [] - size: 966867 - timestamp: 1776819731491 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libpciaccess-0.18-hb9d3cd8_0.conda - sha256: 0bd91de9b447a2991e666f284ae8c722ffb1d84acb594dbd0c031bd656fa32b2 - md5: 70e3400cbbfa03e96dcde7fc13e38c7b + size: 965302 + timestamp: 1778179550580 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libpciaccess-0.19-hb03c661_0.conda + sha256: f41721636a7c2e51bc2c642e1127955ab9c81145470714fdaac44d4d09e4af41 + md5: 33082e13b4769b48cfeb648e15bfe3fc depends: - __glibc >=2.17,<3.0.a0 - - libgcc >=13 + - libgcc >=14 license: MIT license_family: MIT purls: [] - size: 28424 - timestamp: 1749901812541 + size: 29147 + timestamp: 1773533027610 - conda: https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.58-h421ea60_0.conda sha256: 377cfe037f3eeb3b1bf3ad333f724a64d32f315ee1958581fc671891d63d3f89 md5: eba48a68a1a2b9d3c0d9511548db85db @@ -11205,20 +11247,20 @@ packages: purls: [] size: 385227 timestamp: 1776315248638 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libpq-18.3-h9abb657_0.conda - sha256: c7e61b86c273ec1ce92c0e087d1a0f3ed3b9485507c6cd35e03bc63de1b6b03f - md5: 405ec206d230d9d37ad7c2636114cbf4 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libpq-18.4-hd5a49e9_0.conda + sha256: 076742d4a9fa88711c5fc6726b967e6a03b5060e669aa03288c684a7ae03583b + md5: 2772b7ab7bc43f24e9585a714761a255 depends: - __glibc >=2.17,<3.0.a0 - - icu >=78.2,<79.0a0 + - icu >=78.3,<79.0a0 - krb5 >=1.22.2,<1.23.0a0 - libgcc >=14 - - openldap >=2.6.10,<2.7.0a0 - - openssl >=3.5.5,<4.0a0 + - openldap >=2.6.13,<2.7.0a0 + - openssl >=3.5.6,<4.0a0 license: PostgreSQL purls: [] - size: 2865686 - timestamp: 1772136328077 + size: 2754709 + timestamp: 1778786234149 - conda: https://conda.anaconda.org/conda-forge/linux-64/libprotobuf-6.33.5-h2b00c02_0.conda sha256: afbf195443269ae10a940372c1d37cda749355d2bd96ef9587a962abd87f2429 md5: 11ac478fa72cf12c214199b8a96523f4 @@ -11310,9 +11352,9 @@ packages: purls: [] size: 266062 timestamp: 1768190189553 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libsanitizer-14.3.0-h8f1669f_18.conda - sha256: e03ed186eefb46d7800224ad34bad1268c9d19ecb8f621380a50601c6221a4a7 - md5: ad3a0e2dc4cce549b2860e2ef0e6d75b +- conda: https://conda.anaconda.org/conda-forge/linux-64/libsanitizer-14.3.0-h8f1669f_19.conda + sha256: 8766de5423b0a510e2b1bdd1963d0554bdad2119f3e31d8fbd4189af434235ca + md5: 007796e5a595bbc7df4a5e1580d72e1a depends: - __glibc >=2.17,<3.0.a0 - libgcc >=14.3.0 @@ -11320,11 +11362,11 @@ packages: license: GPL-3.0-only WITH GCC-exception-3.1 license_family: GPL purls: [] - size: 7949259 - timestamp: 1771377982207 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libsanitizer-15.2.0-h90f66d4_18.conda - sha256: 0329e23d54a567c259adc962a62172eaa55e6ca33c105ef67b4f3cdb4ef70eaa - md5: ff754fbe790d4e70cf38aea3668c3cb3 + size: 7947790 + timestamp: 1778268494844 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libsanitizer-15.2.0-h90f66d4_19.conda + sha256: 7a58892a52739ce4c0f7109de9e91b4353104748eb04fc6441d88e8af444ba99 + md5: 67eef12ce33f7ff99900c212d7076fc2 depends: - __glibc >=2.17,<3.0.a0 - libgcc >=15.2.0 @@ -11332,8 +11374,8 @@ packages: license: GPL-3.0-only WITH GCC-exception-3.1 license_family: GPL purls: [] - size: 8095113 - timestamp: 1771378289674 + size: 7930689 + timestamp: 1778269054623 - conda: https://conda.anaconda.org/conda-forge/linux-64/libsodium-1.0.21-h280c20c_3.conda sha256: 64e5c80cbce4680a2d25179949739a6def695d72c40ca28f010711764e372d97 md5: 7af961ef4aa2c1136e11dd43ded245ab @@ -11364,39 +11406,38 @@ packages: purls: [] size: 276860 timestamp: 1772479407566 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.53.0-hf4e2dac_0.conda - sha256: ec37c79f737933bbac965f5dc0f08ef2790247129a84bb3114fad4900adce401 - md5: 810d83373448da85c3f673fbcb7ad3a3 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.53.1-h0c1763c_0.conda + sha256: 54cdcd3214313b62c2a8ee277e6f42150d9b748264c1b70d958bf735e420ef8d + md5: 7dc38adcbf71e6b38748e919e16e0dce depends: - __glibc >=2.17,<3.0.a0 - - icu >=78.3,<79.0a0 - libgcc >=14 - libzlib >=1.3.2,<2.0a0 license: blessing purls: [] - size: 958864 - timestamp: 1775753750179 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.53.0-h1b79a29_0.conda - sha256: 1a9d1e3e18dbb0b87cff3b40c3e42703730d7ac7ee9b9322c2682196a81ba0c3 - md5: 8423c008105df35485e184066cad4566 + size: 954962 + timestamp: 1777986471789 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.53.1-h1b79a29_0.conda + sha256: 49daec7c83e70d4efc17b813547824bc2bcf2f7256d84061d24fbfe537da9f74 + md5: 6681822ea9d362953206352371b6a904 depends: - __osx >=11.0 - libzlib >=1.3.2,<2.0a0 license: blessing purls: [] - size: 920039 - timestamp: 1775754485962 -- conda: https://conda.anaconda.org/conda-forge/win-64/libsqlite-3.53.0-hf5d6505_0.conda - sha256: 7a6256ea136936df4c4f3b227ba1e273b7d61152f9811b52157af497f07640b0 - md5: 4152b5a8d2513fd7ae9fb9f221a5595d + size: 920047 + timestamp: 1777987051643 +- conda: https://conda.anaconda.org/conda-forge/win-64/libsqlite-3.53.1-hf5d6505_0.conda + sha256: e70562450332ca8954bc16f3455468cca5ef3695c7d7187ecc87f8fc3c70e9eb + md5: 7fea434a17c323256acc510a041b80d7 depends: - ucrt >=10.0.20348.0 - vc >=14.3,<15 - vc14_runtime >=14.44.35208 license: blessing purls: [] - size: 1301855 - timestamp: 1775753831574 + size: 1304178 + timestamp: 1777986510497 - conda: https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.11.1-hcf80075_0.conda sha256: fa39bfd69228a13e553bd24601332b7cfeb30ca11a3ca50bb028108fe90a7661 md5: eecce068c7e4eddeb169591baac20ac4 @@ -11435,93 +11476,93 @@ packages: purls: [] size: 292785 timestamp: 1745608759342 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h934c35e_18.conda - sha256: 78668020064fdaa27e9ab65cd2997e2c837b564ab26ce3bf0e58a2ce1a525c6e - md5: 1b08cd684f34175e4514474793d44bcb +- conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h934c35e_19.conda + sha256: dff1058c76ec6b8759e41cefa2508162d00e4a5e6721aa68ec3fd10094e702dc + md5: 5794b3bdc38177caf969dabd3af08549 depends: - __glibc >=2.17,<3.0.a0 - - libgcc 15.2.0 he0feb66_18 + - libgcc 15.2.0 he0feb66_19 constrains: - - libstdcxx-ng ==15.2.0=*_18 + - libstdcxx-ng ==15.2.0=*_19 license: GPL-3.0-only WITH GCC-exception-3.1 license_family: GPL purls: [] - size: 5852330 - timestamp: 1771378262446 -- conda: https://conda.anaconda.org/conda-forge/noarch/libstdcxx-devel_linux-64-14.3.0-h9f08a49_118.conda - sha256: b1c3824769b92a1486bf3e2cc5f13304d83ae613ea061b7bc47bb6080d6dfdba - md5: 865a399bce236119301ebd1532fced8d + size: 5852044 + timestamp: 1778269036376 +- conda: https://conda.anaconda.org/conda-forge/noarch/libstdcxx-devel_linux-64-14.3.0-h9f08a49_119.conda + sha256: 1b4263aa5d8c8c659e8e38b66868f42867347e0c8941513ee77269afc00a5186 + md5: d1a866495b9654ccfef5392b8541dc58 depends: - __unix license: GPL-3.0-only WITH GCC-exception-3.1 license_family: GPL purls: [] - size: 20171098 - timestamp: 1771377827750 -- conda: https://conda.anaconda.org/conda-forge/noarch/libstdcxx-devel_linux-64-15.2.0-hd446a21_118.conda - sha256: 138ee40ba770abf4556ee9981879da9e33299f406a450831b48c1c397d7d0833 - md5: a50630d1810916fc252b2152f1dc9d6d + size: 20199810 + timestamp: 1778268389428 +- conda: https://conda.anaconda.org/conda-forge/noarch/libstdcxx-devel_linux-64-15.2.0-hd446a21_119.conda + sha256: a2385f3611d5cd25378f9cf2367183320731709c067ddd08d43330d3170f15b8 + md5: bcfe7eae40158c3e355d2f9d3ed41230 depends: - __unix license: GPL-3.0-only WITH GCC-exception-3.1 license_family: GPL purls: [] - size: 20669511 - timestamp: 1771378139786 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.2.0-hdf11a46_18.conda - sha256: 3c902ffd673cb3c6ddde624cdb80f870b6c835f8bf28384b0016e7d444dd0145 - md5: 6235adb93d064ecdf3d44faee6f468de + size: 20765069 + timestamp: 1778268963689 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.2.0-hdf11a46_19.conda + sha256: 0672b6b6e1791c92e8eccad58081a99d614fcf82bca5841f9dfa3c3e658f83b9 + md5: e5ce228e579726c07255dbf90dc62101 depends: - - libstdcxx 15.2.0 h934c35e_18 + - libstdcxx 15.2.0 h934c35e_19 license: GPL-3.0-only WITH GCC-exception-3.1 license_family: GPL purls: [] - size: 27575 - timestamp: 1771378314494 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libthrift-0.22.0-h454ac66_1.conda - sha256: 4888b9ea2593c36ca587a5ebe38d0a56a0e6d6a9e4bb7da7d9a326aaaca7c336 - md5: 8ed82d90e6b1686f5e98f8b7825a15ef + size: 27776 + timestamp: 1778269074600 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libthrift-0.22.0-h7d032f7_2.conda + sha256: af6025aa4a4fc3f4e71334000d2739d927e2f678607b109ec630cc17d716918a + md5: b6e326fbe1e3948da50ec29cee0380db depends: - __glibc >=2.17,<3.0.a0 - libevent >=2.1.12,<2.1.13.0a0 - libgcc >=14 - libstdcxx >=14 - - libzlib >=1.3.1,<2.0a0 - - openssl >=3.5.1,<4.0a0 + - libzlib >=1.3.2,<2.0a0 + - openssl >=3.5.6,<4.0a0 license: Apache-2.0 license_family: APACHE purls: [] - size: 424208 - timestamp: 1753277183984 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libthrift-0.22.0-h14a376c_1.conda - sha256: 8b703f2c6e47ed5886d7298601b9416b59e823fc8d1a8fa867192c94c5911aac - md5: 3161023bb2f8c152e4c9aa59bdd40975 + size: 423861 + timestamp: 1777018957474 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libthrift-0.22.0-h1fb9c8a_2.conda + sha256: 568bb23db02b050c3903bec05edbcab84960c8c7e5a1710dac3109df997ac7f1 + md5: d006875f9a58a44f92aec9a7ebeb7150 depends: - __osx >=11.0 - libcxx >=19 - libevent >=2.1.12,<2.1.13.0a0 - - libzlib >=1.3.1,<2.0a0 - - openssl >=3.5.1,<4.0a0 + - libzlib >=1.3.2,<2.0a0 + - openssl >=3.5.6,<4.0a0 license: Apache-2.0 license_family: APACHE purls: [] - size: 323360 - timestamp: 1753277264380 -- conda: https://conda.anaconda.org/conda-forge/win-64/libthrift-0.22.0-h23985f6_1.conda - sha256: 87516b128ffa497fc607d5da0cc0366dbee1dbcc14c962bf9ea951d480c7698b - md5: 556d49ad5c2ad553c2844cc570bb71c7 + size: 323017 + timestamp: 1777019893083 +- conda: https://conda.anaconda.org/conda-forge/win-64/libthrift-0.22.0-h2e43b2f_2.conda + sha256: 7ffb48755c4fc4a7cca454e4afea286e4fb47e50e153df1b006b14691f0f43d0 + md5: 42856184560e5cf901551fd414ad25c1 depends: - libevent >=2.1.12,<2.1.13.0a0 - - libzlib >=1.3.1,<2.0a0 - - openssl >=3.5.1,<4.0a0 + - libzlib >=1.3.2,<2.0a0 + - openssl >=3.5.6,<4.0a0 - ucrt >=10.0.20348.0 - vc >=14.3,<15 - vc14_runtime >=14.44.35208 license: Apache-2.0 license_family: APACHE purls: [] - size: 636513 - timestamp: 1753277481158 + size: 634136 + timestamp: 1777019194906 - conda: https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.7.1-h9d88235_1.conda sha256: e5f8c38625aa6d567809733ae04bb71c161a42e44a9fa8227abe61fa5c60ebe0 md5: cd5a90476766d53e901500df9215e927 @@ -12020,32 +12061,34 @@ packages: purls: [] size: 58347 timestamp: 1774072851498 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/llvm-openmp-22.1.4-hc7d1edf_0.conda - sha256: a269273ccf48be6ac582bb958713ba8373262b9157a0fc76b7e5475e8a1d2a78 - md5: 46d04a647df7a4525e487d88068d19ef +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/llvm-openmp-22.1.5-hc7d1edf_1.conda + sha256: 2cd49562feda2bf324651050b2b035080fd635ed0f1c96c9ce7a59eff3cc0029 + md5: 8a4e2a54034b35bc6fa5bf9282913f45 depends: - __osx >=11.0 constrains: - - openmp 22.1.4|22.1.4.* + - openmp 22.1.5|22.1.5.* - intel-openmp <0.0a0 license: Apache-2.0 WITH LLVM-exception + license_family: APACHE purls: [] - size: 286406 - timestamp: 1776846235007 -- conda: https://conda.anaconda.org/conda-forge/win-64/llvm-openmp-22.1.4-h4fa8253_0.conda - sha256: 7d827f8c125ac2fe3a9d5b47c1f95fc540bb8ef78685e4bcf941957257bb1eff - md5: 761757ab617e8bfef18cc422dd02bbad + size: 285806 + timestamp: 1778447786965 +- conda: https://conda.anaconda.org/conda-forge/win-64/llvm-openmp-22.1.5-h4fa8253_1.conda + sha256: 7179e0266125c3333a097b399d0383734ee6c55fbadf332b447237a596e9698f + md5: bffe599d0eb2e78a32872712178e639c depends: - ucrt >=10.0.20348.0 - vc >=14.3,<15 - vc14_runtime >=14.44.35208 constrains: + - openmp 22.1.5|22.1.5.* - intel-openmp <0.0a0 - - openmp 22.1.4|22.1.4.* license: Apache-2.0 WITH LLVM-exception + license_family: APACHE purls: [] - size: 347999 - timestamp: 1776846360348 + size: 347493 + timestamp: 1778448334890 - pypi: https://files.pythonhosted.org/packages/1c/d4/33c8af00f0bf6f552d74f3a054f648af2c5bc6bece97972f3bfadce4f5ec/llvmlite-0.47.0-cp314-cp314-macosx_12_0_arm64.whl name: llvmlite version: 0.47.0 @@ -12096,10 +12139,10 @@ packages: purls: [] size: 139891 timestamp: 1733741168264 -- pypi: https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-py3-none-any.whl name: markdown-it-py - version: 4.0.0 - sha256: 87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147 + version: 4.2.0 + sha256: 9f7ebbcd14fe59494226453aed97c1070d83f8d24b6fc3a3bcf9a38092641c4a requires_dist: - mdurl~=0.1 - psutil ; extra == 'benchmarking' @@ -12127,6 +12170,7 @@ packages: - pytest ; extra == 'testing' - pytest-cov ; extra == 'testing' - pytest-regressions ; extra == 'testing' + - pytest-timeout ; extra == 'testing' - requests ; extra == 'testing' requires_python: '>=3.10' - conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.3-py314h67df5f8_1.conda @@ -12158,7 +12202,7 @@ packages: license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/markupsafe?source=compressed-mapping + - pkg:pypi/markupsafe?source=hash-mapping size: 27256 timestamp: 1772445397216 - conda: https://conda.anaconda.org/conda-forge/win-64/markupsafe-3.0.3-py314h2359020_1.conda @@ -12178,11 +12222,11 @@ packages: - pkg:pypi/markupsafe?source=hash-mapping size: 30022 timestamp: 1772445159549 -- conda: https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.10.8-py314hdafbbf9_0.conda - sha256: 0c9417291ada8df3415ad13d52db38707adaba42584246264294e0faaaa54f77 - md5: 8286e3966eac286d5ac7c7a4afbac812 +- conda: https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.10.9-py314hdafbbf9_0.conda + sha256: afe4442ffad64b9a0b26dbcc6aec6dfb9cf453256b50f4e96bada055d014e29c + md5: 2046de06d7f4149a29c5d0e2cc26d6dd depends: - - matplotlib-base >=3.10.8,<3.10.9.0a0 + - matplotlib-base >=3.10.9,<3.10.10.0a0 - pyside6 >=6.7.2 - python >=3.14,<3.15.0a0 - python_abi 3.14.* *_cp314 @@ -12190,26 +12234,26 @@ packages: license: PSF-2.0 license_family: PSF purls: [] - size: 17473 - timestamp: 1763055464987 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/matplotlib-3.10.8-py314he55896b_0.conda - sha256: 070b99e48cd6dda06086116626203c100e6f34af771b34384848ce5abeaf683e - md5: ad9a3f773f13989b92b41c0eabed5a38 + size: 17876 + timestamp: 1777000591578 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/matplotlib-3.10.9-py314he55896b_0.conda + sha256: eeb9253f5a6c1a5b1251076088a4180f456a2d01048629ac1dc376d2f404e14a + md5: 553de53f80d4eeef68ff2b2ec225ed5f depends: - - matplotlib-base >=3.10.8,<3.10.9.0a0 + - matplotlib-base >=3.10.9,<3.10.10.0a0 - python >=3.14,<3.15.0a0 - python_abi 3.14.* *_cp314 - tornado >=5 license: PSF-2.0 license_family: PSF purls: [] - size: 17538 - timestamp: 1763055987021 -- conda: https://conda.anaconda.org/conda-forge/win-64/matplotlib-3.10.8-py314h86ab7b2_0.conda - sha256: e7b6349b12f7d98ab7b595e01e486d3544083c694e8ee2c45a0b8f17016a7a0a - md5: e786fc5fefad7779cb2d954dd214fa37 + size: 17814 + timestamp: 1777001592449 +- conda: https://conda.anaconda.org/conda-forge/win-64/matplotlib-3.10.9-py314h86ab7b2_0.conda + sha256: d253757f01fa3942d5bf0237a6e1f5e885b4776c41c966da5d3001b7a5857359 + md5: d0f5a1cd8f44e35e736e117c7b6b8fb2 depends: - - matplotlib-base >=3.10.8,<3.10.9.0a0 + - matplotlib-base >=3.10.9,<3.10.10.0a0 - pyside6 >=6.7.2 - python >=3.14,<3.15.0a0 - python_abi 3.14.* *_cp314 @@ -12217,11 +12261,11 @@ packages: license: PSF-2.0 license_family: PSF purls: [] - size: 18016 - timestamp: 1763056036732 -- conda: https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.10.8-py314h1194b4b_0.conda - sha256: ee773261fbd6c76fc8174b0e4e1ce272b0bbaa56610f130e9d3d1f575106f04f - md5: b8683e6068099b69c10dbfcf7204203f + size: 18161 + timestamp: 1777000845679 +- conda: https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.10.9-py314h1194b4b_0.conda + sha256: 94599b0ca937530f7c7ba1e394cbe8420db613da2524bd0000988e9bbe118f0a + md5: 11a821746ad11e642fcc615c3d66aa44 depends: - __glibc >=2.17,<3.0.a0 - contourpy >=1.0.1 @@ -12229,8 +12273,8 @@ packages: - fonttools >=4.22.0 - freetype - kiwisolver >=1.3.1 - - libfreetype >=2.14.1 - - libfreetype6 >=2.14.1 + - libfreetype >=2.14.3 + - libfreetype6 >=2.14.3 - libgcc >=14 - libstdcxx >=14 - numpy >=1.23 @@ -12247,11 +12291,11 @@ packages: license_family: PSF purls: - pkg:pypi/matplotlib?source=hash-mapping - size: 8473358 - timestamp: 1763055439346 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/matplotlib-base-3.10.8-py314hd63e3f0_0.conda - sha256: 198dcc0ed83e78bc7bf48e6ef8d4ecd220e9cf1f07db98508251b2bc0be067f9 - md5: c84152e510d41378b8758826655b6ed7 + size: 8545652 + timestamp: 1777000575998 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/matplotlib-base-3.10.9-py314hc042b31_0.conda + sha256: 8c1912582f457a40e39b9770dc2417c804f5ab1eb1ce73860d24a1414fb56145 + md5: 3252e58ac5ade3ba2dacd5dacfa6e7b8 depends: - __osx >=11.0 - contourpy >=1.0.1 @@ -12260,8 +12304,8 @@ packages: - freetype - kiwisolver >=1.3.1 - libcxx >=19 - - libfreetype >=2.14.1 - - libfreetype6 >=2.14.1 + - libfreetype >=2.14.3 + - libfreetype6 >=2.14.3 - numpy >=1.23 - numpy >=1.23,<3 - packaging >=20.0 @@ -12276,19 +12320,19 @@ packages: license_family: PSF purls: - pkg:pypi/matplotlib?source=hash-mapping - size: 8286510 - timestamp: 1763055937766 -- conda: https://conda.anaconda.org/conda-forge/win-64/matplotlib-base-3.10.8-py314hfa45d96_0.conda - sha256: 82a50284275e8a1818cd3323846f3032dc89bd23a3f80dcf44e34a62b016256b - md5: 9d491a60700e0e90e92607fcc4e2566c + size: 8315491 + timestamp: 1777001530326 +- conda: https://conda.anaconda.org/conda-forge/win-64/matplotlib-base-3.10.9-py314hfa45d96_0.conda + sha256: 9c98854165e99e50aaf3761f1f9efc4e230f0c82bd357ab3426d359de9169441 + md5: f51114063f7f5abd404cff82054e7af2 depends: - contourpy >=1.0.1 - cycler >=0.10 - fonttools >=4.22.0 - freetype - kiwisolver >=1.3.1 - - libfreetype >=2.14.1 - - libfreetype6 >=2.14.1 + - libfreetype >=2.14.3 + - libfreetype6 >=2.14.3 - numpy >=1.23 - numpy >=1.23,<3 - packaging >=20.0 @@ -12305,11 +12349,11 @@ packages: license_family: PSF purls: - pkg:pypi/matplotlib?source=hash-mapping - size: 8185296 - timestamp: 1763055983613 -- conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.2.1-pyhd8ed1ab_0.conda - sha256: 9d690334de0cd1d22c51bc28420663f4277cfa60d34fa5cad1ce284a13f1d603 - md5: 00e120ce3e40bad7bfc78861ce3c4a25 + size: 8263442 + timestamp: 1777000826825 +- conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.2.2-pyhd8ed1ab_0.conda + sha256: 35b43d7343f74452307fd018a1cca92b8f68961ff8e2ab6a81ce0a703c9a3764 + md5: 9acc1c385be401d533ff70ef5b50dae6 depends: - python >=3.10 - traitlets @@ -12317,8 +12361,8 @@ packages: license_family: BSD purls: - pkg:pypi/matplotlib-inline?source=hash-mapping - size: 15175 - timestamp: 1761214578417 + size: 15725 + timestamp: 1778264403247 - pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl name: mdurl version: 0.1.2 @@ -12337,9 +12381,9 @@ packages: - pkg:pypi/memory-profiler?source=hash-mapping size: 36168 timestamp: 1764885507963 -- conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.2.0-pyhcf101f3_0.conda - sha256: d3fb4beb5e0a52b6cc33852c558e077e1bfe44df1159eb98332d69a264b14bae - md5: b11e360fc4de2b0035fc8aaa74f17fd6 +- conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.2.1-pyhcf101f3_0.conda + sha256: b52dc6c78fbbe7a3008535cb8bfd87d70d8053e9250bbe16e387470a9df07070 + md5: b97e84d1553b4a1c765b87fff83453ad depends: - python >=3.10 - typing_extensions @@ -12348,23 +12392,23 @@ packages: license_family: BSD purls: - pkg:pypi/mistune?source=hash-mapping - size: 74250 - timestamp: 1766504456031 -- conda: https://conda.anaconda.org/conda-forge/win-64/mkl-2025.3.1-hac47afa_12.conda - sha256: d7b8343e10053c8527e2e20fd96787d368c97129ffa799e863069a36bd299457 - md5: a3b1ee571898432da7e13ecb7bfd76ae + size: 74567 + timestamp: 1777824616382 +- conda: https://conda.anaconda.org/conda-forge/win-64/mkl-2026.0.0-hac47afa_906.conda + sha256: 5d6c0c02588a655aaaced67f25d1967810830d4336865e319f32cfb41d08de06 + md5: fada5d30be6e95c74ffc528f70268f02 depends: - - llvm-openmp >=22.1.4 - - onemkl-license 2025.3.1 h57928b3_12 - - tbb >=2022.3.0 + - llvm-openmp >=22.1.5 + - onemkl-license 2026.0.0 h57928b3_906 + - tbb >=2023.0.0 - ucrt >=10.0.20348.0 - vc >=14.3,<15 - vc14_runtime >=14.44.35208 license: LicenseRef-IntelSimplifiedSoftwareOct2022 license_family: Proprietary purls: [] - size: 100112670 - timestamp: 1776904283842 + size: 114608976 + timestamp: 1778776186500 - pypi: https://files.pythonhosted.org/packages/72/4e/1339dc6e2557a344f5ba5590872e80346f76f6cb2ac3dd16e4666e88818c/ml_dtypes-0.5.4-cp314-cp314-macosx_10_13_universal2.whl name: ml-dtypes version: 0.5.4 @@ -12426,6 +12470,15 @@ packages: - sphinx ; extra == 'docs' - gmpy2>=2.1.0a4 ; platform_python_implementation != 'PyPy' and extra == 'gmpy' - pytest>=4.6 ; extra == 'tests' +- pypi: https://files.pythonhosted.org/packages/25/1f/cca084ca2572810fff12ea9dbdcbe39eac048f40daf4a9077b49fcbe8cee/msgspec-0.21.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + name: msgspec + version: 0.21.1 + sha256: 3d6b9dc50948eaf65df54d2fd0ff66e6d8c32f116037209ee861810eb9b676cb + requires_dist: + - tomli ; python_full_version < '3.11' and extra == 'toml' + - tomli-w ; extra == 'toml' + - pyyaml ; extra == 'yaml' + requires_python: '>=3.10' - conda: https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyhd8ed1ab_1.conda sha256: d09c47c2cf456de5c09fa66d2c3c5035aa1fa228a1983a433c47b876aa16ce90 md5: 37293a85a0f4f77bbd9cf7aaefc62609 @@ -12437,9 +12490,9 @@ packages: - pkg:pypi/munkres?source=hash-mapping size: 15851 timestamp: 1749895533014 -- conda: https://conda.anaconda.org/conda-forge/noarch/mystmd-1.8.3-pyhcf101f3_0.conda - sha256: eef3ec4ad6f83e8e20a84d18bd6a70ca21a38ee4144a5a6c010a20574d40f292 - md5: a44086c58b2f81dc318ded3c107d6afd +- conda: https://conda.anaconda.org/conda-forge/noarch/mystmd-1.9.0-pyhcf101f3_0.conda + sha256: 3013798e47c4cc6dacd1a780ce4300141762611a23ef6e4327609045a7389e8a + md5: cdb96e61fa01be8d8aa62e96d4d98821 depends: - python >=3.10 - nodejs >=18 @@ -12448,20 +12501,19 @@ packages: license_family: MIT purls: - pkg:pypi/mystmd?source=hash-mapping - size: 2183857 - timestamp: 1775082702883 -- conda: https://conda.anaconda.org/conda-forge/noarch/narwhals-2.20.0-pyhcf101f3_0.conda - sha256: 676cbfbf709ce984a14e3af5aef70f1ec94a29ea3fdec477115ae302b34a1a2d - md5: 6cac1a50359219d786453c6fef819f98 + size: 2184222 + timestamp: 1777661767917 +- conda: https://conda.anaconda.org/conda-forge/noarch/narwhals-2.21.2-pyhcf101f3_0.conda + sha256: 70f43d62450927d51673eecd8823e14f5b3cfebdb43cda1d502eba97162bab42 + md5: 6687827c332121727ce383919e1ec8c2 depends: - python >=3.10 - python license: MIT - license_family: MIT purls: - pkg:pypi/narwhals?source=compressed-mapping - size: 283664 - timestamp: 1776691974709 + size: 284323 + timestamp: 1778929680962 - conda: https://conda.anaconda.org/conda-forge/noarch/nbclient-0.10.4-pyhd8ed1ab_0.conda sha256: 1b66960ee06874ddceeebe375d5f17fb5f393d025a09e15b830ad0c4fffb585b md5: 00f5b8dafa842e0c27c1cd7296aa4875 @@ -12474,7 +12526,7 @@ packages: license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/nbclient?source=compressed-mapping + - pkg:pypi/nbclient?source=hash-mapping size: 28473 timestamp: 1766485646962 - conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.17.1-pyhcf101f3_0.conda @@ -12504,7 +12556,7 @@ packages: license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/nbconvert?source=compressed-mapping + - pkg:pypi/nbconvert?source=hash-mapping size: 202229 timestamp: 1775615493260 - conda: https://conda.anaconda.org/conda-forge/noarch/nbformat-5.10.4-pyhd8ed1ab_1.conda @@ -12522,25 +12574,25 @@ packages: - pkg:pypi/nbformat?source=hash-mapping size: 100945 timestamp: 1733402844974 -- conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda - sha256: 3fde293232fa3fca98635e1167de6b7c7fda83caf24b9d6c91ec9eefb4f4d586 - md5: 47e340acb35de30501a76c7c799c41d7 +- conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.6-hdb14827_0.conda + sha256: fc89f74bbe362fb29fa3c037697a89bec140b346a2469a90f7936d1d7ea4d8a3 + md5: fc21868a1a5aacc937e7a18747acb8a5 depends: - __glibc >=2.17,<3.0.a0 - - libgcc >=13 + - libgcc >=14 license: X11 AND BSD-3-Clause purls: [] - size: 891641 - timestamp: 1738195959188 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/ncurses-6.5-h5e97a16_3.conda - sha256: 2827ada40e8d9ca69a153a45f7fd14f32b2ead7045d3bbb5d10964898fe65733 - md5: 068d497125e4bf8a66bf707254fff5ae + size: 918956 + timestamp: 1777422145199 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/ncurses-6.6-h1d4f5a5_0.conda + sha256: 4ea6c620b87bd1d42bb2ccc2c87cd2483fa2d7f9e905b14c223f11ff3f4c455d + md5: 343d10ed5b44030a2f67193905aea159 depends: - __osx >=11.0 license: X11 AND BSD-3-Clause purls: [] - size: 797030 - timestamp: 1738196177597 + size: 805509 + timestamp: 1777423252320 - conda: https://conda.anaconda.org/conda-forge/noarch/nest-asyncio-1.6.0-pyhd8ed1ab_1.conda sha256: bb7b21d7fd0445ddc0631f64e66d91a179de4ba920b8381f29b9d006a42788c0 md5: 598fd7d4d0de2455fb74f56063969a97 @@ -12694,76 +12746,73 @@ packages: - pkg:pypi/notebook-shim?source=hash-mapping size: 16817 timestamp: 1733408419340 -- pypi: https://files.pythonhosted.org/packages/24/8d/e12d6ff4b9119db3cbf7b2db1ce257576441bd3c76388c786dea74f20b02/numba-0.65.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/4f/2e/8aed9b726d9ba5f11ad287645fd479e88278db3060a25cb1225d730eb2b7/numba-0.65.1-cp314-cp314-macosx_12_0_arm64.whl name: numba - version: 0.65.0 - sha256: 05c0a9fdf75d85f57dee47b719e8d6415707b80aae45d75f63f9dc1b935c29f7 + version: 0.65.1 + sha256: 33f5eb68eb1c843511615d14663ce60258525d6a4c65ab040e2c2b0c4cf17450 requires_dist: - llvmlite>=0.47.0.dev0,<0.48 - numpy>=1.22 - numpy>=1.22,<2.5 requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/56/a4/90edb01e9176053578e343d7a7276bc28356741ee67059aed8ed2c1a4e59/numba-0.65.0-cp314-cp314-macosx_12_0_arm64.whl +- pypi: https://files.pythonhosted.org/packages/56/46/3f7fc04fb853559e74b210e0b62c19974ec844cefec611f9e535f4da3761/numba-0.65.1-cp314-cp314-win_amd64.whl name: numba - version: 0.65.0 - sha256: ee336b398a6fca51b1f626034de99f50cb1bd87d537a166275158a3cee744b82 + version: 0.65.1 + sha256: 2a20fcdabdefbdacf88d85caf70c3b18c4bcb7ebb8f82e6a19486383dd26ab63 requires_dist: - llvmlite>=0.47.0.dev0,<0.48 - numpy>=1.22 - numpy>=1.22,<2.5 requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/73/5b/fbce55ce3d933afbc7ade04df826853e4a846aaa47d58d2fbb669b8f2d08/numba-0.65.0-cp314-cp314-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/87/96/f3eb235fafa82a34e2ab5dd7dc9ffff998ebf5f0bbc23fa56a96aeb44da6/numba-0.65.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl name: numba - version: 0.65.0 - sha256: add297d3e1c08dd884f44100152612fa41e66a51d15fdf91307f9dde31d06830 + version: 0.65.1 + sha256: 71e73029bf53a62cc6afcf96be4bd942290d8b4c55f0a454fb536158115790f7 requires_dist: - llvmlite>=0.47.0.dev0,<0.48 - numpy>=1.22 - numpy>=1.22,<2.5 requires_python: '>=3.10' -- conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.4.3-py314h2b28147_0.conda - sha256: f2ba8cb0d86a6461a6bcf0d315c80c7076083f72c6733c9290086640723f79ec - md5: 36f5b7eb328bdc204954a2225cf908e2 +- conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.4.5-py314h2b28147_0.conda + sha256: 8e796bac2558ff5f2d00a2d0bbe4821d518347a8f70afb53b5acf27adb135197 + md5: 64a8d5cd0553d51590a304a28c184785 depends: - python - libstdcxx >=14 - libgcc >=14 - __glibc >=2.17,<3.0.a0 - - python_abi 3.14.* *_cp314 - - libcblas >=3.9.0,<4.0a0 - liblapack >=3.9.0,<4.0a0 + - python_abi 3.14.* *_cp314 - libblas >=3.9.0,<4.0a0 + - libcblas >=3.9.0,<4.0a0 constrains: - numpy-base <0a0 license: BSD-3-Clause - license_family: BSD purls: - - pkg:pypi/numpy?source=hash-mapping - size: 8927860 - timestamp: 1773839233468 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/numpy-2.4.3-py314h1569ea8_0.conda - sha256: fe565b09011e8b8edb11bc20564ab130b107d4717590c2464d6d7c2a5a53c6da - md5: 0fab9cf4fc5163131387f36742b50c79 + - pkg:pypi/numpy?source=compressed-mapping + size: 8930431 + timestamp: 1778894356567 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/numpy-2.4.5-py314hb79c6fa_0.conda + sha256: 97a9c791cd8f27711b8a73dfcd895b3915e065f05366c2d868524f43d299273a + md5: 38411f00bb92c9cfbd7760037c5d647b depends: - python - libcxx >=19 - - python 3.14.* *_cp314 - __osx >=11.0 + - python_abi 3.14.* *_cp314 - libblas >=3.9.0,<4.0a0 - liblapack >=3.9.0,<4.0a0 - - python_abi 3.14.* *_cp314 - libcblas >=3.9.0,<4.0a0 constrains: - numpy-base <0a0 license: BSD-3-Clause - license_family: BSD purls: - - pkg:pypi/numpy?source=hash-mapping - size: 6993182 - timestamp: 1773839150339 -- conda: https://conda.anaconda.org/conda-forge/win-64/numpy-2.4.3-py314h02f10f6_0.conda - sha256: e4afa67a7350836a1d652f8e7351fe4cb853f8eb8b5c86c9203cefff67669083 - md5: 54355aaff5c94c602b7b9540fbc3ca1d + - pkg:pypi/numpy?source=compressed-mapping + size: 6993432 + timestamp: 1778894368347 +- conda: https://conda.anaconda.org/conda-forge/win-64/numpy-2.4.5-py314h02f10f6_0.conda + sha256: 44a4d599511779ecb615ca4101e9a7b93d4c5bb6d8c1ccbd951ff27084a8fd87 + md5: 212966402b4911c332087a17254f386c depends: - python - vc >=14.3,<15 @@ -12776,11 +12825,10 @@ packages: constrains: - numpy-base <0a0 license: BSD-3-Clause - license_family: BSD purls: - - pkg:pypi/numpy?source=hash-mapping - size: 7311362 - timestamp: 1773839141373 + - pkg:pypi/numpy?source=compressed-mapping + size: 7315426 + timestamp: 1778894390109 - conda: https://conda.anaconda.org/conda-forge/noarch/numpy-typing-compat-20251206.2.4-pyhd6139ff_0.conda sha256: 7e5d83982e309d0e4196f3d3d370d91eb6f2a24336c44fcc8c0b6dc903486983 md5: 5d93ad80a62295da5f6950227ea72c66 @@ -12795,10 +12843,10 @@ packages: - pkg:pypi/numpy-typing-compat?source=hash-mapping size: 13975 timestamp: 1767188739549 -- pypi: https://files.pythonhosted.org/packages/3d/ec/c9b2998aebe3149dee2769e501257e048c8701de51263925f4dff76ddedc/nvidia_cublas-13.4.0.1-py3-none-manylinux_2_27_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/f8/79/0cefdaa1d9e45018a227bac64a79b92d2733cde28a8fd09c65362de08622/nvidia_cublas-13.4.1.1-py3-none-manylinux_2_27_x86_64.whl name: nvidia-cublas - version: 13.4.0.1 - sha256: 53bf22e2ccbf644db74b6cc21cea7f5efb1a52aa64515438b430abbd05af4106 + version: 13.4.1.1 + sha256: 28c983c8c03aa9a2d7b36cddcef2bfeeea85e13241d77df7622665502159f347 requires_dist: - nvidia-cuda-nvrtc requires_python: '>=3' @@ -12868,17 +12916,17 @@ packages: version: 12.9.79 sha256: 25bba2dfb01d48a9b59ca474a1ac43c6ebf7011f1b0b8cc44f54eb6ac48a96c3 requires_python: '>=3' -- pypi: https://files.pythonhosted.org/packages/3c/8e/0c3aeef5f8a9507ad0b2d6fb3f28f38997cda1c7e7f614adc00ceb64a901/nvidia_cudnn_cu12-9.21.1.3-py3-none-manylinux_2_27_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/a0/8f/2ede6b758b7524608472010f632bdd3370ea271d715d1d66044614b84cdc/nvidia_cudnn_cu12-9.22.0.52-py3-none-manylinux_2_27_x86_64.whl name: nvidia-cudnn-cu12 - version: 9.21.1.3 - sha256: ffd5bf372071423c441f1de01af35abd6b6f3921a8ab80b23db8ba69a12131b0 + version: 9.22.0.52 + sha256: 391b9a7ee6386daaca7f8dca41e83c2c99f760c9581a0400755e87b4287b8847 requires_dist: - nvidia-cublas-cu12 requires_python: '>=3' -- pypi: https://files.pythonhosted.org/packages/85/be/68e659d798aaad24817f10acbad952491b43df668376b704618296887f62/nvidia_cudnn_cu13-9.21.1.3-py3-none-manylinux_2_27_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/57/96/ce2cb84b5e8bb94dd55f554e3454b91e9ecd6708aa27d4a7b12f287613bc/nvidia_cudnn_cu13-9.22.0.52-py3-none-manylinux_2_27_x86_64.whl name: nvidia-cudnn-cu13 - version: 9.21.1.3 - sha256: 7fa0250b9b0841d4ce41486d777d01160247235ddb8e2ea89e5fcfa4b4c66cd4 + version: 9.22.0.52 + sha256: 7b24277af8cd2e4e5be731f5cf910255105d4b92481999771b99dbffee75d03e requires_dist: - nvidia-cublas requires_python: '>=3' @@ -12967,14 +13015,14 @@ packages: version: 13.2.78 sha256: f5aa433631109bbdec81802c5b5f319bf10bc891fe2f212e4e445845211d6f77 requires_python: '>=3' -- conda: https://conda.anaconda.org/conda-forge/win-64/onemkl-license-2025.3.1-h57928b3_12.conda - sha256: 03eac4174077397a5bc480021e62412e73e80f34072d81053899f65dfe1045c7 - md5: 29ad104e60faa7ed1dc549ec029764bb +- conda: https://conda.anaconda.org/conda-forge/win-64/onemkl-license-2026.0.0-h57928b3_906.conda + sha256: 2c62b4b31da810043a47014a410c546015fcc17f39d8929ba989b2f0086dc71f + md5: 331614e966c27e5ec2a9715c9d17e9a0 license: LicenseRef-IntelSimplifiedSoftwareOct2022 license_family: Proprietary purls: [] - size: 40890 - timestamp: 1776904134221 + size: 41154 + timestamp: 1778775952813 - conda: https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.5.4-h55fea9a_0.conda sha256: 3900f9f2dbbf4129cf3ad6acf4e4b6f7101390b53843591c53b00f034343bc4d md5: 11b3379b191f63139e29c0d19dee24cd @@ -13091,13 +13139,14 @@ packages: - sqlalchemy>=1.3 - typing-extensions requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/fe/16/00261f20f467b9e8950a76ec1749f01359bf47f2fc3dac5e206de99835c0/optree-0.19.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/9c/1a/4834b1f2fb1847412353d7342eb7a1d001a4f3bd9d24155e057135a4aa44/optree-0.19.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl name: optree - version: 0.19.0 - sha256: fb220bb85128c8de71aeffb9c38be817569e4bca413b38d5e0de11ba6471ef4a + version: 0.19.1 + sha256: 3d0e1493429ae1d1a5e34855774ee604c974a8f76656bd0e562cdbf9466c9b1f requires_dist: - typing-extensions>=4.6.0 - typing-extensions>=4.12.0 ; python_full_version >= '3.13' + - attrs ; extra == 'attrs' - jax ; extra == 'jax' - numpy ; extra == 'numpy' - torch ; extra == 'torch' @@ -13127,37 +13176,36 @@ packages: - sphinx-rtd-theme ; extra == 'docs' - sphinxcontrib-bibtex ; extra == 'docs' - docutils ; extra == 'docs' + - attrs ; extra == 'docs' - jax[cpu] ; extra == 'docs' - numpy ; extra == 'docs' - torch ; extra == 'docs' requires_python: '>=3.9' -- conda: https://conda.anaconda.org/conda-forge/noarch/optype-0.17.0-pyhc364b38_0.conda - sha256: 3ab9de4022c1ae8e3fe98677e9e599048fbe42ae31e2468ecf0c1c765743fd56 - md5: da9da5767306c6fd9488917e4d0b16c3 +- conda: https://conda.anaconda.org/conda-forge/noarch/optype-0.17.1-pyhc364b38_0.conda + sha256: f24c0f0079e59da6805940f7ef07f162753d7f5f45b119b94c5b7b77d69a3e58 + md5: 778ad501a956ef4097f1745f7d87c877 depends: - python >=3.11 - typing-extensions >=4.10 - python license: BSD-3-Clause - license_family: BSD purls: - pkg:pypi/optype?source=hash-mapping - size: 58603 - timestamp: 1773012417794 -- conda: https://conda.anaconda.org/conda-forge/noarch/optype-numpy-0.17.0-pyhada4073_0.conda - sha256: 0459e7589a7bb76be1230d8a2737548322e8483b5370a1c35a16651f76c64f8b - md5: d756c951c6a80171d8dba6136347c070 + size: 58476 + timestamp: 1779057587870 +- conda: https://conda.anaconda.org/conda-forge/noarch/optype-numpy-0.17.1-pyh3cfb546_0.conda + sha256: 079480a4ad35bf4cc887afac73e0cb6bf6977e99d23c4c0e971435608f485913 + md5: 109f922b55bac042f1f815c80ff617ba depends: - python >=3.11 - numpy >=1.26,<2.7 - numpy-typing-compat >=20250818.1.26,<20251207.1.0 - - optype ==0.17.0 pyhc364b38_0 + - optype ==0.17.1 pyhc364b38_0 - python license: BSD-3-Clause - license_family: BSD purls: [] - size: 10983 - timestamp: 1773012417794 + size: 10746 + timestamp: 1779057587870 - conda: https://conda.anaconda.org/conda-forge/linux-64/orc-2.3.0-h21090e2_0.conda sha256: a60c2578c8422e0c67206d269767feb4d3e627511558b6866e5daf2231d5214d md5: 8027fce94fdfdf2e54f9d18cbae496df @@ -13229,22 +13277,22 @@ packages: - pkg:pypi/overrides?source=hash-mapping size: 30139 timestamp: 1734587755455 -- conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.1-pyhc364b38_0.conda - sha256: 171d977bc977fd80f2a05de3d4b7d571c4ec3cdea436ed364e5cd50547c50881 - md5: b8ae38639d323d808da535fb71e31be8 +- conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.2-pyhc364b38_0.conda + sha256: 3906abfb6511a3bb309e39b9b1b7bc38f50a723971de2395489fd1f379255890 + md5: 4c06a92e74452cfa53623a81592e8934 depends: - python >=3.8 - python license: Apache-2.0 license_family: APACHE purls: - - pkg:pypi/packaging?source=compressed-mapping - size: 89360 - timestamp: 1776209387231 -- pypi: https://files.pythonhosted.org/packages/15/88/3cdd54fa279341afa10acf8d2b503556b1375245dccc9315659f795dd2e9/pandas-3.0.2-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl + - pkg:pypi/packaging?source=hash-mapping + size: 91574 + timestamp: 1777103621679 +- pypi: https://files.pythonhosted.org/packages/38/55/792619469bab9882d8bbd5865d45a72f6478762d04a9af4bf0d08c503e95/pandas-3.0.3-cp314-cp314-win_amd64.whl name: pandas - version: 3.0.2 - sha256: deeca1b5a931fdf0c2212c8a659ade6d3b1edc21f0914ce71ef24456ca7a6535 + version: 3.0.3 + sha256: 3c20a521bbb85902f79f7270c80a59e1b5452d96d170c034f207181870f97ac5 requires_dist: - numpy>=1.26.0 ; python_full_version < '3.14' - numpy>=2.3.3 ; python_full_version >= '3.14' @@ -13292,7 +13340,7 @@ packages: - pyqt5>=5.15.9 ; extra == 'clipboard' - qtpy>=2.4.2 ; extra == 'clipboard' - zstandard>=0.23.0 ; extra == 'compression' - - pytz>=2024.2 ; extra == 'timezone' + - pytz>=2020.1 ; extra == 'timezone' - adbc-driver-postgresql>=1.2.0 ; extra == 'all' - adbc-driver-sqlite>=1.2.0 ; extra == 'all' - beautifulsoup4>=4.12.3 ; extra == 'all' @@ -13318,7 +13366,7 @@ packages: - pytest>=8.3.4 ; extra == 'all' - pytest-xdist>=3.6.1 ; extra == 'all' - python-calamine>=0.3.0 ; extra == 'all' - - pytz>=2024.2 ; extra == 'all' + - pytz>=2020.1 ; extra == 'all' - pyxlsb>=1.0.10 ; extra == 'all' - qtpy>=2.4.2 ; extra == 'all' - scipy>=1.14.1 ; extra == 'all' @@ -13331,10 +13379,10 @@ packages: - xlsxwriter>=3.2.0 ; extra == 'all' - zstandard>=0.23.0 ; extra == 'all' requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/95/25/bdb9326c3b5455f8d4d3549fce7abcf967259de146fe2cf7a82368141948/pandas-3.0.2-cp314-cp314-macosx_11_0_arm64.whl +- pypi: https://files.pythonhosted.org/packages/58/3b/1cdec6772bdbaf7b25dab360c59f03cadf05492dd724c6540af905389b07/pandas-3.0.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl name: pandas - version: 3.0.2 - sha256: 0555c5882688a39317179ab4a0ed41d3ebc8812ab14c69364bbee8fb7a3f6288 + version: 3.0.3 + sha256: 9d71c63ae4ebdbf70209742096f1fc46a83a0613c99d4b23766cced9ff8cd62a requires_dist: - numpy>=1.26.0 ; python_full_version < '3.14' - numpy>=2.3.3 ; python_full_version >= '3.14' @@ -13382,7 +13430,7 @@ packages: - pyqt5>=5.15.9 ; extra == 'clipboard' - qtpy>=2.4.2 ; extra == 'clipboard' - zstandard>=0.23.0 ; extra == 'compression' - - pytz>=2024.2 ; extra == 'timezone' + - pytz>=2020.1 ; extra == 'timezone' - adbc-driver-postgresql>=1.2.0 ; extra == 'all' - adbc-driver-sqlite>=1.2.0 ; extra == 'all' - beautifulsoup4>=4.12.3 ; extra == 'all' @@ -13408,7 +13456,7 @@ packages: - pytest>=8.3.4 ; extra == 'all' - pytest-xdist>=3.6.1 ; extra == 'all' - python-calamine>=0.3.0 ; extra == 'all' - - pytz>=2024.2 ; extra == 'all' + - pytz>=2020.1 ; extra == 'all' - pyxlsb>=1.0.10 ; extra == 'all' - qtpy>=2.4.2 ; extra == 'all' - scipy>=1.14.1 ; extra == 'all' @@ -13421,10 +13469,10 @@ packages: - xlsxwriter>=3.2.0 ; extra == 'all' - zstandard>=0.23.0 ; extra == 'all' requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/db/60/aba6a38de456e7341285102bede27514795c1eaa353bc0e7638b6b785356/pandas-3.0.2-cp314-cp314-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/68/10/bf2d6738d72748b961a3751ab89522d58c54efc36a8e1a12161216cd45cf/pandas-3.0.3-cp314-cp314-macosx_11_0_arm64.whl name: pandas - version: 3.0.2 - sha256: b35d14bb5d8285d9494fe93815a9e9307c0876e10f1e8e89ac5b88f728ec8dcf + version: 3.0.3 + sha256: bab900348131a7db1f69a7309ef141fd5680f1487094193bcbbb61791573bf8f requires_dist: - numpy>=1.26.0 ; python_full_version < '3.14' - numpy>=2.3.3 ; python_full_version >= '3.14' @@ -13472,7 +13520,7 @@ packages: - pyqt5>=5.15.9 ; extra == 'clipboard' - qtpy>=2.4.2 ; extra == 'clipboard' - zstandard>=0.23.0 ; extra == 'compression' - - pytz>=2024.2 ; extra == 'timezone' + - pytz>=2020.1 ; extra == 'timezone' - adbc-driver-postgresql>=1.2.0 ; extra == 'all' - adbc-driver-sqlite>=1.2.0 ; extra == 'all' - beautifulsoup4>=4.12.3 ; extra == 'all' @@ -13498,7 +13546,7 @@ packages: - pytest>=8.3.4 ; extra == 'all' - pytest-xdist>=3.6.1 ; extra == 'all' - python-calamine>=0.3.0 ; extra == 'all' - - pytz>=2024.2 ; extra == 'all' + - pytz>=2020.1 ; extra == 'all' - pyxlsb>=1.0.10 ; extra == 'all' - qtpy>=2.4.2 ; extra == 'all' - scipy>=1.14.1 ; extra == 'all' @@ -13535,9 +13583,9 @@ packages: - pkg:pypi/pandocfilters?source=hash-mapping size: 11627 timestamp: 1631603397334 -- conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.6-pyhcf101f3_0.conda - sha256: 42b2d77ccea60752f3aa929a6413a7835aaacdbbde679f2f5870a744fa836b94 - md5: 97c1ce2fffa1209e7afb432810ec6e12 +- conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.7-pyhcf101f3_0.conda + sha256: 611882f7944b467281c46644ffde6c5145d1a7730388bcde26e7e86819b0998e + md5: 39894c952938276405a1bd30e4ce2caf depends: - python >=3.10 - python @@ -13545,8 +13593,8 @@ packages: license_family: MIT purls: - pkg:pypi/parso?source=hash-mapping - size: 82287 - timestamp: 1770676243987 + size: 82472 + timestamp: 1777722955579 - pypi: https://files.pythonhosted.org/packages/b1/29/c028a0731e202035f0e2e0bfbf1a3e46ad6c628cbb17f6f1cc9eea5d9ff1/pathlib_abc-0.5.2-py3-none-any.whl name: pathlib-abc version: 0.5.2 @@ -13706,7 +13754,7 @@ packages: license: MIT license_family: MIT purls: - - pkg:pypi/platformdirs?source=compressed-mapping + - pkg:pypi/platformdirs?source=hash-mapping size: 25862 timestamp: 1775741140609 - conda: https://conda.anaconda.org/conda-forge/noarch/plotly-6.6.0-pyhd8ed1ab_0.conda @@ -13721,7 +13769,7 @@ packages: license: MIT license_family: MIT purls: - - pkg:pypi/plotly?source=compressed-mapping + - pkg:pypi/plotly?source=hash-mapping size: 5251872 timestamp: 1772628857717 - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.6.0-pyhf9edf01_1.conda @@ -13733,7 +13781,7 @@ packages: license: MIT license_family: MIT purls: - - pkg:pypi/pluggy?source=compressed-mapping + - pkg:pypi/pluggy?source=hash-mapping size: 25877 timestamp: 1764896838868 - pypi: https://files.pythonhosted.org/packages/da/9e/21c4088d1d433cfbf0578f138a6df59527dbd9e9d67355ee31b17e6dc774/portion-2.6.1-py3-none-any.whl @@ -13743,9 +13791,9 @@ packages: requires_dist: - sortedcontainers~=2.2 requires_python: '>=3.9' -- conda: https://conda.anaconda.org/conda-forge/linux-64/prek-0.3.10-hb17b654_0.conda - sha256: 9442c1195eaa4167cc7b21ea3bd1e7ec94129998e606584479588fa18b0591d8 - md5: a8252316cb85fd80192018604181759b +- conda: https://conda.anaconda.org/conda-forge/linux-64/prek-0.4.0-hb17b654_0.conda + sha256: ae3ce60cd7215b04e0499c6986fa821a137ae738dffdd1b1f35cdf7b9e089cdd + md5: 0191ccc9cb65fcb8e00b668ac10d2be7 depends: - __glibc >=2.17,<3.0.a0 - libgcc >=14 @@ -13753,30 +13801,30 @@ packages: - __glibc >=2.17 license: MIT purls: [] - size: 5852661 - timestamp: 1776778241538 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/prek-0.3.10-h6fdd925_0.conda - sha256: 35881b9207adf8ac226e7cca09129dbb147a626f0015e09a4c42bc4fc729d4dd - md5: 2adf0a690252af3b7ef9ef38e88ac7ea + size: 5930558 + timestamp: 1778760368958 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/prek-0.4.0-h6fdd925_0.conda + sha256: 332120f7179e5f4cef56a3bb7bb4887f7ce2ff8f4347e2fe6ef33dd28293249d + md5: e02fa7591f1947d64ff730302c69da56 depends: - __osx >=11.0 constrains: - __osx >=11.0 license: MIT purls: [] - size: 5418706 - timestamp: 1776778575197 -- conda: https://conda.anaconda.org/conda-forge/win-64/prek-0.3.10-h18a1a76_0.conda - sha256: 05ff34464aa9f3dca745bb24e1f3072352190f9dd6ba31739f6d515c2e268b1e - md5: 2d88de0604c4b8145a2305605c99d765 + size: 5524733 + timestamp: 1778760475939 +- conda: https://conda.anaconda.org/conda-forge/win-64/prek-0.4.0-h18a1a76_0.conda + sha256: 4967a93f96809043c01058882c0e5f6c96cbdeb2cf517ff04735e2baf22fee85 + md5: d708a878f88b37234ff3eef6bd5b3d19 depends: - vc >=14.3,<15 - vc14_runtime >=14.44.35208 - ucrt >=10.0.20348.0 license: MIT purls: [] - size: 6214766 - timestamp: 1776778293394 + size: 6283077 + timestamp: 1778760414491 - conda: https://conda.anaconda.org/conda-forge/linux-64/prometheus-cpp-1.3.0-ha5d0236_0.conda sha256: 013669433eb447548f21c3c6b16b2ed64356f726b5f77c1b39d5ba17a8a4b8bc md5: a83f6a2fdc079e643237887a37460668 @@ -13829,7 +13877,7 @@ packages: license: Apache-2.0 license_family: Apache purls: - - pkg:pypi/prometheus-client?source=compressed-mapping + - pkg:pypi/prometheus-client?source=hash-mapping size: 57113 timestamp: 1775771465170 - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.52-pyha770c72_0.conda @@ -13955,8 +14003,8 @@ packages: - python >=3.14,<3.15.0a0 - python_abi 3.14.* *_cp314 license: Apache-2.0 - purls: - - pkg:pypi/pyarrow?source=compressed-mapping + license_family: APACHE + purls: [] size: 26828 timestamp: 1776927974177 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyarrow-24.0.0-py314he55896b_0.conda @@ -13971,6 +14019,7 @@ packages: - python >=3.14,<3.15.0a0 - python_abi 3.14.* *_cp314 license: Apache-2.0 + license_family: APACHE purls: [] size: 26896 timestamp: 1776928739464 @@ -13986,6 +14035,7 @@ packages: - python >=3.14,<3.15.0a0 - python_abi 3.14.* *_cp314 license: Apache-2.0 + license_family: APACHE purls: [] size: 27124 timestamp: 1776928424429 @@ -14005,8 +14055,9 @@ packages: - apache-arrow-proc * cpu - numpy >=1.23,<3 license: Apache-2.0 + license_family: APACHE purls: - - pkg:pypi/pyarrow?source=compressed-mapping + - pkg:pypi/pyarrow?source=hash-mapping size: 4818190 timestamp: 1776927934653 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyarrow-core-24.0.0-py314h109bba2_0_cpu.conda @@ -14025,6 +14076,7 @@ packages: - apache-arrow-proc * cpu - numpy >=1.23,<3 license: Apache-2.0 + license_family: APACHE purls: - pkg:pypi/pyarrow?source=hash-mapping size: 4334926 @@ -14046,6 +14098,7 @@ packages: - numpy >=1.23,<3 - libprotobuf >=6.33.5 license: Apache-2.0 + license_family: APACHE purls: - pkg:pypi/pyarrow?source=hash-mapping size: 3670958 @@ -14081,17 +14134,16 @@ packages: license: BSD-2-Clause license_family: BSD purls: - - pkg:pypi/pygments?source=compressed-mapping + - pkg:pypi/pygments?source=hash-mapping size: 893031 timestamp: 1774796815820 - pypi: ./ name: pylcm - version: 0.0.2.dev233+ge54b802c4.d20260515 - sha256: a5709a048bcaff108fd93227188a39e2ac94a7930d6c789af4a8d2fb6ca4eae8 + version: 0.0.2.dev236+g36a531ce4.d20260518 + sha256: 4cac37f325771919acfcd5274aafebe4f14e219107baed018e75697cf79dd1cd requires_dist: - beartype>=0.21 - cloudpickle>=3.1.2 - - dags>=0.5.1 - h5py>=3.12 - jax>=0.9 - jaxtyping>=0.3.2 @@ -14153,10 +14205,10 @@ packages: - pkg:pypi/pyparsing?source=hash-mapping size: 110893 timestamp: 1769003998136 -- pypi: https://files.pythonhosted.org/packages/5a/dc/491b7661614ab97483abf2056be1deee4dc2490ecbf7bff9ab5cdbac86e1/pyreadline3-3.5.4-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/f7/5e/35c856e186b74678c24927847ad9895a51f1bc02a0c6126477a6c6040064/pyreadline3-3.5.6-py3-none-any.whl name: pyreadline3 - version: 3.5.4 - sha256: eaf8e6cc3c49bcccf145fc6067ba8643d1df34d604a1ec0eccbf7a18e6d3fae6 + version: 3.5.6 + sha256: 8449b734232e42a5dcd74048e39b60db2839a4c38cf3ae2bf7707d58b5389c0d requires_dist: - build ; extra == 'dev' - flake8 ; extra == 'dev' @@ -14164,55 +14216,51 @@ packages: - pytest ; extra == 'dev' - twine ; extra == 'dev' requires_python: '>=3.8' -- conda: https://conda.anaconda.org/conda-forge/linux-64/pyside6-6.11.0-py314h3987850_2.conda - sha256: 7102df4eeb04fb8746b336ddd03d3f98a5c6d4742c173de582e609e2f92ffec8 - md5: c77e1fe23b6cf0b6077e5f924ac420c9 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pyside6-6.11.1-py314h3987850_1.conda + sha256: e410d0d4151f418dc75ea2dc38dfb0e7a136090b6874e5ca1c699fa840b4994d + md5: 5d2051f0630a568926943fc53c0aaa4c depends: - python - - qt6-main 6.11.0.* + - qt6-main 6.11.1.* + - libstdcxx >=14 - libgcc >=14 - __glibc >=2.17,<3.0.a0 - - libstdcxx >=14 + - libvulkan-loader >=1.4.341.0,<2.0a0 + - libgl >=1.7.0,<2.0a0 + - libopengl >=1.7.0,<2.0a0 - libxslt >=1.1.43,<2.0a0 + - libegl >=1.7.0,<2.0a0 - python_abi 3.14.* *_cp314 - - libclang13 >=21.1.8 - - libgl >=1.7.0,<2.0a0 - - qt6-main >=6.11.0,<6.12.0a0 + - qt6-main >=6.11.1,<6.12.0a0 + - libclang13 >=22.1.5 - libxml2 - libxml2-16 >=2.14.6 - - libegl >=1.7.0,<2.0a0 - - libvulkan-loader >=1.4.341.0,<2.0a0 - - libopengl >=1.7.0,<2.0a0 license: LGPL-3.0-only - license_family: LGPL purls: - - pkg:pypi/pyside6?source=hash-mapping - - pkg:pypi/shiboken6?source=hash-mapping - size: 13376566 - timestamp: 1776276343130 -- conda: https://conda.anaconda.org/conda-forge/win-64/pyside6-6.11.0-py314h447aaf0_2.conda - sha256: b60387b44ac5c5fa014cb5f25c710bf77dba2e2f0bdd07f76da406aa9e6fc28b - md5: 1d7f556ad3222af1acff0048d3e25539 + - pkg:pypi/pyside6?source=compressed-mapping + size: 13821776 + timestamp: 1778933872780 +- conda: https://conda.anaconda.org/conda-forge/win-64/pyside6-6.11.1-py314h447aaf0_1.conda + sha256: 070802d5e1e1c1feb24d481efbd90b300fb0ecc1ce4312a3bbcbaae4393c05f9 + md5: 638be6b8674e7acf7a84132903cf4c8e depends: - python - - qt6-main 6.11.0.* + - qt6-main 6.11.1.* - vc >=14.3,<15 - vc14_runtime >=14.44.35208 - ucrt >=10.0.20348.0 + - libxslt >=1.1.43,<2.0a0 - libxml2 - libxml2-16 >=2.14.6 + - qt6-main >=6.11.1,<6.12.0a0 - python_abi 3.14.* *_cp314 - libvulkan-loader >=1.4.341.0,<2.0a0 - - qt6-main >=6.11.0,<6.12.0a0 - - libclang13 >=21.1.8 - - libxslt >=1.1.43,<2.0a0 + - libclang13 >=22.1.5 license: LGPL-3.0-only - license_family: LGPL purls: - - pkg:pypi/pyside6?source=hash-mapping - - pkg:pypi/shiboken6?source=hash-mapping - size: 11209595 - timestamp: 1776276413741 + - pkg:pypi/pyside6?source=compressed-mapping + size: 11579652 + timestamp: 1778933912020 - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyh09c184e_7.conda sha256: d016e04b0e12063fbee4a2d5fbb9b39a8d191b5a0042f0b8459188aedeabb0ca md5: e2fd202833c4a981ce8a65974fe4abd1 @@ -14238,15 +14286,15 @@ packages: - pkg:pypi/pysocks?source=hash-mapping size: 21085 timestamp: 1733217331982 -- pypi: https://files.pythonhosted.org/packages/80/b2/bba963dfce0fcbc5020a4f8b4361e132390c4bd78b46cfc7ae355e678b96/pytask-0.5.8-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/d7/54/c30cb1d08258612ece1dfa72c6918998bebecb916c54fca6d806bc780f2b/pytask-0.6.0-py3-none-any.whl name: pytask - version: 0.5.8 - sha256: 217ed6b3e12140c442afa5e333bbb91e98f8d4fd5c746d323fbbf9778985a92f + version: 0.6.0 + sha256: cc4c31ead39f5c64be037640f7bf589b68bd0e87ea9e1a049ba86ceab42c9d13 requires_dist: - - attrs>=21.3.0 - click>=8.1.8,!=8.2.0 - click-default-group>=1.2.4 - - networkx>=2.4.0 + - msgspec>=0.18.6 + - msgspec[toml]>=0.18.6 - optree>=0.9.0 - packaging>=23.0.0 - pluggy>=1.3.0 @@ -14255,6 +14303,7 @@ packages: - tomli>=1 ; python_full_version < '3.11' - typing-extensions>=4.8.0 ; python_full_version < '3.11' - universal-pathlib>=0.2.2 + - networkx>=2.4.0 ; extra == 'dag' requires_python: '>=3.10' - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-9.0.3-pyhc364b38_1.conda sha256: 960f59442173eee0731906a9077bd5ccf60f4b4226f05a22d1728ab9a21a879c @@ -14274,7 +14323,7 @@ packages: license: MIT license_family: MIT purls: - - pkg:pypi/pytest?source=compressed-mapping + - pkg:pypi/pytest?source=hash-mapping size: 299984 timestamp: 1775644472530 - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-cov-7.1.0-pyhcf101f3_0.conda @@ -14289,7 +14338,7 @@ packages: license: MIT license_family: MIT purls: - - pkg:pypi/pytest-cov?source=compressed-mapping + - pkg:pypi/pytest-cov?source=hash-mapping size: 29559 timestamp: 1774139250481 - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-3.8.0-pyhd8ed1ab_0.conda @@ -14398,20 +14447,19 @@ packages: - pkg:pypi/python-dateutil?source=hash-mapping size: 233310 timestamp: 1751104122689 -- conda: https://conda.anaconda.org/conda-forge/noarch/python-discovery-1.2.2-pyhcf101f3_0.conda - sha256: 498ad019d75ba31c7891dc6d9efc8a7ed48cd5d5973f3a9377eb1b174577d3db - md5: feb2e11368da12d6ce473b6573efab41 +- conda: https://conda.anaconda.org/conda-forge/noarch/python-discovery-1.3.1-pyhcf101f3_0.conda + sha256: 4a44f16a36fec7125b72d5a57bea963fa9deadccf65e29bb5ca610cd1d5cc0af + md5: 45ea5eceb1c2e35a08a834869837a090 depends: - python >=3.10 - filelock >=3.15.4 - platformdirs <5,>=4.3.6 - python license: MIT - license_family: MIT purls: - - pkg:pypi/python-discovery?source=hash-mapping - size: 34341 - timestamp: 1775586706825 + - pkg:pypi/python-discovery?source=compressed-mapping + size: 35067 + timestamp: 1778678120896 - conda: https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.21.2-pyhe01879c_0.conda sha256: df9aa74e9e28e8d1309274648aac08ec447a92512c33f61a8de0afa9ce32ebe8 md5: 23029aae904a2ba587daba708208012f @@ -14434,28 +14482,29 @@ packages: purls: [] size: 49806 timestamp: 1775614307464 -- conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-2.0.7-pyhd8ed1ab_0.conda - sha256: 4790787fe1f4e8da616edca4acf6a4f8ed4e7c6967aa31b920208fc8f95efcca - md5: a61bf9ec79426938ff785eb69dbb1960 +- conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-3.2.1-pyh332efcf_0.conda + sha256: 1c55116c22512cef7b01d55ae49697707f2c1fd829407183c19817e2d300fd8d + md5: 1cd2f3e885162ee1366312bd1b1677fd depends: - - python >=3.6 + - python >=3.10 + - typing_extensions license: BSD-2-Clause license_family: BSD purls: - pkg:pypi/python-json-logger?source=hash-mapping - size: 13383 - timestamp: 1677079727691 -- conda: https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2026.1-pyhd8ed1ab_0.conda - sha256: b5494ef54bc2394c6c4766ceeafac22507c4fc60de6cbfda45524fc2fcc3c9fc - md5: d8d30923ccee7525704599efd722aa16 + size: 18969 + timestamp: 1777318679482 +- conda: https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2026.2-pyhd8ed1ab_0.conda + sha256: e943f9c15a6bdba2e1b9f423ab913b3f6b02197b0ef9f8e6b7464d78b59965b9 + md5: f6ad7450fc21e00ecc23812baed6d2e4 depends: - python >=3.10 license: Apache-2.0 license_family: APACHE purls: - - pkg:pypi/tzdata?source=compressed-mapping - size: 147315 - timestamp: 1775223532978 + - pkg:pypi/tzdata?source=hash-mapping + size: 146639 + timestamp: 1777068997932 - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.14-8_cp314.conda build_number: 8 sha256: ad6d2e9ac39751cc0529dd1566a26751a0bf2542adb0c232533d32e176e21db5 @@ -14632,9 +14681,9 @@ packages: purls: [] size: 1377020 timestamp: 1720814433486 -- conda: https://conda.anaconda.org/conda-forge/linux-64/qt6-main-6.11.0-pl5321h16c4a6b_4.conda - sha256: d2cb212a4abd66c13df44771c22ee23c0b013ba1d5dbb5e10e8a13e261a47c6b - md5: c81127acb50fdc7760682495fc9ab088 +- conda: https://conda.anaconda.org/conda-forge/linux-64/qt6-main-6.11.1-pl5321h16c4a6b_0.conda + sha256: 787d9a8eb7bb993e4a543901b8edade35c1c8e75d67cadb65c56a8f9c38119a5 + md5: cdae26862f9e4c674b8443fd267f2401 depends: - libxcb - xcb-util @@ -14645,100 +14694,100 @@ packages: - xcb-util-cursor - libgl-devel - libegl-devel - - libgcc >=14 - __glibc >=2.17,<3.0.a0 - libstdcxx >=14 - - libvulkan-loader >=1.4.341.0,<2.0a0 - - libwebp-base >=1.6.0,<2.0a0 - - libgl >=1.7.0,<2.0a0 - - libegl >=1.7.0,<2.0a0 - - openssl >=3.5.6,<4.0a0 - - dbus >=1.16.2,<2.0a0 - - libxkbcommon >=1.13.1,<2.0a0 - - pcre2 >=10.47,<10.48.0a0 - - krb5 >=1.22.2,<1.23.0a0 - - fontconfig >=2.17.1,<3.0a0 - - fonts-conda-ecosystem - - libxcb >=1.17.0,<2.0a0 - - zstd >=1.5.7,<1.6.0a0 - - xcb-util >=0.4.1,<0.5.0a0 - - xorg-libxcomposite >=0.4.7,<1.0a0 - - xorg-libxxf86vm >=1.1.7,<2.0a0 - - icu >=78.3,<79.0a0 - - xorg-libxdamage >=1.1.6,<2.0a0 + - libgcc >=14 + - libjpeg-turbo >=3.1.4.1,<4.0a0 + - alsa-lib >=1.2.15.3,<1.3.0a0 - xcb-util-renderutil >=0.3.10,<0.4.0a0 - - xcb-util-image >=0.4.0,<0.5.0a0 - - wayland >=1.25.0,<2.0a0 - - libzlib >=1.3.2,<2.0a0 - - xcb-util-keysyms >=0.4.1,<0.5.0a0 - xorg-libxcursor >=1.2.3,<2.0a0 - double-conversion >=3.4.0,<3.5.0a0 - - alsa-lib >=1.2.15.3,<1.3.0a0 - - xorg-libxext >=1.3.7,<2.0a0 - - harfbuzz >=14.1.0 - - libsqlite >=3.53.0,<4.0a0 - - libfreetype >=2.14.3 - - libfreetype6 >=2.14.3 - - libtiff >=4.7.1,<4.8.0a0 - - libdrm >=2.4.125,<2.5.0a0 - - libjpeg-turbo >=3.1.4.1,<4.0a0 - - xcb-util-wm >=0.4.2,<0.5.0a0 - - libcups >=2.3.3,<2.4.0a0 + - dbus >=1.16.2,<2.0a0 + - libglib >=2.88.1,<3.0a0 + - libsqlite >=3.53.1,<4.0a0 - xorg-libx11 >=1.8.13,<2.0a0 - - libpng >=1.6.58,<1.7.0a0 - - xorg-libxtst >=1.2.5,<2.0a0 + - krb5 >=1.22.2,<1.23.0a0 + - libdrm >=2.4.125,<2.5.0a0 + - xorg-libxext >=1.3.7,<2.0a0 + - libwebp-base >=1.6.0,<2.0a0 + - harfbuzz >=14.2.0 - xorg-libice >=1.1.2,<2.0a0 - - xorg-libxrandr >=1.5.5,<2.0a0 + - xorg-libxdamage >=1.1.6,<2.0a0 - libbrotlicommon >=1.2.0,<1.3.0a0 - libbrotlienc >=1.2.0,<1.3.0a0 - libbrotlidec >=1.2.0,<1.3.0a0 + - xorg-libxrandr >=1.5.5,<2.0a0 + - xcb-util-image >=0.4.0,<0.5.0a0 + - libfreetype >=2.14.3 + - libfreetype6 >=2.14.3 + - xorg-libxcomposite >=0.4.7,<1.0a0 + - openssl >=3.5.6,<4.0a0 - xcb-util-cursor >=0.1.6,<0.2.0a0 - - libxml2 - - libxml2-16 >=2.14.6 + - libvulkan-loader >=1.4.341.0,<2.0a0 + - pcre2 >=10.47,<10.48.0a0 + - xorg-libxxf86vm >=1.1.7,<2.0a0 + - icu >=78.3,<79.0a0 + - libxcb >=1.17.0,<2.0a0 + - wayland >=1.25.0,<2.0a0 - xorg-libsm >=1.2.6,<2.0a0 + - libtiff >=4.7.1,<4.8.0a0 - libpq >=18.3,<19.0a0 - - libglib >=2.86.4,<3.0a0 + - libegl >=1.7.0,<2.0a0 + - xcb-util-wm >=0.4.2,<0.5.0a0 + - xcb-util-keysyms >=0.4.1,<0.5.0a0 + - libxkbcommon >=1.13.1,<2.0a0 + - zstd >=1.5.7,<1.6.0a0 + - fontconfig >=2.17.1,<3.0a0 + - fonts-conda-ecosystem + - xorg-libxtst >=1.2.5,<2.0a0 + - libxml2 + - libxml2-16 >=2.14.6 + - libzlib >=1.3.2,<2.0a0 + - libpng >=1.6.58,<1.7.0a0 + - libcups >=2.3.3,<2.4.0a0 + - libgl >=1.7.0,<2.0a0 + - xcb-util >=0.4.1,<0.5.0a0 constrains: - - qt ==6.11.0 + - qt ==6.11.1 license: LGPL-3.0-only license_family: LGPL purls: [] - size: 59928585 - timestamp: 1776322501700 -- conda: https://conda.anaconda.org/conda-forge/win-64/qt6-main-6.11.0-pl5321hfcac499_4.conda - sha256: c7b7e8f40e393837de5f7f50b8da0ba2d42df64069d1d587b3761fa5e2f7d018 - md5: 1aca2896ea9f0d1f0761a7b278f670a0 + size: 60185269 + timestamp: 1778597122245 +- conda: https://conda.anaconda.org/conda-forge/win-64/qt6-main-6.11.1-pl5321hfcac499_0.conda + sha256: 362210095bc596fa32a38f04384db95b3f71f33c84d617e0d8fd1868dd2a89cd + md5: ae7392b852564d9f4f506dfc4ded3c6a depends: - vc >=14.3,<15 - vc14_runtime >=14.44.35208 - ucrt >=10.0.20348.0 - - libvulkan-loader >=1.4.341.0,<2.0a0 + - harfbuzz >=14.2.0 + - icu >=78.3,<79.0a0 - krb5 >=1.22.2,<1.23.0a0 - - libsqlite >=3.53.0,<4.0a0 + - libwebp-base >=1.6.0,<2.0a0 + - libpng >=1.6.58,<1.7.0a0 + - libsqlite >=3.53.1,<4.0a0 + - libzlib >=1.3.2,<2.0a0 + - double-conversion >=3.4.0,<3.5.0a0 + - zstd >=1.5.7,<1.6.0a0 + - openssl >=3.5.6,<4.0a0 + - libtiff >=4.7.1,<4.8.0a0 - libbrotlicommon >=1.2.0,<1.3.0a0 - libbrotlienc >=1.2.0,<1.3.0a0 - libbrotlidec >=1.2.0,<1.3.0a0 - - libglib >=2.86.4,<3.0a0 - pcre2 >=10.47,<10.48.0a0 - - libpng >=1.6.57,<1.7.0a0 - - icu >=78.3,<79.0a0 - - openssl >=3.5.6,<4.0a0 - - zstd >=1.5.7,<1.6.0a0 - - libjpeg-turbo >=3.1.4.1,<4.0a0 - - double-conversion >=3.4.0,<3.5.0a0 - - libtiff >=4.7.1,<4.8.0a0 - - libzlib >=1.3.2,<2.0a0 + - libglib >=2.88.1,<3.0a0 - libfreetype >=2.14.3 - libfreetype6 >=2.14.3 - - harfbuzz >=14.1.0 - - libwebp-base >=1.6.0,<2.0a0 + - libjpeg-turbo >=3.1.4.1,<4.0a0 + - libvulkan-loader >=1.4.341.0,<2.0a0 constrains: - - qt ==6.11.0 + - qt ==6.11.1 license: LGPL-3.0-only license_family: LGPL purls: [] - size: 89539622 - timestamp: 1776298520993 + size: 89576147 + timestamp: 1778597003415 - pypi: https://files.pythonhosted.org/packages/cb/3b/ef57eeb4c6f188fbe756b602dc6afa9d66eb0c2e77f02a3d4d51bfec2aaa/quantecon-0.11.2-py3-none-any.whl name: quantecon version: 0.11.2 @@ -14826,9 +14875,9 @@ packages: - pkg:pypi/referencing?source=hash-mapping size: 51788 timestamp: 1760379115194 -- conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.33.1-pyhcf101f3_0.conda - sha256: c0249bc4bf4c0e8e06d0e7b4d117a5d593cc4ab2144d5006d6d47c83cb0af18e - md5: 10afbb4dbf06ff959ad25a92ccee6e59 +- conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.34.2-pyhcf101f3_0.conda + sha256: 1715246b19c9f85ee022933b4845f2fc14ac9184981b7b7d9b728bec8e9588da + md5: 4a85203c1d80c1059086ae860836ffb9 depends: - python >=3.10 - certifi >=2023.5.7 @@ -14837,13 +14886,12 @@ packages: - urllib3 >=1.26,<3 - python constrains: - - chardet >=3.0.2,<6 + - chardet >=3.0.2,<8 license: Apache-2.0 - license_family: APACHE purls: - pkg:pypi/requests?source=compressed-mapping - size: 63712 - timestamp: 1774894783063 + size: 68709 + timestamp: 1778851103479 - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3339-validator-0.1.4-pyhd8ed1ab_1.conda sha256: 2e4372f600490a6e0b3bac60717278448e323cab1c0fecd5f43f7c56535a99c5 md5: 36de09a8d3e5d5e6f4ee63af49e59706 @@ -14936,18 +14984,18 @@ packages: - pkg:pypi/rpds-py?source=hash-mapping size: 235780 timestamp: 1764543046065 -- conda: https://conda.anaconda.org/conda-forge/linux-64/s2n-1.7.1-h1cbb8d7_1.conda - sha256: dbbe4ab36b90427f12d69fc14a8b601b6bca4185c6c4dd67b8046a8da9daec03 - md5: 9d978822b57bafe72ebd3f8b527bba71 +- conda: https://conda.anaconda.org/conda-forge/linux-64/s2n-1.7.2-hc5a330e_1.conda + sha256: 856866fd519b812db3e092aba308248dd87b5c308186fcffe593f309373ae94c + md5: 3f578c7d2b0bb52469340e4060d48d94 depends: - __glibc >=2.17,<3.0.a0 - libgcc >=14 - - openssl >=3.5.5,<4.0a0 + - openssl >=3.5.6,<4.0a0 license: Apache-2.0 license_family: Apache purls: [] - size: 395083 - timestamp: 1773251675551 + size: 387306 + timestamp: 1777466173323 - conda: https://conda.anaconda.org/conda-forge/linux-64/scipy-1.17.1-py314hf07bd8e_0.conda sha256: 1ae427836d7979779c9005388a05993a3addabcc66c4422694639a4272d7d972 md5: d0510124f87c75403090e220db1e9d41 @@ -14968,7 +15016,7 @@ packages: license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/scipy?source=compressed-mapping + - pkg:pypi/scipy?source=hash-mapping size: 17225275 timestamp: 1771880751368 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/scipy-1.17.1-py314hfc1f868_0.conda @@ -15079,7 +15127,7 @@ packages: license: MIT license_family: MIT purls: - - pkg:pypi/setuptools?source=compressed-mapping + - pkg:pypi/setuptools?source=hash-mapping size: 639697 timestamp: 1773074868565 - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda @@ -15244,10 +15292,10 @@ packages: purls: [] size: 24008591 timestamp: 1765578833462 -- pypi: https://files.pythonhosted.org/packages/65/44/bb509c3d2c0b5a87e7a5af1d5917a402a32ff026f777a6d7cb6990746cbb/tabcompleter-1.4.0-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/cb/fc/8c82be70b8f96d09943360f34cfb2ecdd3035294c51bce4131eeabe56645/tabcompleter-1.4.1-py3-none-any.whl name: tabcompleter - version: 1.4.0 - sha256: d744aa735b49c0a6cc2fb8fcd40077fec47425e4388301010b14e6ce3311368b + version: 1.4.1 + sha256: 26b5cf330a48f32625b00e1664aa589f67c8e98275b6d9c2b85d19917dac1601 requires_dist: - pyreadline3 ; sys_platform == 'win32' requires_python: '>=3.8' @@ -15263,19 +15311,18 @@ packages: - pkg:pypi/tabulate?source=hash-mapping size: 43964 timestamp: 1772732795746 -- conda: https://conda.anaconda.org/conda-forge/win-64/tbb-2022.3.0-h3155e25_2.conda - sha256: abd9a489f059fba85c8ffa1abdaa4d515d6de6a3325238b8e81203b913cf65a9 - md5: 0f9817ffbe25f9e69ceba5ea70c52606 +- conda: https://conda.anaconda.org/conda-forge/win-64/tbb-2023.0.0-hd3d4ead_2.conda + sha256: 8a4053839b8e997a5965e2dff7d6cf3c77be62d82c0e48c8a04a5ed2d2e73035 + md5: 8ee01a693aecff5432069eaaf1183c45 depends: - - libhwloc >=2.12.2,<2.12.3.0a0 + - libhwloc >=2.13.0,<2.13.1.0a0 - ucrt >=10.0.20348.0 - vc >=14.3,<15 - vc14_runtime >=14.44.35208 license: Apache-2.0 - license_family: APACHE purls: [] - size: 155869 - timestamp: 1767886839029 + size: 156515 + timestamp: 1778673901757 - conda: https://conda.anaconda.org/conda-forge/noarch/terminado-0.18.1-pyh6dadd2b_1.conda sha256: b375e8df0d5710717c31e7c8e93c025c37fa3504aea325c7a55509f64e5d4340 md5: e43ca10d61e55d0a8ec5d8c62474ec9e @@ -15367,6 +15414,11 @@ packages: - pkg:pypi/tomli?source=hash-mapping size: 21561 timestamp: 1774492402955 +- pypi: https://files.pythonhosted.org/packages/c7/18/c86eb8e0202e32dd3df50d43d7ff9854f8e0603945ff398974c1d91ac1ef/tomli_w-1.2.0-py3-none-any.whl + name: tomli-w + version: 1.2.0 + sha256: 188306098d013b691fcadc011abd66727d3c414c571bb01b1a174ba8c983cf90 + requires_python: '>=3.9' - conda: https://conda.anaconda.org/conda-forge/linux-64/tornado-6.5.5-py314h5bd0f2a_0.conda sha256: ed8d06093ff530a2dae9ed1e51eb6f908fbfd171e8b62f4eae782d67b420be5a md5: dc1ff1e915ab35a06b6fa61efae73ab5 @@ -15410,39 +15462,39 @@ packages: - pkg:pypi/tornado?source=hash-mapping size: 914835 timestamp: 1774358183098 -- conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.14.3-pyhd8ed1ab_1.conda - sha256: f39a5620c6e8e9e98357507262a7869de2ae8cc07da8b7f84e517c9fd6c2b959 - md5: 019a7385be9af33791c989871317e1ed +- conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.15.0-pyhcf101f3_0.conda + sha256: dfb681579be59c2e790c95f7f49b7529a9b0511d6385ad276e3c8988cbd54d2c + md5: 4bada6a6d908a27262af8ebddf4f7492 depends: - - python >=3.9 + - python >=3.10 + - python license: BSD-3-Clause license_family: BSD purls: - pkg:pypi/traitlets?source=hash-mapping - size: 110051 - timestamp: 1733367480074 -- conda: https://conda.anaconda.org/conda-forge/linux-64/ty-0.0.32-h4e94fc0_0.conda + size: 115165 + timestamp: 1778074251714 +- conda: https://conda.anaconda.org/conda-forge/linux-64/ty-0.0.37-h4e94fc0_0.conda noarch: python - sha256: 5bfdb00561609b5c065a3b8cce3f95dd2c66de4cca9150d7b7819979b4ffe518 - md5: 4e3454fe01079cc508d75ba1b7fcd916 + sha256: 6c78e14e693fd071b7ac0f4caee14182a34c24eb67f71a6e8b64c5c666d47ab0 + md5: a9d531eb76731b39c6919a907ec74153 depends: - python - - __glibc >=2.17,<3.0.a0 - libgcc >=14 + - __glibc >=2.17,<3.0.a0 - _python_abi3_support 1.* - cpython >=3.10 constrains: - __glibc >=2.17 license: MIT - license_family: MIT purls: - pkg:pypi/ty?source=hash-mapping - size: 9600067 - timestamp: 1776721452584 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/ty-0.0.32-hdfcc030_0.conda + size: 9809531 + timestamp: 1778924736785 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/ty-0.0.37-hdfcc030_0.conda noarch: python - sha256: df97a07d76c93cb5849b22422e05ef94e1b17b659c8cc6078d2abdf0ebeb09e7 - md5: 318569c90d67843f52be5570a88e494f + sha256: 20ec9265a68590829049db32ea4cdb8f62694fce33515962eebb21ee1b96b379 + md5: 8ba816369178d668776075e749214c38 depends: - python - __osx >=11.0 @@ -15451,15 +15503,14 @@ packages: constrains: - __osx >=11.0 license: MIT - license_family: MIT purls: - pkg:pypi/ty?source=hash-mapping - size: 8649328 - timestamp: 1776721470806 -- conda: https://conda.anaconda.org/conda-forge/win-64/ty-0.0.32-hc21aad4_0.conda + size: 8832640 + timestamp: 1778924753670 +- conda: https://conda.anaconda.org/conda-forge/win-64/ty-0.0.37-hc21aad4_0.conda noarch: python - sha256: 32ddaab3bf4a6f6a70b15ac477140f2a2f00ad016e998228e1ba0eb4745af4af - md5: a54c912498faf708aefbd1a9ae111a79 + sha256: 9c254e409ea4d419263b0b6432ad908dfb01105dca50e287523115a4b2127179 + md5: bd5d50945659ab7b1c90a49fdf14ead7 depends: - python - vc >=14.3,<15 @@ -15468,21 +15519,21 @@ packages: - _python_abi3_support 1.* - cpython >=3.10 license: MIT - license_family: MIT purls: - pkg:pypi/ty?source=hash-mapping - size: 9634314 - timestamp: 1776721486913 -- conda: https://conda.anaconda.org/conda-forge/noarch/types-pytz-2026.1.1.20260408-pyhd8ed1ab_0.conda - sha256: f7dca54c2e9b1ec9c8a12931128a78b3a75c4b511dd2688b1f06bfe8880df3ae - md5: 792307c4c22804c7ce39900e518f0b20 + size: 9843839 + timestamp: 1778924768700 +- conda: https://conda.anaconda.org/conda-forge/noarch/types-pytz-2026.2.0.20260506-pyhcf101f3_0.conda + sha256: 7c4a1234a4ce213a65574f6080b41693965d00892edce77237ea110efb98a805 + md5: 464d6104d7802b4c92c39c0a6b19370f depends: - python >=3.10 + - python license: Apache-2.0 AND MIT purls: - pkg:pypi/types-pytz?source=hash-mapping - size: 20040 - timestamp: 1775643470173 + size: 20355 + timestamp: 1778054628795 - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda sha256: 7c2df5721c742c2a47b2c8f960e718c930031663ac1174da67c1ed5999f7938c md5: edd329d7d3a4ab45dcf905899a7a6115 @@ -15619,9 +15670,9 @@ packages: - pkg:pypi/uri-template?source=hash-mapping size: 23990 timestamp: 1733323714454 -- conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.6.3-pyhd8ed1ab_0.conda - sha256: af641ca7ab0c64525a96fd9ad3081b0f5bcf5d1cbb091afb3f6ed5a9eee6111a - md5: 9272daa869e03efe68833e3dc7a02130 +- conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.7.0-pyhd8ed1ab_0.conda + sha256: feff959a816f7988a0893201aa9727bbb7ee1e9cec2c4f0428269b489eb93fb4 + md5: cbb88288f74dbe6ada1c6c7d0a97223e depends: - backports.zstd >=1.0.0 - brotli-python >=1.2.0 @@ -15632,48 +15683,48 @@ packages: license_family: MIT purls: - pkg:pypi/urllib3?source=hash-mapping - size: 103172 - timestamp: 1767817860341 -- conda: https://conda.anaconda.org/conda-forge/win-64/vc-14.3-h41ae7f8_34.conda - sha256: 9dc40c2610a6e6727d635c62cced5ef30b7b30123f5ef67d6139e23d21744b3a - md5: 1e610f2416b6acdd231c5f573d754a0f + size: 103560 + timestamp: 1778188657149 +- conda: https://conda.anaconda.org/conda-forge/win-64/vc-14.5-h1b7c187_36.conda + sha256: dbcbad366e38979ac8ca9efb0ec48e5fedf9ce76f9485120c131cab7315c681c + md5: 10eac3d81ceea1be614f1d90045c7e9b depends: - - vc14_runtime >=14.44.35208 + - vc14_runtime >=14.51.36231 track_features: - vc14 license: BSD-3-Clause license_family: BSD purls: [] - size: 19356 - timestamp: 1767320221521 -- conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.44.35208-h818238b_34.conda - sha256: 02732f953292cce179de9b633e74928037fa3741eb5ef91c3f8bae4f761d32a5 - md5: 37eb311485d2d8b2c419449582046a42 + size: 20260 + timestamp: 1779061872533 +- conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.51.36231-h1b9f54f_36.conda + sha256: e6f48954124c4f9419e50b3de7cb4b88f3a6078bf3616e997ea60144d499aa30 + md5: df9d8c15f117f28087b4aa6efa529a56 depends: - ucrt >=10.0.20348.0 - - vcomp14 14.44.35208 h818238b_34 + - vcomp14 14.51.36231 h1b9f54f_36 constrains: - - vs2015_runtime 14.44.35208.* *_34 + - vs2015_runtime 14.51.36231.* *_36 license: LicenseRef-MicrosoftVisualCpp2015-2022Runtime license_family: Proprietary purls: [] - size: 683233 - timestamp: 1767320219644 -- conda: https://conda.anaconda.org/conda-forge/win-64/vcomp14-14.44.35208-h818238b_34.conda - sha256: 878d5d10318b119bd98ed3ed874bd467acbe21996e1d81597a1dbf8030ea0ce6 - md5: 242d9f25d2ae60c76b38a5e42858e51d + size: 739707 + timestamp: 1779061867466 +- conda: https://conda.anaconda.org/conda-forge/win-64/vcomp14-14.51.36231-h1b9f54f_36.conda + sha256: bba3bcaf805eefd0aa14beb3d08a34a81d5d36e6890bd6ce33fcb968429a3bc7 + md5: d929e2c56341be7ae1bd9a77a9b535c2 depends: - ucrt >=10.0.20348.0 constrains: - - vs2015_runtime 14.44.35208.* *_34 + - vs2015_runtime 14.51.36231.* *_36 license: LicenseRef-MicrosoftVisualCpp2015-2022Runtime license_family: Proprietary purls: [] - size: 115235 - timestamp: 1767320173250 -- conda: https://conda.anaconda.org/conda-forge/noarch/virtualenv-21.2.4-pyhcf101f3_0.conda - sha256: 9a07c52fd7fc0d187c53b527e54ea57d4f46302946fee2f9291d035f4f8984f9 - md5: 15be1b64e7a4501abb4f740c28ceadaf + size: 124124 + timestamp: 1779061850036 +- conda: https://conda.anaconda.org/conda-forge/noarch/virtualenv-21.3.3-pyhcf101f3_0.conda + sha256: 67a5a8b0976f11bd821909dd7ffd393ae32879ec22be622344277f04a1450aff + md5: a678237f7d0db44f8a20a21f03844613 depends: - python >=3.10 - distlib >=0.3.7,<1 @@ -15684,21 +15735,20 @@ packages: - typing_extensions >=4.13.2 - python license: MIT - license_family: MIT purls: - - pkg:pypi/virtualenv?source=compressed-mapping - size: 4659433 - timestamp: 1776247061232 -- conda: https://conda.anaconda.org/conda-forge/win-64/vs2015_runtime-14.44.35208-h38c0c73_34.conda - sha256: 63ff4ec6e5833f768d402f5e95e03497ce211ded5b6f492e660e2bfc726ad24d - md5: f276d1de4553e8fca1dfb6988551ebb4 + - pkg:pypi/virtualenv?source=hash-mapping + size: 5154878 + timestamp: 1778710685928 +- conda: https://conda.anaconda.org/conda-forge/win-64/vs2015_runtime-14.51.36231-h84cd919_36.conda + sha256: 720d9bbbd0cd48f80d1b295fd6eb9f2e4accade35226470db97b6f09745df149 + md5: d4e73c2ddb98e1071de99e37f4499c6c depends: - - vc14_runtime >=14.44.35208 + - vc14_runtime >=14.51.36231 license: BSD-3-Clause license_family: BSD purls: [] - size: 19347 - timestamp: 1767320221943 + size: 20247 + timestamp: 1779061872946 - pypi: https://files.pythonhosted.org/packages/8d/96/04e7b441807b26b794da5b11e59ed7f83b2cf8af202bd7eba8ad2fa6046e/wadler_lindig-0.1.7-py3-none-any.whl name: wadler-lindig version: 0.1.7 @@ -15729,17 +15779,17 @@ packages: purls: [] size: 334139 timestamp: 1773959575393 -- conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.6.0-pyhd8ed1ab_0.conda - sha256: e298b508b2473c4227206800dfb14c39e4b14fd79d4636132e9e1e4244cdf4aa - md5: c3197f8c0d5b955c904616b716aca093 +- conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.7.0-pyhd8ed1ab_0.conda + sha256: 1ee2d8384972ecbf8630ce8a3ea9d16858358ad3e8566675295e66996d5352da + md5: eb9538b8e55069434a18547f43b96059 depends: - python >=3.10 license: MIT license_family: MIT purls: - pkg:pypi/wcwidth?source=hash-mapping - size: 71550 - timestamp: 1770634638503 + size: 82917 + timestamp: 1777744489106 - conda: https://conda.anaconda.org/conda-forge/noarch/webcolors-25.10.0-pyhd8ed1ab_0.conda sha256: 21f6c8a20fe050d09bfda3fb0a9c3493936ce7d6e1b3b5f8b01319ee46d6c6f6 md5: 6639b6b0d8b5a284f027a2003669aa65 diff --git a/pyproject.toml b/pyproject.toml index 4decbb38e..4bf957a61 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,7 +31,8 @@ dynamic = [ "version" ] dependencies = [ "beartype>=0.21", "cloudpickle>=3.1.2", - "dags>=0.5.1", + # Require the beartype fix, replace once release is there + # "dags>=0.6.0", "h5py>=3.12", "jax>=0.9", "jaxtyping>=0.3.2", @@ -149,12 +150,8 @@ ty = "ty check" jax = ">=0.9" pdbp = "*" pylcm = { path = ".", editable = true } -# Pin dags to the feat/no-type-check-flag branch (PR -# OpenSourceEconomics/dags#82): its wrappers advertise the `*args, -# **kwargs` forwarder shape on `__annotations__`, so beartype's import -# claw treats them as permissive forwarders. Replace with `dags>=0.6` -# once that PR is released. -dags = { git = "https://github.com/OpenSourceEconomics/dags.git", rev = "cf59c04" } +# Remove and comment in `dags >= 0.6.0` above once released. +dags = { git = "https://github.com/OpenSourceEconomics/dags.git", branch = "main" } [tool.pixi.tasks] asv-compare = "asv compare" asv-preview = "asv preview" From 307046681d43e91a77adf3b977c4454093c62a95 Mon Sep 17 00:00:00 2001 From: Hans-Martin von Gaudecker Date: Mon, 18 May 2026 08:37:07 +0200 Subject: [PATCH 77/77] Clear ty diagnostics surfaced by the relock MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three small fixes: - `dispatchers.py`: `vmapped.__signature__ = signature` gets a `ty: ignore[invalid-assignment]` — the runtime mutation is intentional; the `FunctionWithArrayReturn` TypeVar bound doesn't declare `__signature__` as writable. Same TypeVar bound also lacks `__name__`, so the error-message access uses `getattr(...)` with a `repr(func)` fallback. - `typing.ActiveFunction`: `age: int | float` is too strict — contravariance means it forbids model authors from annotating `age: int` (annual grids) or `age: float` (sub-annual). Switch to `age: Any` (matching the `UserFunction` precedent) so authors can pin the annotation to whichever type matches their grid. - `mahler_yum_2024._model`: `final_age_alive` / `initial_age` are derived from `ages.values[...]` which jaxtyping reports as `Array`; wrap with `int(...)` to land on a plain Python int (correct for the `step="2Y"` grid this model uses). Co-Authored-By: Claude Opus 4.7 (1M context) --- src/lcm/typing.py | 9 ++++++++- src/lcm/utils/dispatchers.py | 5 +++-- src/lcm_examples/mahler_yum_2024/_model.py | 8 ++++---- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/lcm/typing.py b/src/lcm/typing.py index 627bcb5b9..ada8acb40 100644 --- a/src/lcm/typing.py +++ b/src/lcm/typing.py @@ -272,8 +272,15 @@ def __call__( class ActiveFunction(Protocol): """Function that determines if a regime is active at a given age. + The single positional argument is the age value emitted by the + `AgeGrid`; its runtime type is `int` for annual grids and `float` + for sub-annual ones. The argument is typed as `Any` so model + authors can pin the annotation to whichever concrete type matches + their grid (`int` or `float`) without tripping contravariance + checks at the assignment site. + Only used for type checking. """ - def __call__(self, age: float, /) -> bool: ... + def __call__(self, age: Any, /) -> bool: ... # noqa: ANN401 diff --git a/src/lcm/utils/dispatchers.py b/src/lcm/utils/dispatchers.py index 0bb018a4c..6c2b47841 100644 --- a/src/lcm/utils/dispatchers.py +++ b/src/lcm/utils/dispatchers.py @@ -147,7 +147,7 @@ def vmap_1d( in_axes_for_vmap[p] = 0 vmapped = vmap(func, in_axes=in_axes_for_vmap) - vmapped.__signature__ = signature + vmapped.__signature__ = signature # ty: ignore[invalid-assignment] if callable_with == "only_kwargs": out = allow_only_kwargs(vmapped, enforce=False) @@ -239,7 +239,8 @@ def _base_productmap_batched( if param.kind == inspect.Parameter.POSITIONAL_ONLY: raise FunctionDispatchError( "Positional-only parameters are not allowed in dispatched functions. " - f"The parameter '{name}' to the function {func.__name__} " + f"The parameter '{name}' to the function " + f"{getattr(func, '__name__', repr(func))} " "is POSITIONAL_ONLY." ) diff --git a/src/lcm_examples/mahler_yum_2024/_model.py b/src/lcm_examples/mahler_yum_2024/_model.py index a237877d9..65c8b79c3 100644 --- a/src/lcm_examples/mahler_yum_2024/_model.py +++ b/src/lcm_examples/mahler_yum_2024/_model.py @@ -308,11 +308,11 @@ def savings_constraint( return net_income + (wealth) * r >= (saving) -def alive_is_active(age: int, final_age_alive: float) -> bool: +def alive_is_active(age: int, final_age_alive: int) -> bool: return age <= final_age_alive -def dead_is_active(age: int, initial_age: float) -> bool: +def dead_is_active(age: int, initial_age: int) -> bool: return age > initial_age @@ -320,7 +320,7 @@ def dead_is_active(age: int, initial_age: float) -> bool: ALIVE_REGIME = Regime( transition=MarkovTransition(next_regime), - active=partial(alive_is_active, final_age_alive=ages.values[-2]), + active=partial(alive_is_active, final_age_alive=int(ages.values[-2])), states={ "wealth": LinSpacedGrid(start=0, stop=49, n_points=50), "health": DiscreteGrid(Health), @@ -382,7 +382,7 @@ def dead_utility(discount_type: DiscreteState) -> FloatND: # noqa: ARG001 DEAD_REGIME = Regime( transition=None, - active=partial(dead_is_active, initial_age=ages.values[0]), + active=partial(dead_is_active, initial_age=int(ages.values[0])), states={ "discount_type": DiscreteGrid(DiscountType), },