Using the SWITCHES database¶This example does the following:
In [1]:
import warnings
warnings.filterwarnings("ignore")
warnings.simplefilter("ignore")
import requests
import moose
import matplotlib
%matplotlib notebook
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from string import ascii_lowercase as al
import numpy as np
import lhsmdu
1. Downloading a CSPACE file from the SWITCHES database¶1.1 Click on the "Bistable Models" in the left menu bar from the website homepage at http://switches.ncbs.res.in.¶1.2 Select a model configuration size. In this example, we select 3x3 i.e. 3 reactions between 3 reactants.¶1.3 Select a Model ID from this list. Here we pick M3x3.113.¶1.4 You can also search by configuration size or other parameters using the basic and advanced search functionality on the left menu bar.¶1.5 Save the CSPACE (or SBML) file for further use.¶
1.6 Alternatively, get the CSPACE file directly from Python¶In [2]:
url = 'https://switches.ncbs.res.in/file/M3x3.113.62.cspace'
r = requests.get(url, allow_redirects=True)
open('M3x3.113.62.cspace', 'wb').write(r.content)
In [3]:
modelName = "M3x3.113.62.cspace"
2. SWITCHES format¶In [4]:
def parseTopology(model):
readModel = open(model,'r')
modelData = readModel.readlines()
elements = modelData[0].split(' ')
modelName, modelString, reactionPars = elements[0][:-1], elements[1], elements[2:]
return modelName, modelString, reactionPars
In [5]:
def findNumReacs(modelString, reactionPars):
''' Finds the number of molecules and reactions in the topology '''
reactionList = modelString.split('|')
reactionDim = len(reactionList)-2 ## To get rid of the first and the last empty list and each reaction has 2 rates
return reactionDim
2.1 SWITCHES format is a compact representation of a chemical reaction network.¶It has three parts, model ID, model string, and the reaction parameters. In [6]:
modelID, modelString, reactionPars = parseTopology(modelName)
In [7]:
print(modelID)
2.2 Model ID is a hierarchical description of the model.¶
In [8]:
print(modelString)
2.3 Model string describes all the reactant and reactions in the reaction network.¶The reactions are taken from this table of basic reaction types. The first letter (upper caps) describes the reaction type (A-L). The next three letters (lower caps) describe the reactant labels. In [9]:
print(reactionPars)
2.4 Reaction parameters are reactant concentrations and reaction constants (Kf, Kb for reversible and kcat and Km for enzymatic reactions).¶First the concentration of reactants are ordered alphabetically. So, concentration of $a = 2.3570 \mu M$ , $b = 0.0638 \mu M$ and $c = 0.2656 \mu M$. Then for reversible, non-enzymatic reactions: Kf, Kb and for enzymatic reactions kcat, Km. Our model has two enzymatic reactions DabX and Jbca. Therefore, the order of reaction constants for this model is $K_f(AabX): 0.0190 s^{-1}$ , $K_b(AabX) : 0.0183 s^{-1}$ , $k_{cat}(DabX): 2.3604 s^{-1}$ , $K_m(DabX): 0.7087 \mu M$ , $k_{cat}(Jbca): 1.8815 s^{-1}$ , $K_m(Jbca): 0.0303 \mu M$ 3. Finding steady states with MOOSE¶In [10]:
runtime = 500 #seconds
numIterations = 1000 # Number of times the steady-state finder will run with the same parameters.
numRates = 500 # Total number of random rates to sample
minScale = -1 # Minimum (Order of magnitude change in parameters)
maxScale = 1 # Maximum (Order of magnitude change in parameters)
microMolarScaling = 1e-3 # Scaling factor to convert to SI units of milliMolar
numMols = int(modelID[1]) # Number of reactants in the reaction network
numReacs = int(modelID[3])*2 # Two parameters for each reaction.
In [11]:
scalingVector = np.logspace(minScale, maxScale, num=numRates, base=10)
3.1 Simulating with MOOSE to find fixed points.¶MOOSE steady state solver is used to find steady states of the reaction network for a random set of rates sampled above. The stability of these states is determined by analyzing the eigenvalues of the Jacobian around the fixed points found. In [12]:
dj = moose.loadModel(modelName, 'model', 'gsl')
In [13]:
tolerance = 0.05 # parameters to determine when two concentration vectors can be called identical.
offset = 1e-3
In [14]:
def setupSteadyState(modelName, kineticsBasePath = '/model/kinetics'):
''' Sets up steady-state finder in MOOSE'''
simdt = 1e-3
plotDt = 1e-2
ksolve = moose.Ksolve( kineticsBasePath + '/ksolve' )
stoich = moose.Stoich( kineticsBasePath +'/stoich' )
stoich.compartment = moose.element(kineticsBasePath)
stoich.ksolve = ksolve
stoich.path = kineticsBasePath + "/##"
state = moose.SteadyState( kineticsBasePath +'/state' )
#### Set clocks here
moose.useClock(4, kineticsBasePath +"/##[]", "process")
moose.setClock(4, float(simdt))
moose.setClock(5, float(simdt))
moose.useClock(5, kineticsBasePath +'/ksolve', 'process' )
moose.setClock(8, float(plotDt))
moose.reinit()
state.stoich = stoich
state.convergenceCriterion = 1e-6
return state, ksolve
In [15]:
def deleteSetup(kineticsBasePath = '/model/kinetics'):
moose.delete(moose.Stoich(kineticsBasePath + '/stoich'))
moose.delete(moose.Ksolve(kineticsBasePath + '/ksolve'))
moose.delete(moose.SteadyState(kineticsBasePath + '/state'))
In [16]:
def uniqueElements(elements,errorRadius, offset):
''' Seperates out different numerically determined fixed points as identical or different'''
uniqueList = []
flag = 0
finalList = []
# multiplicity = []
for element in elements:
for iteration in range(len(uniqueList)):
if len(uniqueList[iteration])>1:
element2 = np.sum(uniqueList[iteration],0)/len(uniqueList[iteration]) ## Summing across an axis and averaging
else:
element2 = uniqueList[iteration][0]
status = isSimilarTo(element, element2, errorRadius,offset)
if (status):
uniqueList[iteration].append(element)
flag = 1
break
else:
flag = 0
if flag == 0:
uniqueList.append([element])
for iterable in uniqueList:
elementAppend = np.sum(iterable,0)/len(iterable)
finalList.append(elementAppend)
# multiplicity.append(len(iterable))
return finalList
In [17]:
def isSimilarTo(list1, list2, tolerance, offset):
''' It takes in arrays and calculates element wise distances between them. The offset here can set the scale (for instance it is meaningless to detect less than a molcule'''
distElementWise = [abs(a - b) for a,b in zip(list1,list2)]
denominator = [a + b + offset for a,b in zip(list1,list2)]
diffError = [ (a/b) for a,b in zip(distElementWise,denominator)] ## upper bounded by 1 and lower bounded by zero.
status = all(error <= tolerance for error in diffError)
return status
In [18]:
def simpleaxis(axes, every=False):
''' Pretty plotting '''
if not isinstance(axes, (list, np.ndarray)):
axes = [axes]
for ax in axes:
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
if every:
ax.spines['bottom'].set_visible(False)
ax.spines['left'].set_visible(False)
ax.get_xaxis().tick_bottom()
ax.get_yaxis().tick_left()
ax.set_title('')
In [19]:
a,b,c = [], [], []
kineticsBasePath = '/model/kinetics'
AabX = moose.element("/model/kinetics/AabX")
DabX = moose.element("/model/kinetics/b/DabX")
Jbca = moose.element("/model/kinetics/c/Jbca")
plota = moose.element("/model/graphs/plota")
plotb = moose.element("/model/graphs/plotb")
plotc = moose.element("/model/graphs/plotc")
state, ksolve = setupSteadyState(modelName)
original_AabX_Kf = AabX.Kf
original_AabX_Kb = AabX.Kb
original_DabX_Km = DabX.Km
original_Jbca_Km = Jbca.Km
original_DabX_kcat = DabX.kcat
original_Jbca_kcat = Jbca.kcat
3.2 First we find steady-states around a known set of bistable parameters to show how the model can gain or lose bistable behaviour.¶In [20]:
AabX.Kf, AabX.Kb, DabX.Km, DabX.kcat, Jbca.Km, Jbca.kcat = [3.94276674e+00, 2.37393225e+02, 3.59605588e-01, 1.12050907e+02, 1.30874338e-03, 5.73402235e+01] # Known bistable parameters
DabX.Km*=microMolarScaling
Jbca.Km*=microMolarScaling
scaled_DabX_kcat = DabX.kcat * scalingVector
allStables_DabX = []
allUnstables_DabX = []
allSaddles_DabX = []
for scaled_kcat in scaled_DabX_kcat:
stableVector = []
unStableVector = []
saddleVector = []
DabX.kcat = scaled_kcat
for iterations in range(numIterations):
deleteSetup()
state, ksolve = setupSteadyState(modelName)
state.randomInit()
# moose.start(1)
state.resettle()
failedSteadyState = np.isnan(ksolve.nVec[0].any())
if not failedSteadyState:
vector = [pool.conc/microMolarScaling for pool in moose.wildcardFind(kineticsBasePath + '/##[ISA=PoolBase]')]
if np.count_nonzero(vector)>1 and state.solutionStatus == 0:
if state.stateType==0:
stableVector.append(vector)
elif state.stateType == 1:
unStableVector.append(vector)
elif state.stateType == 2:
saddleVector.append(vector)
else:
print("Somthing strange found")
moose.reinit()
stable = uniqueElements(stableVector, tolerance, offset)
unstable = uniqueElements(unStableVector, tolerance, offset)
saddle = uniqueElements(saddleVector, tolerance, offset)
allStables_DabX.append(stable)
allUnstables_DabX.append(unstable)
allSaddles_DabX.append(saddle)
In [21]:
isBistable = []
stable_a_high = []
stable_b_high = []
stable_a_low = []
stable_b_low = []
saddle_a = []
saddle_b = []
a_threshold, b_threshold = 2, 0.03
for j in range(numRates):
stable = allStables_DabX[j]
unstable = allUnstables_DabX[j]
saddle = allSaddles_DabX[j]
if len(stable) == 2:
isBistable.append(1)
stable_a_high.append(np.max(zip(*stable)[0]))
stable_a_low.append(np.min(zip(*stable)[0]))
stable_b_high.append(np.max(zip(*stable)[1]))
stable_b_low.append(np.min(zip(*stable)[1]))
if len(saddle):
saddle_a.append(zip(*saddle)[0][0])
saddle_b.append(zip(*saddle)[1][0])
else:
saddle_a.append(np.nan)
saddle_b.append(np.nan)
else:
isBistable.append(0)
## Just to get pretty plots
if np.max(zip(*stable)[0]) > a_threshold:
stable_a_high.append(np.max(zip(*stable)[0]))
stable_a_low.append(np.nan)
else:
stable_a_high.append(np.nan)
stable_a_low.append(np.min(zip(*stable)[0]))
if np.max(zip(*stable)[1]) > b_threshold:
stable_b_high.append(np.max(zip(*stable)[1]))
stable_b_low.append(np.nan)
else:
stable_b_high.append(np.nan)
stable_b_low.append(np.min(zip(*stable)[1]))
saddle_a.append(np.nan)
saddle_b.append(np.nan)
3.3 1D bifurcation plot with DabX $k_{cat}$¶Grey region shows the bistable area, where we found two stable fixed-points (grey dots) and saddle points (dotted black line). In [22]:
oneIndices = np.arange(np.where(np.array(isBistable)==1)[0][0], np.where(np.array(isBistable)==1)[0][-1]+1)
bistableBoundaries = np.zeros(len(isBistable), dtype=int)
bistableBoundaries[oneIndices] = 1
In [23]:
# %matplotlib inline
fontsize = 12
fig, ax = plt.subplots()
ax.plot(scaled_DabX_kcat, stable_a_high, c='grey')#, s=4)
ax.plot(scaled_DabX_kcat, stable_a_low, c='grey')#, s=4)
ax.plot(scaled_DabX_kcat, saddle_a, 'k--')
ax.set_xlabel("DabX kcat ($s^{-1}$)", fontsize=fontsize)
ax.set_ylabel("Conc. a ($\mu$M)", fontsize=fontsize)
ax.set_xscale("log")
ax.set_xticks([10,100,1000])
ax.tick_params(axis='both', which='major', labelsize=fontsize)
ax.tick_params(axis='both', which='minor', labelsize=fontsize)
ax.fill_between(x=scaled_DabX_kcat, y1= ax.get_ylim()[0], y2 = ax.get_ylim()[1], where= list(bistableBoundaries), alpha=0.2, color='gray')
simpleaxis(ax)
fig.set_figwidth(5)
fig.set_figheight(5)
# plt.savefig("20915_BifurcationPlot_DabX.svg")
plt.show()
# plt.close()
3.3 Sampling efficiently across parameter space using latin hypercube sampling.¶We use an efficient sampling method called Latin Hypercube sampling which allows us to sample high dimensionsal parameter space efficiently. The lhsmdu repo is available here https://github.com/sahilm89/lhsmdu. In [24]:
minScale = -3 # Order of magnitude change in parameters
maxScale = 3
In [25]:
k = lhsmdu.sample(numReacs,numRates) ## Returns an efficient sample in numReacsxnumRates dimensions.
In [26]:
scalingVector = np.power(10,k*(maxScale-minScale) + minScale) ## Scaling logarithmically
In [27]:
scaled_AabX_Kf = np.array(scalingVector[0])[0]
scaled_AabX_Kb = np.array(scalingVector[1])[0]
scaled_DabX_Km = np.array(scalingVector[2])[0]
scaled_DabX_kcat = np.array(scalingVector[3])[0]
scaled_Jbca_Km = np.array(scalingVector[4])[0]
scaled_Jbca_kcat = np.array(scalingVector[5])[0]
rateMat = np.matrix([scaled_AabX_Kf, scaled_AabX_Kb, scaled_DabX_Km, scaled_DabX_kcat, scaled_Jbca_Km, scaled_Jbca_kcat])
In [28]:
rateNum = 0
allStables= []
allUnstables= []
allSaddles = []
for r in rateMat.T:
aabx_kf, aabx_kb, dabx_Km, dabx_kcat, jbca_Km, jbca_kcat = np.ravel(r)
stableVector = []
unStableVector = []
saddleVector = []
AabX.Kf = aabx_kf
AabX.Kb = aabx_kb
DabX.Km = dabx_Km* microMolarScaling
Jbca.Km = jbca_Km* microMolarScaling
DabX.kcat = dabx_kcat
Jbca.kcat = jbca_kcat
deleteSetup()
state, ksolve = setupSteadyState(modelName)
for iterations in range(numIterations):
state.randomInit()
state.resettle()
failedSteadyState = np.isnan(ksolve.nVec[0].any())
if not failedSteadyState:
vector = [pool.conc/microMolarScaling for pool in moose.wildcardFind(kineticsBasePath + '/##[ISA=PoolBase]')]
if np.count_nonzero(vector)>1 and state.solutionStatus == 0:
if state.stateType==0:
stableVector.append(vector)
elif state.stateType == 1:
unStableVector.append(vector)
elif state.stateType == 2:
saddleVector.append(vector)
else:
# print("Strange ", state.stateType)
pass
moose.reinit()
stable = uniqueElements(stableVector, tolerance, offset)
unstable = uniqueElements(unStableVector, tolerance, offset)
saddle = uniqueElements(saddleVector, tolerance, offset)
allStables.append(stable)
allUnstables.append(unstable)
allSaddles.append(saddle)
rateNum+=1
In [29]:
isBistable = []
stable_a_high = []
stable_b_high = []
stable_a_low = []
stable_b_low = []
saddle_a = []
saddle_b = []
for j in range(numRates):
stable = allStables_DabX[j]
unstable = allUnstables_DabX[j]
saddle = allSaddles_DabX[j]
if len(stable[0]) == 2:
isBistable.append(1)
saddle_a.append(zip(*saddle)[0][0])
saddle_b.append(zip(*saddle)[1][0])
else:
isBistable.append(0)
saddle_a.append(np.nan)
saddle_b.append(np.nan)
stable_a_high.append(np.max(zip(*stable)[0]))
stable_a_low.append(np.min(zip(*stable)[0]))
stable_b_high.append(np.max(zip(*stable)[1]))
stable_b_low.append(np.min(zip(*stable)[1]))
3.4 2-D Bifurcation plot for two rates (DabX $k_{cat}$ and Jbca $k_{cat}$), but with all rates randomly sampled.¶Black dots are bistable fixed points and orange dots are saddle points. Grey points are monostable. In [48]:
%matplotlib inline
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
s = 48 #markersize
isBistable = []
#colors = plt.cm.viridis(np.linspace(0,1,numRates))
for j in range(numRates):
stable = allStables[j]
unstable = allUnstables[j]
saddle = allSaddles[j]
for fp in stable:
if len(stable) ==2:
ax.scatter(np.log10(scaled_DabX_kcat[j]), np.log10(scaled_Jbca_kcat[j]), fp[0],c='k', s=s, marker='.')
else:
ax.scatter(np.log10(scaled_DabX_kcat[j]), np.log10(scaled_Jbca_kcat[j]), fp[0],c='gray', s=s, marker='.')
for fp in saddle:
if len(stable[0]) ==2:
ax.scatter(np.log10(scaled_DabX_kcat[j]), np.log10(scaled_Jbca_kcat[j]), fp[0], c='orange', s=s, marker='.')
else:
ax.scatter(np.log10(scaled_DabX_kcat[j]), np.log10(scaled_Jbca_kcat[j]), fp[0], c='orange', s=s, marker='.')
# for fp in unstable:
# ax.scatter(np.log10(scaled_DabX_kcat[j]), np.log10(scaled_Jbca_kcat[j]), fp[0], c='gray', s=24, marker='o')
if len(stable) == 2:
isBistable.append(1)
else:
isBistable.append(0)
ax.set_xlabel("DabX kcat ($s^{-1}$)", fontsize=fontsize)
ax.set_ylabel("Jbca kcat ($s^{-1}$)", fontsize=fontsize)
ax.set_zlabel("A (mM)", fontsize=fontsize)
ax.view_init(azim=44, elev=3)
ax.set_xticks([-3,0,3])
ax.set_yticks([-3,0,3])
ax.set_xticklabels(["$10^{-3}$","1","$10^{3}$"], fontsize=fontsize)
ax.set_yticklabels(["$10^{-3}$","1","$10^{3}$"], fontsize=fontsize)
fig.set_figwidth(5)
fig.set_figheight(5)
plt.show()
3.5 Rate-vector plot: The dark lines represent the bistable parameters among all the other (gray) parameter sets.¶In [31]:
######### Here not plotting the parameter heatmap, but the line-plot. ##########
fig, ax = plt.subplots()
for i in range(rateMat.shape[1]):
if isBistable[i] == 1:
ax.plot(range(rateMat.shape[0]), np.ravel(rateMat[:,i]), c='k', alpha=1.0, linewidth=1, zorder=2)
else:
ax.plot(range(rateMat.shape[0]), np.ravel(rateMat[:,i]), c='gray', alpha=0.05, linewidth=1, zorder=1)
xticks = np.arange(0,rateMat.shape[0])
ax.set_xticks(xticks)
rows = ['$K_f$', '$K_b$', '$K_m$', '$k_{cat}$', '$K_m$', '$k_{cat}$']
ax.set_xticklabels(rows,minor=False, fontsize=fontsize,rotation=0)
colors = ['k', 'k', 'green', 'green', 'purple', 'purple' ]
for xtick, color in zip(ax.get_xticklabels(), colors):
xtick.set_color(color)
ax.set_yscale('log')
yticks= np.logspace(-3,3,7 ,base=10)
ax.set_yticks(yticks, minor=False)
columns = [str(ylab) for ylab in yticks]
ax.set_yticklabels(columns,minor=False, fontsize=fontsize)
plt.ylabel('Reaction Rates', fontsize=fontsize)
simpleaxis(ax)
fig.set_figheight(5)
fig.set_figwidth(5)
plt.show()
4. Simulate the model and visualize bistability deterministically and stochastically¶4.1 Deterministic simulation with random external perturbations¶Here we pick one set of rates that we found to be bistable and assign it to our reaction network. Then we simulate the model deterministically over time and give it a kick by exchanging molecules between reactants $a$ and $b$. Note that we can only do this because this is valid according to this model's stoichiometry due to reaction $a \leftrightarrow b$. Black trace is concentration of $a$. In [32]:
bistableRates = rateMat.T[np.where(np.array(isBistable)==1)]
In [33]:
print(np.array(allStables)[np.where(np.array(isBistable)==1)])
In [34]:
bistableIndex = 1 # Chosing the first bistable rate set.
In [35]:
simdt = 1e-3
plotDt = 5e-2
runtime = 50
In [36]:
bistableRates = np.array(bistableRates[bistableIndex])[0]
AabX.Kf, AabX.Kb, DabX.Km, DabX.kcat, Jbca.Km, Jbca.kcat = bistableRates
DabX.Km*=microMolarScaling
Jbca.Km*=microMolarScaling
In [37]:
def setupDetSolver(kineticsBasePath = '/model/kinetics', simdt = simdt, plotDt=plotDt):
ksolve = moose.Ksolve( kineticsBasePath + '/ksolve' )
stoich = moose.Stoich(kineticsBasePath + '/stoich' )
ksolve.numAllVoxels= 1
stoich = moose.Stoich( kineticsBasePath +'/stoich' )
stoich.compartment = moose.element(kineticsBasePath)
stoich.ksolve = ksolve
stoich.path =kineticsBasePath + "/##"
state = moose.SteadyState( kineticsBasePath +'/state' )
state.stoich = stoich
state.convergenceCriterion = 1e-6
#### Set clocks here
moose.setClock(4, float(simdt))
moose.useClock(4, kineticsBasePath + "/##", "process")
moose.setClock(5, float(simdt))
moose.useClock(5, kineticsBasePath + '/ksolve', 'process' )
moose.useClock(8, '/model/graphs/#', 'process' )
moose.setClock(8, float(plotDt))
table = {}
moose.reinit()
for species in moose.wildcardFind(kineticsBasePath+ '/##[ISA=PoolBase]'):
tableName = '/model/graphs/plot'+ species.name
if not moose.exists(tableName):
table[species] = moose.Table(tableName)
moose.connect(table[species], 'requestOut', species, 'getConc')
moose.setClock(table[species].tick, float(plotDt))
else:
table[species] = moose.element(tableName)
moose.connect(table[species], 'requestOut', species, 'getConc')
moose.setClock(table[species].tick, float(plotDt))
# matrixTable = moose.Table2("/model/graphs/matrices")
# moose.connect(matrixTable, 'requestOut', state, 'showMatrices')
moose.reinit()
return ksolve, state
In [64]:
def plotTimeCourse(alist, blist, plotDt=plotDt):
fontsize=12
sumSpc = 0
for species in moose.wildcardFind(kineticsBasePath+ '/##[ISA=PoolBase]'):
sumSpc+=species.concInit
ncols = len(alist)
fig, ax = plt.subplots(figsize=(5*ncols,5), ncols=ncols)
for iteration in range(len(alist)):
for a,b in zip(alist[iteration], blist[iteration]):
ax[iteration].plot(np.arange(len(a))*plotDt, a, 'k', linewidth=1)
# ax.plot(np.arange(len(b))*plotDt, b, 'gray', linewidth=1)
ax[iteration].tick_params(axis='both', which='major', labelsize=fontsize)
ax[iteration].tick_params(axis='both', which='minor', labelsize=fontsize)
ax[iteration].set_xlabel("Time (s)", fontsize=fontsize)
ax[iteration].set_ylabel("Conc. $a$ ($\mu$M)", fontsize=fontsize)
ax[iteration].set_ylim((0,sumSpc/microMolarScaling))
simpleaxis(ax)
return plt, ax
In [39]:
deleteSetup()
ksolve, state = setupDetSolver()
plota = moose.element("/model/graphs/plota")
plotb = moose.element("/model/graphs/plotb")
plotc = moose.element("/model/graphs/plotc")
In [40]:
alist, blist, clist = {}, {}, {}
frac = [0.05, 0.95] ## Swapping ratios to perturb the system with
numIterations = len(frac)
a = moose.element("/model/kinetics/a")
b = moose.element("/model/kinetics/b")
c = moose.element("/model/kinetics/c")
In [41]:
a.conc, b.conc
Out[41]:
In [52]:
for iteration in range(numIterations):
alist[iteration] = []
blist[iteration] = []
clist[iteration] = []
moose.start(float(runtime))
## Swapping molecules between a and b
kickConc = frac[iteration] * a.conc
a.conc = a.conc - kickConc
b.conc = b.conc + kickConc
moose.start(float(runtime))
## Exchanging back molecules between a and b
kickConc = frac[iteration] * b.conc
a.conc = a.conc + kickConc
b.conc = b.conc - kickConc
moose.start(float(runtime))
alist[iteration].append(plota.vector[2:]/microMolarScaling)
blist[iteration].append(plotb.vector[2:]/microMolarScaling)
clist[iteration].append(plotc.vector[2:]/microMolarScaling)
moose.reinit()
In the 5% perturbation case, both attempts fail to lead to a transition. With 95% perturbation, the first attempt leads to a transition, but the second attempt with the swapping back of molecules fails.¶In [71]:
plt, ax = plotTimeCourse(alist, blist, plotDt=plotDt)
ax[0].text(x=100, y = 2.5, s="1, 5% $a$", color='r', fontsize=fontsize)
ax[0].text(x=200, y = 2.5, s="2, 5% $b$", color='r', fontsize=fontsize)
ax[1].text(x=100, y = 2.5, s="1, 95% $a$", color='r', fontsize=fontsize)
ax[1].text(x=200, y = 2.5, s="2, 95% $b$", color='r', fontsize=fontsize)
plt.show()
4.2 Stochastic simulation (internal fluctations)¶Bistable behavior of the model that we found can be seen using stochastic simulation, where the concentration vectors show spontaneous transitions between two stable-states. Simulations were done at 0.008 fL for 300 seconds. Black trace is concentration of molecule $a$. In [72]:
runtime = 300
vol = 8e-21
numIterations = 5
In [73]:
def setupStochSolver(kineticsBasePath = '/model/kinetics', simdt = simdt, plotDt=plotDt):
ksolve = moose.Ksolve( kineticsBasePath + '/ksolve' )
gsolve = moose.Gsolve(kineticsBasePath + '/gsolve')
stoich = moose.Stoich(kineticsBasePath + '/stoich' )
stoich = moose.Stoich( kineticsBasePath +'/stoich' )
stoich.compartment = moose.element(kineticsBasePath)
state = moose.SteadyState( kineticsBasePath +'/state' )
gsolve.numAllVoxels= 1
stoich.compartment = moose.element(kineticsBasePath )
stoich.ksolve = gsolve
stoich.path =kineticsBasePath + "/##"
state.stoich = stoich
state.convergenceCriterion = 1e-6
#### Set clocks here
moose.setClock(4, float(simdt))
moose.useClock(4, kineticsBasePath + "/##", "process")
moose.setClock(5, float(simdt))
moose.useClock(5, kineticsBasePath + '/gsolve', 'process' )
moose.useClock(8, '/model/graphs/#', 'process' )
moose.setClock(8, float(plotDt))
table = {}
moose.reinit()
for species in moose.wildcardFind(kineticsBasePath+ '/##[ISA=PoolBase]'):
tableName = '/model/graphs/plot'+ species.name
if not moose.exists(tableName):
table[species] = moose.Table(tableName)
moose.connect(table[species], 'requestOut', species, 'getConc')
moose.setClock(table[species].tick, float(plotDt))
else:
table[species] = moose.element(tableName)
moose.connect(table[species], 'requestOut', species, 'getConc')
moose.setClock(table[species].tick, float(plotDt))
moose.reinit()
return gsolve, state
In [74]:
deleteSetup()
gsolve, state = setupStochSolver()
moose.element("/model/kinetics/").volume = vol #m^3 = 1000L
In [75]:
alist, blist, clist = {}, {}, {}
for iteration in range(numIterations):
alist[iteration] = []
blist[iteration] = []
clist[iteration] = []
state.randomInit()
moose.start(runtime)
a = plota.vector[1:]/microMolarScaling
b = plotb.vector[1:]/microMolarScaling
c = plotc.vector[1:]/microMolarScaling
moose.reinit()
alist[iteration].append(a)
blist[iteration].append(b)
clist[iteration].append(c)
Spontaneous transitions of $a$ between a high and a low state can be seen in the figure below.¶In [76]:
plt, ax = plotTimeCourse(alist, blist, plotDt=plotDt)
plt.show()
|