-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Forms of propensity calculations #35
base: master
Are you sure you want to change the base?
Changes from all commits
8985e96
df877d3
5a60cd9
62d037a
de698ef
cf462cc
cc2c522
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -80,12 +80,13 @@ class StochasticSystem(object): | |
(and zero everywhere else). | ||
''' | ||
|
||
def __init__(self, stoichiometry, rates, random_seed=0): | ||
def __init__(self, stoichiometry, rates, forms=None, random_seed=0): | ||
''' | ||
This invokes the Obsidian C module (see obsidianmodule.c) with the | ||
stoichiometry, reaction rates and a variety of derived values. Once constructed, | ||
this can be invoked by calling `evolve` with a duration and initial state, since | ||
the stoichiometry will be shared among all calls. | ||
stoichiometry, reaction rates, the form of each reaction (optional) | ||
and a variety of derived values. Once constructed, this can be invoked | ||
by calling `evolve` with a duration and initial state, since the | ||
stoichiometry will be shared among all calls. | ||
|
||
There are four derived values, each of which is a list of variable length | ||
lists. In order to pass this into C, these nested lists are flattened and two | ||
|
@@ -101,7 +102,14 @@ def __init__(self, stoichiometry, rates, random_seed=0): | |
''' | ||
|
||
self.stoichiometry = stoichiometry | ||
self.rates = rates | ||
if rates.ndim == 1: | ||
rates = [[rate] for rate in rates.astype(np.float64)] | ||
self.rates_flat, self.rates_lengths, self.rates_indexes = flat_indexes(rates) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good! This means our rates are always in the same form, even if there is just a single rate per reaction. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Apart from not being clear why 'rates' would be something other than 1D,
|
||
|
||
if forms is not None: | ||
self.forms = forms | ||
else: | ||
self.forms = np.zeros(stoichiometry.shape[0], dtype=np.int64) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Again, good. Leave all the optional functionality in the python and pass one consistent form to the C. Thanks for this. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it's a bit more elegant to write code like if forms is None:
forms = # some sort of default
self.forms = forms which separates out the input parsing from the attribute assignment. However this is more my preference than a standard, if I'm not mistaken. This is a common enough case that you'd think there would be a standard but I've never seen mention of one. |
||
|
||
self.random_seed = random_seed | ||
|
||
|
@@ -117,7 +125,10 @@ def __init__(self, stoichiometry, rates, random_seed=0): | |
self.obsidian = obsidian.obsidian( | ||
self.random_seed, | ||
self.stoichiometry, | ||
self.rates, | ||
self.rates_flat, | ||
self.rates_lengths, | ||
self.rates_indexes, | ||
self.forms, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. More args! Let's talk about what it looks like to make structs for the nested lists.... may be more trouble than it is worth, but could also clarify some things. It is a tradeoff we should evaluate. |
||
self.reactants_lengths, | ||
self.reactants_indexes, | ||
self.reactants_flat, | ||
|
@@ -131,7 +142,7 @@ def __init__(self, stoichiometry, rates, random_seed=0): | |
|
||
def evolve(self, duration, state): | ||
steps, time, events, outcome = self.obsidian.evolve(duration, state) | ||
occurrences = np.zeros(len(self.rates)) | ||
occurrences = np.zeros(self.stoichiometry.shape[0]) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think |
||
for event in events: | ||
occurrences[event] += 1 | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -24,6 +24,12 @@ choose(long n, long k) { | |
return combinations; | ||
} | ||
|
||
// Compute the fractional saturation | ||
double | ||
fraction_saturation(long s, double k) { | ||
return ((double) s / (s + k)); | ||
} | ||
|
||
// Perform the Gillespie algorithm with the given stoichiometry, reaction rates and | ||
// initial state for the provided duration. | ||
|
||
|
@@ -34,11 +40,14 @@ choose(long n, long k) { | |
// contains all the information about the reactions this system performs. Each | ||
// row is a reaction, and each column is a substrate, so every entry is the | ||
// change in counts of each substrate when the given reaction is performed. | ||
// * rates: an array of length `reactions_count` that encodes the base rate for | ||
// each reaction. The actual propensity is further dependent on the counts for | ||
// each reactant in the reaction. | ||
// * rates_flat: an array of length `reactions_count` (or greater if the reaction | ||
// is not of the default form 0) that encodes the base rate for each reaction. | ||
// The actual propensity is further dependent on the counts for each reactant | ||
// in the reaction. | ||
// * forms: an array of length `reactions_count` that indicates the type of | ||
// propensity computation that is associated with each reaction. | ||
// * duration: How long to run the simulation for. | ||
// * state: An array of length `substrates_count` that contains the inital count | ||
// * state: An array of length `substrates_count` that contains the initial count | ||
// for each substrate. The outcome of the algorithm will involve an array of | ||
// the same size that signifies the counts of each substrate after all the | ||
// reactions are performed. | ||
|
@@ -83,7 +92,10 @@ evolve(MTState *random_state, | |
int reactions_count, | ||
int substrates_count, | ||
long *stoichiometry, | ||
double *rates, | ||
double *rates_flat, | ||
long *rates_lengths, | ||
long *rates_indexes, | ||
long *forms, | ||
|
||
long *reactants_lengths, | ||
long *reactants_indexes, | ||
|
@@ -158,18 +170,47 @@ evolve(MTState *random_state, | |
// First update all propensities that were affected by the previous event | ||
for (up = 0; up < update_length; up++) { | ||
|
||
// Find which reaction to update and initialize the propensity for that reaction | ||
// with the rate for that reaction | ||
// Find which reaction to update | ||
reaction = update[up]; | ||
propensities[reaction] = rates[reaction]; | ||
|
||
// Go through each reactant and calculate its contribution to the propensity | ||
// based on the counts of the corresponding substrate, which is then multiplied | ||
// by the reaction's original rate and the contributions from other reactants | ||
for (reactant = 0; reactant < reactants_lengths[reaction]; reactant++) { | ||
index = reactants_indexes[reaction] + reactant; | ||
count = outcome[reactants[index]]; | ||
propensities[reaction] *= choose(count, reactions[index]); | ||
|
||
// Compute propensity depending on the reaction's form | ||
switch(forms[reaction]) { | ||
|
||
case 0: // Standard Gillespie | ||
// Initialize the propensity for reaction with its rate constant | ||
propensities[reaction] = rates_flat[reaction]; | ||
|
||
// Go through each reactant and calculate its contribution to the | ||
// propensity based on the counts of the corresponding substrate, | ||
// which is then multiplied by the reaction's original rate and the | ||
// contributions from other reactants | ||
for (reactant = 0; reactant < reactants_lengths[reaction]; reactant++) { | ||
index = reactants_indexes[reaction] + reactant; | ||
count = outcome[reactants[index]]; | ||
propensities[reaction] *= choose(count, reactions[index]); | ||
} | ||
|
||
break; | ||
|
||
case 1: // Michaelis-Menten | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Right, the
There will get to be a point where this switch gets unwieldy, but it serves for now. |
||
// Initialize the propensity for reaction with its maximal propensity | ||
index = rates_indexes[reaction]; | ||
propensities[reaction] = rates_flat[index]; | ||
|
||
// Go through each reactant and calculate its contribution to the | ||
// propensity based on the counts of the corresponding substrates | ||
// relative to their Michaelis-Menten constant. | ||
for (reactant = 0; reactant < reactants_lengths[reaction]; reactant++) { | ||
index = reactants_indexes[reaction] + reactant; | ||
count = outcome[reactants[index]]; | ||
propensities[reaction] *= fraction_saturation(count, | ||
rates_flat[rates_indexes[reaction] + reactant + 1]); | ||
} | ||
|
||
break; | ||
|
||
default: | ||
printf("arrow.obsidian.evolve - unexpected form: %ld", forms[reaction]); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This probably needs to throw an error rather than just print a message. |
||
} | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -39,7 +39,10 @@ typedef struct { | |
int reactions_count; | ||
int substrates_count; | ||
long *stoichiometry; | ||
double *rates; | ||
double *rates_flat; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Love how many places you have to add this.... Looking forward to evaluating @1fish2's proposal of using cython for the glue (module) code here. |
||
long *rates_lengths; | ||
long *rates_indexes; | ||
long *forms; | ||
|
||
long *reactants_lengths; | ||
long *reactants_indexes; | ||
|
@@ -66,7 +69,10 @@ newObsidianObject(int random_seed, | |
int reactions_count, | ||
int substrates_count, | ||
long *stoichiometry, | ||
double *rates, | ||
double *rates_flat, | ||
long *rates_lengths, | ||
long *rates_indexes, | ||
long *forms, | ||
|
||
long *reactants_lengths, | ||
long *reactants_indexes, | ||
|
@@ -103,7 +109,10 @@ newObsidianObject(int random_seed, | |
self->reactions_count = reactions_count; | ||
self->substrates_count = substrates_count; | ||
self->stoichiometry = stoichiometry; | ||
self->rates = rates; | ||
self->rates_flat = rates_flat; | ||
self->rates_lengths = rates_lengths; | ||
self->rates_indexes = rates_indexes; | ||
self->forms = forms; | ||
|
||
self->reactants_lengths = reactants_lengths; | ||
self->reactants_indexes = reactants_indexes; | ||
|
@@ -138,7 +147,7 @@ Obsidian_demo(ObsidianObject *self, PyObject *args) | |
if (!PyArg_ParseTuple(args, ":demo")) | ||
return NULL; | ||
|
||
print_array(self->rates, self->reactions_count); | ||
print_array(self->rates_flat, self->reactions_count); | ||
|
||
Py_INCREF(Py_None); | ||
return Py_None; | ||
|
@@ -187,7 +196,10 @@ Obsidian_evolve(ObsidianObject *self, PyObject *args) | |
self->reactions_count, | ||
self->substrates_count, | ||
self->stoichiometry, | ||
self->rates, | ||
self->rates_flat, | ||
self->rates_lengths, | ||
self->rates_indexes, | ||
self->forms, | ||
self->reactants_lengths, | ||
self->reactants_indexes, | ||
self->reactants, | ||
|
@@ -364,7 +376,10 @@ _invoke_obsidian(PyObject *self, PyObject *args) { | |
|
||
int random_seed; | ||
PyObject *stoichiometry_obj, | ||
*rates_obj, | ||
*rates_flat_obj, | ||
*rates_lengths_obj, | ||
*rates_indexes_obj, | ||
*forms_obj, | ||
|
||
*reactants_lengths_obj, | ||
*reactants_indexes_obj, | ||
|
@@ -380,10 +395,13 @@ _invoke_obsidian(PyObject *self, PyObject *args) { | |
*substrates_obj; | ||
|
||
if (!PyArg_ParseTuple(args, | ||
"iOOOOOOOOOOOO", | ||
"iOOOOOOOOOOOOOOO", | ||
&random_seed, | ||
&stoichiometry_obj, | ||
&rates_obj, | ||
&rates_flat_obj, | ||
&rates_lengths_obj, | ||
&rates_indexes_obj, | ||
&forms_obj, | ||
|
||
&reactants_lengths_obj, | ||
&reactants_indexes_obj, | ||
|
@@ -406,8 +424,16 @@ _invoke_obsidian(PyObject *self, PyObject *args) { | |
long *stoichiometry = (long *) PyArray_DATA(stoichiometry_array); | ||
|
||
// Import the rates for each reaction | ||
PyObject *rates_array = array_for(rates_obj, NPY_DOUBLE); | ||
double *rates = (double *) PyArray_DATA(rates_array); | ||
PyObject *rates_flat_array = array_for(rates_flat_obj, NPY_DOUBLE); | ||
double *rates_flat = (double *) PyArray_DATA(rates_flat_array); | ||
PyObject *rates_lengths_array = array_for(rates_lengths_obj, NPY_INT64); | ||
long *rates_lengths = (long *) PyArray_DATA(rates_lengths_array); | ||
PyObject *rates_indexes_array = array_for(rates_indexes_obj, NPY_INT64); | ||
long *rates_indexes = (long *) PyArray_DATA(rates_indexes_array); | ||
|
||
// Import the forms for each reaction | ||
PyObject *forms_array = array_for(forms_obj, NPY_INT64); | ||
long *forms = (long *) PyArray_DATA(forms_array); | ||
|
||
// Pull all the precalculated nested arrays | ||
PyObject *reactants_lengths_array = array_for(reactants_lengths_obj, NPY_INT64); | ||
|
@@ -438,7 +464,10 @@ _invoke_obsidian(PyObject *self, PyObject *args) { | |
reactions_count, | ||
substrates_count, | ||
stoichiometry, | ||
rates, | ||
rates_flat, | ||
rates_lengths, | ||
rates_indexes, | ||
forms, | ||
|
||
reactants_lengths, | ||
reactants_indexes, | ||
|
@@ -459,6 +488,10 @@ _invoke_obsidian(PyObject *self, PyObject *args) { | |
} | ||
|
||
// Clean up all the PyObject * references | ||
Py_XDECREF(rates_flat_array); | ||
Py_XDECREF(rates_lengths_array); | ||
Py_XDECREF(rates_indexes_array); | ||
|
||
Py_XDECREF(reactants_lengths_array); | ||
Py_XDECREF(reactants_indexes_array); | ||
Py_XDECREF(reactants_array); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This needs a doc string, as I've forgotten what 'rates' is and don't know where to look. :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are these stochastic rate constants, perhaps? How do they play with the new 'forms'? Are 'forms' like kinetic rate laws sans the outermost proportionality constant?