Throughout our project, we have developed our mathematical and computational models in order to facilitate experimental design, predict the function of our system, and to characterise the effect of our individual genetic components on its effectiveness as a whole.
Our computer-aided design approach composes three parts, each serving their distinct purposes:
In our first model, we utilise a system of ordinary differential equations representing the bacterium's gene regulatory network, in order to inform parameters for optimal uptake and cell stress.
We first defined a function of time representing the production of LutH, composing two Hill equations—the first representing the natural LutH production as exists in the wild-type, which we assume to be inhibited by lanthanide concentration, and the second representing our engineered LutH production, induced by IPTG:
We then defined a function representing the production of tLanM. We define PH(t) and PM(t) to use different IPTG concentrations, as ideally the promoters would use different inducers. The function composes a single Hill equation, representing the IPTG-induced tLanM production:
Using these two production functions, we form a function representing the stress in the system. Stress is increased by lanthanide ion concentration and greater production of LutH and tLanM. As the lanthanide concentration approaches Ld, stress approaches infinity:
We then use this function in χ(t), representing the proportion to which he rate of LutH and LanM production are scaled:
We then represent the formation of tLanM-Ln complexes by a proportion of the total tLanM and Ln present in the system:
Rate of change of LutH is then represented by a differential equation, whereby production of LutH is scaled down by stress, while degraded LutH is removed from the system at a constant rate:
Rate of change of tLanM is implemented similarly, with tLanM production being scaled down by stress, then decayed tLanM being removed. We also subtract the tLanM in tLanM-Ln complexes.
Rate of change of lanthanides in the cell is represented in 3 parts, composing the subtraction of decayed lanthanides and those sequestered by the tLanM, with a term representing uptake from the environment. We use the Michaelis-Menten enzymatic kinetics function as we assume that the mechanics will be largely similar, scaled by the amount of LutH in the system:
δ: | The rate of formation of tLanM-Ln3+ complexes |
XmaxH1: | The maximum rate of wild-type LutH production |
KL: | Dissociation constant for lanthanides |
nH1: | Hill coefficient for wild-type LutH production |
XmaxH2: | The maximum rate of IPTG-induced LutH production |
KI: | Dissociation constant for IPTG |
nH2: | Hill coefficient for IPTG-induced LutH production |
XmaxM: | The maximum rate of IPTG-induced tLanM production |
IH: | [IPTG] for LutH |
nM: | Hill coefficient for IPTG-induced tLanM production |
IM: | [IPTG] for tLanM |
nHs: | Degree to which LutH production affects stress |
nMs: | Degree to which LanM production affects stress |
µ: | Degree to which lanthanide concentration affects stress |
Ld: | Concentration of intracellular lanthanides resulting in cell death |
α: | Rate of decay of LutH |
Vmax: | Limiting rate of lanthanide uptake |
Lout: | Concentration of extracellular lanthanides |
Kmm: | Michaelis-Menten constant for uptake |
β: | Rate of decay of Lanthanides |
γ: | Rate of decay of tLanM |
In order to prepare our model for analysis, we implemented the ODE
system in Python, using scipy
to solve our equations.
We first defined our ODE system, implementing a step response for the lanthanides:
grn.py
# Define the system of ODEs
def ode_system(t, y, params):
H, L, M = y
L_d, X_max_H1, X_max_H2, X_max_M, K_L, K_I, alpha, V_max, L_out, K_mm, beta, delta, gamma, I_H, I_M, n_H1, n_H2, n_M, n_H_s, n_M_s, mu = params
# Step function
L_out = L_out * np.heaviside((t - 80), 0) # Simulate L_out at t > 80
# Production terms
P_H = X_max_H1 / (1 + (L / K_L) ** n_H1) + X_max_H2 / (1 + K_I / I_H ** n_H2)
P_M = X_max_M / (1 + K_I / I_M ** n_M)
# Stress function
S = (P_H ** n_H_s + P_M ** n_M_s) * (L ** mu) / (L_d - L)
chi = 1 / (1 + S)
# Differential equations
dH_dt = chi * P_H - alpha * H
dL_dt = (V_max * H * L_out) / (K_mm + L_out) - beta * L - delta * M * L
dM_dt = chi * P_M - gamma * M - delta * M * L
return [dH_dt, dL_dt, dM_dt]
We then set our initial conditions and parameters:
grn.py
# Initial conditions
y0 = [0.1, 0.0, 0.1] # Initial concentrations for H, L, M
# Time points
t_span = (0, 200) # Start and end times
t_eval = np.linspace(0, 200, 2000) # Time points where solution is evaluated
# Parameters
params = (L_d, X_max_H1, X_max_H2, X_max_M, K_L, K_I, alpha, V_max, L_out, K_mm,
beta, delta, gamma, I_H, I_M, n_H1, n_H2, n_M, n_H_s, n_M_s, mu)
And finally solved the equations:
grn.py
sol = solve_ivp(ode_system, t_span, y0, args=(params,), t_eval=t_eval, method='RK45')
if sol.success:
print("ODE solver executed successfully.")
# Extract the results
H, L, M = sol.y
# Plot the results
plt.figure(figsize=(12, 8))
plt.plot(sol.t, H, label='LutH (H)')
plt.plot(sol.t, L, label='Free intracellular lanthanides (L)')
plt.plot(sol.t, M, label='Unbound truncated lanM (M)')
plt.xlabel('Time')
plt.ylabel('Concentration')
plt.legend()
plt.ylim([0, 1500])
plt.show()
One of the main purposes of our GRN model was to find optimum parameters for our system, and so we generated several heatmaps for our various parameters to find values for low stress, whilst maintaining effective uptake:
From this we were able to determine the ratios of XmaxH2 to XmaxM, such that the cell would both not reach the death threshold (where stress is negative in the stress plot), and would not produce so much tLanM that the lanthanide concentration would never reach the threshold required for chemotaxis (where L is 0 in the lanthanides plot).
This ratio was of particular importance to the wet lab team, because the only available, characterised inducible constructs for M. extorquens were exclusively IPTG-inducible. Therefore, the control over expression levels was at the level of DNA design and not titration of IPTG, which is much more time-consuming and expensive to characterise in the lab than in the model.
Beyond the scope of our project, another future potential use of our GRN model was to predict the effects of different affinities of LanM. There exist multiple variations of LanM, such as those described in Mattocks et al. (2023), each with varying affinities for lanthanides, and so it would be useful to preemptively select for one resulting in a most optimal system.
To do this, we varied δ—an indirect measure of affinity—to observe its effects on stress and lanthanide concentration:
The parameter delta is the rate of complex formation between lanthanides and LanM. This is indirectly related to the affinity of LanM variants for their associated lanthanides. By varying the parameter, we can identify ranges of acceptable values that are neither too low to be functional or otherwise stressful. This can also be used to calculate required ratios of lanM and lutH expression for different LanMs, as has been explained above.
From solving the ODE system for these values, we produced a graph of the concentrations of our species over time:
We found that the on the introduction of lanthanides to the system (time = 80), the concentration of tLanM would rapidly decrease, while lanthanide concentration stayed close to 0. This was in line with tLanM-Ln complexes forming and sequestering all lanthanides whilst depleting tLanM. Once tLanM was fully depleted, lanthanide concentration began to rapidly increase, decreasing LutH production in the process due to stress.
In order to perform a more granular analysis on the behaviour of our system's emergent dynamics, we chose to incorporate our GRN model into an agent-based model. This would involve simulating each individual bacterium within a population, each implementing their own system of ordinary differential equations, such that we would be able to accurately simulate the effect of our GRN on the behaviour of the bacteria. This would then allow us to selectively alter or disable various aspects of our GRN, most notably in order to determine the significance of each of our engineered genetic components towards a fully functioning and efficient system as a whole.
With the core design of the model in place, we began to look into methods of performing such a simulation. Our research led us into discussions with Dr Criseida Zamora, who advised us on the suitability of BSim2.0, a software package written in Java for agent-based modelling of bacterial populations. BSim was especially suitable for our project by allowing us to seamlessly integrate our GRN into a configurable computational model, and by its implementation of chemical fields, able to represent our concentrations of extracellular species over time.
Parameters | |
---|---|
taxisThreshold | [Ln] in cell required to initiate chemotaxis |
deathThreshold | Value of stress required to initiate cell death, fitted from experimental data |
growthRate | Maximum rate of cell growth |
kGrowth | Degree to which stress affects growth, fitted from experimental data |
foodUptakeRate | Rate of food uptake |
Methods | |
action | Executes on every tick: diffuse, grow, die, chemotaxis, replicate, uptake food, uptake Ln |
calculateSurfaceAreaGrowthRate | Returns growth rate, scaled down by stress |
pEndRun | Returns probability of ending a run, which is lower when undergoing chemotaxis |
uptakeLn | Solves ODE system, setting new values for intracellular [Ln] and subtracting from the extracellular Ln field |
uptakeFood | Subtracts food from field |
getFreeLnIn | Returns quantity of Ln inside cell |
getFreeLnInConc | Returns [free Ln] inside cell |
getFreeLnInConcAvg | Returns [free Ln] inside cell, averaged over the past n seconds |
getTotalLnInConc | Returns [total Ln] inside cell (including sequestered Ln) |
getSequesteredLnInConc | Returns [sequestered Ln] |
getStress | Returns value of stress from GRN |
Parameters | |
---|---|
delta | The rate of formation of tLanM-Ln3+ complexes |
k_L | Dissociation constant for lanthanides |
n_H1 | Hill coefficient for wild-type LutH production |
k_I | Dissociation constant for IPTG |
n_H2: | Hill coefficient for engineered LutH production |
I_H | [IPTG] for LutH |
n_M | Hill coefficient for engineered tLanM production |
I_M | [IPTG] for tLanM |
mu | Degree to which lanthanide concentration affects stress |
L_lethal | Concentration of intracellular lanthanides resulting in cell death |
alpha | Rate of decay of LutH |
V_max | Limiting rate of lanthanide uptake |
L_out: | Concentration of extracellular lanthanides |
k_mm | Michaelis-Menten constant for uptake |
delta | Rate of decay of Lanthanides |
gamma | Rate of decay of tLanM |
Methods | |
derivativeSystem | Defines the differential equations |
Hill | Activatory Hill function |
HillInhib | Inhibitory Hill function |
setExternalLnLevel | Sets L_out |
Parameters | |
---|---|
export | Set to false to preview, true to export to files |
exportPath | Folder to export files to |
useMultithreading | Set to true to enable multithreading |
threads | Number of threads for multithreading |
boundSize | Simulation bound size |
mult | System scale |
simTime | Time to run simulation for |
cX | X-position of chemoattractant |
cY | Y-position of chemoattractant |
cC | Concentration of chemoattractant |
lC | Concentration of lanthanides |
fC | Concentration of food |
cdiffusivity | Diffusivity of chemoattractant |
lnDiffusivity | Diffusivity of lanthanides |
lnTime | Time at which lanthanides are introduced |
decayRate | Rate of decay of chemoattractant |
population | Initial population of bacteria |
fieldres | Resolution of chemical fields |
Methods | |
run | Runs the simulation |
As a starting point, we created the BeaconBacterium
class as a subclass of BSimBacterium
. This was so that our class would inherit the growth, replication, stochastic diffusion, and run-and-tumble behaviour of BSimBacterium
.
There is however, insufficient characterisation of Mex's movement patterns, and so we assumed that the qualities of Mex were similar enough to E. coli that we could largely maintain these same behaviours, despite Mex having only a single flagellum. There would additionally be little benefit to implementing a chemotactic system specific to Mex, considering our limited time frame and that for the purposes of our model, it would have minor, if any effect on our analysis.
Our first consideration was the incorporation of the GRN, via the class BeaconGRN
, implementing the BSimOdeSystem
interface. We use the same parameters in this system as in our GRN ODEs model:
BeaconGRN::derivativeSystem
public double[] derivativeSystem(double t, double[] y) {
double[] dy = new double[numEq];
double H = y[0];
double L = y[1];
double M = y[2];
double h = delta*M*L; // Sequestered Ln/LanM complexes
double MM = (v_max*L_out) / (k_M + L_out); // Michaelis-Menten kinetics
double P_H = HillInhib(L, n_H1, k_L) // Natural LutH production rate
+ Hill(I_H, n_H2, k_I); // IPTG induced LutH production rate
double P_M = Hill(I_M, n_M, k_I); // IPTG induced tLanM production rate
// Stress function
s = (P_H + P_M) * Math.pow(L, mu) / (L_lethal - L);
double chi = 1 / (1 + s);
dy[0] = chi*P_H - alpha*H; // Scale LutH production by stress, subtract decay
dy[1] = H*MM - beta*L - h; // Scale Ln uptake by [LutH], subtract decay and sequestered Ln/LanM complexes
dy[2] = chi*P_M - gamma*M - h; // Scale LanM production by stress, subtract decay and sequestered Ln/LanM complexes
// Store uptake without decay or sequestered to determine value to subtract from env. Ln
dy[3] = H*MM;
// Total Ln in cell, including sequestered
dy[4] = H*MM - beta*L;
return dy;
}
protected static double Hill(double Ligand, double n, double K_d) {
final double d = 1 + K_d / Math.pow(Ligand, n);
assert d != 0: "Denominator of Hill equation cannot be 0";
return 1 / d;
}
protected static double HillInhib(double Ligand, double n, double K_d) {
final double d = 1 + Math.pow(Ligand/K_d, n);
assert d != 0: "Denominator of Hill equation cannot be 0";
return 1 / d;
}
We first defined the high-level behaviour of the bacterium, to be executed on each tick in the simulation. The method first checks if the bacterium should die (i.e, stress, s
in the GRN, is higher than a threshold, deathThreshold
):
BeaconBacterium::action
/** Diffuse, grow, die, chemotaxis, replicate */
@Override
public void action() {
// If dead, do nothing
if(this.isDead() || this.checkShouldDie()) {
this.die();
return;
}
It then sets the growth rate of the bacterium, based on stress and availability of food. The method then halts movement and growth in absence of food, to represent bacteriostasis, before calling super.action()
to perform the movement, growth, stochastic diffusion processes of BSimBacterium
:
BeaconBacterium::action
// Decrease growth rate with increasing stress, or halt growth if no food
this.setSurfaceAreaGrowthRate(this.calculateSurfaceAreaGrowthRate());
// Stop moving if in stasis
this.forceMagnitude = this.checkShouldStasis() ?
0 : this.initalForceMagnitude;
// Movement, growth, diffusion
super.action();
Finally, the bacterium uptakes lanthanides and food from the environment. The uptake of food is represented as a constant decrease in the concentration of food in the BSimChemicalField
foodField
, which is defined in the simulation:
BeaconBacterium::action
// Adjust internal and external [Ln]
this.uptakeLn();
// Take food from environment
this.uptakeFood();
}
The uptakeLn()
method is implemented in a similar manner, though in much more detail. In sequential order, the method behaves as follows:
BeaconBacterium::uptakeLn
/** Removes Ln from environment, and updates internal [Ln] */
public void uptakeLn() {
// Subtract from environment the difference in [Ln] now vs previous timestep,
// and update BeaconBacterium internal [Ln] value to reflect this
this.lnField.addQuantity(this.getPosition(), - this.updateInternalLnLevel());
}
/**
* Solves GRN for current time, updating this.y, this.lnUValues, this.lnIn
* @return uptaken [Ln] from environment
*/
public double updateInternalLnLevel() {
// Set environmental [Ln] in GRN to current Ln Field value at bacterium's position
this.getGRN().setExternalLnLevel(this.lnField.getConc(this.getPosition()));
// Commented out to avoid terrible performance, dubious behaviour
// // divide by number of bacteria in Ln field cell to avoid magic lanthanides
// / this.getNumberOfBacteriaInSamePosition());
// Solve ODEs
this.y = BSimOdeSolver.rungeKutta45(this.grn,
this.sim.getTime()/60, this.y, this.sim.getDt()/60);
// Store new stress value
this.stressValues.memorise(this.grn.s);
// Store new uptaken [Ln] value
this.lnUValues.memorise(this.y[3]);
this.lnFreeConcValues.memorise(this.getFreeLnInConc());
// Set internal [Ln] to value from ODEs in GRN
// this.setLnIn(this.getLnIn() + L_outDelta*sim.getDt());
this.setFreeLnIn(this.y[1]); // Set free [Ln] in cell
this.setTotalLnIn(this.y[4]); // Set total [Ln] in cell
// Return value for amount of Ln uptaken from environment
return this.lnUValues.getFirst() - this.lnUValues.getLast();
}
We then needed to be able to activate chemotaxis upon the intracellular lanthanide concentration surpassing a specified threshold.
More biologically accurate would be to model the production of methyl-accepting chemotaxis proteins (MCPs), however we felt that this would add unnecessary complexity to our model. Instead, we change the probability of ending a run, depending on the lanthanide concentration, and if the bacterium is moving up the chemoattractant gradient. When both the lanthanide concentration is greater than the threshold, and the bacterium is moving up the concentration gradient, the probability of ending a run decreases.
We can then observe chemotaxis as an emergent behaviour in the final simulation by the mean x-position of the bacteria increasing over time, as the bacteria migrate up the chemoattractant gradient, where the gradient is along the x-axis.
BeaconBacterium::pEndRunUp
/** @return probability of ending run, using chemotaxis value if [Ln] has passed threshold,
* and if the bacterium is moving up the chemoattractant gradient. Otherwise use default */
@Override
public double pEndRun() {
return (this.goal != null && this.movingUpGradient()
// Only activate chemotaxis when Ln has passed threshold
&& this.getFreeLnInConcAvg() > this.getTaxisThreshold()) ?
pEndRunUp : pEndRunElse; // pEndRunUp for chemotaxis
}
Growth is inherited from BSimBacterium
as a constant increase in radius over time, however we were interested in the potential effect of the lanthanide concentration as an inhibitor to growth. This being the case, we vary the growth rate by a function of stress.
As the stress approaches the death threshold, scaled by a constant parameter kGrowth
, the growth rate decreases from the base rate growthRate
:
BeaconBacterium::calculateGrowthRate
/** @return growth rate, scaled down by stress or 0 if in stasis */
public double calculateSurfaceAreaGrowthRate() {
// Do not grow if no food
if (this.checkShouldStasis()) {
return 0;
}
// Otherwise grow less with more stress
return this.growthRate /
(1 + (this.getStress()
/ (this.getDeathThreshold() * this.kGrowth)));
}
To implement cell division, we use a similar method to BSimBacterium::replicate
, whereby the bacterium spawns a child bacterium, setting both bacteriums' radii such that their surface areas are half that of the original bacterium. We additionally split the lanthanides contained in each bacterium evenly between the two, and scale various values accordingly so as to maintain an effective chemotaxis and death threshold checking system:
BeaconBacterium::replicate
public void replicate() {
this.setRadiusFromSurfaceArea(this.surfaceArea(this.replicationRadius)/2);
BeaconBacterium child = new BeaconBacterium(this.sim, new Vector3d(this.position),
this.lnField, this.foodField, this.goal, this.childList);
child.setRadius(radius);
child.setSurfaceAreaGrowthRate(surfaceAreaGrowthRate);
this.childList.add(child);
// Split Ln equally
this.setFreeLnIn(this.getFreeLnIn() / 2);
child.setFreeLnIn(this.getFreeLnIn() / 2);
this.setTotalLnIn(this.getTotalLnIn() / 2);
child.setTotalLnIn(this.getTotalLnIn() / 2);
// Split LutH, LanM, Ln equally
for (int i = 0; i < child.y.length; ++i) {
this.y[i] /= 2;
child.y[i] = this.y[i];
}
for (int i = 0; i < this.lnUValues.size(); ++i) {
this.lnUValues.set(i, this.lnUValues.get(i)/2);
child.lnUValues.set(i, this.lnUValues.get(i));
}
}
With the BeaconBacterium
implemented, we could begin to build our simulation. We defined 3 chemical fields, representing the chemoattractant, lanthanides, and food (lanthanides are introduced later as a step response):
BSimBeacon::run
final BSimChemicalField cField = new BSimChemicalField(sim, new int[]{this.fieldres,1,1}, this.cDiffusivity, this.decayRate);
final BSimChemicalField lnField = new BSimChemicalField(sim, new int[]{this.fieldres,this.fieldres,1}, this.lnDiffusivity, 0.0);
final BSimChemicalField foodField = new BSimChemicalField(sim, new int[]{this.fieldres,this.fieldres,1}, 10.0, 0.0);
// Homogenous food plane
foodField.linearGradient(0, this.fC/scale, this.fC/scale);
// Linear gradient for chemoattractant
cField.linearGradient(0, 0, this.cC);
cField.update();
We then initialise the bacteria as randomly distributed across the space, in random positions and rotations. Once the simulation time has reached the parameter lnTime
, the lanthanides field is set as a homogenous plane:
BSimBeacon::run
// Ln step response
if (sim.getTime() == BSimBeacon.this.lnTime) {
lnField.linearGradient(0,
BSimBeacon.this.lC*BSimBeacon.this.mult,
BSimBeacon.this.lC*BSimBeacon.this.mult);
lnField.update();
}
In order to evaluate the effectiveness of our system, we implemented a procedure to calculate the total lanthanide concentrations, including lanthanides in the environment and bacteria, so that we could observe the total distribution of lanthanides over time:
BSimBeacon$_::during
@Override
public void during() {
String buffer = this.sim.getFormattedTime() + "\n";
double[][] lnConcs = new double[lnField.getBoxes()[0]][lnField.getBoxes()[1]];
// Add internal [Ln]s to appropriate cells
for (BSimBacterium b : bacteria) {
lnConcs[(int) ((b.getPosition().getX()/this.sim.getBound().getX())*lnConcs.length)]
[(int) ((b.getPosition().getY()/this.sim.getBound().getY())*lnConcs[0].length)]
+= ((BeaconBacterium) b).getTotalLnIn();
}
// Add external [Ln]s to appropriate gridcells, write to buffer
for (int i = 0; i < lnConcs[0].length; ++i) {
for (int j = 0; j < lnConcs.length; ++j) {
lnConcs[j][i] += lnField.getConc(i, j, 0);
buffer += lnConcs[j][i] + (j == lnConcs.length - 1 ? "" : ",");
}
buffer += "\n";
}
buffer += "\n";
this.write(buffer);
}
This then produced a csv file representing the lanthanide concentrations across the plane over time:
We found that our simulated bacteria would behave as predicted:
In order to determine the importance of each of our individual genetic components on the effectiveness of the system to migrate lanthanides across the space, we selectively disabled parts of the GRN and compared the behaviours of each simulation:
We found that in the low lanthanide concentrations such as those we use in our experiments, there were minor differences between results; namely, that the migration of lanthanides occurred at a slightly higher rate in the bacteria with induced tLanM and LutH production, than in the bacteria with solely the tar MCP.
On the other hand, in higher lanthanide concentrations as might be used in an industrial process, the differences are much more pronounced:
Here we found that in the full engineered system, the bacteria have the ability to carry a greater quantity of lanthanides and so movement occurs at a higher rate in comparison to the simulation implementing solely our engineered chemotaxis.
We also found that when expressed independently of each other, the induced LutH and tLanM systems failed to deliver at all. This is respectively due to the lack of tLanM to sequester lanthanides resulting in unduly stress and therefore cell death, and the overabundance of tLanM compared to lanthanides resulting in all free lanthanides being sequestered, thus the cells being unable to initiate chemotaxis.
We additionally wanted a model to simulate the entire population across a larger space, in order to be able to visualise our system's behaviour on a much grander scale, potentially even to the scale of an industrial implementation, and also to inform us as to the parameters of our experimental design. To do this we used a system of partial differential equations.
Name | Value | Units | Reference |
---|---|---|---|
Chemoattractant diffusivity | 600 | µm2s-1 | B10NUMB3R5 [2024] Diffusion coefficient of glucose in water, Diffusion coefficient of glucose in water - Generic - BNID 104089. Available at: https://bionumbers.hms.harvard.edu/bionumber.aspx?id=104089&ver=7 (Accessed: July 2024) |
Bacteria diffusivity | 200 to 1900 | µm2s-1 | Licata NA, Mohari B, Fuqua C, Setayeshgar S. (2016) Diffusion of Bacterial Cells in Porous Media. Biophys J. 110(1):247-57. |
Chemoattractant sensitivity | 10e-5 to 10e-3 | cm2s-1 | Berg, H. (2004) E. coli in Motion. Berlin: Springer-Verlag. |
Food sensitivity | 10e-5 to 10e-3 | cm2s-1 | Berg, H. (2004) E. coli in Motion. Berlin: Springer-Verlag. |
Maximum lanthanides uptake | 100 | µM | Vu HN, Subuyuj GA, Vijayakumar S, Good NM, Martinez-Gomez NC, Skovran E. (2016) Lanthanide-Dependent Regulation of Methanol Oxidation Systems in Methylobacterium extorquens AM1 and Their Contribution to Methanol Growth. J Bacteriol. 198(8):1250-9. |
Average extracellular lanthanide concentration | 50 | µM | Vu HN, Subuyuj GA, Vijayakumar S, Good NM, Martinez-Gomez NC, Skovran E. (2016) Lanthanide-Dependent Regulation of Methanol Oxidation Systems in Methylobacterium extorquens AM1 and Their Contribution to Methanol Growth. J Bacteriol. 198(8):1250-9. |
Rate of death of cells | 1.83 ± 0.20 | Per 1000 cells per hour | Brot, L. et al. (2021) Death rate during the exponential growth of E. Coli, arXiv.org. Available at: https://arxiv.org/pdf/2112.06073 (Accessed: July 2024) |
Food diffusion coefficient | 1000 | µm2s-1 | B10NUMB3R5 [2024] Diffusion coefficient of glucose in water, Diffusion coefficient of glucose in water - Generic - BNID 104089. Available at: https://bionumbers.hms.harvard.edu/bionumber.aspx?id=104089&ver=7 (Accessed: July 2024) |
Maximum specific grow rate | 0.14 to 0.25 | Per hour | Hu, B., Yang, YM., Beck, D.A.C. et al. (2016) Comprehensive molecular characterization of Methylobacterium extorquens AM1 adapted for 1-butanol tolerance. Biotechnol Biofuels 9, 84 and Carroll SM, Xue KS, Marx CJ. (2014) Laboratory divergence of Methylobacterium extorquens AM1 through unintended domestication and past selection for antibiotic resistance. BMC Microbiol. 14:2. |
Half saturation constant | 0.2 to 2.5 | Mmol | Belkhelfa S, Roche D, Dubois I, Berger A, Delmas VA, Cattolico L, Perret A, Labadie K, Perdereau AC, Darii E, Pateau E, de Berardinis V, Salanoubat M, Bouzon M, Döring V. (2019) Continuous Culture Adaptation of Methylobacterium extorquens AM1 and TK 0001 to Very High Methanol Concentrations. Front Microbiol. 10:1313. |
Yield coefficient | 0.44 | Gram of cell per gram of methanol | Nayak. D. and Marx C.J. (2014) Genetic and Phenotypic Comparison of Facultative Methylotrophy between Methylobacterium extorquens Strains PA1 and AM1. Plos One. |
With consistent units (ommitted parameters have been determined indirectly by fitting the model with the wet lab data):
Chemoattractant diffusivity | 600 µm2s-1 |
Bacteria diffusivity | 200 to 1900 µm2s-1 |
Chemoattractant sensitivity | 103 to 105 µm2s-1 |
Food sensitivity | 103 to 105 µm2s-1 |
Maximum lanthanides uptake | 100 µM |
Average lanthanides concentration | 50 µM |
Average rate of death of cells | (5.08 ± 0.55) ×10−4 per second per 1000 cells |
Diffusion coefficient of food | 600 µm2s-1 |
Maximum specific growth rate | (3.89 to 6.94)×10−5 per second |
Half saturation constant | 0.2 to 2.5 mM |
Diffusion Coefficient of Food | 0.44 g cells/g methanol |
No-flux boundaries: the zero-gradient (no-flux) boundary conditions prevent any flow of material in or out of the system, which may not accurately represent real-world systems where bacteria or chemicals could diffuse beyond the boundaries of the modelled region.
Fixed time step: a fixed time step is used for solving the partial differential equations (PDEs). While convenient for simulations, it may lead to numerical instability if the time step is too large relative to the rate of diffusion or chemotactic response, especially in regions with steep gradients.
u(x,y,t) | bacterial concentration at position (x,y) and time t |
v(x,y,t) | chemoattractant concentration at position (x,y) and time t |
f(x,y,t) | nutrient concentration at position (x,y) and time t |
l(x,y,t) | lanthanide concentration at position (x,y) and time t |
a(x,y,t) | active bacterial concentration at position (x,y) and time t |
Bacterial Population:
Where:
Chemoattractant:
Where:
Nutrients:
Where:
Lanthanides concentration:
Where:
Active cells
Where:
The model provided for engineered chemotaxis of Methylobacterium extorquens AM1 after lanthanide uptake is fundamentally an extension of the classic Keller-Segel model for chemotaxis. The Keller-Segel model describes how cells (such as bacteria) move in response to a chemical signal (the chemoattractant) in their environment.
The Keller-Segel model is composed of two primary partial differential equations:
Where:
While the Keller-Segel model focusses on simple bacterial chemotaxis to a chemical signal, this model introduces additional complexity:
In order to solve our PDE system, we used the Python library FiPy
, a finite volume PDE solver developed by NIST.
We first initialised our parameters, initial conditions, and boundary conditions:
population.py
# Define the grid
nx = ny = 120
dx = dy = 100
mesh = Grid2D(dx=dx, dy=dy, nx=nx, ny=ny)
np.random.seed(70)
# Define the parameters
Du = 1000 # Diffusion coefficient for u 200 to 1900 µm^2/s
Dv = 600 # Diffusion coefficient for v 600 µm^2/s
chi = 1000 # Chemotactic sensitivity 10e3 to 10e5 µm^2/s
alpha = 0.1 # Production rate of v
beta = 0.05 # Decay rate of v
r = 0.00005 # Maximum specific grow rate (3.89 to 6.94)x10^-5 per second
c_max = 1 # Maximum concentration of cells
Df = 1200 # Diffision term for nutrients 600 µm^2/s
h = 1 # Half saturation constant 0.2 to 2.5 mM
theta = 1000 # Chemotactic sensitivity for food
Yc = 0.44 # Yield coefficient gram of cells per gram of food
kd = 0.0005 # Rate at which cells die in the absence of food (5.08±0.55)x10ˆ4 per second per 1000 cells
omega = 0.01 # Uptake rate of lenthanides scaling factor
Dl = 600 # Diffusion coefficient lenthanides
lag = 5 # Lag between lenthanides uptake and chemotaxis towards chemoattractant
lenc = [] # Lanthanides concentration
max_v = 50 # Maximum chemiotractor concentration in mMol
max_f = 50 # Maximum initial concentration of nutrients in mMol
lavg = 50 # Average uptake of lanthanides per cell 50 mMol
chem_centre = 0 # Centre of the chemoattractant
# Define the variables
u = CellVariable(name="u", mesh=mesh, value=1.0) # Bacterial Concentration
v = CellVariable(name="v", mesh=mesh, value=0.0) # Chemoattractant Concentration
f = CellVariable(name="f", mesh=mesh, value=0.0) # Nutrients Concentration
l = CellVariable(name="l", mesh=mesh, value=0.0) # Lanthanides Concentration
a = CellVariable(name="a", mesh=mesh, value=0.0) # Concentration of active cells
diff = CellVariable(name="diff", mesh=mesh, value=0.0) # Difference between Lanthanides concentration
# Initial conditions (localized peak in the center)
x, y = mesh.cellCenters
x_centre, y_centre = nx/2, ny/2
r_squared = (x-x_centre)**2 + (y-y_centre)**2
# Set the initial conditions
u.setValue(0.05 * np.random.rand(nx*ny)) # Initial bacteria concentration 0.01 - 0.1 bacteria/µm^2
v.setValue(max_v * np.exp(-((x - nx*dx/2 + chem_centre)**2 + (y - ny*dy/2 + chem_centre)**2) / (2*(2000**2))))
f.setValue(50)
# f.setValue( 0.5 * max_f * np.ones(nx*ny) + 0.5 * max_f * np.random.rand(nx*ny))
l.setValue(0.3 * np.random.rand(nx*ny))
lenc.append(l.value.copy())
a.setValue(0.0) # Initialize active cells to 0
# Set boundary conditions
# Impose low flux on the left and right boundaries (Neumann BC)
u.faceGrad.constrain(0, mesh.exteriorFaces)
a.faceGrad.constrain(0, mesh.exteriorFaces)
f.faceGrad.constrain(0, mesh.exteriorFaces)
l.faceGrad.constrain(0, mesh.exteriorFaces)
u.constrain(0.01, mesh.exteriorFaces)
a.constrain(0, mesh.exteriorFaces)
l.constrain(0, mesh.exteriorFaces)
Then defined our equations:
population.py
# Define the PDEs
eq_u = (TransientTerm(var=u) ==
-((theta * (1/(1+(1+u+a)**2)) * u * f.grad[0]).grad[0] # Attraction to nutrients
+ (theta * ((1/(1+(1+u+a)**2))) * u * f.grad[1]).grad[1]) #
+ ((Du * u.grad[0]).grad[0] + (Du * u.grad[1]).grad[1]) # Cell diffusion term
+ (r * (f/(h + f)) * (u+a) - kd/1000 * u) # Cell growth term
+ diff * u/20) # Cell Activation term
eq_v = (TransientTerm(var=v) == ((Dv * v.grad[0]).grad[0] + (Dv * v.grad[1]).grad[1])) # Chemoattractant Diffusion term
eq_f = (TransientTerm(var=f) ==
((Df * f.grad[0]).grad[0] + (Df * f.grad[1]).grad[1]) # Nutrients Diffusion term
- 100/Yc * r * (f/(h + f)) * (u+a)) # Nutrients consumption
eq_l = (TransientTerm(var=l) ==
((Dl * l.grad[0]).grad[0] + (Dl * l.grad[1]).grad[1]) # Lanthanides diffusion term
- omega * u * l # Lanthanide absorption by cells
+ kd/1000 * a * lavg) # Lanthanides release by cells
eq_a = (TransientTerm(var=a) ==
-(((chi * (((max_v-v)/max_v)/(1+(1+u+a)**2)) ) * a * v.grad[0]).grad[0] # Chemotaxis
+ ((chi * (((max_v-v)/max_v)/(1+(1+u+a)**2)) ) * a * v.grad[1]).grad[1]) #
-((theta * ((1/(1+(1+u+a)**2))) * a * f.grad[0]).grad[0] # Attraction to nutrients
+ (theta * (1/(1+(1+u+a)**2)) * a * f.grad[1]).grad[1]) #
+ ((Du * a.grad[0]).grad[0] + (Du * a.grad[1]).grad[1]) # Cell diffusion term
- diff * u/20 # Cell activation term
- kd/1000 * a) # Cell death
And finally solved and visualised the system:
population.py
# Visualisation
for step in tqdm(range(steps), desc="Time-stepping"):
vconvection_x = (chi * (((max_v-v)/max_v)/(1+(1+u+a)**2)) ) * a * v.grad[0]
vconvection_y = (chi * (((max_v-v)/max_v)/(1+(1+u+a)**2))) * a * v.grad[1]
f_grad = f.grad
fconvection_x = theta * (1/(1+(u+a)**2)) * a * f_grad[0]
fconvection_y = theta * (1/(1+(u+a)**2)) * a * f_grad[1]
# Compute the divergence of the convection term
div_convection = - (fconvection_x.grad[0] + fconvection_y.grad[1])
if len(lanc) >= lag:
diff.setValue(lanc[-1] - lanc[-lag])
diff.setValue(np.minimum(0,diff.value))
# Updating Lanthanides Concentration
lanc.append(l.value.copy())
if len(lanc) > lag:
lanc.pop(0)
if step % print_step == 0:
with h5py.File(hdf5_file, "a") as r:
r["cell"][int(step/print_step)] = u.value.reshape(nx, ny)
r["Ln"][int(step/print_step)] = l.value.reshape(nx, ny)
r["food"][int(step/print_step)] = f.value.reshape(nx, ny)
#r["Divergence"][step/print_step] = div_convection.value.reshape(nx, ny)
r["active cells"][int(step/print_step)] = a.value.reshape(nx, ny)
#r["chem"][step/print_step] = v.value.reshape(nx, ny)
# Solve the PDEs
eq_u.solve(dt=timeStepDuration)
eq_v.solve(dt=timeStepDuration)
eq_f.solve(dt=timeStepDuration)
eq_l.solve(dt=timeStepDuration)
eq_a.solve(dt=timeStepDuration)
# Add Gaussian noise to variables
u.setValue(u.value + noise_level_u * np.random.normal(0, 1, u.value.shape))
v.setValue(v.value + noise_level_v * np.random.normal(0, 1, v.value.shape))
f.setValue(f.value + noise_level_f * np.random.normal(0, 1, f.value.shape))
l.setValue(l.value + noise_level_l * np.random.normal(0, 1, l.value.shape))
u.setValue(np.maximum(u.value, 0))
v.setValue(np.maximum(v.value, 0))
f.setValue(np.maximum(f.value, 0))
l.setValue(np.maximum(l.value, 0))
a.setValue(np.maximum(a.value, 0))
f.setValue(np.minimum(f.value, max_f))
print("Frames saved.")
Running the simulation with the chemoattractant emanating from the centre of the plane, we produced the following results:
As time passes, the environmental lanthanide concentration (top right) decreases as the bacteria uptake the lanthanides, while the cells move towards the centre, bringing the lanthanides with them. This then also represents the net movement of lanthanides towards the centre.
Running the simulation with the chemoattractant emanating from the left side of the plane, we produced the following results:
This configuration was shown to be comparably effective, showing the cells moving towards the left side.
The population model significantly contributed to our experimental design and wet lab procedures by enabling predictions of assay outcomes under various conditions. It firstly allowed us to anticipate the behavior of the bacterial population in response to different gradients, helping us to forecast the results of future assays. Secondly, the model suggested that a central inoculation point for the chemoattractant would be more efficient for maximising bacterial chemotaxis. This insight directly informed the design of our experiments, ensuring more effective use of resources and time in the wet lab. Lastly, the model's ability to simulate different diffusion coefficients allowed us to experiment with varying media densities. This helped us select the optimal media concentration, improving bacterial motility and chemotactic response, leading to more reliable experimental results. Overall, the model was a powerful tool for both guiding experimental decisions and enhancing the accuracy of the wet lab's experimental outcomes.