1. Basic introduction (new method + market share)

Adding a new method

This tutorial shows how to use the add_method intervention to introduce a new contraceptive method into an FPsim run at a specified year. You can use it in several ways:

  • Brand new method: Define a full fp.Method and add it, copying switching behavior from an existing method.
  • Quick clone: Clone an existing method (e.g. implants) with minimal config; useful for generic “copy” scenarios.
  • Clone + override: Clone a method and override specific attributes (name, efficacy, duration, etc.) via method_pars.
  • Market splitting: Introduce a new method that takes a fraction of another method’s market share using split_shares.

You always specify when the method becomes available (year) and which existing method to copy switching behavior from (copy_from). Optionally you can pass a custom method, method_pars, and/or split_shares.

Setup

Import FPsim, Starsim, and set common simulation parameters. We’ll use a small population and short run for quick demos.

import fpsim as fp
import starsim as ss
import numpy as np
import matplotlib.pyplot as plt

pars = dict(
    n_agents=2000,
    start=2000,
    stop=2020,
    location='kenya',
    verbose=0,
    analyzers=fp.method_mix_over_time(),
)
intro_year = 2010  # Year when new methods will be introduced

The simplest use case: define a new method with fp.Method, introduce it at a given year, copy switching behavior from an existing method, and optionally take part of that method’s market share with split_shares.

# Define the new method
new_injectable = fp.Method(
    name='new_inj',
    label='New Injectable',
    efficacy=0.995,
    modern=True,
    dur_use=ss.lognorm_ex(mean=3, std=1.5),
)

# Intervention: introduce in intro_year, copy from injectables, take 50% of their share
intv_basic = fp.add_method(
    year=intro_year,
    method=new_injectable,
    copy_from='inj',
    split_shares=0.50,
    verbose=True,
)

sim_baseline = fp.Sim(pars=pars, label='Baseline').run()
sim_basic = fp.Sim(pars=pars, interventions=[intv_basic], label='With New Injectable').run()

print(f"Baseline mCPR: {sim_baseline.results.contraception.mcpr[-1]*100:.1f}%")
print(f"With new method mCPR: {sim_basic.results.contraception.mcpr[-1]*100:.1f}%")
Loading data from files in /home/runner/work/fpsim/fpsim/fpsim/locations/kenya/data... 
Applying calibration parameters for kenya...
Loading data from files in /home/runner/work/fpsim/fpsim/fpsim/locations/kenya/data... 
Applying calibration parameters for kenya...
Registered new method "new_inj" (idx=10), will activate in year 2010
Activating new contraceptive method "new_inj" in year 2010.0
add_method finalized: "new_inj" has 38 users (3.80% of method mix)
Baseline mCPR: 47.0%
With new method mCPR: 47.2%

2. Quick clone (copy existing method)

Add a copy of an existing method without defining a full Method: omit method and use copy_from. The new method is named {source}_copy unless you override it in method_pars.

intv_clone = fp.add_method(
    year=intro_year,
    copy_from='impl',
    verbose=True,
)

sim_clone = fp.Sim(pars=pars, interventions=[intv_clone], label='With Implant Clone').run()
Loading data from files in /home/runner/work/fpsim/fpsim/fpsim/locations/kenya/data... 
Applying calibration parameters for kenya...
Registered new method "impl_copy" (idx=10), will activate in year 2010
Activating new contraceptive method "impl_copy" in year 2010.0
add_method finalized: "impl_copy" has 111 users (12.08% of method mix)

3. Clone + override with improved contraceptive technology

Clone an existing method and override specific attributes via method_pars. In this next example we introduce an improved injectable with higher efficacy and a 10% longer relative duration of use.

intv_override = fp.add_method(
    year=intro_year,
    copy_from='inj',
    method_pars={
        'name': 'inj_improved',
        'label': 'Improved Injectable',
        'efficacy': 0.998,
        'rel_dur_use': 1.1
    },
)

sim_override = fp.Sim(pars=pars, interventions=[intv_override], label='With Improved Injectable').run()
Loading data from files in /home/runner/work/fpsim/fpsim/fpsim/locations/kenya/data... 
Applying calibration parameters for kenya...
Registered new method "inj_improved" (idx=10), will activate in year 2010
Activating new contraceptive method "inj_improved" in year 2010.0
add_method finalized: "inj_improved" has 47 users (5.18% of method mix)

4. Market splitting (split_shares)

Model a new product that takes a fixed fraction of an existing method’s users (e.g. self-injectable DMPA-SC taking share from clinic injectables). Use split_shares between 0 and 1; the new method gets that fraction of users who would have chosen copy_from.

dmpasc_pars = dict(
    name='dmpasc',
    label='DMPA-SC (Self-Injectable)',
    efficacy=0.997,
    dur_use=ss.lognorm_ex(mean=0.25, std=0.1),
)

intv_split = fp.add_method(
    year=intro_year,
    method_pars=dmpasc_pars,
    copy_from='inj',
    split_shares=0.40,
    verbose=True,
)

sim_split = fp.Sim(pars=pars, interventions=[intv_split], label='With DMPA-SC').run()

# Method-specific usage at end
cm = sim_split.connectors.contraception
fp_mod = sim_split.connectors.fp
inj_usage = fp_mod.method_mix[cm.methods['inj'].idx, -1] * 100
dmpasc_usage = fp_mod.method_mix[cm.methods['dmpasc'].idx, -1] * 100
print(f"Injectable: {inj_usage:.1f}%  |  DMPA-SC: {dmpasc_usage:.1f}%")
Loading data from files in /home/runner/work/fpsim/fpsim/fpsim/locations/kenya/data... 
Applying calibration parameters for kenya...
Registered new method "dmpasc" (idx=10), will activate in year 2010
Activating new contraceptive method "dmpasc" in year 2010.0
add_method finalized: "dmpasc" has 25 users (2.76% of method mix)
Injectable: 12.2%  |  DMPA-SC: 2.8%

We can visualize how the market share evolved over time using the method_mix_over_time analyzer. The plot below shows the share of injectables and DMPA-SC over time, highlighting how DMPA-SC captured a portion of the injectable market when it was introduced in 2010.

# Plot the method mix over time, focusing on injectables and DMPA-SC
# The plot shows share (%) and only the two methods involved in the market split
fig = sim_split.analyzers[0].plot(methods=['inj', 'dmpasc'], share=True)
plt.show()

Alternative: Adding methods from simulation startThe examples above use the add_method intervention to introduce methods partway through a simulation. If you need a method to be available from the very beginning of the simulation, use the manual method creation approach below.

First, make a copy of the default list of contraceptive methods:

my_methods = fp.make_methods()

Define a new method with its attributes:1. A simple name (used inside the code)2. Efficacy (as a decimal)3. Whether it is a modern method4. Duration of use (in months or as a distribution)5. A label (used in plots)

new_method = fp.Method(name='new', efficacy=0.96, modern=True, dur_use=15, label='New method')

Add the method to the list:

my_methods += new_method

Create a method choice module that includes your new method:

method_choice = fp.RandomChoice(methods=my_methods)

Now run simulations comparing baseline (without the new method) to a scenario with the new method:

pars = dict(
    n_agents   = 10_000,    
    location   = 'senegal',    
    start      = 2000,     
    stop       = 2012,    
    exposure_factor = 1.0  # Overall scale factor on probability of becoming pregnant
)
s1 = fp.Sim(pars=pars, label='Baseline')
s2 = fp.Sim(pars=pars, contraception_module=method_choice, label='New Method')
msim = ss.MultiSim(sims=[s1, s2])
msim.run()
Loading data from files in /home/runner/work/fpsim/fpsim/fpsim/locations/senegal/data... 
Applying calibration parameters for senegal...
Loading data from files in /home/runner/work/fpsim/fpsim/fpsim/locations/senegal/data... 
Applying calibration parameters for senegal...
Initializing sim "Baseline" with 10000 agents
Initializing sim "New Method" with 10000 agents

  Running "Baseline": 2000.01.01 ( 0/145) (0.00 s)  ———————————————————— 1%

  Running "New Method": 2000.01.01 ( 0/145) (0.00 s)  ———————————————————— 1%

  Running "New Method": 2001.01.01 (12/145) (0.17 s)  •——————————————————— 9%

  Running "Baseline": 2001.01.01 (12/145) (0.22 s)  •——————————————————— 9%

  Running "New Method": 2002.01.01 (24/145) (0.39 s)  •••————————————————— 17%

  Running "Baseline": 2002.01.01 (24/145) (0.51 s)  •••————————————————— 17%

  Running "New Method": 2003.01.01 (36/145) (0.61 s)  •••••——————————————— 26%

  Running "Baseline": 2003.01.01 (36/145) (0.81 s)  •••••——————————————— 26%

  Running "New Method": 2004.01.01 (48/145) (0.84 s)  ••••••—————————————— 34%

  Running "New Method": 2005.01.01 (60/145) (1.07 s)  ••••••••———————————— 42%

  Running "Baseline": 2004.01.01 (48/145) (1.11 s)  ••••••—————————————— 34%

  Running "New Method": 2006.01.01 (72/145) (1.30 s)  ••••••••••—————————— 50%

  Running "Baseline": 2005.01.01 (60/145) (1.42 s)  ••••••••———————————— 42%

  Running "New Method": 2007.01.01 (84/145) (1.54 s)  •••••••••••————————— 59%

  Running "Baseline": 2006.01.01 (72/145) (1.75 s)  ••••••••••—————————— 50%

  Running "New Method": 2008.01.01 (96/145) (1.78 s)  •••••••••••••——————— 67%

  Running "New Method": 2009.01.01 (108/145) (2.01 s)  •••••••••••••••————— 75%

  Running "Baseline": 2007.01.01 (84/145) (2.08 s)  •••••••••••————————— 59%

  Running "New Method": 2010.01.01 (120/145) (2.26 s)  ••••••••••••••••———— 83%

  Running "Baseline": 2008.01.01 (96/145) (2.42 s)  •••••••••••••——————— 67%

  Running "New Method": 2011.01.01 (132/145) (2.50 s)  ••••••••••••••••••—— 92%

  Running "Baseline": 2009.01.01 (108/145) (2.77 s)  •••••••••••••••————— 75%

  Running "New Method": 2012.01.01 (144/145) (2.76 s)  •••••••••••••••••••• 100%


  Running "Baseline": 2010.01.01 (120/145) (3.11 s)  ••••••••••••••••———— 83%

  Running "Baseline": 2011.01.01 (132/145) (3.50 s)  ••••••••••••••••••—— 92%

  Running "Baseline": 2012.01.01 (144/145) (3.86 s)  •••••••••••••••••••• 100%
MultiSim("Baseline"; n_sims: 2; base: Sim(Baseline; n=10000; 2000—2012; demographics=deaths; connectors=contraception, edu, fp))

Compare the contraceptive prevalence rate between the two scenarios:

msim.plot(key='cpr');
Figure(1536x960)