Simulating evolution

COMETS is able to perform simulations that include the appearance of mutants containing reaction deletions and additions. In this small example, we will perform a serial transfer experiment starting with a clonal Escherichia coli population, and simulate the random appearance of reaction deletion mutants. We will then visualize the dynamics of all genotypes in time.

Load the model

We start by importing the necessary libraries and loading the E. coli model.

import os
import cobra

import pandas as pd
import cometspy as c
import matplotlib.pyplot as plt

# load model 
model = cobra.io.load_model("e_coli_core")

Using license file /home/djordje/gurobi.lic
Academic license - for non-commercial use only

Remove the bounds for all exchange reactions in the model to allow them to be controlled dynamically by COMETS

# Remove bounds from exchange reactions
for i in model.reactions:
    if 'EX_' in i.id:
        i.lower_bound =-1000.0

Set up the layout

We create a well mixed environment with a glucose minimal media. Here, we use the custom add_typical_trace_metabolites method to add trace metabolites (ions, metals etc) in unlimited amounts (static flag).

# generate layout
test_tube = c.layout()
test_tube.set_specific_metabolite('glc__D_e', 0.0001)
test_tube.add_typical_trace_metabolites(amount=1000)

# add model
wt = c.model(model)
wt.initial_pop = [0, 0, 1e-7]
test_tube.add_model(wt)
building empty layout model
models will need to be added with layout.add_model()
Warning: The added metabolite (glc__D_e) is notable to be taken up by any of the current models

Set up simulation parameters

Create a params object, and modify the needed parameters. The simulation in this example simulation consists of 10 days of experiment, with a 1:2 transfer every 3h. The mutation rate will be \(10^{-7}\) deletion events per reaction and generation. The cellSize parameter sets the amount of biomass that appears when a mutant occurs (i.e., one mutant cell appears).

# .. load parameters and layout from file
evo_params = c.params()

evo_params.set_param('timeStep', 0.1)             # hours

evo_params.set_param('maxCycles', 2400)            # simulate 10 serial transfers of 24h each (timeStep = 0.1)
evo_params.set_param('batchDilution', True)
evo_params.set_param('dilFactor', 0.5)            # Dilution to apply
evo_params.set_param('dilTime', 3)                # hours

evo_params.set_param('evolution', True)
evo_params.set_param('mutRate', 1e-8)             # 
evo_params.set_param('cellSize', 1e-10)           # cellSize should always be larger than maxSpaceBiomass
evo_params.set_param('minSpaceBiomass', 1e-11)    # make sure it is smaller than cell size!


evo_params.set_param('BiomassLogRate', 1)

Run the simulation

We now create the COMETS object using the above layout and parameters, and run the simulation.

# create comets object from the loaded parameters and layout 
evo_simulation = c.comets(test_tube, evo_params)

In case a warning like Warning: java class libraries cannot be found is returned, you may consider to double check your .bashrc:

# COMETS
export COMETS_HOME=/<path_to>/comets_linux/comets_2.12.5

# Python bindings
export PYTHONPATH=$COMETS_HOME/lib/cometspy-master:$PYTHONPATH

# Java classpath (CRITICAL FIX)
export COMETS_JAVA_CLASSPATH="$COMETS_HOME/lib/*:$COMETS_HOME/lib/or-tools/9.4.1874/*"

# Gurobi
export GUROBI_HOME=/opt/gurobi/linux64
export PATH=$PATH:$GUROBI_HOME/bin
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$GUROBI_HOME/lib

Once comets is ready to go, you may run the simulation:

# run comets simulation
evo_simulation.run()
Running COMETS simulation ...
Done!

Visualize the results

We can visualize the population dynamics of all species over time (color coded) using standard Python plotting tools.


fig, ax = plt.subplots(figsize=(15, 5))

species_to_mut = evo_simulation.genotypes.set_index("Species")["Mutation"].to_dict()

for species, grp in evo_simulation.biomass.groupby("species"):
    grp = grp.sort_values("cycle")
    grp = grp[grp["biomass"] > 0]
    ax.plot(grp["cycle"], grp["biomass"])
    # find peak point
    peak_idx = grp["biomass"].idxmax()
    peak = grp.loc[peak_idx]
    label = species_to_mut.get(species, species)
    ax.text(
        peak["cycle"],
        peak["biomass"],
        label,
        fontsize=8,
        ha="center",
        va="bottom"
    )

ax.set_yscale("log")
ax.set_ylabel("Biomass (g)")
ax.set_xlabel("Cycle")

plt.show()
Text(0,0.5,'Biomass (g)')

In order to analyze the results, it is also helpful to visualize the genotypes data frame, which contains all the mutants that ever appeared during the simulation. The data frame contains three columns: The ancestor, the mutation, and the name of the resulting genotype, which is assigned as a random hash.

evo_simulation.genotypes.head()
          Ancestor Mutation                               Species
0      NO_ANCESTOR   NO_MUT                       e_coli_core.cmd
1  e_coli_core.cmd   del_88  1a10c078-ca36-4145-b956-53006511ba98
2  e_coli_core.cmd   del_11  2b1fc102-808f-43df-86b0-d967fc66decf
3  e_coli_core.cmd   del_75  12f368aa-3afc-4e50-9b3f-0901c6f0d713
4  e_coli_core.cmd   del_93  47a5ccf6-f7b7-452a-a802-d656e995d46f