← Continuous-Time Markov Chains

05

Programming one CTMC event

We now combine the total rate, exponential waiting time, conditional event selection and biological state change to produce exactly one complete continuous-time transition.

The unique purpose of this lesson

1. Calculate ratesUse the current state only.
2. Generate waiting timeDetermine when the jump occurs.
3. Select eventDetermine which jump occurs.
4. Update stateApply one integer compartment change.

This page completes this mechanism once. It does not repeat the mechanism into a trajectory; that loop is the unique purpose of Lesson 6.

Scenario

At current time \(t=12\) days, use:

\[ (S,E,I,R)=(990,5,5,0), \qquad N=1000, \]
\[ \beta=0.3,\qquad \sigma=0.2,\qquad \gamma=0.1\text{ per day}. \]

The current event rates are:

\[ a_1=1.485,\qquad a_2=1.000,\qquad a_3=0.500, \]

for infection, progression and recovery, respectively. The total rate is \(a_0=2.985\) events/day.

Use two independent random numbers

Waiting-time draw

\[\tau=-\frac{\log U_1}{a_0}.\]

\(U_1\) determines when the event occurs.

Event-selection draw

\[U_2\sim\operatorname{Uniform}(0,1).\]

\(U_2\) determines which event occurs.

Use separate independent draws. Reusing the same uniform number would create an artificial relationship between waiting time and event type that is not part of the CTMC model.

Advance continuous time

The current state remains unchanged throughout the waiting period. At its end, the selected event occurs instantaneously:

\[ t_{\mathrm{next}}=t+\tau. \]

Unlike a DTMC, this increment is not a fixed \(\Delta t\). Every CTMC event generally produces a different positive waiting time.

Select the event from rate ratios

The cumulative conditional boundaries are:

\[ c_1=\frac{a_1}{a_0}, \qquad c_2=\frac{a_1+a_2}{a_0}. \]

There is no no-change choice because waiting has already been represented by \(\tau\).

Apply exactly one state update

Infection

\[(S,E,I,R)\to(S-1,E+1,I,R).\]

Progression

\[(S,E,I,R)\to(S,E-1,I+1,R).\]

Recovery

\[(S,E,I,R)\to(S,E,I-1,R+1).\]

The event rate controls selection, but the selected event controls the integer-valued compartment changes.

Do not update while calculating rates

All rates must be calculated from the same current state. Only after both random decisions have been made should the program form the next state.

Using next_S, next_E, next_I and next_R keeps the current and next states separate and avoids mixing values from different times.

What if no event is possible?

If \(a_0=0\), the current state is absorbing:

  • there is no finite waiting time;
  • no event type can be normalised or selected; and
  • the state remains unchanged indefinitely.

A robust function should detect this case before calculating \(-\log(U_1)/a_0\).

Validation after the transition

Non-negative countsNo compartment may fall below zero.
Population conservationThe next counts must still sum to \(N\).
Increasing timeFor \(a_0>0\), the generated event time must not precede the current time.

Interactive Python laboratory

Click Run code to perform one complete event. The result shows rates, two random numbers, waiting time, selected event, event time and before/after state.

Interactive PythonOne complete SEIR CTMC event

Output

Run the code to see the result.

Understand the combined code

CodePurpose
rates.sum()Calculates the current total rate.
u_time = 1.0 - rng.random()Generates a positive uniform value for the logarithm.
u_event = rng.random()Uses a separate random draw for event selection.
next_time = time + waiting_timeAdvances continuous time by the generated waiting duration.
next_S, next_E, next_I, next_R = ...Assigns the complete next state in the selected branch.
float("inf")Represents indefinite waiting when no transition rate is positive.

Read the result correctly

The generated waiting time tells how long the process remains in the current state. At the reported next-event time, the selected event changes the state by one allowed transition.

Running the code with another seed can change both the waiting time and event type. Neither result alone is an average prediction; each is one valid random CTMC event.

Try these experiments

  1. Change the seed and observe that the two random draws and resulting event change.
  2. Set \(E=0\) while keeping \(I>0\). Confirm that progression probability becomes zero.
  3. Use \((S,E,I,R)=(990,10,0,0)\). Progression remains possible even when infection and recovery rates are zero.
  4. Use \((S,E,I,R)=(990,0,0,10)\). Confirm that the function reports an absorbing state rather than dividing by zero.
  5. Temporarily reuse u_time as u_event, then restore the independent draw and explain why coupling them is inappropriate.

What this lesson has—and has not—done

You can now:

  • calculate all rates from one current state;
  • generate one exponential waiting time;
  • select one event using an independent uniform draw;
  • advance continuous time and update the state;
  • handle absorbing states; and
  • validate population counts and time direction.

Not yet: the mechanism has been executed only once. Lesson 6 repeats it, recalculates rates after every jump and stores a complete Gillespie trajectory.