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 introducedAdding 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.Methodand 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.
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
/opt/hostedtoolcache/Python/3.13.15/x64/lib/python3.13/site-packages/starsim/time.py:1961: RuntimeWarning: divide by zero encountered in log1p
self._rate = -np.log1p(-v) # Will raise a NumPy warning if it's an array with 1.0 values
Activating new contraceptive method "impl_copy" in year 2010.0
add_method finalized: "impl_copy" has 96 users (10.02% 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
/opt/hostedtoolcache/Python/3.13.15/x64/lib/python3.13/site-packages/starsim/time.py:1961: RuntimeWarning: divide by zero encountered in log1p
self._rate = -np.log1p(-v) # Will raise a NumPy warning if it's an array with 1.0 values
Activating new contraceptive method "inj_improved" in year 2010.0
add_method finalized: "inj_improved" has 36 users (3.66% of method mix)
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_methodCreate 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
/opt/hostedtoolcache/Python/3.13.15/x64/lib/python3.13/site-packages/starsim/time.py:1961: RuntimeWarning: divide by zero encountered in log1p
self._rate = -np.log1p(-v) # Will raise a NumPy warning if it's an array with 1.0 values
/opt/hostedtoolcache/Python/3.13.15/x64/lib/python3.13/site-packages/starsim/time.py:1961: RuntimeWarning: divide by zero encountered in log1p
self._rate = -np.log1p(-v) # Will raise a NumPy warning if it's an array with 1.0 values
Running "Baseline": 2000 ( 0/145) (0.00 s) ———————————————————— 1%
Running "New Method": 2000 ( 0/145) (0.00 s) ———————————————————— 1%
Running "New Method": 2001 (12/145) (0.21 s) •——————————————————— 9%
Running "Baseline": 2001 (12/145) (0.26 s) •——————————————————— 9%
Running "New Method": 2002 (24/145) (0.43 s) •••————————————————— 17%
Running "Baseline": 2002 (24/145) (0.53 s) •••————————————————— 17%
Running "New Method": 2003 (36/145) (0.66 s) •••••——————————————— 26%
Running "Baseline": 2003 (36/145) (0.80 s) •••••——————————————— 26%
Running "New Method": 2004 (48/145) (0.88 s) ••••••—————————————— 34%
Running "Baseline": 2004 (48/145) (1.09 s) ••••••—————————————— 34%
Running "New Method": 2005 (60/145) (1.15 s) ••••••••———————————— 42%
Running "Baseline": 2005 (60/145) (1.38 s) ••••••••———————————— 42%
Running "New Method": 2006 (72/145) (1.38 s) ••••••••••—————————— 50%
Running "New Method": 2007 (84/145) (1.62 s) •••••••••••————————— 59%
Running "Baseline": 2006 (72/145) (1.68 s) ••••••••••—————————— 50%
Running "New Method": 2008 (96/145) (1.88 s) •••••••••••••——————— 67%
Running "Baseline": 2007 (84/145) (2.00 s) •••••••••••————————— 59%
Running "New Method": 2009 (108/145) (2.13 s) •••••••••••••••————— 75%
Running "Baseline": 2008 (96/145) (2.32 s) •••••••••••••——————— 67%
Running "New Method": 2010 (120/145) (2.38 s) ••••••••••••••••———— 83%
Running "Baseline": 2009 (108/145) (2.64 s) •••••••••••••••————— 75%
Running "New Method": 2011 (132/145) (2.64 s) ••••••••••••••••••—— 92%
Running "New Method": 2012 (144/145) (2.90 s) •••••••••••••••••••• 100%
Running "Baseline": 2010 (120/145) (2.97 s) ••••••••••••••••———— 83%
Running "Baseline": 2011 (132/145) (3.35 s) ••••••••••••••••••—— 92%
Running "Baseline": 2012 (144/145) (3.69 s) •••••••••••••••••••• 100%
MultiSim("Baseline"; n_sims: 2; base: Sim(Baseline; n=10000; 2000—2012; demographics=fp, deaths; connectors=contraception, edu))
Compare the contraceptive prevalence rate between the two scenarios:
msim.plot(key='cpr');Figure(1536x960)


