MicrosimModel
models.MicrosimModel(
clock,
states,
state_rewards,
interventions,
transition_probabilities=None,
event_times=None,
population=None,
cycle_length=1.0,
n_cycles=60,
horizon=60.0,
discount_rate=0.03,
cycle_correction='half_cycle',
n_individuals=1000,
initial_state=0,
effect='qaly',
independent_streams=False,
duration_groups=None,
max_events=10000,
)Individual-level microsimulation engine, discrete- or continuous-time.
Build one with MicrosimModel.discrete or MicrosimModel.continuous. Each constructor carries only the parameters its kernel uses, so a parameter that belongs to the other clock is a TypeError at the call site.
The discrete clock vectorizes over individuals and loops over cycles. The state is an integer vector; each cycle one rng.random(n) draw and a cumulative-probability comparison samples the next state. History enters through two attribute columns the engine maintains and passes to transition_probabilities and state_rewards: cycle (0-based cycle index) and time_in_state (cycles the individual has spent in its current state).
The continuous clock races competing time-to-event samplers. event_times returns sampled times to each competing event; the engine takes the earliest, advances the clock to it, and accrues cost and utility continuously over the elapsed segment. There is no cycle grid; horizon truncates. The current clock is passed to event_times and state_reward_rates as a time attribute column.
discount_rate is an annual rate on an annual clock. cycle_length scales the discrete clock: with cycle_length=0.5 each cycle discounts by half a year.
Every user-supplied model function receives the intervention name as its second argument, so an intervention can branch on the name as well as through parameter decision levers.
Example
import numpy as np, pandas as pd from heormodel.models import MicrosimModel def transition_probabilities(params, intervention, state, attrs, rng): … probs = np.zeros((len(state), 2)) … probs[state == 0] = [1 - params[“p_die”], params[“p_die”]] … probs[state == 1] = [0.0, 1.0] # dead is absorbing … return probs def state_rewards(params, intervention, state, attrs): … alive = (state == 0).astype(float) … return alive * params[“cost”], alive engine = MicrosimModel.discrete( … states=(“healthy”, “dead”), … transition_probabilities=transition_probabilities, … state_rewards=state_rewards, … population=500, interventions=[“care”], n_cycles=10, … cycle_correction=“none”) draws = pd.DataFrame({“p_die”: [0.1], “cost”: [1000.0]}, … index=pd.RangeIndex(1, name=“iteration”)) engine.evaluate(draws).interventions [‘care’]
Methods
| Name | Description |
|---|---|
| continuous | Build a continuous-time microsimulation that races event samplers. |
| discrete | Build a discrete-time microsimulation on a fixed cycle grid. |
continuous
models.MicrosimModel.continuous(
states,
event_times,
state_reward_rates,
interventions,
horizon,
population=None,
discount_rate=0.03,
n_individuals=1000,
initial_state=0,
effect='qaly',
independent_streams=False,
max_events=10000,
)Build a continuous-time microsimulation that races event samplers.
Randomness is supplied by heormodel.run.run_psa at run time, not at construction; the engine holds no seed of its own.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| states | tuple[str, …] |
State labels; the first is the default starting state. | required |
| event_times | Callable[…, NDArray[np.float64]] |
fn(params, intervention, state, attrs, rng) -> times, shape (n, n_states), the sampled time to each destination state. Use inf where a transition cannot occur, including a state’s own column and every column of an absorbing state. |
required |
| state_reward_rates | Callable[…, tuple[NDArray[np.float64], NDArray[np.float64]]] |
fn(params, intervention, state, attrs) -> (cost_rate, utility_rate), the per-year cost and utility flows of each individual’s current state, each shape (n,). |
required |
| interventions | InterventionSpec |
A sequence of intervention names or heormodel.models.Intervention objects; an Intervention may carry parameter decision levers merged into params for that intervention. Order is preserved in Outcomes. |
required |
| horizon | float |
Time horizon in years; trajectories truncate here. | required |
| population | PopulationSpec |
Attribute sampler fn(rng, n) -> DataFrame for heterogeneity, or an int count for a featureless population. None uses n_individuals with no attributes. |
None |
| discount_rate | float |
Annual discount rate for costs and effects (0.03 by default). | 0.03 |
| n_individuals | int |
Population size when population is a sampler or None. |
1000 |
| initial_state | str | int |
Starting state label or index. | 0 |
| effect | str |
Name of the effect column (QALYs by default). | 'qaly' |
| independent_streams | bool |
Give each intervention its own population and simulation stream instead of common random numbers. | False |
| max_events | int |
Safety cap on events per individual before raising. | 10000 |
discrete
models.MicrosimModel.discrete(
states,
transition_probabilities,
state_rewards,
interventions,
n_cycles,
population=None,
cycle_length=1.0,
discount_rate=0.03,
cycle_correction='half_cycle',
n_individuals=1000,
initial_state=0,
effect='qaly',
independent_streams=False,
duration_groups=None,
)Build a discrete-time microsimulation on a fixed cycle grid.
Randomness is supplied by heormodel.run.run_psa at run time, not at construction; the engine holds no seed of its own.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| states | tuple[str, …] |
State labels; the first is the default starting state. | required |
| transition_probabilities | Callable[…, NDArray[np.float64]] |
fn(params, intervention, state, attrs, rng) -> probs, shape (n, n_states) with each row summing to 1. |
required |
| state_rewards | Callable[…, tuple[NDArray[np.float64], NDArray[np.float64]]] |
fn(params, intervention, state, attrs) -> (cost, utility), the per-cycle cost and utility of each individual’s current state, each shape (n,). |
required |
| interventions | InterventionSpec |
A sequence of intervention names or heormodel.models.Intervention objects; an Intervention may carry parameter decision levers merged into params for that intervention. Order is preserved in Outcomes. |
required |
| n_cycles | int |
Number of cycles to simulate. | required |
| population | PopulationSpec |
Attribute sampler fn(rng, n) -> DataFrame for heterogeneity, or an int count for a featureless population. None uses n_individuals with no attributes. |
None |
| cycle_length | float |
Years per cycle; scales the discount clock. | 1.0 |
| discount_rate | float |
Annual discount rate for costs and effects (0.03 by default). | 0.03 |
| cycle_correction | str |
"half_cycle" (default), "simpson", or "none"; see heormodel.models.markov.gen_wcc. |
'half_cycle' |
| n_individuals | int |
Population size when population is a sampler or None. |
1000 |
| initial_state | str | int |
Starting state label or index. | 0 |
| effect | str |
Name of the effect column (QALYs by default). | 'qaly' |
| independent_streams | bool |
Give each intervention its own population and simulation stream instead of common random numbers. | False |
| duration_groups | Mapping[str, Sequence[str | int]] | None |
Optional map of attribute name to a set of state labels. For each entry the engine maintains a per-individual counter of consecutive cycles spent in that set of states (0 on the first such cycle, reset when the individual leaves the set) and passes it to transition_probabilities and state_rewards in attrs. Unlike time_in_state, which counts one exact state, a duration group spans several states, so a sojourn that progresses (Sick to Sicker) keeps counting. |
None |