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