Skip to content
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

C++ Hybrid Solver: Events #594

Merged
merged 22 commits into from
Jan 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
4a3d8ff
Initial design for `Event` static methods
jtcooper10 Jul 31, 2021
4878386
Refactor C++ parameters to be vectorized
jtcooper10 Aug 4, 2021
afd9d97
Implement basic `Event` class in C++
jtcooper10 Aug 5, 2021
743a59c
Python-side template macros for events
jtcooper10 Aug 5, 2021
ed83291
Fix assignment ID templating
jtcooper10 Aug 5, 2021
7b9e7d2
Fix assignment ID templating
jtcooper10 Aug 5, 2021
3d678e6
Unfinished version of events for simulation loop
jtcooper10 Aug 6, 2021
a026adf
Queue for non-persistent events
jtcooper10 Aug 14, 2021
49113bb
Root-finder and updates to trigger pipeline
jtcooper10 Sep 9, 2021
1a56c10
Basic direct event trigger detection
jtcooper10 Oct 3, 2021
bcbe4d1
Basic event toggle functions for `Integrator`
jtcooper10 Nov 4, 2021
fb001d3
Trigger execution for root-finding
jtcooper10 Nov 17, 2021
ee31ca3
Bug fixes before merge
jtcooper10 Nov 18, 2021
bfe8890
Unit tests for events and bug fixes
jtcooper10 Nov 19, 2021
356aad0
Initial value templating
jtcooper10 Nov 20, 2021
7f290d8
Event trigger clean-up and initial values implementation
jtcooper10 Nov 20, 2021
bf4bdf2
Merge branch 'develop' into feature/cpp-hybrid-events
jtcooper10 Nov 20, 2021
2b56cb1
Bug-fix: errant `continue`, mis-ordered initialization
jtcooper10 Dec 20, 2021
f9015fc
Merge branch 'develop' of github.com:StochSS/GillesPy2 into feature/c…
briandrawert Jan 7, 2022
6c77fa0
Added default names for Reactions, Rate Rules, Assignment Rules, Even…
BryanRumsey Jan 7, 2022
84f0afc
Added missing imports
BryanRumsey Jan 7, 2022
31d49af
Merge branch 'auto-gen-names' of github.com:StochSS/GillesPy2 into fe…
briandrawert Jan 7, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion gillespy2/core/assignmentrule.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""

import uuid

from gillespy2.core.sortableobject import SortableObject
from gillespy2.core.jsonify import Jsonify

Expand All @@ -36,9 +38,12 @@ class AssignmentRule(SortableObject, Jsonify):
"""

def __init__(self, variable=None, formula=None, name=None):
if name in (None, ""):
self.name = f'ar{uuid.uuid4()}'.replace('-', '_')
else:
self.name = name
self.variable = variable
self.formula = formula
self.name = name

def __str__(self):
return self.variable + ': ' + self.formula
Expand Down
9 changes: 8 additions & 1 deletion gillespy2/core/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""

import uuid

from gillespy2.core.gillespyError import *
from gillespy2.core.jsonify import Jsonify

Expand All @@ -39,7 +41,12 @@ class EventAssignment(Jsonify):

"""

def __init__(self, variable=None, expression=None):
def __init__(self, name=None, variable=None, expression=None):

if name in (None, ""):
self.name = f'evn{uuid.uuid4()}'.replace('-', '_')
else:
self.name = name

self.variable = variable
self.expression = expression
Expand Down
7 changes: 6 additions & 1 deletion gillespy2/core/functiondefinition.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""

import uuid

from gillespy2.core.sortableobject import SortableObject
from gillespy2.core.jsonify import Jsonify

Expand All @@ -38,7 +40,10 @@ def __init__(self, name="", function=None, args=[]):
if function is None:
raise TypeError("Function string provided for FunctionDefinition cannot be None")

self.name = name
if name in (None, ""):
self.name = f'fd{uuid.uuid4()}'.replace('-', '_')
else:
self.name = name
self.function_string = function
self.args = args

Expand Down
7 changes: 6 additions & 1 deletion gillespy2/core/raterule.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""

import uuid

from gillespy2.core.sortableobject import SortableObject
from gillespy2.core.jsonify import Jsonify

Expand All @@ -34,9 +36,12 @@ class RateRule(SortableObject, Jsonify):
"""

def __init__(self, variable=None, formula='', name=None):
if name in (None, ""):
self.name = f'rr{uuid.uuid4()}'.replace('-', '_')
else:
self.name = name
self.formula = formula
self.variable = variable
self.name = name

def __str__(self):
try:
Expand Down
5 changes: 4 additions & 1 deletion gillespy2/core/reaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,10 @@ def __init__(self, name="", reactants={}, products={}, propensity_function=None,
"""

# Metadata
self.name = name
if name in (None, ""):
self.name = f'rxn{uuid.uuid4()}'.replace('-', '_')
else:
self.name = name
self.annotation = ""

# We might use this flag in the future to automatically generate
Expand Down
4 changes: 2 additions & 2 deletions gillespy2/solvers/cpp/build/build_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,14 +110,14 @@ def prepare(self, model: "Union[Model, template_gen.SanitizedModel]", variable=F

# If a raw GillesPy2 model was provided, convert it to a sanitized model.
if isinstance(model, gillespy2.Model):
model = template_gen.SanitizedModel(model)
model = template_gen.SanitizedModel(model, variable=variable)
elif not isinstance(model, template_gen.SanitizedModel):
raise TypeError(f"Build engine expected gillespy2.Model or SanitizedModel type: received {type(model)}")

# Build the template and write it to the temp directory and remove the sample template_definitions header.
template_file = self.template_dir.joinpath(self.template_definitions_name)
template_file.unlink()
template_gen.write_definitions(str(template_file), model.get_template(variable=variable))
template_gen.write_definitions(str(template_file), model.get_template())
custom_definitions = model.get_options()
if custom_definitions is not None:
options_file = self.template_dir.joinpath(self.template_options_name)
Expand Down
33 changes: 23 additions & 10 deletions gillespy2/solvers/cpp/build/template_gen.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,23 +34,32 @@ class SanitizedModel:
:type model: gillespy2.Model
"""
reserved_names = {
"vol": "V",
"t": "t",
}

def __init__(self, model: Model):
def __init__(self, model: Model, variable=False):
self.model = model
self.variable = variable

self.species: "OrderedDict[str, Species]" = OrderedDict()
self.species_names = model.sanitized_species_names()
for species_name, sanitized_name in self.species_names.items():
self.species_id: "OrderedDict[str, int]" = OrderedDict()
for spec_id, spec_entry in enumerate(self.species_names.items()):
species_name, sanitized_name = spec_entry
self.species[sanitized_name] = model.get_species(species_name)
self.species_id[species_name] = spec_id

self.parameters: "OrderedDict[str, Parameter]" = OrderedDict()
self.parameter_names = model.sanitized_parameter_names()
self.parameter_names: "OrderedDict[str, str]" = OrderedDict()
self.parameter_names["vol"] = "P[0]" if variable else "C[0]"
self.parameter_id: "OrderedDict[str, int]" = OrderedDict()
for param_id, param_name in enumerate(model.listOfParameters.keys(), start=1):
if param_name not in self.parameter_names:
self.parameter_names[param_name] = f"P[{param_id}]" if variable else f"C[{param_id}]"
self.parameter_id[param_name] = param_id
for parameter_name, sanitized_name in self.parameter_names.items():
self.parameters[sanitized_name] = model.get_parameter(parameter_name) \
if parameter_name != "vol" else Parameter(name="V", expression=model.volume)
if parameter_name != "vol" else Parameter(name=sanitized_name, expression=str(model.volume))

base_namespace = {
# ORDER IS IMPORTANT HERE!
Expand Down Expand Up @@ -153,7 +162,7 @@ def use_rate_rule(self, rate_rule: "RateRule") -> "SanitizedModel":
log.warning(f"Could not sanitize rate rule formula expression: {rate_rule.formula}")
return self

def get_template(self, variable=False) -> "dict[str, str]":
def get_template(self) -> "dict[str, str]":
"""
Creates a dictionary of C++ macro definitions from the given model.
The keys of the dictionary contain the name of the macro definition.
Expand All @@ -168,7 +177,7 @@ def get_template(self, variable=False) -> "dict[str, str]":
results = dict({})

# Get definitions for variables
parameter_definitions = template_def_variables(self, variable)
parameter_definitions = template_def_variables(self, self.variable)
results.update(parameter_definitions)

# Get definitions for species
Expand Down Expand Up @@ -332,11 +341,15 @@ def template_def_variables(model: SanitizedModel, variable=False) -> "dict[str,
parameter_type = "VARIABLE" if variable else "CONSTANT"
# Parameter entries, parsed and formatted
parameter_set = []
for param_name, parameter in model.parameters.items():
parameter_set.append(f"{parameter_type}({param_name},{parameter.expression})")
for param_id, parameter in enumerate(model.parameters.values()):
parameter_set.append(f"{parameter_type}({param_id},{parameter.expression})")

return {
"GPY_PARAMETER_VALUES": " ".join(parameter_set)
"GPY_PARAMETER_VALUES": " ".join(parameter_set),
# Currently assumes all variable or all constant.
# For partially variable models, modify to compute these two separately.
"GPY_PARAMETER_NUM_VARIABLES": str(len(parameter_set)) if variable else "0",
"GPY_PARAMETER_NUM_CONSTANTS": str(len(parameter_set)) if not variable else "0",
}


Expand Down
5 changes: 5 additions & 0 deletions gillespy2/solvers/cpp/c_base/model.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@

namespace Gillespy {

int Reaction::s_num_constants;
int Reaction::s_num_variables;
std::shared_ptr<double> Reaction::s_variables;
std::shared_ptr<const double> Reaction::s_constants;

template <typename PType>
Model<PType>::Model(
std::vector<std::string> species_names,
Expand Down
70 changes: 58 additions & 12 deletions gillespy2/solvers/cpp/c_base/model.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,17 @@

#pragma once

#include "template.h"

#include <cmath>
#include <memory>
#include <string>
#include <vector>
#include <iostream>

namespace Gillespy {
namespace Gillespy
{
typedef unsigned int ReactionId;

template <typename PType>
struct Species {
Expand All @@ -48,6 +52,59 @@ namespace Gillespy {

// List of reactions who's propensities will change when this reaction fires.
std::unique_ptr<int[]> species_change;

inline static double propensity(
ReactionId reaction_id,
double *state,
double *parameters,
const double *constants)
{
return map_propensity(reaction_id, state, parameters, constants);
}

inline static double propensity(
ReactionId reaction_id,
unsigned int *state,
double *parameters,
const double *constants)
{
return map_propensity(reaction_id, state, parameters, constants);
}

inline static double propensity(
ReactionId reaction_id,
int *state,
double *parameters,
const double *constants)
{
return map_propensity(reaction_id, state, parameters, constants);
}

inline static double propensity(ReactionId reaction_id, double *state)
{
return map_propensity(reaction_id, state, s_variables.get(), s_constants.get());
}

inline static double propensity(ReactionId reaction_id, int *state)
{
return map_propensity(reaction_id, state, s_variables.get(), s_constants.get());
}

inline static double propensity(ReactionId reaction_id, unsigned int *state)
{
return map_propensity(reaction_id, state, s_variables.get(), s_constants.get());
}

inline static void load_parameters()
{
s_variables = std::shared_ptr<double>(get_variables(&s_num_variables));
s_constants = std::shared_ptr<const double>(get_constants(&s_num_constants));
}

static int s_num_variables;
static int s_num_constants;
static std::shared_ptr<double> s_variables;
static std::shared_ptr<const double> s_constants;
};

template <typename PType>
Expand All @@ -67,15 +124,6 @@ namespace Gillespy {
);
};

class IPropensityFunction {
public:
virtual double evaluate(unsigned int reaction_number, unsigned int *state) = 0;
virtual double TauEvaluate(unsigned int reaction_number, const int *S) = 0;
virtual double ODEEvaluate(int reaction_number, const std::vector<double> &S) = 0;

virtual ~IPropensityFunction() {};
};

template <typename PType>
struct Simulation {
int random_seed;
Expand All @@ -95,8 +143,6 @@ namespace Gillespy {

Model<PType> *model;

IPropensityFunction *propensity_function;

template <class T> friend std::ostream &operator << (std::ostream &os, const Simulation<T> &simulation);

// output_results_buffer: Writes the contents of the entire simulation trajectory.
Expand Down
24 changes: 1 addition & 23 deletions gillespy2/solvers/cpp/c_base/ode_cpp_solver/ODESimulation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,25 +39,6 @@ unsigned int number_timesteps = 0;
double end_time = 100.0;
double increment = 0;

class PropensityFunction : public IPropensityFunction
{
public:
double evaluate(unsigned int reaction_number, unsigned int *S)
{
return 1.0;
}

double TauEvaluate(unsigned int reaction_number, const int *S)
{
return 1.0;
}

double ODEEvaluate(int reaction_number, const std::vector<double> &S)
{
return map_ode_propensity(reaction_number, S);
}
};

int main(int argc, char *argv[]) {
ArgParser parser = ArgParser(argc, argv);

Expand All @@ -72,6 +53,7 @@ int main(int argc, char *argv[]) {
number_timesteps = parser.timesteps;
increment = parser.increment;

Reaction::load_parameters();
Model<double> model(species_names, species_populations, reaction_names);
add_reactions(model);

Expand All @@ -80,15 +62,13 @@ int main(int argc, char *argv[]) {
random_seed = time(NULL);
}

IPropensityFunction *propensity_function = new PropensityFunction();
Simulation<double> simulation;

simulation.model = &model;
simulation.end_time = end_time;
simulation.random_seed = random_seed;
simulation.number_timesteps = number_timesteps;
simulation.number_trajectories = number_trajectories;
simulation.propensity_function = propensity_function;
simulation.current_time = 0.0;
simulation.output_interval = parser.output_interval;

Expand All @@ -98,7 +78,5 @@ int main(int argc, char *argv[]) {
ODESolver(&simulation, increment);
simulation.output_buffer_final(std::cout);

delete propensity_function;

return 0;
}
2 changes: 1 addition & 1 deletion gillespy2/solvers/cpp/c_base/ode_cpp_solver/ODESolver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ namespace Gillespy
for (sunindextype reaction_index = 0; reaction_index < number_reactions; reaction_index++)
{
// Calculate propensity for each reaction at the current state.
propensity.push_back(simulation->propensity_function->ODEEvaluate((int)reaction_index, current_state));
propensity.push_back(Reaction::propensity(reaction_index, ydata));

for (sunindextype species_index = 0; species_index < number_species; species_index++)
{
Expand Down
Loading