Skip to content

Indirect Response Models (IRM)

Mechanism-based PD models where drug affects the production (Kin) or elimination (Kout) of a response variable.


Available Functions

Function Model Type Mechanism
simulate_pkpd_indirect_response IRM-III Inhibition of Kout
simulate_pkpd_irm1 IRM-I Inhibition of Kin
simulate_pkpd_irm2 IRM-II Stimulation of Kin
simulate_pkpd_irm4 IRM-IV Stimulation of Kout

The Four IRM Types

Model Target Direction Effect on R
IRM-I Kin (production) Inhibition Decreases R
IRM-II Kin (production) Stimulation Increases R
IRM-III Kout (elimination) Inhibition Increases R
IRM-IV Kout (elimination) Stimulation Decreases R

IRM-I: Inhibition of Production

Function Signature

neopkpd.simulate_pkpd_irm1(
    cl: float, v: float, doses: list[dict],
    kin: float, kout: float, r0: float,
    imax: float, ic50: float,
    t0: float, t1: float, saveat: list[float],
    pk_kind: str = "OneCompIVBolus",
    ka: float | None = None,
    q: float | None = None,
    v2: float | None = None,
    ...
) -> dict

Equation

\[\frac{dR}{dt} = K_{in} \cdot (1 - I(C)) - K_{out} \cdot R\]

Where: \(I(C) = \frac{I_{max} \cdot C}{IC_{50} + C}\)

Example: Corticosteroid Effect on Cortisol

import neopkpd

# Cortisol dynamics: baseline 15 mcg/dL, t1/2 ~1.5 h
kout = 0.693 / 1.5  # 0.46/h
r0 = 15.0
kin = kout * r0

result = neopkpd.simulate_pkpd_irm1(
    cl=2.0, v=50.0,
    doses=[{"time": 0.0, "amount": 10.0}],
    kin=kin, kout=kout, r0=r0,
    imax=0.9, ic50=0.01,  # Very potent
    t0=0.0, t1=48.0,
    saveat=[i * 0.5 for i in range(97)]
)

response = result['observations']['response']
print(f"Baseline cortisol: {r0} mcg/dL")
print(f"Minimum cortisol: {min(response):.1f} mcg/dL")

IRM-II: Stimulation of Production

Equation

\[\frac{dR}{dt} = K_{in} \cdot (1 + S(C)) - K_{out} \cdot R\]

Where: \(S(C) = \frac{S_{max} \cdot C}{SC_{50} + C}\)

Example: EPO Effect on Red Blood Cells

import neopkpd

# RBC dynamics: long turnover
kout = 0.693 / (30 * 24)  # 30-day half-life
r0 = 5.0  # million/mcL
kin = kout * r0

result = neopkpd.simulate_pkpd_irm2(
    cl=0.5, v=10.0,
    doses=[{"time": i * 168, "amount": 100.0} for i in range(4)],  # Weekly
    kin=kin, kout=kout, r0=r0,
    smax=3.0, sc50=0.1,
    t0=0.0, t1=672.0,  # 4 weeks
    saveat=[i * 12 for i in range(57)]
)

response = result['observations']['response']
print(f"Baseline RBC: {r0} million/mcL")
print(f"Maximum RBC: {max(response):.2f} million/mcL")

IRM-III: Inhibition of Elimination

Function (simpler signature)

neopkpd.simulate_pkpd_indirect_response(
    cl: float, v: float, doses: list[dict],
    kin: float, kout: float, r0: float,
    imax: float, ic50: float,
    t0: float, t1: float, saveat: list[float],
    pk_kind: str = "OneCompIVBolus",
    ka: float | None = None,
    ...
) -> dict

Equation

\[\frac{dR}{dt} = K_{in} - K_{out} \cdot (1 - I(C)) \cdot R\]

Example: Warfarin Effect

import neopkpd

# Clotting factor dynamics
kout = 0.693 / 36.0  # 36-hour half-life
r0 = 100.0  # % of normal
kin = kout * r0

result = neopkpd.simulate_pkpd_indirect_response(
    cl=0.1, v=10.0,
    doses=[{"time": 0.0, "amount": 5.0}],
    kin=kin, kout=kout, r0=r0,
    imax=0.95, ic50=1.5,
    t0=0.0, t1=120.0,
    saveat=[i * 1.0 for i in range(121)]
)

response = result['observations']['response']
print(f"Baseline: {r0}%")
print(f"Maximum response: {max(response):.1f}%")
print(f"Time to max: {response.index(max(response))} h")

IRM-IV: Stimulation of Elimination

Equation

\[\frac{dR}{dt} = K_{in} - K_{out} \cdot (1 + S(C)) \cdot R\]

Example: Laxative Effect

import neopkpd

kout = 0.693 / 12.0  # 12-hour transit
r0 = 100.0
kin = kout * r0

result = neopkpd.simulate_pkpd_irm4(
    cl=5.0, v=50.0,
    doses=[{"time": 0.0, "amount": 200.0}],
    kin=kin, kout=kout, r0=r0,
    smax=5.0, sc50=0.5,
    t0=0.0, t1=24.0,
    saveat=[i * 0.25 for i in range(97)]
)

response = result['observations']['response']
print(f"Minimum response: {min(response):.1f}")
print(f"Theoretical minimum: {r0 / (1 + 5.0):.1f}")

Comparing IRM Types

import neopkpd

# Same baseline and turnover
kout = 0.1  # 1/h
r0 = 100.0
kin = kout * r0

# Same PK
doses = [{"time": 0.0, "amount": 100.0}]

# IRM-I: Inhibit production → Decrease
result_irm1 = neopkpd.simulate_pkpd_irm1(
    cl=2.0, v=20.0, doses=doses,
    kin=kin, kout=kout, r0=r0,
    imax=0.8, ic50=2.0,
    t0=0.0, t1=48.0, saveat=[i for i in range(49)]
)

# IRM-II: Stimulate production → Increase
result_irm2 = neopkpd.simulate_pkpd_irm2(
    cl=2.0, v=20.0, doses=doses,
    kin=kin, kout=kout, r0=r0,
    smax=3.0, sc50=2.0,
    t0=0.0, t1=48.0, saveat=[i for i in range(49)]
)

# IRM-IV: Stimulate elimination → Decrease
result_irm4 = neopkpd.simulate_pkpd_irm4(
    cl=2.0, v=20.0, doses=doses,
    kin=kin, kout=kout, r0=r0,
    smax=4.0, sc50=2.0,
    t0=0.0, t1=48.0, saveat=[i for i in range(49)]
)

print("Model    | Min/Max Response")
print("-" * 30)
print(f"IRM-I    | {min(result_irm1['observations']['response']):.1f} (decrease)")
print(f"IRM-II   | {max(result_irm2['observations']['response']):.1f} (increase)")
print(f"IRM-IV   | {min(result_irm4['observations']['response']):.1f} (decrease)")

Steady State Predictions

# At high drug concentration (complete effect):

# IRM-I: R_min = R0 × (1 - Imax)
r0, imax = 100.0, 0.8
print(f"IRM-I steady state: {r0 * (1 - imax):.1f}")

# IRM-II: R_max = R0 × (1 + Smax)
r0, smax = 100.0, 3.0
print(f"IRM-II steady state: {r0 * (1 + smax):.1f}")

# IRM-III: R_max = R0 / (1 - Imax)  [limited by Imax < 1]
r0, imax = 100.0, 0.8
print(f"IRM-III steady state: {r0 / (1 - imax):.1f}")

# IRM-IV: R_min = R0 / (1 + Smax)
r0, smax = 100.0, 4.0
print(f"IRM-IV steady state: {r0 / (1 + smax):.1f}")

Model Selection Guide

Observation Suggested Model
Drug decreases biomarker IRM-I or IRM-IV
Drug increases biomarker IRM-II or IRM-III
Effect persists after drug washout Any IRM
Known to inhibit synthesis IRM-I
Known to stimulate synthesis IRM-II
Known to inhibit degradation IRM-III
Known to stimulate elimination IRM-IV

Visualization

import neopkpd
from neopkpd.viz import plot_pkpd_profile

result = neopkpd.simulate_pkpd_irm1(
    cl=2.0, v=20.0,
    doses=[{"time": 0.0, "amount": 100.0}],
    kin=10.0, kout=0.1, r0=100.0,
    imax=0.8, ic50=2.0,
    t0=0.0, t1=48.0,
    saveat=[i * 0.5 for i in range(97)]
)

fig = plot_pkpd_profile(
    result['t'],
    result['observations']['conc'],
    result['observations']['response'],
    title="IRM-I: Inhibition of Production",
    effect_label="Response"
)

Equations Summary

Model dR/dt Steady State
IRM-I \(K_{in}(1-I(C)) - K_{out}R\) \(R_0(1-I_{max})\)
IRM-II \(K_{in}(1+S(C)) - K_{out}R\) \(R_0(1+S_{max})\)
IRM-III \(K_{in} - K_{out}(1-I(C))R\) \(R_0/(1-I_{max})\)
IRM-IV \(K_{in} - K_{out}(1+S(C))R\) \(R_0/(1+S_{max})\)

Common: \(R_0 = K_{in}/K_{out}\), Recovery \(t_{1/2} = \ln(2)/K_{out}\)


See Also