Skip to content

Commit

Permalink
Merge pull request #314 from GillesPy2/develop
Browse files Browse the repository at this point in the history
Release 1.3.2
  • Loading branch information
seanebum authored Apr 6, 2020
2 parents b2562fe + ac4fe69 commit e092c69
Show file tree
Hide file tree
Showing 36 changed files with 327 additions and 198 deletions.
44 changes: 44 additions & 0 deletions .codeclimate.yml
Original file line number Diff line number Diff line change
@@ -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"
6 changes: 4 additions & 2 deletions .coveragerc
Original file line number Diff line number Diff line change
@@ -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/*
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,4 @@ __pycache__
.ipynb_checkpoints*
.coverage
htmlcov
/build
Binary file added .graphics/coverage.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 3 additions & 3 deletions .graphics/coverage.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 10 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,9 +147,9 @@ New developments happen primarily in the [`develop`](https://github.com/GillesPy

<p align="center">

| 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
-------
Expand Down
2 changes: 1 addition & 1 deletion gillespy2/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,4 @@
from gillespy2.core import *
import gillespy2.sbml
import gillespy2.solvers
import gillespy2.example_models
#import gillespy2.example_models
2 changes: 1 addition & 1 deletion gillespy2/__version__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
110 changes: 41 additions & 69 deletions gillespy2/core/gillespy2.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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 \
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion gillespy2/solvers/cpp/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
26 changes: 26 additions & 0 deletions gillespy2/solvers/cpp/example_models.py
Original file line number Diff line number Diff line change
@@ -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']
37 changes: 25 additions & 12 deletions gillespy2/solvers/numpy/basic_ode_solver.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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):
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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
Loading

0 comments on commit e092c69

Please sign in to comment.