diff --git a/.codeclimate.yml b/.codeclimate.yml
new file mode 100644
index 000000000..aac3960d4
--- /dev/null
+++ b/.codeclimate.yml
@@ -0,0 +1,44 @@
+---
+version: "2"
+plugins:
+ fixme:
+ enabled: true
+ config:
+ strings:
+ - FIXME
+ - BUG
+ - TODO
+ markdownlint:
+ enabled: false
+ pep8:
+ enabled: true
+ radon:
+ enabled: true
+checks:
+ argument-count:
+ enabled: false
+ complex-logic:
+ enabled: true
+ file-lines:
+ enabled: false
+ method-complexity:
+ enabled: true
+ method-count:
+ enabled: false
+ method-lines:
+ enabled: false
+ nested-control-flow:
+ enabled: false
+ return-statements:
+ enabled: true
+ similar-code:
+ enabled: false
+ identical-code:
+ enabled: false
+exclude_patterns:
+ - "**/docs/"
+ - "**/examples/"
+ - "**/test/"
+ - "**/*.ipynb/"
+ - "**/*.sh"
+ - "**/*.bat"
diff --git a/.coveragerc b/.coveragerc
index 2552d07a3..3db7cdffd 100644
--- a/.coveragerc
+++ b/.coveragerc
@@ -1,3 +1,5 @@
[run]
-omit = */.local/lib/python3.6/site-packages/numpy/*
- */.local/lib/python3.6/site-packages/scipy/*
+omit = */.local/lib/python3.*/site-packages/numpy/*
+ */.local/lib/python3.*/site-packages/scipy/*
+ */.local/lib/python3.*/site-packages/Cython/*
+ */.local/lib/python3.*/site-packages/pyximport/*
diff --git a/.gitignore b/.gitignore
index 1c6f2a937..ab81724aa 100644
--- a/.gitignore
+++ b/.gitignore
@@ -68,3 +68,4 @@ __pycache__
.ipynb_checkpoints*
.coverage
htmlcov
+/build
diff --git a/.graphics/coverage.png b/.graphics/coverage.png
new file mode 100644
index 000000000..101b2c24a
Binary files /dev/null and b/.graphics/coverage.png differ
diff --git a/.graphics/coverage.svg b/.graphics/coverage.svg
index 902993710..0644a48b3 100644
--- a/.graphics/coverage.svg
+++ b/.graphics/coverage.svg
@@ -9,13 +9,13 @@
-| Master Branch | Develop Branch | Coverage | -|:---------------:|:--------------:|:--------:| -| [![Build Status](https://travis-ci.org/GillesPy2/GillesPy2.svg?branch=master)](https://travis-ci.org/GillesPy2/GillesPy2) | [![Build Status](https://travis-ci.org/GillesPy2/GillesPy2.svg?branch=develop)](https://travis-ci.org/GillesPy2/GillesPy2) | ![Coverage](https://raw.githubusercontent.com/GillesPy2/GillesPy2/develop/.graphics/coverage.svg?sanitize=true) | +| Master Branch | Develop Branch | Coverage | Maintainability | +|:---------------:|:--------------:|:--------:|:---------------:| +| [![Build Status](https://travis-ci.org/GillesPy2/GillesPy2.svg?branch=master)](https://travis-ci.org/GillesPy2/GillesPy2) | [![Build Status](https://travis-ci.org/GillesPy2/GillesPy2.svg?branch=develop)](https://travis-ci.org/GillesPy2/GillesPy2) | ![Coverage](https://raw.githubusercontent.com/GillesPy2/GillesPy2/develop/.graphics/coverage.svg?sanitize=true) | [![Maintainability](https://api.codeclimate.com/v1/badges/990ac9d778d681d32eea/maintainability)](https://codeclimate.com/github/GillesPy2/GillesPy2/maintainability) | License ------- diff --git a/gillespy2/__init__.py b/gillespy2/__init__.py index 6f52486c3..bc96a798a 100644 --- a/gillespy2/__init__.py +++ b/gillespy2/__init__.py @@ -13,4 +13,4 @@ from gillespy2.core import * import gillespy2.sbml import gillespy2.solvers -import gillespy2.example_models +#import gillespy2.example_models diff --git a/gillespy2/__version__.py b/gillespy2/__version__.py index 20271ff36..d9150b37b 100644 --- a/gillespy2/__version__.py +++ b/gillespy2/__version__.py @@ -5,7 +5,7 @@ # @website https://github.com/GillesPy2/GillesPy2 # ============================================================================= -__version__ = '1.3.1' +__version__ = '1.3.2' __title__ = 'GillesPy2' __description__ = 'Python interface for Gillespie-style biochemical simulations' __url__ = 'https://github.com/GillesPy2/GillesPy2' diff --git a/gillespy2/core/gillespy2.py b/gillespy2/core/gillespy2.py index d806aebae..81f731988 100644 --- a/gillespy2/core/gillespy2.py +++ b/gillespy2/core/gillespy2.py @@ -6,6 +6,7 @@ from __future__ import division import signal, os import numpy as np +import uuid from contextlib import contextmanager from collections import OrderedDict from gillespy2.core.results import Results,EnsembleResults @@ -601,75 +602,42 @@ def run(self, solver=None, timeout=0, **solver_args): solver-specific arguments to be passed to solver.run() """ - if os.name == 'nt' and timeout > 0: - from gillespy2.core import log - log.warning('Timeouts are not currently supported in Windows.') - @contextmanager - def time_out(time): - # Register a function to raise a TimeoutError on the signal. - signal.signal(signal.SIGALRM, raise_time_out) - # Schedule the signal to be sent after ``time``. - signal.alarm(time) - - try: - yield - except TimeoutError: - print('GillesPy2 solver simulation exceeded timeout') - pass - finally: - # Unregister the signal so it won't be triggered - # if the time_out is not reached. - signal.signal(signal.SIGALRM, signal.SIG_IGN) + if solver is not None: + if ((isinstance(solver, type) + and issubclass(solver, GillesPySolver))) or issubclass(type(solver), GillesPySolver): + solver_results, rc = solver.run(model=self, t=self.tspan[-1], + increment=self.tspan[-1] - self.tspan[-2], timeout=timeout, **solver_args) + else: + raise SimulationError( + "argument 'solver' to run() must be a subclass of GillesPySolver") + else: + from gillespy2.solvers.auto import SSASolver + solver = SSASolver + solver_results, rc = SSASolver.run(model=self, t=self.tspan[-1], + increment=self.tspan[-1] - + self.tspan[-2], timeout=timeout, **solver_args) - def raise_time_out(signum, frame): + if rc == 33: from gillespy2.core import log - import sys - def excepthook(type, value, traceback): - pass - sys.excepthook = excepthook log.warning('GillesPy2 simulation exceeded timeout.') - raise SimulationTimeoutError() - - - with time_out(timeout): - if solver is not None: - if ((isinstance(solver, type) - and issubclass(solver, GillesPySolver))) or issubclass(type(solver), GillesPySolver): - if solver.name == 'SSACSolver': - signal.signal(signal.SIGALRM, signal.SIG_IGN) - solver_args['timeout'] = timeout - solver_results, rc = solver.run(model=self, t=self.tspan[-1], increment=self.tspan[-1] - self.tspan[-2], **solver_args) - else: - raise SimulationError( - "argument 'solver' to run() must be a subclass of GillesPySolver") - else: - from gillespy2.solvers.auto import SSASolver - solver = SSASolver - if solver.name == 'SSACSolver': - signal.signal(signal.SIGALRM, signal.SIG_IGN) - solver_args['timeout'] = timeout - solver_results, rc = SSASolver.run(model=self, t=self.tspan[-1], - increment=self.tspan[-1] - self.tspan[-2], **solver_args) - - if rc == 33: - from gillespy2.core import log - log.warning('GillesPy2 simulation exceeded timeout.') - - if isinstance(solver_results[0], (np.ndarray)): - return solver_results - - if len(solver_results) is 1: - return Results(data=solver_results[0], model=self, - solver_name=solver.name, rc=rc) - - if len(solver_results) > 1: - results_list = [] - for i in range(0,solver_args.get('number_of_trajectories')): - results_list.append(Results(data=solver_results[i],model=self,solver_name=solver.name, - rc=rc)) - return EnsembleResults(results_list) - else: - raise ValueError("number_of_trajectories must be non-negative and non-zero") + + if isinstance(solver_results[0], (np.ndarray)): + return solver_results + if len(solver_results) == 1: + return Results(data=solver_results[0], model=self, + solver_name=solver.name, rc=rc) + + elif len(solver_results) > 1: + results_list = [] + for i in range(0,solver_args.get('number_of_trajectories')): + results_list.append(Results(data=solver_results[i],model=self,solver_name=solver.name, + rc=rc)) + return EnsembleResults(results_list) + elif isinstance(solver_results, (np.ndarray)): + return solver_results + + else: + raise ValueError("number_of_trajectories must be non-negative and non-zero") class Species(SortableObject): @@ -734,7 +702,8 @@ def __init__(self, name="", initial_value=0, constant=False, if mode == 'continuous': self.initial_value = np.float(initial_value) else: - if not isinstance(initial_value, int): raise ValueError('Discrete values must be of type int.') + if np.int(initial_value) != initial_value: + raise ValueError("'initial_value' for Species with mode='discrete' must be an integer value. Change to mode='continuous' to use floating point values.") self.initial_value = np.int(initial_value) if not allow_negative_populations: if self.initial_value < 0: raise ValueError('A species initial value must be \ @@ -932,7 +901,7 @@ class Reaction(SortableObject): Attributes ---------- name : str - The name by which the reaction is called. + The name by which the reaction is called (optional). reactants : dict The reactants that are consumed in the reaction, with stoichiometry. An example would be {R1 : 1, R2 : 2} if the reaction consumes two of R1 and @@ -968,7 +937,10 @@ def __init__(self, name="", reactants={}, products={}, """ # Metadata - self.name = name + if name == "" or name is None: + self.name = 'rxn' + str(uuid.uuid4()).replace('-', '_') + else: + self.name = name self.annotation = "" # We might use this flag in the future to automatically generate diff --git a/gillespy2/solvers/cpp/__init__.py b/gillespy2/solvers/cpp/__init__.py index affac39ed..a9b422460 100644 --- a/gillespy2/solvers/cpp/__init__.py +++ b/gillespy2/solvers/cpp/__init__.py @@ -2,7 +2,7 @@ from gillespy2.core import log def check_cpp_support(): - from gillespy2.example_models import Example + from gillespy2.solvers.cpp.example_models import Example try: model = Example() results = model.run(solver=SSACSolver) diff --git a/gillespy2/solvers/cpp/example_models.py b/gillespy2/solvers/cpp/example_models.py new file mode 100644 index 000000000..64d65e88c --- /dev/null +++ b/gillespy2/solvers/cpp/example_models.py @@ -0,0 +1,26 @@ +from gillespy2.core import Model, Species, Reaction, Parameter +import numpy as np + + +class Example(Model): + """ + This is a simple example for mass-action degradation of species S. + """ + + def __init__(self, parameter_values=None): + # Initialize the model. + Model.__init__(self, name="Example") + # Species + S = Species(name='Sp', initial_value=100) + self.add_species([S]) + # Parameters + k1 = Parameter(name='k1', expression=3.0) + self.add_parameter([k1]) + # Reactions + rxn1 = Reaction(name='S degradation', reactants={S: 1}, products={}, rate=k1) + self.add_reaction([rxn1]) + self.timespan(np.linspace(0, 20, 101)) + + +__all__ = ['Trichloroethylene', 'LacOperon', 'Schlogl', 'MichaelisMenten', + 'ToggleSwitch', 'Example', 'Tyson2StateOscillator', 'Oregonator'] diff --git a/gillespy2/solvers/numpy/basic_ode_solver.py b/gillespy2/solvers/numpy/basic_ode_solver.py index 7575490d3..913a709c9 100644 --- a/gillespy2/solvers/numpy/basic_ode_solver.py +++ b/gillespy2/solvers/numpy/basic_ode_solver.py @@ -1,6 +1,6 @@ """GillesPy2 Solver for ODE solutions.""" -import signal +from threading import Thread, Event from scipy.integrate import ode from scipy.integrate import odeint from collections import OrderedDict @@ -13,13 +13,15 @@ class BasicODESolver(GillesPySolver): This Solver produces the deterministic continuous solution via ODE. """ name = "BasicODESolver" - interrupted = False rc = 0 + stop_event = None + result = None def __init__(self): name = "BasicODESolver" - interrupted = False rc = 0 + stop_event = None + result = None @staticmethod def __f(t, y, curr_state, model, c_prop): @@ -49,7 +51,8 @@ def __f(t, y, curr_state, model, c_prop): @classmethod def run(self, model, t=20, number_of_trajectories=1, increment=0.05, - show_labels=True, integrator='lsoda', integrator_options={}, **kwargs): + show_labels=True, integrator='lsoda', integrator_options={}, + timeout=None, **kwargs): """ :param model: gillespy2.model class object @@ -66,18 +69,26 @@ def run(self, model, t=20, number_of_trajectories=1, increment=0.05, """ if not isinstance(self, BasicODESolver): self = BasicODESolver() + self.stop_event = Event() + if timeout is not None and timeout <=0: timeout = None if len(kwargs) > 0: for key in kwargs: log.warning('Unsupported keyword argument to {0} solver: {1}'.format(self.name, key)) if number_of_trajectories > 1: log.warning("Generating duplicate trajectories for model with ODE Solver. Consider running with only 1 trajectory.") + sim_thread = Thread(target=self.__run, args=(model,), kwargs={'t':t, + 'number_of_trajectories':number_of_trajectories, + 'increment':increment, 'show_labels':show_labels, + 'timeout':timeout}) + sim_thread.start() + sim_thread.join(timeout=timeout) + self.stop_event.set() + while self.result is None: pass + return self.result, self.rc + + def __run(self, model, t=20, number_of_trajectories=1, increment=0.05, timeout=None, + show_labels=True, integrator='lsoda', integrator_options={}, **kwargs): - - def timed_out(signum, frame): - self.rc = 33 - self.interrupted = True - - signal.signal(signal.SIGALRM, timed_out) start_state = [model.listOfSpecies[species].initial_value for species in model.listOfSpecies] # create mapping of species dictionary to array indices @@ -119,7 +130,9 @@ def timed_out(signum, frame): rhs.set_initial_value(y0, curr_time).set_f_params(curr_state, model, c_prop) while entry_count < timeline.size - 1: - if self.interrupted: break + if self.stop_event.is_set(): + self.rc = 33 + break int_time = curr_time + increment entry_count += 1 y0 = rhs.integrate(int_time) @@ -137,5 +150,5 @@ def timed_out(signum, frame): results = [results_as_dict] * number_of_trajectories else: results = np.stack([result] * number_of_trajectories, axis=0) - + self.result = results return results, self.rc diff --git a/gillespy2/solvers/numpy/basic_tau_hybrid_solver.py b/gillespy2/solvers/numpy/basic_tau_hybrid_solver.py index 36313cfb0..142dad290 100644 --- a/gillespy2/solvers/numpy/basic_tau_hybrid_solver.py +++ b/gillespy2/solvers/numpy/basic_tau_hybrid_solver.py @@ -3,7 +3,7 @@ from scipy.integrate import ode, solve_ivp import heapq import numpy as np -import signal +import threading import gillespy2 from gillespy2.solvers.numpy import Tau from gillespy2.core import GillesPySolver, log @@ -46,12 +46,12 @@ class BasicTauHybridSolver(GillesPySolver): interchangeably or simultaneously. """ name = "BasicTauHybridSolver" - interrupted = False rc = 0 + result = None + stop_event = None def __init__(self): name = 'BasicTauHybridSolver' - interrupted = False rc = 0 def __toggle_reactions(self, model, all_compiled, deterministic_reactions, dependencies, curr_state, det_spec): @@ -454,6 +454,7 @@ def __integrate(self, integrator, integrator_options, curr_state, y0, model, cur curr_state['time'] = curr_time # Integrate until end or tau is reached + # TODO: Need a way to exit solve_ivp when timeout is triggered sol = solve_ivp(rhs, [curr_time, model.tspan[-1]], y0, method=integrator, dense_output=True, events=tau_event, **integrator_options) @@ -724,7 +725,7 @@ def __map_state(self, species, parameters, compiled_reactions, events, curr_stat def run(self, model, t=20, number_of_trajectories=1, increment=0.05, seed=None, debug=False, profile=False, show_labels=True, tau_tol=0.03, event_sensitivity=100, integrator='LSODA', - integrator_options={}, **kwargs): + integrator_options={}, timeout=None, **kwargs): """ Function calling simulation of the model. This is typically called by the run function in GillesPy2 model objects and will inherit those parameters which are passed with the model as the arguments this run function. @@ -768,19 +769,38 @@ def run(self, model, t=20, number_of_trajectories=1, increment=0.05, seed=None, Example use: {max_step : 0, rtol : .01} """ - def timed_out(signum, frame): - self.interrupted = True - self.rc = 33 - - signal.signal(signal.SIGALRM, timed_out) - if not isinstance(self, BasicTauHybridSolver): self = BasicTauHybridSolver() + self.stop_event = threading.Event() + if len(kwargs) > 0: for key in kwargs: log.warning('Unsupported keyword argument to {0} solver: {1}'.format(self.name, key)) + if timeout is not None and timeout <= 0: timeout = None + + sim_thread = threading.Thread(target=self.__run, args=(model,), kwargs={'t':t, + 'number_of_trajectories':number_of_trajectories, + 'increment':increment, 'seed':seed, + 'debug':debug, 'profile':profile,'show_labels':show_labels, + 'timeout':timeout, 'tau_tol':tau_tol, + 'event_sensitivity':event_sensitivity, + 'integrator':integrator, + 'integrator_options':integrator_options}) + sim_thread.start() + sim_thread.join(timeout=timeout) + self.stop_event.set() + while self.result is None: pass + return self.result, self.rc + + + + def __run(self, model, t=20, number_of_trajectories=1, increment=0.05, seed=None, + debug=False, profile=False, show_labels=True, + tau_tol=0.03, event_sensitivity=100, integrator='LSODA', + integrator_options={}, **kwargs): + if debug: print("t = ", t) print("increment = ", increment) @@ -853,7 +873,10 @@ def timed_out(signum, frame): # Main trajectory loop for trajectory_num in range(number_of_trajectories): - if self.interrupted: break + if self.stop_event.is_set(): + print('exiting') + self.rc = 33 + break trajectory = trajectory_base[trajectory_num] # NumPy array containing this simulation's results propensities = OrderedDict() # Propensities evaluated at current state @@ -897,7 +920,9 @@ def timed_out(signum, frame): # Each save step while curr_time < model.tspan[-1]: - if self.interrupted: break + if self.stop_event.is_set(): + self.rc = 33 + break # Get current propensities if not pure_ode: for i, r in enumerate(model.listOfReactions): @@ -962,5 +987,6 @@ def timed_out(signum, frame): print("Total Steps Taken: ", len(steps_taken)) print("Total Steps Rejected: ", steps_rejected) + self.result = simulation_data return simulation_data, self.rc diff --git a/gillespy2/solvers/numpy/basic_tau_leaping_solver.py b/gillespy2/solvers/numpy/basic_tau_leaping_solver.py index 0d4079236..bc560efea 100644 --- a/gillespy2/solvers/numpy/basic_tau_leaping_solver.py +++ b/gillespy2/solvers/numpy/basic_tau_leaping_solver.py @@ -2,7 +2,7 @@ import random, math, sys, warnings -import signal +from threading import Thread, Event import numpy as np from gillespy2.solvers.numpy import Tau from gillespy2.core import GillesPySolver, log @@ -10,8 +10,9 @@ class BasicTauLeapingSolver(GillesPySolver): name = 'BasicTauLeapingSolver' - interrupted = False rc = 0 + stop_event = None + result = None """ A Basic Tau Leaping Solver for GillesPy2 models. This solver uses an algorithm calculates multiple reactions in a single step over a given tau step size. The change in propensities @@ -22,8 +23,9 @@ class BasicTauLeapingSolver(GillesPySolver): def __init__(self, debug=False, profile=False): name = "BasicTauLeapingSolver" - interrupted = False rc = 0 + stop_event = None + result = None self.debug = debug self.profile = profile @@ -59,7 +61,8 @@ def __get_reactions(self, step, curr_state, curr_time, save_time, propensities, @classmethod def run(self, model, t=20, number_of_trajectories=1, increment=0.05, seed=None, - debug=False, profile=False, show_labels=True, tau_tol=0.03, **kwargs): + debug=False, profile=False, show_labels=True, + timeout=None, tau_tol=0.03, **kwargs): """ Function calling simulation of the model. This is typically called by the run function in GillesPy2 model objects @@ -89,19 +92,31 @@ def run(self, model, t=20, number_of_trajectories=1, increment=0.05, seed=None, show_labels : bool (True) Use names of species as index of result object rather than position numbers. """ - def timed_out(signum, frame): - self.rc = 33 - self.interrupted = True - - signal.signal(signal.SIGALRM, timed_out) - if not isinstance(self, BasicTauLeapingSolver): self = BasicTauLeapingSolver(debug=debug, profile=profile) + self.stop_event = Event() + if timeout is not None and timeout <= 0: timeout = None if len(kwargs) > 0: for key in kwargs: log.warning('Unsupported keyword argument to {0} solver: {1}'.format(self.name, key)) + + sim_thread = Thread(target=self.__run, args=(model,), kwargs={'t':t, + 'number_of_trajectories':number_of_trajectories, + 'increment':increment, 'seed':seed, + 'debug':debug, 'show_labels':show_labels, + 'timeout':timeout, 'tau_tol':tau_tol}) + sim_thread.start() + sim_thread.join(timeout=timeout) + self.stop_event.set() + while self.result is None: pass + return self.result, self.rc + + def __run(self, model, t=20, number_of_trajectories=1, increment=0.05, seed=None, + debug=False, profile=False, show_labels=True, + timeout=None, tau_tol=0.03, **kwargs): + if debug: print("t = ", t) print("increment = ", increment) @@ -138,7 +153,9 @@ def timed_out(signum, frame): simulation_data = [] for trajectory_num in range(number_of_trajectories): - if self.interrupted: break + if self.stop_event.is_set(): + self.rc = 33 + break start_state = [0] * (len(model.listOfReactions) + len(model.listOfRateRules)) propensities = {} curr_state = {} @@ -176,11 +193,15 @@ def timed_out(signum, frame): #Each save step while entry_count < timeline.size: - if self.interrupted: break + if self.stop_event.is_set(): + self.rc = 33 + break #Until save step reached while curr_time < save_time: - if self.interrupted: break + if self.stop_event.is_set(): + self.rc = 33 + break propensity_sum = 0 for i, r in enumerate(model.listOfReactions): @@ -262,5 +283,5 @@ def timed_out(signum, frame): print(steps_taken) print("Total Steps Taken: ", len(steps_taken)) print("Total Steps Rejected: ", steps_rejected) - + self.result = simulation_data return simulation_data, self.rc diff --git a/gillespy2/solvers/numpy/ssa_solver.py b/gillespy2/solvers/numpy/ssa_solver.py index 17c3e9cee..4e780fb64 100644 --- a/gillespy2/solvers/numpy/ssa_solver.py +++ b/gillespy2/solvers/numpy/ssa_solver.py @@ -1,4 +1,4 @@ -import signal +from threading import Thread, Event from gillespy2.core import GillesPySolver, Model, Reaction, log import random import math @@ -7,16 +7,19 @@ class NumPySSASolver(GillesPySolver): name = "NumPySSASolver" - interrupted = False rc = 0 + stop_event = None + result = None def __init__(self): name = 'NumPySSASolver' - interrupted = False rc = 0 + stop_event = None + result = None @classmethod - def run(self, model, t=20, number_of_trajectories=1, increment=0.05, seed=None, debug=False, show_labels=True, **kwargs): + def run(self, model, t=20, number_of_trajectories=1, increment=0.05, + seed=None, debug=False, show_labels=True, timeout=None, **kwargs): """ Run the SSA algorithm using a NumPy for storing the data in arrays and generating the timeline. :param model: The model on which the solver will operate. @@ -30,20 +33,31 @@ def run(self, model, t=20, number_of_trajectories=1, increment=0.05, seed=None, :param show_labels: Use names of species as index of result object rather than position numbers. :return: a list of each trajectory simulated. """ - def timed_out(signum, frame): - self.rc = 33 - self.interrupted = True - - signal.signal(signal.SIGALRM, timed_out) - if not isinstance(self, NumPySSASolver): self = NumPySSASolver() + self.stop_event = Event() + if timeout is not None and timeout <= 0: timeout = None + if len(kwargs) > 0: for key in kwargs: log.warning('Unsupported keyword argument to {0} solver: {1}'.format(self.name, key)) + sim_thread = Thread(target=self.__run, args=(model,), kwargs={'t':t, + 'number_of_trajectories':number_of_trajectories, + 'increment':increment, 'seed':seed, + 'debug':debug, 'show_labels':show_labels, + 'timeout':timeout}) + sim_thread.start() + sim_thread.join(timeout=timeout) + self.stop_event.set() + while self.result is None: pass + return self.result, self.rc + + def __run(self, model, t=20, number_of_trajectories=1, increment=0.05, + seed=None, debug=False, show_labels=True, timeout=None): + random.seed(seed) # create mapping of species dictionary to array indices species_mappings = model.sanitized_species_names() @@ -87,7 +101,9 @@ def timed_out(signum, frame): # begin simulating each trajectory simulation_data = [] for trajectory_num in range(number_of_trajectories): - if self.interrupted: break + if self.stop_event.is_set(): + self.rc = 33 + break # copy initial state data trajectory = trajectory_base[trajectory_num] entry_count = 1 @@ -96,7 +112,9 @@ def timed_out(signum, frame): propensity_sums = np.zeros(number_reactions) # calculate initial propensity sums while entry_count < timeline.size: - if self.interrupted: break + if self.stop_event.is_set(): + self.rc = 33 + break # determine next reaction for i in range(number_reactions): propensity_sums[i] = propensity_functions[i](current_state) @@ -118,7 +136,9 @@ def timed_out(signum, frame): print('current_time: ', current_time) # determine time passed in this reaction while entry_count < timeline.size and timeline[entry_count] <= current_time: - if self.interrupted: break + if self.stop_event.is_set(): + self.rc = 33 + break trajectory[entry_count, 1:] = current_state entry_count += 1 for potential_reaction in range(number_reactions): @@ -146,4 +166,5 @@ def timed_out(signum, frame): simulation_data.append(data) else: simulation_data = trajectory_base - return simulation_data, self.rc + self.result = simulation_data + return self.result, self.rc diff --git a/run_coverage.sh b/run_coverage.sh index a0744553f..4bdca736b 100755 --- a/run_coverage.sh +++ b/run_coverage.sh @@ -3,6 +3,5 @@ #pip3 install coverage-badge #pip3 install python-libsbml -coverage run --source=gillespy2 --omit=gillespy2/solvers/stochkit/* test/run_tests.py -m develop +coverage run --source=gillespy2 --omit=*gillespy2/solvers/stochkit/*,*gillespy2/solvers/cython/* test/run_tests.py -m develop coverage html -coverage-badge -fo .graphics/coverage.svg diff --git a/test/TestBattery.py b/test/TestBattery.py index 57e0df309..e70c4aca9 100644 --- a/test/TestBattery.py +++ b/test/TestBattery.py @@ -1,12 +1,12 @@ -from tqdm import tqdm -import click +try: + from tqdm import tqdm +except ImportError: + raise ImportError('tqdm is required. Please install it.') import statistics from itertools import product from scipy import stats from timeit import default_timer as timer import gillespy2 -from gillespy2.solvers.python import * -# BasicSSASolver from gillespy2.solvers.numpy import * # BasicODESolver, BasicRootSolver, BasicTauLeapingSolver, NumPySSASolver, TauLeapingSolver from gillespy2.solvers.cython import * @@ -17,23 +17,21 @@ # SSASolver from gillespy2.solvers.stochkit import * # StochKitODESolver, StochKitSolver -from gillespy2.example_models import * +from example_models import * -@click.command() -@click.option('--gillespy2_home', default="/home/jackson/Research/GillesPy2/", help='Location of Gillespy2 Directory') -@click.option('--number_of_samples', prompt='Sample Size', help='The Number of times that you want to run it.') -@click.option('--stochkit_home', prompt='Location of Stochkit', help='The Number of times that ') -@click.option('--acceptable deviation', prompt='Numeric Value of Statistical Variation', help='') -@click.option('--output_file', prompt='Name of Output File', help='The name of the output file') -def timing_battery(gillespy2_home, number_of_samples, stochkit_home, acceptable_deviation, output_file): +def timing_battery(number_of_samples, acceptable_deviation): + gillespy2_home = ".." + stochkit_home = "../../Stochkit/StochKit" + output_file = "TestBattery_output.txt" + solver_list = [] key, value = None, None for key, value in globals().items(): if isinstance(value, type) and issubclass(value, gillespy2.GillesPySolver) and value not in solver_list: solver_list.append(value) - model_list = [Example(), Trichloroethylene(), MichaelisMenten(), Schlogl()] #Updated + model_list = [Example(), Trichloroethylene(), MichaelisMenten(), Schlogl()] #Update timing_list = [] diff --git a/test/TestingBattery.ipynb b/test/TestingBattery.ipynb index d66f56455..e755763ad 100644 --- a/test/TestingBattery.ipynb +++ b/test/TestingBattery.ipynb @@ -18,7 +18,10 @@ "import numpy\n", "from itertools import product\n", "from timeit import default_timer as timer\n", - "from tqdm import tqdm\n", + "try:\n", + " from tqdm import tqdm\n", + "except ImportError:\n", + " raise ImportError('tqdm is required. Please install it.')\n", "sys.path.append(\"..\")\n", "import gillespy2\n", "from scipy import stats\n", @@ -266,4 +269,4 @@ }, "nbformat": 4, "nbformat_minor": 2 -} +} \ No newline at end of file diff --git a/gillespy2/example_models.py b/test/example_models.py similarity index 99% rename from gillespy2/example_models.py rename to test/example_models.py index 04032fa17..fa079ae8d 100644 --- a/gillespy2/example_models.py +++ b/test/example_models.py @@ -259,7 +259,7 @@ def __init__(self, parameter_values=None): # creation of X: rxn1 = Reaction(name='X production', reactants={}, products={X: 1}, - propensity_function='300.0 * 1.0 / (1.0 + (Y * Y / (300.0 * 300.0)))') + propensity_function='300 * 1.0 / (1.0 + (Y * Y / (300 * 300)))') # degradadation of X: rxn2 = Reaction(name='X degradation', reactants={X: 1}, products={}, rate=kdx) diff --git a/test/run_tests.py b/test/run_tests.py index 189107d68..a6e870081 100644 --- a/test/run_tests.py +++ b/test/run_tests.py @@ -18,7 +18,6 @@ sys.path.append(os.path.join(os.path.dirname(__file__), '..')) - import test_cython_ssa_solver import test_empty_model import test_model import test_ode_solver @@ -32,7 +31,6 @@ import test_results modules = [ - test_cython_ssa_solver, test_empty_model, test_model, test_ode_solver, diff --git a/test/test_StochML.py b/test/test_StochML.py index bc0d651bf..15cda5587 100644 --- a/test/test_StochML.py +++ b/test/test_StochML.py @@ -1,5 +1,5 @@ import unittest -from gillespy2.example_models import Example +from example_models import Example from gillespy2.core import Model, StochMLDocument from gillespy2.core.gillespyError import * from gillespy2.solvers.numpy.basic_ode_solver import BasicODESolver diff --git a/test/test_all_solvers.py b/test/test_all_solvers.py index c74c84d20..4a48ac046 100644 --- a/test/test_all_solvers.py +++ b/test/test_all_solvers.py @@ -2,7 +2,7 @@ import numpy as np import gillespy2 -from gillespy2.example_models import Example, Oregonator +from example_models import Example, Oregonator from gillespy2.core.results import EnsembleResults, Results from gillespy2.solvers.cpp.ssa_c_solver import SSACSolver from gillespy2.solvers.numpy.basic_ode_solver import BasicODESolver @@ -10,16 +10,10 @@ from gillespy2.solvers.numpy.basic_tau_leaping_solver import BasicTauLeapingSolver from gillespy2.solvers.numpy.basic_tau_hybrid_solver import BasicTauHybridSolver -from gillespy2.solvers.cython import can_use_cython -if can_use_cython: - from gillespy2.solvers.cython.cython_ssa_solver import CythonSSASolver - class TestAllSolvers(unittest.TestCase): solvers = [SSACSolver, BasicODESolver, NumPySSASolver, BasicTauLeapingSolver, BasicTauHybridSolver] - if can_use_cython: - solvers.append(CythonSSASolver) model = Example() for sp in model.listOfSpecies.values(): diff --git a/test/test_basic_tau_hybrid_solver.py b/test/test_basic_tau_hybrid_solver.py index b2624fd2a..b1bd46702 100644 --- a/test/test_basic_tau_hybrid_solver.py +++ b/test/test_basic_tau_hybrid_solver.py @@ -1,7 +1,7 @@ import unittest import numpy as np import gillespy2 -from gillespy2.example_models import Example +from example_models import Example from gillespy2.solvers.numpy.basic_tau_hybrid_solver import BasicTauHybridSolver from gillespy2.core.gillespyError import * from gillespy2.core.results import results diff --git a/test/test_basic_tau_leaping_solver.py b/test/test_basic_tau_leaping_solver.py index 035f309ef..5150d69ff 100644 --- a/test/test_basic_tau_leaping_solver.py +++ b/test/test_basic_tau_leaping_solver.py @@ -1,6 +1,6 @@ import unittest import numpy as np -from gillespy2.example_models import Example +from example_models import Example from gillespy2.solvers.numpy.basic_tau_leaping_solver import BasicTauLeapingSolver from gillespy2.core.results import results diff --git a/test/test_cython_ssa_solver.py b/test/test_cython_ssa_solver.py deleted file mode 100644 index 1da03912d..000000000 --- a/test/test_cython_ssa_solver.py +++ /dev/null @@ -1,18 +0,0 @@ -import unittest -from gillespy2.example_models import Example - -from gillespy2.solvers.cython import can_use_cython -if can_use_cython: - from gillespy2.solvers.cython.cython_ssa_solver import CythonSSASolver - - -class TestCythonSSASolver(unittest.TestCase): - - def test_run_example(self): - if can_use_cython: - model = Example() - results = model.run(solver=CythonSSASolver) - - -if __name__ == '__main__': - unittest.main() diff --git a/test/test_example_models.py b/test/test_example_models.py index 7afedb9cd..ce372c852 100644 --- a/test/test_example_models.py +++ b/test/test_example_models.py @@ -1,7 +1,8 @@ import unittest -from gillespy2.example_models import * +from example_models import * from gillespy2.core.gillespyError import * -from gillespy2.solvers.numpy.basic_ode_solver import BasicODESolver +from gillespy2.solvers.numpy import BasicODESolver +#from TestBattery import * class TestExampleModels(unittest.TestCase): @@ -32,7 +33,9 @@ def test_example_example(self): def test_tyson2StateOscillator_example(self): tyson2StateOscillator_model = Tyson2StateOscillator() results = tyson2StateOscillator_model.run() - + + #def test_test_battery(self): + #timing_battery(100, 0.1) if __name__ == '__main__': unittest.main() diff --git a/test/test_hybrid_solver.py b/test/test_hybrid_solver.py index 059f76014..4ea6dee54 100644 --- a/test/test_hybrid_solver.py +++ b/test/test_hybrid_solver.py @@ -2,7 +2,7 @@ import numpy as np import gillespy2 from gillespy2.core.gillespyError import * -from gillespy2.example_models import Example +from example_models import Example from gillespy2.solvers.numpy.basic_tau_hybrid_solver import BasicTauHybridSolver diff --git a/test/test_model.py b/test/test_model.py index fc2df021e..f4d2b9877 100644 --- a/test/test_model.py +++ b/test/test_model.py @@ -28,6 +28,27 @@ def test_duplicate_species_names(self): with self.assertRaises(ModelError): model.add_species(species2) + def test_no_reaction_name(self): + model = Model() + rate = Parameter(name='rate', expression=0.5) + model.add_parameter(rate) + species1 = Species('A', initial_value=0) + species2 = Species('B', initial_value=0) + model.add_species([species1, species2]) + # add two reactions that has no name + reaction1 = Reaction(reactants={species1: 1}, products={species2: 1}, rate=rate) + reaction2 = Reaction(reactants={species2: 1}, products={species1: 1}, rate=rate) + model.add_reaction([reaction1, reaction2]) + + + def test_int_type_mismatch(self): + model = Model() + y1 = np.int64(5) + y2 = np.int32(5) + species1 = Species('A', initial_value=y1) + species2 = Species('B', initial_value=y2) + model.add_species([species1, species2]) + def test_duplicate_reaction_name(self): model = Model() rate = Parameter(name='rate', expression=0.5) diff --git a/test/test_numpy_ssa_solver.py b/test/test_numpy_ssa_solver.py index 3234f1610..77f3c64d0 100644 --- a/test/test_numpy_ssa_solver.py +++ b/test/test_numpy_ssa_solver.py @@ -1,6 +1,6 @@ import unittest import numpy as np -from gillespy2.example_models import Example +from example_models import Example from gillespy2.solvers.numpy.ssa_solver import NumPySSASolver diff --git a/test/test_ode_solver.py b/test/test_ode_solver.py index 20ff5df29..1ca1cea9a 100644 --- a/test/test_ode_solver.py +++ b/test/test_ode_solver.py @@ -1,7 +1,7 @@ import unittest import numpy as np import gillespy2 -from gillespy2.example_models import Example +from example_models import Example from gillespy2.solvers.numpy.basic_ode_solver import BasicODESolver diff --git a/test/test_results.py b/test/test_results.py index 09b42f114..f1572b690 100644 --- a/test/test_results.py +++ b/test/test_results.py @@ -4,10 +4,7 @@ from gillespy2.core import Model, Species, Reaction, Parameter, Results, EnsembleResults class TestResults(unittest.TestCase): - - #def name_of_test(self): - #Put test code here - + def test_to_csv_single_result_no_data(self): result = Results(data=None) test_nametag = "test_nametag" diff --git a/test/test_simple_model.py b/test/test_simple_model.py index 353508cb0..693a58b1d 100644 --- a/test/test_simple_model.py +++ b/test/test_simple_model.py @@ -154,7 +154,6 @@ def test_model_parameters_correct(self): def test_model_has_rate_rules(self): rate_rules = self.model.listOfRateRules - print(rate_rules) self.assertEqual(rate_rules['B'].variable, 'B', msg='Has incorrect species') self.assertEqual(rate_rules['B'].formula, 'cos(t)', msg='{0} has incorrect type'.format(rate_rules)) diff --git a/test/test_ssa_c_solver.py b/test/test_ssa_c_solver.py index 775e1208f..d8730d11b 100644 --- a/test/test_ssa_c_solver.py +++ b/test/test_ssa_c_solver.py @@ -1,7 +1,7 @@ import unittest import tempfile from gillespy2.core.gillespyError import DirectoryError -from gillespy2.example_models import Example +from example_models import Example from gillespy2.solvers.cpp.ssa_c_solver import SSACSolver diff --git a/test/test_tau_leaping_solver.py b/test/test_tau_leaping_solver.py index 7bfc77f2b..c4062e478 100644 --- a/test/test_tau_leaping_solver.py +++ b/test/test_tau_leaping_solver.py @@ -1,6 +1,6 @@ import unittest import numpy as np -from gillespy2.example_models import Example +from example_models import Example from gillespy2.solvers.numpy.basic_tau_leaping_solver import BasicTauLeapingSolver diff --git a/test/time_solvers.py b/test/time_solvers.py index eef9dcad5..d24d3c83a 100644 --- a/test/time_solvers.py +++ b/test/time_solvers.py @@ -1,7 +1,10 @@ import sys sys.path.insert(0,'..') -from tqdm import trange +try: + from tqdm import tqdm +except ImportError: + raise ImportError('tqdm is required. Please install it.') from itertools import product from timeit import default_timer as timer import matplotlib.pyplot as plt @@ -13,8 +16,6 @@ #BasicSSASolver from gillespy2.solvers.numpy import * #BasicODESolver, BasicRootSolver, BasicTauLeapingSolver, NumPySSASolver, TauLeapingSolver -from gillespy2.solvers.cython import * -#CythonSSASolver from gillespy2.solvers.cpp import * #SSACSolver from gillespy2.solvers.auto import *