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

Fix hybrid switching #853

Merged
merged 21 commits into from
Aug 9, 2022
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
8900ef3
Changed how CV is calculated, now using time-averaged. Corrected CV c…
briandrawert Jul 25, 2022
6b5e59f
clean up
briandrawert Jul 26, 2022
91a6105
Fixing the tau step size selection code, needed reactants and product…
briandrawert Jul 26, 2022
8637d1e
Using time-averaged CV for TauHybridCSolver, and swtich_tol is now pa…
briandrawert Jul 26, 2022
59e0c61
for switch_tol=0.005, the C and python solvers differ
briandrawert Jul 26, 2022
9b5c135
removing the tau step size from hybrid switching calculation
briandrawert Jul 27, 2022
2db2140
going back to CV_a
briandrawert Jul 27, 2022
0daa2d1
Merge branch 'develop' of github.com:StochSS/GillesPy2 into no_tau_in…
briandrawert Jul 27, 2022
1d18841
Fixed bugs in Tau-Hybrid solvers
briandrawert Jul 28, 2022
f73678c
recucing test runtimr
briandrawert Jul 29, 2022
7467d5e
checking tests
briandrawert Aug 1, 2022
90c8f72
fixed bug in dependency graph
briandrawert Aug 1, 2022
c94ccdc
re-enabling all integration tests
briandrawert Aug 1, 2022
c7360b1
Merge pull request #854 from StochSS/no_tau_in_switching_calc
briandrawert Aug 1, 2022
383af1f
removing commented code
briandrawert Aug 2, 2022
886f2cf
addressing review comments
briandrawert Aug 3, 2022
38fa0b4
Adding 'profile_reactions' to tau-hybrid solver for easy visualizatio…
briandrawert Aug 5, 2022
227cb3d
Merge branch 'develop' of github.com:StochSS/GillesPy2 into fix_hybri…
briandrawert Aug 9, 2022
3cb1422
Merge branch 'develop' of github.com:StochSS/GillesPy2 into fix_hybri…
briandrawert Aug 9, 2022
0cc5104
fixing problem generated by merge
briandrawert Aug 9, 2022
f132b22
fixed issues created by merge
briandrawert Aug 9, 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
370 changes: 370 additions & 0 deletions examples/Advanced_Features/Hybrid_Switching_Tests.ipynb

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion gillespy2/solvers/cpp/build/build_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ 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, variable=variable)
elif not isinstance(model, template_gen.SanitizedModel):
elif not hasattr(model,'get_template'):
briandrawert marked this conversation as resolved.
Show resolved Hide resolved
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.
Expand Down
24 changes: 21 additions & 3 deletions gillespy2/solvers/cpp/build/template_gen.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ def __init__(self, model: Model, variable=False):
# Reactions: maps reaction names to their stoichiometry matrix.
# Stoichiometry matrix maps a sanitized species name to its stoichiometry.
self.reactions: "OrderedDict[str, dict[str, int]]" = OrderedDict()
self.reaction_reactants: "OrderedDict[str, dict[str, int]]" = OrderedDict()
self.reaction_products: "OrderedDict[str, dict[str, int]]" = OrderedDict()
# Rate Rules: maps sanitized species names to their corresponding rate rule expression.
self.rate_rules: "OrderedDict[str, str]" = OrderedDict()
# Options: custom definitions that can be supplied by the solver, maps macros to their definitions.
Expand Down Expand Up @@ -122,15 +124,19 @@ def use_reaction(self, reaction: "Reaction") -> "SanitizedModel":
:type reaction: gillespy2.Reaction
"""
self.reactions[reaction.name] = {spec: int(0) for spec in self.species_names.values()}
self.reaction_reactants[reaction.name] = {spec: int(0) for spec in self.species_names.values()}
self.reaction_products[reaction.name] = {spec: int(0) for spec in self.species_names.values()}
for reactant, stoich_value in reaction.reactants.items():
if isinstance(reactant, Species):
if hasattr(reactant,'name'):
briandrawert marked this conversation as resolved.
Show resolved Hide resolved
reactant = self.species_names[reactant.name]
self.reactions[reaction.name][reactant] -= int(stoich_value)
self.reaction_reactants[reaction.name][reactant] = int(stoich_value)

for product, stoich_value in reaction.products.items():
if isinstance(product, Species):
if hasattr(product,'name'):
product = self.species_names[product.name]
self.reactions[reaction.name][product] += int(stoich_value)
self.reaction_products[reaction.name][product] = int(stoich_value)

return self

Expand Down Expand Up @@ -401,18 +407,30 @@ def template_def_reactions(model: SanitizedModel, ode=False) -> "dict[str, str]"
"""
num_reactions = str(len(model.reactions))
reaction_set = OrderedDict()
reactants_set = OrderedDict()
products_set = OrderedDict()

for rxn_name, reaction in model.reactions.items():
stoich = [str(int(reaction[species])) for species in model.species_names.values()]
reaction_set[rxn_name] = f"{{{','.join(stoich)}}}"
for rxn_name, reaction_reactants in model.reaction_reactants.items():
reactants_count = [str(int(reaction_reactants[species])) for species in model.species_names.values()]
reactants_set[rxn_name] = f"{{{','.join(reactants_count)}}}"
for rxn_name, reaction_products in model.reaction_products.items():
products_count = [str(int(reaction_products[species])) for species in model.species_names.values()]
products_set[rxn_name] = f"{{{','.join(products_count)}}}"

reaction_names = " ".join([f"REACTION_NAME({rxn})" for rxn in reaction_set.keys()])
reaction_set = f"{{{','.join(reaction_set.values())}}}"
reactants_set = f"{{{','.join(reactants_set.values())}}}"
products_set = f"{{{','.join(products_set.values())}}}"

return {
"GPY_NUM_REACTIONS": num_reactions,
"GPY_REACTIONS": reaction_set,
"GPY_REACTION_NAMES": reaction_names,
"GPY_REACTIONS": reaction_set,
"GPY_REACTION_REACTANTS": reactants_set,
"GPY_REACTION_PRODUCTS": products_set,
}


Expand Down
4 changes: 2 additions & 2 deletions gillespy2/solvers/cpp/c_base/Tau/tau.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,12 @@ namespace Gillespy

for (int spec = 0; spec < model.number_species; spec++)
{
if (model.reactions[r].species_change[spec] > 0)
if (model.reactions[r].products_change[spec] > 0)
{
tau_args.products[r].push_back(spec);
}

else if (model.reactions[r].species_change[spec] < 0)
else if (model.reactions[r].reactants_change[spec] > 0)
{
rxn_order += 1;
tau_args.reactions_reactants[r].push_back(spec);
Expand Down
11 changes: 10 additions & 1 deletion gillespy2/solvers/cpp/c_base/model.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,13 @@ namespace Gillespy {
for (unsigned int reaction = 0; reaction < number_reactions; reaction++) {
reactions[reaction].name = reaction_names[reaction];
reactions[reaction].species_change = std::make_unique<int[]>(number_species);
reactions[reaction].reactants_change = std::make_unique<int[]>(number_species);
reactions[reaction].products_change = std::make_unique<int[]>(number_species);

for (unsigned int species = 0; species < number_species; species++) {
reactions[reaction].species_change[species] = 0;
reactions[reaction].reactants_change[species] = 0;
reactions[reaction].products_change[species] = 0;
}

reactions[reaction].affected_reactions = std::vector<unsigned int>();
Expand All @@ -66,7 +70,12 @@ namespace Gillespy {
for (unsigned int r1 = 0; r1 < number_reactions; r1++) {
for (unsigned int r2 = 0; r2 < number_reactions; r2++) {
for (unsigned int s = 0; s < number_species; s++) {
if (reactions[r2].species_change[s] != 0) {
if( r1 == r2 ){
if ( reactions[r2].reactants_change[s] > 0 ){
reactions[r1].affected_reactions.push_back(r2);
}
}else if( reactions[r1].products_change[s] > 0 &&
briandrawert marked this conversation as resolved.
Show resolved Hide resolved
reactions[r2].reactants_change[s] > 0 ){
reactions[r1].affected_reactions.push_back(r2);
}
}
Expand Down
2 changes: 2 additions & 0 deletions gillespy2/solvers/cpp/c_base/model.h
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ namespace Gillespy

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

inline static double propensity(
ReactionId reaction_id,
Expand Down
136 changes: 82 additions & 54 deletions gillespy2/solvers/cpp/c_base/tau_hybrid_cpp_solver/HybridModel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -304,37 +304,35 @@ namespace Gillespy
int num_species = species.size();
std::set<int> det_rxns;

for (int rxn_i = 0; rxn_i < reactions.size(); ++rxn_i)
for (int rxn_i = 0; rxn_i < num_reactions; ++rxn_i)
{
// start with the assumption that reaction is determinstic
HybridReaction &rxn = reactions[rxn_i];
rxn.mode = SimulationState::CONTINUOUS;

// iterate through the dependent species of this reaction
// Loop breaks if we've already determined that it is to be marked as discrete.
for (int spec_i = 0; spec_i < num_species && rxn.mode == SimulationState::CONTINUOUS; ++spec_i)
{
// Reaction has a dependency on a species if its dx is positive or negative.
// Any species with "dependency" change of 0 is by definition not a dependency.
if (rxn.get_base_reaction()->species_change[spec_i] == 0)
{
if (rxn.get_base_reaction()->reactants_change[spec_i] == 0 &&
rxn.get_base_reaction()->products_change[spec_i] == 0) {
continue;
}

// if any of the dependencies are set by the user as discrete OR
// have been set as dynamic and has not been flagged as deterministic,
// allow it to be modelled discretely
if (species[spec_i].user_mode == SimulationState::DYNAMIC)
{
if (species[spec_i].user_mode == SimulationState::DYNAMIC) {
rxn.mode = species[spec_i].partition_mode;
} else
{
} else {
rxn.mode = species[spec_i].user_mode;
}
// Loop breaks if we've already determined that it is to be marked as discrete.
if(rxn.mode == SimulationState::DISCRETE){
break;
}
}

if (rxn.mode == SimulationState::CONTINUOUS)
{
if (rxn.mode == SimulationState::CONTINUOUS) {
det_rxns.insert(rxn_i);
}
}
Expand All @@ -343,90 +341,120 @@ namespace Gillespy
}

void partition_species(
double current_time,
std::vector<HybridReaction> &reactions,
std::vector<HybridSpecies> &species,
const std::vector<double> &propensity_values,
std::vector<double> &curr_state,
double tau_step,
const TauArgs<double> &tauArgs)
{


// coefficient of variance- key:species id, value: cv
std::map<int, double> cv;
std::map<int, double> CV;
// means
std::map<int, double> means;
// standard deviation
std::map<int, double> sd;

// Initialize means and sd's
for (int spec_i = 0; spec_i < species.size(); ++spec_i)
{
for (int spec_i = 0; spec_i < species.size(); ++spec_i) {
HybridSpecies &spec = species[spec_i];

if (spec.user_mode == SimulationState::DYNAMIC)
{
if (spec.user_mode == SimulationState::DYNAMIC) {
means.insert({spec_i, curr_state[spec_i]});
sd.insert({spec_i, 0});
}
}

// calculate means and standard deviations for dynamic-mode species involved in reactions
for (int rxn_i = 0; rxn_i < reactions.size(); ++rxn_i)
{
for (int rxn_i = 0; rxn_i < reactions.size(); ++rxn_i) {
HybridReaction &rxn = reactions[rxn_i];

for (int spec_i = 0; spec_i < species.size(); ++spec_i)
{
// Only dynamic species whose mean/SD is requested are to be considered.
if (means.count(spec_i) <= 0)
{
for (int spec_i = 0; spec_i < species.size(); ++spec_i) {
HybridSpecies &spec = species[spec_i];
// Only dynamic species are to be considered.
if (spec.user_mode != SimulationState::DYNAMIC) {
continue;
}
// Selected species is either a reactant or a product, depending on whether
// dx is positive or negative.
// 0-dx species are not dependencies of this reaction, so dx == 0 is ignored.
int spec_dx = rxn.get_base_reaction()->species_change[spec_i];
if (spec_dx < 0)
{
// Selected species is either a reactant or a product,
if(rxn.get_base_reaction()->reactants_change[spec_i] > 0) {
// Selected species is a reactant.
means[spec_i] -= (tau_step * propensity_values[rxn_i] * spec_dx);
sd[spec_i] += (tau_step * propensity_values[rxn_i] * std::pow(spec_dx, 2));
} else if (spec_dx > 0)
{
means[spec_i] -= (propensity_values[rxn_i] * rxn.get_base_reaction()->reactants_change[spec_i]);
sd[spec_i] += (propensity_values[rxn_i] * std::pow(rxn.get_base_reaction()->reactants_change[spec_i], 2));
}
if(rxn.get_base_reaction()->products_change[spec_i] > 0) {
// Selected species is a product.
HybridSpecies &product = species[spec_i];
means[spec_i] += (tau_step * propensity_values[rxn_i] * spec_dx);
sd[spec_i] += (tau_step * propensity_values[rxn_i] * std::pow(spec_dx, 2));
means[spec_i] += (propensity_values[rxn_i] * rxn.get_base_reaction()->products_change[spec_i]);
sd[spec_i] += (propensity_values[rxn_i] * std::pow(rxn.get_base_reaction()->products_change[spec_i], 2));
}
}
}

// calculate coefficient of variation using means and sd
for (int spec_i = 0; spec_i < species.size(); ++spec_i) {
HybridSpecies &spec = species[spec_i];
// Only dynamic species are to be considered.
if (spec.user_mode != SimulationState::DYNAMIC) {
continue;
}
if (means[spec_i] > 0 && sd[spec_i] > 0) {
CV[spec_i] = (sqrt(sd[spec_i]) / means[spec_i]);
} else {
CV[spec_i] = 1;
}
}

// keep a history of the past CV values, and calculate a time-average values
std::map<int, double> CV_a;
static std::map<int, std::queue<double>> cv_history;
static std::map<int, double> cv_history_sum;
int history_length = 12;
//
if( current_time == 0.0 ){ // reset cv_history at start of trajectory
cv_history.clear();
cv_history_sum.clear();
}

for (int spec_i = 0; spec_i < species.size(); ++spec_i)
{
if (means.count(spec_i) <= 0)
{

HybridSpecies &spec = species[spec_i];
// Only dynamic species are to be considered.
if (spec.user_mode != SimulationState::DYNAMIC){
continue;
}

if(cv_history.count(spec_i) == 0){
cv_history[spec_i] = std::queue<double>();
}
if(cv_history_sum.count(spec_i) == 0){
cv_history_sum[spec_i] = 0.0;
}
cv_history[spec_i].push(CV[spec_i]);
cv_history_sum[spec_i] += CV[spec_i];
if(cv_history[spec_i].size() > history_length){
double removed = cv_history[spec_i].front();
cv_history[spec_i].pop();
cv_history_sum[spec_i] -= removed;
}
CV_a[spec_i] = cv_history_sum[spec_i]/cv_history[spec_i].size();
}

// Select DISCRETE or CONTINOUS mode for each species
for (int spec_i = 0; spec_i < species.size(); ++spec_i) {
HybridSpecies &spec = species[spec_i];
if (spec.switch_min == 0)
{
// (default value means switch min not set, use switch tol)
if (means[spec_i] > 0)
{
cv[spec_i] = (sd[spec_i] / means[spec_i]);
} else
{
cv[spec_i] = 1;
}
//std::cerr<<"\t\tspec"<<spec_i<<" cv="<<cv[spec_i]<<"=("<<sd[spec_i]<<" / "<<means[spec_i]<<") switch_tol="<<spec.switch_tol<<"\n";

spec.partition_mode = cv[spec_i] < spec.switch_tol
// Only dynamic species are to be considered.
if (spec.user_mode != SimulationState::DYNAMIC) {
continue;
}
unsigned int prev_partition = spec.partition_mode;
if (spec.switch_min == 0) {
// (default value means switch min not set, use switch tol)
spec.partition_mode = CV_a[spec_i] < spec.switch_tol
? SimulationState::CONTINUOUS
: SimulationState::DISCRETE;
} else {
//std::cerr<<"\t\tspec"<<spec_i<<" mean="<<means[spec_i]<<" switch_min="<<spec.switch_min<<"\n";
spec.partition_mode = means[spec_i] > spec.switch_min
? SimulationState::CONTINUOUS
: SimulationState::DISCRETE;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,7 @@ namespace Gillespy
std::vector<HybridSpecies> &species);

void partition_species(
double current_time,
std::vector<HybridReaction> &reactions,
std::vector<HybridSpecies> &species,
const std::vector<double> &propensity_values,
Expand Down
Loading