From 04277c226bf155098aa17de8dbabac30fcd6d730 Mon Sep 17 00:00:00 2001 From: Momentopolis Date: Thu, 26 Mar 2020 01:33:07 -0400 Subject: [PATCH 01/24] Made minor changes trying to fix divide by zero error. Moved example_models.py to test folder. --- .graphics/coverage.png | Bin 1548 -> 1604 bytes .graphics/coverage.svg | 6 ++-- gillespy2/__init__.py | 2 +- gillespy2/solvers/cpp/__init__.py | 2 +- gillespy2/solvers/cpp/example_models.py | 26 ++++++++++++++ run_coverage.sh | 2 -- test/TestBattery.py | 19 ++++------- {gillespy2 => test}/example_models.py | 2 +- test/run_tests.py | 22 ++++++------ test/test_StochML.py | 2 +- test/test_all_solvers.py | 12 +++---- test/test_basic_tau_hybrid_solver.py | 2 +- test/test_basic_tau_leaping_solver.py | 2 +- test/test_cython_ssa_solver.py | 2 +- test/test_example_models.py | 43 +++++++++++++----------- test/test_hybrid_solver.py | 2 +- test/test_numpy_ssa_solver.py | 2 +- test/test_ode_solver.py | 2 +- test/test_ssa_c_solver.py | 2 +- test/test_tau_leaping_solver.py | 2 +- 20 files changed, 88 insertions(+), 66 deletions(-) create mode 100644 gillespy2/solvers/cpp/example_models.py rename {gillespy2 => test}/example_models.py (99%) diff --git a/.graphics/coverage.png b/.graphics/coverage.png index efcbd3c53c4363a562b91f75deeb53ada1700589..101b2c24ab43ca2bf317682fd4ef0b7d2d43be6c 100644 GIT binary patch delta 1476 zcmV;#1v~nT48#nOZhv1%L_t(o!|m98Y*ytN$MMf2rIdn&LLZd2(Aq+2p^X%fwjz#-HZREh;!pzT9x z3l+Kor9AuNurq*L}|KzVCCN6WY2p7Jt)4r{J2a-GMGz=`y1e z(Pc&_qRWgA8uN5;AUJ&k}x^&mGnGjEIZg6v}?`@ z9l4V@_$Ojp=>H~%N0U61{NJ+v9m>Bz<2;gYL_a0@AW5I_;!M2D+`H&sM#-yWFQaxk*B+o@CbcWLeg(-hS$&d=^O)5_ z>Vur9BV{X_vZ&im_8EjI${;kA#SfCWm);fhSWD3Wu5D&fIY*=P*?jrDl^rnSXey1B zq~_Bji+}t)-Wp0ex>qo1Gzkd|TTW>mC1bf|0KJ#;MgarAPfan&12L)nbwh1^foFy| zyZ}$qJc9jsZ9E732)y@pG`Dxp(c^S`$)Qp^o8u~LHe^CDSxKqy>6S^?=BIrK=N_uWnIZIr%3MKOgBQ8AI!8)@iA!*fAg_DO0UAfe^*dBgX1 zz=%~8RC6ws*NO<2(XSi5-lg|Z%GVHgKfU&lx}EZkt=FbJMdNN%kx|Jj#Y}mP=6%%s z^7HGqxlC0*%kUgNx`(*;m@t92mJ@x4QGdgD|K&>$=M;Mzi7V#N7z#I0i{8gC<;i8@ zqnz79OA(`bP*zCe1wuEs)=NyM^bShL5njN+@f@k6GL;cke9({hd8D7E=5z9%{P(>O z&fx5i_~VUaouPZTKtKKz1@BXzL2MMc-Pk|vpG_Pvm2Hoa{whri$s0k_a+=SyU4OUD z*UO0ls;^}#C<)`nkp$d$W6PM z_z|Va9IpD;di!4>vy3~IG5mJ+%_sl1VCjlZBJu=z{W&s$^L^T%_ew}u#Gq9?aGYBk znVcDH9^xNo`<;yWknw}4{2`V1wSQiB{aikLhVbt=S$bT}*~O6! z)VC1JAiaw4%Ao1k!_-ECn3QjkJ~+@@>e%x{*kd`voC@Wpd$;^V6 zi6D#&hFHApVzR{O;uJQ~ESZafGmGI5$Dn_xGxf3!VOyL!NR+T~5nV+tYgy$M+8%C| zT5F}J$kRWjcMNxwQon}rkJ9iC@7+bs7T(%G z>N>LTp=lK(2YbH>jRPgTT!?Z8r?8A(CjjTn~RDnhI;V&mhCIz-w($Un}AI@ag&`zU$G=!jfi_w!O)^t3?0G=`_l3C1W;) zx3g;mg->!~8`1C6bAT!3ock&n&s|zKk~4TC@_#=haV|$fjNC)+ViF^q+7;xp?k43v zPR{2@2hoQqSVedtub1()g^na?cQT=eQ+@fgYTEaa zTudq4LGDV9en9bNQk!||6|Vaed51aOPrrV*sn#c%SVF=wQl?UUCkamzsbf+niJO8k z`3YJ(N!rGd3X0$7EJpRsymcyNt0>Rr%YQFYlS)Z>kPBTM@0V6c;{qBFGWs^gRuMW! z^Eisy2xT+kZVFfRuR9nLH<6RU$G690rzXJ!P4R`n(-A7WcJUGr#fplJzJXQ-M*(@xs&58gRHgUsh>`hRn< zw?bDl_3trzFLMsjn8neSPxousKyecb9%9;j4y<7MjltFx`8wf8D9hnQHQi$duKQWZ z-owe4NdIj-mih*-A7s{DRQ!UShe+NQ&tG{92iKAJ2i`x%g_{T;Aa^szr}y8p|Euk8 zPHd*Fmsk;nE#&nbOegQK~9B==ItA0v$VqB{A5h6b$)=J(LjE~Wg z7Q{>L4QejD!QmL8He5b`Ywi{(qNrtR^+` zMbGT>qu&F3sF3nqUVWJh+leg+R^QnFAtU%N`epn-F#O-t5MtPjA;iA{Kv}_r^7OV1 z0000bbVXQnWMOn=I%9HWVRU5xGB7eQEif`IF)&myFgi6dIx{mZFf}?bFiFEW7ytkO zC3HntbYx+4WjbwdWNBu30FxUA2_P_3F)%tcGCDIeEig4YFfcftfTREb002ovPDHLk FV1jC4$2|Z5 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 @@ - + coverage coverage - 76% - 76% + 26% + 26% 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/solvers/cpp/__init__.py b/gillespy2/solvers/cpp/__init__.py index abb2a2c36..a52644cfe 100644 --- a/gillespy2/solvers/cpp/__init__.py +++ b/gillespy2/solvers/cpp/__init__.py @@ -1,7 +1,7 @@ from gillespy2.solvers.cpp.ssa_c_solver import SSACSolver def check_cpp_support(): - from gillespy2.example_models import Example + import example_models 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/run_coverage.sh b/run_coverage.sh index 30830c3d1..e6567afb1 100755 --- a/run_coverage.sh +++ b/run_coverage.sh @@ -5,5 +5,3 @@ coverage run --source=gillespy2 --omit=gillespy2/solvers/stochkit/* test/run_tests.py -m develop coverage html -coverage-badge -fo .graphics/coverage.svg -convert .graphics/coverage.svg .graphics/coverage.png diff --git a/test/TestBattery.py b/test/TestBattery.py index 57e0df309..2d74db77a 100644 --- a/test/TestBattery.py +++ b/test/TestBattery.py @@ -1,12 +1,9 @@ from tqdm import tqdm -import click 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 +14,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/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 1e54ff139..eb4550906 100644 --- a/test/run_tests.py +++ b/test/run_tests.py @@ -31,17 +31,17 @@ import test_sys_init modules = [ - test_cython_ssa_solver, - test_empty_model, - test_model, - test_ode_solver, - test_hybrid_solver, - test_simple_model, - test_ssa_c_solver, - test_SBML, - test_example_models, - test_all_solvers, - test_sys_init +# test_cython_ssa_solver, +# test_empty_model, +# test_model, +# test_ode_solver, +# test_hybrid_solver, +# test_simple_model, +# test_ssa_c_solver, +# test_SBML, + test_example_models, +# test_all_solvers, +# test_sys_init ] for module in modules: 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 97087b5c8..f1a819bc6 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 @@ -35,14 +35,14 @@ def test_instantiated(self): for solver in self.solvers: self.model.run(solver=solver()) - def test_return_type(self): + #def test_return_type(self): for solver in self.solvers: self.assertTrue(isinstance(self.results[solver], np.ndarray)) self.assertTrue(isinstance(self.results[solver][0], np.ndarray)) self.assertTrue(isinstance(self.results[solver][0][0], np.ndarray)) self.assertTrue(isinstance(self.results[solver][0][0][0], np.float)) - def test_return_type_show_labels(self): + #def test_return_type_show_labels(self): for solver in self.solvers: self.assertTrue(isinstance(self.labeled_results[solver], Results)) self.assertTrue(isinstance(self.labeled_results[solver]['Sp'], np.ndarray)) @@ -57,7 +57,7 @@ def test_return_type_show_labels(self): self.assertTrue(isinstance(self.labeled_results_more_trajectories[solver][0]['Sp'][0], np.float)) - def test_random_seed(self): + #def test_random_seed(self): for solver in self.solvers: same_results = self.model.run(solver=solver, show_labels=False, seed=1) self.assertTrue(np.array_equal(same_results, self.results[solver])) @@ -65,13 +65,13 @@ def test_random_seed(self): diff_results = self.model.run(solver=solver, show_labels=False, seed=2) self.assertFalse(np.array_equal(diff_results, self.results[solver])) - def test_extraneous_args(self): + #def test_extraneous_args(self): for solver in self.solvers: with self.assertLogs(level='WARN'): model = Example() results = model.run(solver=solver, nonsense='ABC') - def test_timeout(self): + #def test_timeout(self): for solver in self.solvers: with self.assertLogs(level='WARN'): model = Oregonator() 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 index 1da03912d..4f977164c 100644 --- a/test/test_cython_ssa_solver.py +++ b/test/test_cython_ssa_solver.py @@ -1,5 +1,5 @@ import unittest -from gillespy2.example_models import Example +from example_models import Example from gillespy2.solvers.cython import can_use_cython if can_use_cython: diff --git a/test/test_example_models.py b/test/test_example_models.py index 7afedb9cd..eff86bb3b 100644 --- a/test/test_example_models.py +++ b/test/test_example_models.py @@ -1,38 +1,41 @@ 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 TestBattery import * class TestExampleModels(unittest.TestCase): - def test_trichloroethylene_example(self): - trichloroethylene_model = Trichloroethylene() - results = trichloroethylene_model.run() + #def test_trichloroethylene_example(self): + # trichloroethylene_model = Trichloroethylene() + # results = trichloroethylene_model.run() - def test_lacOperon_example(self): - lacOperon_model = LacOperon() - results = lacOperon_model.run(solver=BasicODESolver) + #def test_lacOperon_example(self): + # lacOperon_model = LacOperon() + # results = lacOperon_model.run(solver=BasicODESolver) - def test_schlogl_example(self): - schlogl_model = Schlogl() - results = schlogl_model.run() + #def test_schlogl_example(self): + # schlogl_model = Schlogl() + # results = schlogl_model.run() - def test_michaelisMenten_example(self): - michaelisMenten_model = MichaelisMenten() - results = michaelisMenten_model.run() + #def test_michaelisMenten_example(self): + # michaelisMenten_model = MichaelisMenten() + # results = michaelisMenten_model.run() - def test_toggleSwitch_example(self): - toggleSwitch_model = ToggleSwitch() - results = toggleSwitch_model.run() + #def test_toggleSwitch_example(self): + # toggleSwitch_model = ToggleSwitch() + # results = toggleSwitch_model.run() - def test_example_example(self): - example_model = Example() - results = example_model.run() + #def test_example_example(self): + # example_model = Example() + # results = example_model.run() 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 cce6920a5..87baa334b 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_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_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 From 629d42a7afbd617254affa3837f6f3d71e5e68ac Mon Sep 17 00:00:00 2001 From: Momentopolis Date: Thu, 26 Mar 2020 15:18:20 -0400 Subject: [PATCH 02/24] Removed tests associated with cython solver --- test/test_all_solvers.py | 6 ------ test/test_cython_ssa_solver.py | 18 ------------------ test/test_example_models.py | 1 - test/test_results.py | 5 +---- test/time_solvers.py | 2 -- 5 files changed, 1 insertion(+), 31 deletions(-) delete mode 100644 test/test_cython_ssa_solver.py diff --git a/test/test_all_solvers.py b/test/test_all_solvers.py index 380f37f58..d3f69d5ce 100644 --- a/test/test_all_solvers.py +++ b/test/test_all_solvers.py @@ -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_cython_ssa_solver.py b/test/test_cython_ssa_solver.py deleted file mode 100644 index 4f977164c..000000000 --- a/test/test_cython_ssa_solver.py +++ /dev/null @@ -1,18 +0,0 @@ -import unittest -from 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 eff86bb3b..32bc443a2 100644 --- a/test/test_example_models.py +++ b/test/test_example_models.py @@ -1,7 +1,6 @@ import unittest from example_models import * from gillespy2.core.gillespyError import * -from gillespy2.solvers.numpy.basic_ode_solver import BasicODESolver from TestBattery import * class TestExampleModels(unittest.TestCase): 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/time_solvers.py b/test/time_solvers.py index eef9dcad5..4bf8ce9c2 100644 --- a/test/time_solvers.py +++ b/test/time_solvers.py @@ -13,8 +13,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 * From de0be18cb3df701361f24f63f4165414dd2b916a Mon Sep 17 00:00:00 2001 From: Momentopolis Date: Thu, 26 Mar 2020 15:36:36 -0400 Subject: [PATCH 03/24] Removed print statement in test_simple_model that was showing up when running the tests. Also uncommented the example model tests since the divide by zero error is no longer an issue --- test/run_tests.py | 2 -- test/test_example_models.py | 36 ++++++++++++++++++------------------ test/test_simple_model.py | 1 - 3 files changed, 18 insertions(+), 21 deletions(-) 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_example_models.py b/test/test_example_models.py index 32bc443a2..9c514ea97 100644 --- a/test/test_example_models.py +++ b/test/test_example_models.py @@ -5,29 +5,29 @@ class TestExampleModels(unittest.TestCase): - #def test_trichloroethylene_example(self): - # trichloroethylene_model = Trichloroethylene() - # results = trichloroethylene_model.run() + def test_trichloroethylene_example(self): + trichloroethylene_model = Trichloroethylene() + results = trichloroethylene_model.run() - #def test_lacOperon_example(self): - # lacOperon_model = LacOperon() - # results = lacOperon_model.run(solver=BasicODESolver) + def test_lacOperon_example(self): + lacOperon_model = LacOperon() + results = lacOperon_model.run(solver=BasicODESolver) - #def test_schlogl_example(self): - # schlogl_model = Schlogl() - # results = schlogl_model.run() + def test_schlogl_example(self): + schlogl_model = Schlogl() + results = schlogl_model.run() - #def test_michaelisMenten_example(self): - # michaelisMenten_model = MichaelisMenten() - # results = michaelisMenten_model.run() + def test_michaelisMenten_example(self): + michaelisMenten_model = MichaelisMenten() + results = michaelisMenten_model.run() - #def test_toggleSwitch_example(self): - # toggleSwitch_model = ToggleSwitch() - # results = toggleSwitch_model.run() + def test_toggleSwitch_example(self): + toggleSwitch_model = ToggleSwitch() + results = toggleSwitch_model.run() - #def test_example_example(self): - # example_model = Example() - # results = example_model.run() + def test_example_example(self): + example_model = Example() + results = example_model.run() def test_tyson2StateOscillator_example(self): tyson2StateOscillator_model = Tyson2StateOscillator() 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)) From acb221d869b7f9d71f44438134ce44ecfc343cee Mon Sep 17 00:00:00 2001 From: Momentopolis Date: Tue, 31 Mar 2020 13:22:47 -0400 Subject: [PATCH 04/24] Removed comments on test_all_solvers.py --- test/test_all_solvers.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/test_all_solvers.py b/test/test_all_solvers.py index d3f69d5ce..4a48ac046 100644 --- a/test/test_all_solvers.py +++ b/test/test_all_solvers.py @@ -31,14 +31,14 @@ def test_instantiated(self): for solver in self.solvers: self.model.run(solver=solver()) - #def test_return_type(self): + def test_return_type(self): for solver in self.solvers: self.assertTrue(isinstance(self.results[solver], np.ndarray)) self.assertTrue(isinstance(self.results[solver][0], np.ndarray)) self.assertTrue(isinstance(self.results[solver][0][0], np.ndarray)) self.assertTrue(isinstance(self.results[solver][0][0][0], np.float)) - #def test_return_type_show_labels(self): + def test_return_type_show_labels(self): for solver in self.solvers: self.assertTrue(isinstance(self.labeled_results[solver], Results)) self.assertTrue(isinstance(self.labeled_results[solver]['Sp'], np.ndarray)) @@ -53,7 +53,7 @@ def test_instantiated(self): self.assertTrue(isinstance(self.labeled_results_more_trajectories[solver][0]['Sp'][0], np.float)) - #def test_random_seed(self): + def test_random_seed(self): for solver in self.solvers: same_results = self.model.run(solver=solver, show_labels=False, seed=1) self.assertTrue(np.array_equal(same_results, self.results[solver])) @@ -61,13 +61,13 @@ def test_instantiated(self): diff_results = self.model.run(solver=solver, show_labels=False, seed=2) self.assertFalse(np.array_equal(diff_results, self.results[solver])) - #def test_extraneous_args(self): + def test_extraneous_args(self): for solver in self.solvers: with self.assertLogs(level='WARN'): model = Example() results = model.run(solver=solver, nonsense='ABC') - #def test_timeout(self): + def test_timeout(self): for solver in self.solvers: with self.assertLogs(level='WARN'): model = Oregonator() From bf9c56a333ca89ec9765f1917a4c7ffaeaa879b1 Mon Sep 17 00:00:00 2001 From: Momentopolis Date: Tue, 31 Mar 2020 16:20:14 -0400 Subject: [PATCH 05/24] Changed run_coverage.sh so it does not include the cython solver files --- run_coverage.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/run_coverage.sh b/run_coverage.sh index e6567afb1..4bdca736b 100755 --- a/run_coverage.sh +++ b/run_coverage.sh @@ -3,5 +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 From 4b04031fa8d240eb8c64d6b0d7f487361bde1224 Mon Sep 17 00:00:00 2001 From: Mason <31526990+makdl@users.noreply.github.com> Date: Wed, 1 Apr 2020 12:51:00 -0400 Subject: [PATCH 06/24] Create .codeclimate.yml --- .codeclimate.yml | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 .codeclimate.yml 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" From b52901c979212f1cb071729eb90cbd68933c7a61 Mon Sep 17 00:00:00 2001 From: Mason <31526990+makdl@users.noreply.github.com> Date: Wed, 1 Apr 2020 12:52:23 -0400 Subject: [PATCH 07/24] Update .coveragerc --- .coveragerc | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) 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/* From 1d7137f657d04b978ae3de5492bb312c1a4dc717 Mon Sep 17 00:00:00 2001 From: Mason <31526990+makdl@users.noreply.github.com> Date: Wed, 1 Apr 2020 12:57:28 -0400 Subject: [PATCH 08/24] Update .travis.yml --- .travis.yml | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index f58feb1bb..b65dc9e25 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,12 +2,20 @@ os: linux dist: xenial language: python python: "3.6" +before_script: + - "curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter" + - "chmod +x ./cc-test-reporter" + - "./cc-test-reporter before-build" install: - - pip3 install . + - pip3 install -r requirements.txt - pip3 install python-libsbml - pip3 install cython + - pip3 install coverage script: - python test/run_tests.py -m release + - "coverage run --source=gillespy2 --omit=gillespy2/solvers/stochkit/* test/run_tests.py -m develop" +after_script: + - "coverage xml" + - "if [[ \"$TRAVIS_TEST_RESULT\" == 0 ]]; then ./cc-test-reporter after-build -t coverage.py --exit-code $TRAVIS_TEST_RESULT; fi" deploy: # API token stored in env var PYPI_PASSWORD on Travis CI provider: pypi From 5e35fffbb61ff348f5f45b496eac3d1b3ed10d94 Mon Sep 17 00:00:00 2001 From: Mason <31526990+makdl@users.noreply.github.com> Date: Wed, 1 Apr 2020 13:10:03 -0400 Subject: [PATCH 09/24] Added Code Climate maintainability badge --- README.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 2e5cbc5c6..14b3351c3 100644 --- a/README.md +++ b/README.md @@ -147,9 +147,9 @@ New developments happen primarily in the [`develop`](https://github.com/GillesPy

-| 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 ------- @@ -177,7 +177,6 @@ Authors and history * [**Mason Kidwell**](https://github.com/makdl) * [**Jesse Reeve**](https://github.com/jdreeve) * [**Fin Carter**](https://github.com/Fin109) -* [**Bryan Rumsey**](https://github.com/BryanRumsey) Acknowledgments --------------- From 07633ee7a55dc56e5c0ab6402e0c131e5f947955 Mon Sep 17 00:00:00 2001 From: m-muted <31526990+m-muted@users.noreply.github.com> Date: Wed, 1 Apr 2020 16:18:07 -0400 Subject: [PATCH 10/24] Try-except blocks for tqdm, fixed unit tests needing tqdm --- test/TestBattery.py | 5 ++++- test/TestingBattery.ipynb | 7 +++++-- test/test_example_models.py | 3 ++- test/time_solvers.py | 5 ++++- 4 files changed, 15 insertions(+), 5 deletions(-) diff --git a/test/TestBattery.py b/test/TestBattery.py index 2d74db77a..e70c4aca9 100644 --- a/test/TestBattery.py +++ b/test/TestBattery.py @@ -1,4 +1,7 @@ -from tqdm import tqdm +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 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/test/test_example_models.py b/test/test_example_models.py index 9c514ea97..ce372c852 100644 --- a/test/test_example_models.py +++ b/test/test_example_models.py @@ -1,7 +1,8 @@ import unittest from example_models import * from gillespy2.core.gillespyError import * -from TestBattery import * +from gillespy2.solvers.numpy import BasicODESolver +#from TestBattery import * class TestExampleModels(unittest.TestCase): diff --git a/test/time_solvers.py b/test/time_solvers.py index 4bf8ce9c2..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 From 41c75c8b399ba637a788b38f22671e4e86584fbf Mon Sep 17 00:00:00 2001 From: seanebum Date: Fri, 3 Apr 2020 13:17:35 -0400 Subject: [PATCH 11/24] began removing python signals, model.run now does not use signals at all, will not work with solvers other than NumPySSA until changes are propogated to all solvers. Added threading .run wrapper to NumPySSASolver for timeouts. These should now work on all OS for NumPySSASolver. --- gillespy2/core/gillespy2.py | 97 +++++++++------------------ gillespy2/solvers/numpy/ssa_solver.py | 45 +++++++++---- 2 files changed, 62 insertions(+), 80 deletions(-) diff --git a/gillespy2/core/gillespy2.py b/gillespy2/core/gillespy2.py index d806aebae..5c8385540 100644 --- a/gillespy2/core/gillespy2.py +++ b/gillespy2/core/gillespy2.py @@ -601,75 +601,40 @@ 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], **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) + + 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") class Species(SortableObject): diff --git a/gillespy2/solvers/numpy/ssa_solver.py b/gillespy2/solvers/numpy/ssa_solver.py index 17c3e9cee..02b229cd3 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,17 @@ class NumPySSASolver(GillesPySolver): name = "NumPySSASolver" - interrupted = False rc = 0 + stop_event = Event() + result = None def __init__(self): name = 'NumPySSASolver' - interrupted = False rc = 0 @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,12 +31,6 @@ 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): @@ -44,6 +39,21 @@ def timed_out(signum, frame): 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 +97,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 +108,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 +132,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 +162,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 From 53854629fff6e867138170530c7ff1ec6ec0a994 Mon Sep 17 00:00:00 2001 From: seanebum Date: Fri, 3 Apr 2020 14:48:13 -0400 Subject: [PATCH 12/24] updated basic tau hybrid solver to use threaded timeouts, this still needs some way to safely exit the solve_ivp function when timeout is registered. Also made a fix to allow timeout to be used multiple times in the same kernel for the same solver --- gillespy2/core/gillespy2.py | 9 ++-- .../solvers/numpy/basic_tau_hybrid_solver.py | 51 ++++++++++++++----- gillespy2/solvers/numpy/ssa_solver.py | 7 ++- 3 files changed, 48 insertions(+), 19 deletions(-) diff --git a/gillespy2/core/gillespy2.py b/gillespy2/core/gillespy2.py index 5c8385540..c1dbf495f 100644 --- a/gillespy2/core/gillespy2.py +++ b/gillespy2/core/gillespy2.py @@ -619,20 +619,19 @@ def run(self, solver=None, timeout=0, **solver_args): 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) == 1: return Results(data=solver_results[0], model=self, solver_name=solver.name, rc=rc) - if len(solver_results) > 1: + 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") diff --git a/gillespy2/solvers/numpy/basic_tau_hybrid_solver.py b/gillespy2/solvers/numpy/basic_tau_hybrid_solver.py index 36313cfb0..10b333722 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,39 @@ 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 == 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 +874,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 +921,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 +988,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/ssa_solver.py b/gillespy2/solvers/numpy/ssa_solver.py index 02b229cd3..a1a06ada4 100644 --- a/gillespy2/solvers/numpy/ssa_solver.py +++ b/gillespy2/solvers/numpy/ssa_solver.py @@ -8,12 +8,14 @@ class NumPySSASolver(GillesPySolver): name = "NumPySSASolver" rc = 0 - stop_event = Event() + stop_event = None result = None def __init__(self): name = 'NumPySSASolver' rc = 0 + stop_event = None + result = None @classmethod def run(self, model, t=20, number_of_trajectories=1, increment=0.05, @@ -36,10 +38,11 @@ def run(self, model, t=20, number_of_trajectories=1, increment=0.05, if not isinstance(self, NumPySSASolver): self = NumPySSASolver() + self.stop_event = Event() + 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, From 2bd5bd8072d3653cbad15701a25faf47aa2d4944 Mon Sep 17 00:00:00 2001 From: seanebum Date: Fri, 3 Apr 2020 15:56:41 -0400 Subject: [PATCH 13/24] added handling for timeout <=0, now sets to None for threading --- gillespy2/solvers/numpy/basic_tau_hybrid_solver.py | 3 +-- gillespy2/solvers/numpy/ssa_solver.py | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gillespy2/solvers/numpy/basic_tau_hybrid_solver.py b/gillespy2/solvers/numpy/basic_tau_hybrid_solver.py index 10b333722..facd6b10a 100644 --- a/gillespy2/solvers/numpy/basic_tau_hybrid_solver.py +++ b/gillespy2/solvers/numpy/basic_tau_hybrid_solver.py @@ -778,8 +778,7 @@ def run(self, model, t=20, number_of_trajectories=1, increment=0.05, seed=None, for key in kwargs: log.warning('Unsupported keyword argument to {0} solver: {1}'.format(self.name, key)) - if timeout == 0: - timeout = None + if timeout <= 0: timeout = None sim_thread = threading.Thread(target=self.__run, args=(model,), kwargs={'t':t, 'number_of_trajectories':number_of_trajectories, diff --git a/gillespy2/solvers/numpy/ssa_solver.py b/gillespy2/solvers/numpy/ssa_solver.py index a1a06ada4..05d43d04c 100644 --- a/gillespy2/solvers/numpy/ssa_solver.py +++ b/gillespy2/solvers/numpy/ssa_solver.py @@ -39,6 +39,7 @@ def run(self, model, t=20, number_of_trajectories=1, increment=0.05, self = NumPySSASolver() self.stop_event = Event() + if timeout <= 0: timeout = None if len(kwargs) > 0: for key in kwargs: From cad2ba778da3f219030632906e191176d5b6b5b3 Mon Sep 17 00:00:00 2001 From: seanebum Date: Fri, 3 Apr 2020 17:23:10 -0400 Subject: [PATCH 14/24] added consideration for timeout=None when comparing timeout to 0 --- gillespy2/solvers/numpy/basic_tau_hybrid_solver.py | 2 +- gillespy2/solvers/numpy/ssa_solver.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gillespy2/solvers/numpy/basic_tau_hybrid_solver.py b/gillespy2/solvers/numpy/basic_tau_hybrid_solver.py index facd6b10a..142dad290 100644 --- a/gillespy2/solvers/numpy/basic_tau_hybrid_solver.py +++ b/gillespy2/solvers/numpy/basic_tau_hybrid_solver.py @@ -778,7 +778,7 @@ def run(self, model, t=20, number_of_trajectories=1, increment=0.05, seed=None, for key in kwargs: log.warning('Unsupported keyword argument to {0} solver: {1}'.format(self.name, key)) - if timeout <= 0: timeout = None + 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, diff --git a/gillespy2/solvers/numpy/ssa_solver.py b/gillespy2/solvers/numpy/ssa_solver.py index 05d43d04c..4e780fb64 100644 --- a/gillespy2/solvers/numpy/ssa_solver.py +++ b/gillespy2/solvers/numpy/ssa_solver.py @@ -39,7 +39,7 @@ def run(self, model, t=20, number_of_trajectories=1, increment=0.05, self = NumPySSASolver() self.stop_event = Event() - if timeout <= 0: timeout = None + if timeout is not None and timeout <= 0: timeout = None if len(kwargs) > 0: for key in kwargs: From c37743e62cb34f22408d157d3746de822aef5b71 Mon Sep 17 00:00:00 2001 From: Brian Drawert Date: Mon, 6 Apr 2020 09:43:26 -0400 Subject: [PATCH 15/24] bumped the version to 1.3.2 --- gillespy2/__version__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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' From f48038c9acf2c4eec8cdc73b57cb24e7352b3ba7 Mon Sep 17 00:00:00 2001 From: Brian Drawert Date: Mon, 6 Apr 2020 10:26:33 -0400 Subject: [PATCH 16/24] automatically make names for reactions if not provided --- gillespy2/core/gillespy2.py | 8 ++++++-- test/test_model.py | 13 +++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/gillespy2/core/gillespy2.py b/gillespy2/core/gillespy2.py index d806aebae..3f2c49224 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 @@ -932,7 +933,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 +969,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/test/test_model.py b/test/test_model.py index fc2df021e..d9e8e1aac 100644 --- a/test/test_model.py +++ b/test/test_model.py @@ -28,6 +28,19 @@ 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 a reaction 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]) + model.run() + def test_duplicate_reaction_name(self): model = Model() rate = Parameter(name='rate', expression=0.5) From 29eb090042872f73f5ab928178a249fb87b87582 Mon Sep 17 00:00:00 2001 From: Brian Drawert Date: Mon, 6 Apr 2020 10:48:10 -0400 Subject: [PATCH 17/24] cleanup --- test/test_model.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/test_model.py b/test/test_model.py index d9e8e1aac..1a8cb0f73 100644 --- a/test/test_model.py +++ b/test/test_model.py @@ -35,11 +35,10 @@ def test_no_reaction_name(self): species1 = Species('A', initial_value=0) species2 = Species('B', initial_value=0) model.add_species([species1, species2]) - # add a reaction that has no name + # 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]) - model.run() def test_duplicate_reaction_name(self): model = Model() From f5b1e49e3572f04d0e92c31905c65b018f000428 Mon Sep 17 00:00:00 2001 From: Brian Drawert Date: Mon, 6 Apr 2020 11:37:59 -0400 Subject: [PATCH 18/24] better test for int, better error message --- gillespy2/core/gillespy2.py | 5 +++-- test/test_model.py | 9 +++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/gillespy2/core/gillespy2.py b/gillespy2/core/gillespy2.py index 3f2c49224..2723c5a08 100644 --- a/gillespy2/core/gillespy2.py +++ b/gillespy2/core/gillespy2.py @@ -732,10 +732,11 @@ def __init__(self, name="", initial_value=0, constant=False, mode_list = ['continuous', 'dynamic', 'discrete'] if self.mode not in mode_list: raise SpeciesError('Species mode must be either \'continuous\', \'dynamic\', or \'discrete\'.') - if mode == 'continuous': + if mode == 'continuous' or model == 'dynamic': self.initial_value = np.float(initial_value) else: - if not isinstance(initial_value, int): raise ValueError('Discrete values must be of type int.') + if int(initial_value) != initial_value: + raise ValueError("'initial_value' for Species with mode='discrete' must be an integer value.") 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 \ diff --git a/test/test_model.py b/test/test_model.py index 1a8cb0f73..f4d2b9877 100644 --- a/test/test_model.py +++ b/test/test_model.py @@ -40,6 +40,15 @@ def test_no_reaction_name(self): 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) From 2541bf420db32d286556c99b6537b00dcb50e0c3 Mon Sep 17 00:00:00 2001 From: Brian Drawert Date: Mon, 6 Apr 2020 12:10:58 -0400 Subject: [PATCH 19/24] fixed bug, better message --- gillespy2/core/gillespy2.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gillespy2/core/gillespy2.py b/gillespy2/core/gillespy2.py index 2723c5a08..254ef860b 100644 --- a/gillespy2/core/gillespy2.py +++ b/gillespy2/core/gillespy2.py @@ -732,11 +732,11 @@ def __init__(self, name="", initial_value=0, constant=False, mode_list = ['continuous', 'dynamic', 'discrete'] if self.mode not in mode_list: raise SpeciesError('Species mode must be either \'continuous\', \'dynamic\', or \'discrete\'.') - if mode == 'continuous' or model == 'dynamic': + if mode == 'continuous': self.initial_value = np.float(initial_value) else: - if int(initial_value) != initial_value: - raise ValueError("'initial_value' for Species with mode='discrete' must be an integer value.") + 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 \ From 1f1f8e0cf670c66da42703f1189275f7e4f5956c Mon Sep 17 00:00:00 2001 From: seanebum Date: Mon, 6 Apr 2020 12:25:24 -0400 Subject: [PATCH 20/24] small fix for c solver imports --- gillespy2/solvers/cpp/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gillespy2/solvers/cpp/__init__.py b/gillespy2/solvers/cpp/__init__.py index 0acdc6723..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(): - import example_models + from gillespy2.solvers.cpp.example_models import Example try: model = Example() results = model.run(solver=SSACSolver) From 86aca67e1f452387d531f771d26474905eb14520 Mon Sep 17 00:00:00 2001 From: Brian Drawert Date: Mon, 6 Apr 2020 12:35:59 -0400 Subject: [PATCH 21/24] fixed git ignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 1c6f2a937..ab81724aa 100644 --- a/.gitignore +++ b/.gitignore @@ -68,3 +68,4 @@ __pycache__ .ipynb_checkpoints* .coverage htmlcov +/build From 108fe5b902f73500f3c93e78d5a00a7c515547a4 Mon Sep 17 00:00:00 2001 From: seanebum Date: Mon, 6 Apr 2020 13:22:06 -0400 Subject: [PATCH 22/24] updated call to solver.run for auto solver --- gillespy2/core/gillespy2.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gillespy2/core/gillespy2.py b/gillespy2/core/gillespy2.py index 0e9a9de9b..f234cff3c 100644 --- a/gillespy2/core/gillespy2.py +++ b/gillespy2/core/gillespy2.py @@ -614,7 +614,8 @@ def run(self, solver=None, timeout=0, **solver_args): 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], **solver_args) + increment=self.tspan[-1] - + self.tspan[-2], timeout=timeout, **solver_args) if rc == 33: from gillespy2.core import log From d2dc790e8ab1462da3b1195f38ba0803cb0e005e Mon Sep 17 00:00:00 2001 From: seanebum Date: Mon, 6 Apr 2020 13:55:32 -0400 Subject: [PATCH 23/24] changed basic ode solver to use threads instead of signals for timeouts --- gillespy2/solvers/numpy/basic_ode_solver.py | 37 ++++++++++++++------- 1 file changed, 25 insertions(+), 12 deletions(-) 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 From b9ec12ff2184a328cf4d8676a3d65abaf7d94f05 Mon Sep 17 00:00:00 2001 From: seanebum Date: Mon, 6 Apr 2020 14:59:24 -0400 Subject: [PATCH 24/24] replaced condiiional check for show_labels=False in gillespy2 core and implemented threaded timeouts in basic tau leaping solver --- gillespy2/core/gillespy2.py | 2 + .../solvers/numpy/basic_tau_leaping_solver.py | 49 +++++++++++++------ 2 files changed, 37 insertions(+), 14 deletions(-) diff --git a/gillespy2/core/gillespy2.py b/gillespy2/core/gillespy2.py index f234cff3c..81f731988 100644 --- a/gillespy2/core/gillespy2.py +++ b/gillespy2/core/gillespy2.py @@ -621,6 +621,8 @@ def run(self, solver=None, timeout=0, **solver_args): 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) == 1: return Results(data=solver_results[0], model=self, solver_name=solver.name, rc=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