Skip to content

Commit

Permalink
Merge pull request #676 from StochSS/develop
Browse files Browse the repository at this point in the history
Release v1.6.8
  • Loading branch information
BryanRumsey authored Feb 22, 2022
2 parents abee5e4 + e26e39c commit 8fd756a
Show file tree
Hide file tree
Showing 50 changed files with 918 additions and 347 deletions.
2 changes: 1 addition & 1 deletion gillespy2/__version__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
# =============================================================================


__version__ = '1.6.7'
__version__ = '1.6.8'

__title__ = 'GillesPy2'
__description__ = 'Python interface for Gillespie-style biochemical simulations'
Expand Down
1 change: 1 addition & 0 deletions gillespy2/core/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import logging
from .assignmentrule import *
from .cleanup import *
from .events import *
from .functiondefinition import *
from .gillespyError import *
Expand Down
30 changes: 30 additions & 0 deletions gillespy2/core/cleanup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
"""
GillesPy2 is a modeling toolkit for biochemical simulation.
Copyright (C) 2019-2021 GillesPy2 developers.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""

import os
import shutil
import tempfile

def cleanup_tempfiles():
'''
Cleanup all tempfiles in gillespy2 build.
'''
tempdir = tempfile.gettempdir()
for file_obj in os.listdir(tempdir):
if file_obj.startswith("gillespy2_build"):
shutil.rmtree(os.path.join(tempdir, file_obj))
4 changes: 2 additions & 2 deletions gillespy2/core/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ def __init__(self, name=None, variable=None, expression=None):
'valid string expression')

def __str__(self):
return self.variable.name + ': ' + self.expression
return f"{self.variable}: {self.expression}"

class EventTrigger(Jsonify):
"""
Expand Down Expand Up @@ -261,4 +261,4 @@ def add_assignment(self, assignment):
else:
raise ModelError("Unexpected parameter for add_assignment. Parameter must be EventAssignment or list of "
"EventAssignments")
return assignment
return assignment
18 changes: 16 additions & 2 deletions gillespy2/core/gillespySolver.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""

from .gillespyError import SimulationError
from .gillespyError import SimulationError, ModelError
from typing import Set, Type


class GillesPySolver:
Expand Down Expand Up @@ -79,4 +80,17 @@ def get_increment(self, increment):
`increment` argument from this `solver.run()` call.
"""
)
return increment
return increment

@classmethod
def get_supported_features(cls) -> "Set[Type]":
return set()

@classmethod
def validate_sbml_features(cls, model):
unsupported_features = model.get_model_features() - cls.get_supported_features()
if unsupported_features:
unsupported_features = [feature.__name__ for feature in unsupported_features]
raise ModelError(f"Could not run Model, "
f"SBML Features not supported by {cls.name}: " +
", ".join(unsupported_features))
111 changes: 67 additions & 44 deletions gillespy2/core/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""

import gillespy2
from gillespy2.core.jsonify import TranslationTable
from gillespy2.core.reaction import *
from gillespy2.core.raterule import RateRule
Expand All @@ -26,6 +26,8 @@
from gillespy2.core.results import Trajectory,Results
from collections import OrderedDict
from gillespy2.core.gillespyError import *
from .gillespyError import SimulationError
from typing import Set, Type

try:
import lxml.etree as eTree
Expand Down Expand Up @@ -280,28 +282,28 @@ def sanitized_species_names(self):

def problem_with_name(self, name):
if name in Model.reserved_names:
return ModelError(
raise ModelError(
'Name "{}" is unavailable. It is reserved for internal GillesPy use. Reserved Names: ({}).'.format(name,
Model.reserved_names))
if name in self.listOfSpecies:
return ModelError('Name "{}" is unavailable. A species with that name exists.'.format(name))
raise ModelError('Name "{}" is unavailable. A species with that name exists.'.format(name))
if name in self.listOfParameters:
return ModelError('Name "{}" is unavailable. A parameter with that name exists.'.format(name))
raise ModelError('Name "{}" is unavailable. A parameter with that name exists.'.format(name))
if name in self.listOfReactions:
return ModelError('Name "{}" is unavailable. A reaction with that name exists.'.format(name))
raise ModelError('Name "{}" is unavailable. A reaction with that name exists.'.format(name))
if name in self.listOfEvents:
return ModelError('Name "{}" is unavailable. An event with that name exists.'.format(name))
raise ModelError('Name "{}" is unavailable. An event with that name exists.'.format(name))
if name in self.listOfRateRules:
return ModelError('Name "{}" is unavailable. A rate rule with that name exists.'.format(name))
raise ModelError('Name "{}" is unavailable. A rate rule with that name exists.'.format(name))
if name in self.listOfAssignmentRules:
return ModelError('Name "{}" is unavailable. An assignment rule with that name exists.'.format(name))
raise ModelError('Name "{}" is unavailable. An assignment rule with that name exists.'.format(name))
if name in self.listOfFunctionDefinitions:
return ModelError('Name "{}" is unavailable. A function definition with that name exists.'.format(name))
raise ModelError('Name "{}" is unavailable. A function definition with that name exists.'.format(name))
if name.isdigit():
return ModelError('Name "{}" is unavailable. Names must not be numeric strings.'.format(name))
raise ModelError('Name "{}" is unavailable. Names must not be numeric strings.'.format(name))
for special_character in Model.special_characters:
if special_character in name:
return ModelError(
raise ModelError(
'Name "{}" is unavailable. Names must not contain special characters: {}.'.format(name,
Model.special_characters))

Expand Down Expand Up @@ -334,9 +336,7 @@ def add_species(self, obj):
self.add_species(S)
else:
try:
problem = self.problem_with_name(obj.name)
if problem is not None:
raise problem
self.problem_with_name(obj.name)
self.listOfSpecies[obj.name] = obj
self._listOfSpecies[obj.name] = 'S{}'.format(len(self._listOfSpecies))
except Exception as e:
Expand Down Expand Up @@ -417,9 +417,7 @@ def add_parameter(self, params):
self.add_parameter(p)
else:
if isinstance(params, Parameter) or type(params).__name__ == 'Parameter':
problem = self.problem_with_name(params.name)
if problem is not None:
raise problem
self.problem_with_name(params.name)
self.update_namespace()
params._evaluate(self.namespace)
self.listOfParameters[params.name] = params
Expand Down Expand Up @@ -500,9 +498,7 @@ def add_reaction(self, reactions):
self.add_reaction(r)
else:
try:
problem = self.problem_with_name(reactions.name)
if problem is not None:
raise problem
self.problem_with_name(reactions.name)
reactions.verify()
self.validate_reactants_and_products(reactions)
if reactions.name is None or reactions.name == '':
Expand Down Expand Up @@ -542,9 +538,7 @@ def add_rate_rule(self, rate_rules):
self.add_rate_rule(rr)
else:
try:
problem = self.problem_with_name(rate_rules.name)
if problem is not None:
raise problem
self.problem_with_name(rate_rules.name)
if len(self.listOfAssignmentRules) != 0:
for i in self.listOfAssignmentRules.values():
if rate_rules.variable == i.variable:
Expand Down Expand Up @@ -587,9 +581,7 @@ def add_event(self, event):
self.add_event(e)
else:
try:
problem = self.problem_with_name(event.name)
if problem is not None:
raise problem
self.problem_with_name(event.name)
if event.trigger is None or not hasattr(event.trigger, 'expression'):
raise ModelError(
'An Event must contain a valid trigger.')
Expand All @@ -614,9 +606,7 @@ def add_function_definition(self, function_definitions):
self.add_function_definition(fd)
else:
try:
problem = self.problem_with_name(function_definitions.name)
if problem is not None:
raise problem
self.problem_with_name(function_definitions.name)
self.listOfFunctionDefinitions[function_definitions.name] = function_definitions
except Exception as e:
raise ParameterError(
Expand All @@ -634,9 +624,7 @@ def add_assignment_rule(self, assignment_rules):
self.add_assignment_rule(ar)
else:
try:
problem = self.problem_with_name(assignment_rules.name)
if problem is not None:
raise problem
self.problem_with_name(assignment_rules.name)
if len(self.listOfRateRules) != 0:
for i in self.listOfRateRules.values():
if assignment_rules.variable == i.variable:
Expand Down Expand Up @@ -840,7 +828,7 @@ def get_element(self, ename):
return self.get_assignment_rule(ename)
if ename in self.listOfFunctionDefinitions:
return self.get_function_definition(ename)
return 'Element not found!'
raise ModelError(f"model.get_element(): element={ename} not found")


def get_best_solver(self):
Expand All @@ -856,28 +844,33 @@ def get_best_solver(self):
"""
from gillespy2.solvers.numpy import can_use_numpy
hybrid_check = False
if len(self.get_all_assignment_rules()) or len(self.get_all_rate_rules()) \
or len(self.get_all_function_definitions()) or len(self.get_all_events()):
chybrid_check = True
if len(self.get_all_rate_rules()) or len(self.get_all_events()):
hybrid_check = True
if len(self.get_all_assignment_rules()) or len(self.get_all_function_definitions()):
hybrid_check = True
chybrid_check = False

if len(self.get_all_species()) and hybrid_check == False:
for i in self.get_all_species():
tempMode = self.get_species(i).mode
if tempMode == 'dynamic' or tempMode == 'continuous':
hybrid_check = True
break
if can_use_numpy and hybrid_check:
from gillespy2 import TauHybridSolver
return TauHybridSolver

elif not can_use_numpy and hybrid_check:
raise ModelError('TauHybridSolver is the only solver currently that supports '
'AssignmentRules, RateRules, FunctionDefinitions, or Events. '
'Please install Numpy.')

from gillespy2.solvers.cpp.build.build_engine import BuildEngine
can_use_cpp = not len(BuildEngine.get_missing_dependencies())

if not can_use_cpp and not can_use_numpy:
raise ModelError('Dependency Error, cannot run model.')

if can_use_cpp and hybrid_check and chybrid_check:
from gillespy2 import TauHybridCSolver
return TauHybridCSolver
elif can_use_numpy and hybrid_check:
from gillespy2 import TauHybridSolver
return TauHybridSolver

if can_use_cpp is False and can_use_numpy and not hybrid_check:
from gillespy2 import NumPySSASolver
return NumPySSASolver
Expand All @@ -893,6 +886,9 @@ def get_best_solver_algo(self, algorithm):
from gillespy2.solvers.numpy import can_use_numpy
from gillespy2.solvers.cpp.build.build_engine import BuildEngine
can_use_cpp = not len(BuildEngine.get_missing_dependencies())
chybrid_check = True
if len(self.get_all_assignment_rules()) or len(self.get_all_function_definitions()):
chybrid_check = False

if not can_use_cpp and can_use_numpy:
raise ModelError("Please install C++ or Numpy to use GillesPy2 solvers.")
Expand Down Expand Up @@ -920,9 +916,36 @@ def get_best_solver_algo(self, algorithm):
else:
from gillespy2 import ODESolver
return ODESolver

elif algorithm == 'Tau-Hybrid':
if can_use_cpp and chybrid_check:
from gillespy2 import TauHybridCSolver
return TauHybridCSolver
else:
from gillespy2 import TauHybridSolver
return TauHybridSolver

else:
raise ModelError("Invalid value for the argument 'algorithm' entered. "
"Please enter 'SSA', 'ODE', or 'Tau-leaping'.")
"Please enter 'SSA', 'ODE', 'Tau-leaping', or 'Tau-Hybrid'.")

def get_model_features(self) -> "Set[Type]":
"""
Determine what solver-specific model features are present on the model.
Used to validate that the model is compatible with the given solver.
:returns: Set containing the classes of every solver-specific feature present on the model.
"""
features = set()
if len(self.listOfEvents):
features.add(gillespy2.Event)
if len(self.listOfRateRules):
features.add(gillespy2.RateRule)
if len(self.listOfAssignmentRules):
features.add(gillespy2.AssignmentRule)
if len(self.listOfFunctionDefinitions):
features.add(gillespy2.FunctionDefinition)
return features

def run(self, solver=None, timeout=0, t=None, increment=None, show_labels=True, cpp_support=False, algorithm=None,
**solver_args):
Expand Down
10 changes: 6 additions & 4 deletions gillespy2/solvers/cpp/build/build_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,13 +91,15 @@ def prepare(self, model: "Union[Model, template_gen.SanitizedModel]", variable=F

# If the output directory is None, create and set it to a temporary directory.
if self.output_dir is None:
self.output_dir = Path(tempfile.mkdtemp())
self.output_dir = Path(tempfile.mkdtemp(
prefix='gillespy2_build_', dir=os.environ.get('GILLESPY2_TMPDIR'))
)

# If object files haven't been compiled yet, go ahead and compile them with make.
# Precompilation only happens if the cache is enabled but hasn't been built yet.
# Make target for individual simulation will still succeed if prebuild() isn't called.
self.obj_dir = self.output_dir.joinpath("obj")
self.template_dir = self.output_dir.joinpath("template")
self.obj_dir = self.output_dir.joinpath("gillespy2_obj")
self.template_dir = self.output_dir.joinpath("gillespy2_template")

if gillespy2.cache_enabled:
self.obj_dir = self.__get_cache_dir()
Expand Down Expand Up @@ -186,4 +188,4 @@ def clean(self):
return

if self.output_dir.exists():
shutil.rmtree(self.output_dir)
shutil.rmtree(self.output_dir, ignore_errors=True)
Loading

0 comments on commit 8fd756a

Please sign in to comment.