Metapopulation

A. Migration

Vector-borne model with susceptible and infected hosts and vectors, showing a metapopulation model setup with multiple populations connected to each other by migrating hosts.

Population A is connected to Population B and to Clustered Population 4 (both are one-way connections). Clustered Populations 0-4 are all connected to each other in two-way connections.

Isolated population is not connected to any others.

Two different pathogen genotypes are initially seeded into Populations A and B.

[1]:
from opqua.model import Model

Model initialization and setup

Create a new Model object

[2]:
model = Model() # Make a new model object.

Define a Setup for our system

Create a new set of parameters called setup_normal to be used to simulate a population in the model. Use the default parameter set for a vector-borne model.

[3]:
model.newSetup( # Create a new Setup.
    'setup_normal',
        # Name of the setup.
    preset='vector-borne'
        # Use default 'vector-borne' parameters.
    )

We make a second setup called setup_cluster with the same parameters, but doubles contact rate of the first setup.

[4]:
model.newSetup( # Create a new Setup.
    'setup_cluster',
        # Name of the setup.
    contact_rate_host_vector = ( 2 * model.setups['setup_normal'].contact_rate_host_vector ),
        # rate of host-vector contact events, not necessarily transmission, assumes
        # constant population density.
    preset='vector-borne'
        # Use default 'vector-borne' parameters.
    )

Create a population in our model

Create a population of 20 hosts and 20 vectors called population_A. The population uses parameters stored in setup_normal.

[5]:
model.newPopulation(    # Create a new Population.
    'population_A',
        # Unique identifier for this population in the model.
    'setup_normal',
        # Predefined Setup object with parameters for this population.
    num_hosts=20,
        # Number of hosts in the population with.
    num_vectors=20
        # Number of vectors in the population with.
    )

Create a second population of 20 hosts and 20 vectors called population_B. The population uses parameters stored in setup_normal. The two populations that will be connected.

[6]:
model.newPopulation(    # Create a new Population.
    'population_B',
        # Unique identifier for this population in the model.
    'setup_normal',
        # Predefined Setup object with parameters for this population.
    num_hosts=20,
        # Number of hosts in the population with.
    num_vectors=20
        # Number of vectors in the population with.
    )

Create a thrid population of 20 hosts and 20 vectors called isolated_population that will remain isolated. The population uses parameters stored in setup_normal.

[7]:
model.newPopulation(    # Create a new Population.
    'isolated_population',
        # Unique identifier for this population in the model.
    'setup_normal',
        # Predefined Setup object with parameters for this population.
    num_hosts=20,
        # Number of hosts in the population with.
    num_vectors=20
        # Number of vectors in the population with.
    )

Create a cluster of 5 populations connected to each other with a migration rate of 2e-3 between each of them in both directions. Each population has an numbered ID with the prefix clustered_population_, has the parameters defined in the setup_cluster setup, and has 20 hosts and vectors.

[8]:
model.createInterconnectedPopulations(  # Create new populations, link all of them to each other.
    5,
        # number of populations to be created.
    'clustered_population_',
        # prefix for IDs to be used for this population in the model.
    'setup_cluster',
        # Predefined Setup object with parameters for this population.
    host_migration_rate=2e-3,
        #  host migration rate between populations
    vector_migration_rate=0,
        # vector migration rate between populations
    host_host_contact_rate=0,
        # host-host inter-population contact rate between populations
    vector_host_contact_rate=0,
        # host-vector inter-population contact rate between populations
    num_hosts=20,
        # number of hosts to initialize population with.
    num_vectors=20
        # number of hosts to initialize population with.
    )

Now, we link population_A to one of the clustered populations with a one-way migration rate of 2e-3.

[9]:
model.linkPopulationsHostMigration(
        # Set host-vector contact rate from one population towards another.
    'population_A',
        # Origin population ID for which migration rate will
        # be specified.
    'clustered_population_4',
        # destination population ID for which migration rate
        # will be specified.
    2e-3
        # migration rate from one population to the neighbor.
    )

We link population_A to population_B with a one-way migration rate of 2e-3.

[10]:
model.linkPopulationsHostMigration(
        # Set host-vector contact rate from one population towards another.
    'population_A',
        # Origin population ID for which migration rate will
        # be specified.
    'population_B',
        # destination population ID for which migration rate
        # will be specified.
    2e-3
        # migration rate from one population to the neighbor.
    )

Manipulate hosts and vectors in the population

Add pathogens with a genome of AAAAAAAAAA to 20 random hosts in population population_A.

[11]:
model.addPathogensToHosts( # Add specified pathogens to random hosts.
    'population_A',
        # ID of population to be modified.
    {'AAAAAAAAAA':5}
        # Dictionary containing pathogen genomes to add as keys and
        # number of hosts each one will be added to as values.
    )

population_B starts with GGGGGGGGGG genotype pathogens.

[12]:
model.addPathogensToHosts( # Add specified pathogens to random hosts.
    'population_B',
        # ID of population to be modified.
    {'GGGGGGGGGG':5}
        # Dictionary containing pathogen genomes to add as keys and
        # number of hosts each one will be added to as values.
    )

Model simulation

[13]:
model.run(          # Simulate model for a specified time between two time points.
    0,              # Initial time point.
    100,            # Final time point.
    time_sampling=0 # how many events to skip before saving a snapshot of the system state.
    )
Simulating time: 83.06461341318253, event: CONTACT_VECTOR_HOST
Simulating time: 100.06274296487011 END

Output data manipulation and visualization

Create a table with the results of the given model history

[14]:
data = model.saveToDataFrame(
        # Creates a pandas Dataframe in long format with the given model history,
        # with one host or vector per simulation time in each row.
    'metapopulations_migration_example.csv'
        # Name of the file to save the data to.
    )
data
Saving file...
[Parallel(n_jobs=8)]: Using backend LokyBackend with 8 concurrent workers.
[Parallel(n_jobs=8)]: Done   2 tasks      | elapsed:    0.3s
[Parallel(n_jobs=8)]: Batch computation too fast (0.19491923660278324s.) Setting batch_size=2.
[Parallel(n_jobs=8)]: Done   9 tasks      | elapsed:    0.3s
[Parallel(n_jobs=8)]: Done  16 tasks      | elapsed:    0.3s
[Parallel(n_jobs=8)]: Done  26 tasks      | elapsed:    0.3s
[Parallel(n_jobs=8)]: Batch computation too fast (0.027785778045654297s.) Setting batch_size=4.
[Parallel(n_jobs=8)]: Done  44 tasks      | elapsed:    0.4s
[Parallel(n_jobs=8)]: Batch computation too fast (0.024601459503173828s.) Setting batch_size=8.
[Parallel(n_jobs=8)]: Done  76 tasks      | elapsed:    0.4s
[Parallel(n_jobs=8)]: Done 120 tasks      | elapsed:    0.5s
[Parallel(n_jobs=8)]: Batch computation too fast (0.040442705154418945s.) Setting batch_size=16.
[Parallel(n_jobs=8)]: Done 224 tasks      | elapsed:    0.6s
[Parallel(n_jobs=8)]: Batch computation too fast (0.06669497489929199s.) Setting batch_size=32.
[Parallel(n_jobs=8)]: Done 408 tasks      | elapsed:    0.8s
[Parallel(n_jobs=8)]: Batch computation too fast (0.0938570499420166s.) Setting batch_size=64.
[Parallel(n_jobs=8)]: Done 606 tasks      | elapsed:    0.8s
[Parallel(n_jobs=8)]: Done 714 tasks      | elapsed:    0.8s
[Parallel(n_jobs=8)]: Done 793 tasks      | elapsed:    0.8s
[Parallel(n_jobs=8)]: Done 810 tasks      | elapsed:    0.8s
[Parallel(n_jobs=8)]: Done 829 tasks      | elapsed:    0.9s
[Parallel(n_jobs=8)]: Done 848 tasks      | elapsed:    0.9s
[Parallel(n_jobs=8)]: Done 869 tasks      | elapsed:    0.9s
[Parallel(n_jobs=8)]: Done 890 tasks      | elapsed:    0.9s
[Parallel(n_jobs=8)]: Done 918 out of 918 | elapsed:    0.9s finished
...file saved.
[14]:
Time Population Organism ID Pathogens Protection Alive
0 0.0 population_A Host population_A_0 NaN NaN True
1 0.0 population_A Host population_A_1 NaN NaN True
2 0.0 population_A Host population_A_2 NaN NaN True
3 0.0 population_A Host population_A_3 NaN NaN True
4 0.0 population_A Host population_A_4 NaN NaN True
... ... ... ... ... ... ... ...
293755 100.0 clustered_population_4 Vector clustered_population_4_15 NaN NaN True
293756 100.0 clustered_population_4 Vector clustered_population_4_16 NaN NaN True
293757 100.0 clustered_population_4 Vector clustered_population_4_17 NaN NaN True
293758 100.0 clustered_population_4 Vector clustered_population_4_18 NaN NaN True
293759 100.0 clustered_population_4 Vector clustered_population_4_19 NaN NaN True

293760 rows × 7 columns

Creates a line or stacked line plot with dynamics of a compartment across populations in the model, with one line for each population.

[15]:
plot = model.populationsPlot( # Create plot with aggregated totals per population across time.
    'metapopulations_migration_example.png',
        # Name of the file to save the plot to.
    data,
        # Dataframe with model history.
    num_top_populations=8,
        # how many populations to count separately and include as columns, remainder will be
        # counted under column “Other”
    track_specific_populations=['isolated_population'],
        # Make sure to plot the isolated population totals if not in the top
        # infected populations.
    y_label='Infected hosts'
        # change y label
    )
_images/metapopulation_36_0.png

B. Population contact

Vector-borne model with susceptible and infected hosts and vectors, showing a metapopulation model setup with multiple populations connected to each other by “population contact” events between vectors and hosts, in which a vector and a host from different populations contact each other without migrating from one population to another.

Population A is connected to Population B and to Clustered Population 4 (both are one-way connections).

Clustered Populations 0-4 are all connected to each other in two-way connections.

Isolated population is not connected to any others.

Two different pathogen genotypes are initially seeded into Populations A and B.

[ ]:
from opqua.model import Model

Model initialization and setup

Create a new Model object

[ ]:
model = Model() # Make a new model object.

Define a Setup for our system

Create a new set of parameters called setup_normal to be used to simulate a population in the model. Use the default parameter set for a vector-borne model.

[ ]:
model.newSetup(     # Create a new Setup.
    'setup_normal',
        # Name of the setup.
    preset='vector-borne'
        # Use default 'vector-borne' parameters.
    )

We make a second setup called setup_cluster with the same parameters, but doubles contact rate of the first setup.

[ ]:
model.newSetup(
    'setup_cluster',
        # Name of the setup.
    contact_rate_host_vector = ( 2 * model.setups['setup_normal'].contact_rate_host_vector ),
        # rate of host-vector contact events, not necessarily transmission, assumes
        # constant population density.
    preset='vector-borne'
        # Use default 'vector-borne' parameters.
    )

Create a population in our model

Create a population of 20 hosts and 20 vectors called population_A. The population uses parameters stored in setup_normal.

[ ]:
model.newPopulation(    # Create a new Population.
    'population_A',
        # Unique identifier for this population in the model.
    'setup_normal',
        # Predefined Setup object with parameters for this population.
    num_hosts=20,
        # Number of hosts in the population with.
    num_vectors=20
        # Number of vectors in the population with.
    )

Create a second population of 20 hosts and 20 vectors called population_B. The population uses parameters stored in setup_normal. The two populations that will be connected.

[ ]:
model.newPopulation(    # Create a new Population.
    'population_B',
        # Unique identifier for this population in the model.
    'setup_normal',
        # Predefined Setup object with parameters for this population.
    num_hosts=20,
        # Number of hosts in the population with.
    num_vectors=20
        # Number of vectors in the population with.
    )

Create a thrid population of 20 hosts and 20 vectors called isolated_population that will remain isolated. The population uses parameters stored in setup_normal.

[ ]:
model.newPopulation(    # Create a new Population.
    'isolated_population',
        # Unique identifier for this population in the model.
    'setup_normal',
        # Predefined Setup object with parameters for this population.
    num_hosts=20,
        # Number of hosts in the population with.
    num_vectors=20
        # Number of vectors in the population with.
    )

Create a cluster of 5 populations connected to each other with a population contact rate of 1e-2 between each of them in both directions. Each population has an numbered ID with the prefix clustered_population_, has the parameters defined in the setup_cluster setup, and has 20 hosts and vectors.

[ ]:
model.createInterconnectedPopulations(  # Create new populations, link all of them to each other.
    5,
        # number of populations to be created.
    'clustered_population_',
        # prefix for IDs to be used for this population in the model.
    'setup_cluster',
        # Predefined Setup object with parameters for this population.
    host_migration_rate=0,
        #  host migration rate between populations
    vector_migration_rate=0,
        # vector migration rate between populations
    vector_host_contact_rate=2e-2,
        # host-host inter-population contact rate between populations
    host_vector_contact_rate=2e-2,
        # host-vector inter-population contact rate between populations
    num_hosts=20,
        # number of hosts to initialize population with.
    num_vectors=20
        # number of hosts to initialize population with.
    )

Now, we link population_A to one of the clustered populations with a one-way migration rate of 2e-3.

[ ]:
model.linkPopulationsHostVectorContact(
        # Set host-vector contact rate from one population towards another.
    'population_A',
        # Origin population ID for which migration rate will
        # be specified.
    'clustered_population_4',
        # destination population ID for which migration rate
        # will be specified.
    2e-2
        # migration rate from one population to the neighbor.
    )

We link population_A to one of the clustered populations with a one-way population contact rate of 1e-2 for population_A hosts and clustered_population_4 vectors. Note that for population contacts, both populations need to have contact rates towards each other (migration does not require this)

[ ]:
model.linkPopulationsVectorHostContact(
        # Set host-vector contact rate from one population towards another.
    'clustered_population_4',
        # Origin population ID for which migration rate will
        # be specified.
    'population_A',
        # destination population ID for which migration rate
        # will be specified.
    2e-2
        # migration rate from one population to the neighbor.
    )

We link population_A to population_B with a one-way migration rate of 2e-2.

[ ]:
model.linkPopulationsHostVectorContact(
        # Set host-vector contact rate from one population towards another.
    'population_A',
        # Origin population ID for which migration rate will
        # be specified.
    'population_B',
        # destination population ID for which migration rate
        # will be specified.
    2e-2
        # migration rate from one population to the neighbor.
    )

We link population_A to population_B with a one-way population contact rate of 2e-2 for population_A hosts and population_B vectors. Note that for population contacts, both populations need to have contact rates towards each other (migration does not require this)

[ ]:
model.linkPopulationsVectorHostContact(
        # Set host-vector contact rate from one population towards another.
    'population_B',
        # Origin population ID for which migration rate will
        # be specified.
    'population_A',
        # destination population ID for which migration rate
        # will be specified.
    2e-2
        # migration rate from one population to the neighbor.
    )

Manipulate hosts and vectors in the population

population_A starts with AAAAAAAAAA genotype pathogens.

[ ]:
model.addPathogensToHosts( # Add specified pathogens to random hosts.
    'population_A',
        # ID of population to be modified.
    {'AAAAAAAAAA':5}
        # Dictionary containing pathogen genomes to add as keys and
        # number of hosts each one will be added to as values.
    )

population_B starts with GGGGGGGGGG genotype pathogens.

[ ]:
model.addPathogensToHosts( # Add specified pathogens to random hosts.
    'population_B',
        # ID of population to be modified.
    {'GGGGGGGGGG':5}
        # Dictionary containing pathogen genomes to add as keys and
        # number of hosts each one will be added to as values.
    )

Model simulation

[ ]:
model.run(          # Simulate model for a specified time between two time points.
    0,              # Initial time point.
    100,            # Final time point.
    time_sampling=0 # how many events to skip before saving a snapshot of the system state.
    )
Simulating time: 100.1491768759948 END

Output data manipulation and visualization

Create a table with the results of the given model history

[ ]:
data = model.saveToDataFrame(
        # Creates a pandas Dataframe in long format with the given model history,
        # with one host or vector per simulation time in each row.
    'metapopulations_population_contact_example.csv'
        # Name of the file to save the data to.
    )
data
Saving file...
[Parallel(n_jobs=8)]: Using backend LokyBackend with 8 concurrent workers.
[Parallel(n_jobs=8)]: Done   2 tasks      | elapsed:    0.3s
[Parallel(n_jobs=8)]: Done   9 tasks      | elapsed:    0.3s
[Parallel(n_jobs=8)]: Batch computation too fast (0.19925533388085942s.) Setting batch_size=2.
[Parallel(n_jobs=8)]: Done  16 tasks      | elapsed:    0.3s
[Parallel(n_jobs=8)]: Done  25 tasks      | elapsed:    0.4s
[Parallel(n_jobs=8)]: Batch computation too fast (0.017675399780273438s.) Setting batch_size=4.
[Parallel(n_jobs=8)]: Done  36 tasks      | elapsed:    0.4s
[Parallel(n_jobs=8)]: Done  58 tasks      | elapsed:    0.4s
[Parallel(n_jobs=8)]: Batch computation too fast (0.030938148498535156s.) Setting batch_size=8.
[Parallel(n_jobs=8)]: Done  96 tasks      | elapsed:    0.5s
[Parallel(n_jobs=8)]: Batch computation too fast (0.039101600646972656s.) Setting batch_size=16.
[Parallel(n_jobs=8)]: Done 168 tasks      | elapsed:    0.6s
[Parallel(n_jobs=8)]: Batch computation too fast (0.07192206382751465s.) Setting batch_size=32.
[Parallel(n_jobs=8)]: Done 288 tasks      | elapsed:    0.7s
[Parallel(n_jobs=8)]: Done 453 tasks      | elapsed:    0.7s
[Parallel(n_jobs=8)]: Done 528 tasks      | elapsed:    0.7s
[Parallel(n_jobs=8)]: Done 545 tasks      | elapsed:    0.7s
[Parallel(n_jobs=8)]: Done 562 tasks      | elapsed:    0.7s
[Parallel(n_jobs=8)]: Done 581 tasks      | elapsed:    0.7s
[Parallel(n_jobs=8)]: Done 611 out of 611 | elapsed:    0.7s finished
...file saved.
Time Population Organism ID Pathogens Protection Alive
0 0.0 population_A Host population_A_0 NaN NaN True
1 0.0 population_A Host population_A_1 NaN NaN True
2 0.0 population_A Host population_A_2 AAAAAAAAAA NaN True
3 0.0 population_A Host population_A_3 AAAAAAAAAA NaN True
4 0.0 population_A Host population_A_4 NaN NaN True
... ... ... ... ... ... ... ...
195515 100.0 clustered_population_4 Vector clustered_population_4_15 NaN NaN True
195516 100.0 clustered_population_4 Vector clustered_population_4_16 NaN NaN True
195517 100.0 clustered_population_4 Vector clustered_population_4_17 NaN NaN True
195518 100.0 clustered_population_4 Vector clustered_population_4_18 NaN NaN True
195519 100.0 clustered_population_4 Vector clustered_population_4_19 NaN NaN True

195520 rows × 7 columns

Creates a line or stacked line plot with dynamics of a compartment across populations in the model, with one line for each population.

[ ]:
plot = model.populationsPlot( # Plot infected hosts per population over time.
    'metapopulations_population_contact_example.png',
        # Name of the file to save the plot to.
    data,
        # Dataframe with model history.
    num_top_populations=8,
        # how many populations to count separately and include as columns, remainder will be
        # counted under column “Other”
    track_specific_populations=['isolated_population'],
        # Make sure to plot th isolated population totals if not in the top
        # infected populations.
    y_label='Infected hosts'
        # change y label
    )
_images/metapopulation_77_0.png