diff --git a/.gitignore b/.gitignore index 0a6843a..21a461a 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,8 @@ *.pyc *.pdf *.class - +*.tokens +python/build +python/dist +python/SpatialKappa.egg-info +python/SpatialKappa/share \ No newline at end of file diff --git a/Makefile b/Makefile index de86af7..eda9250 100644 --- a/Makefile +++ b/Makefile @@ -5,9 +5,16 @@ # OR sudo apt-get install antlr3 # N.B.: The CLASSPATH has to have both parts and has to be aboslute -CLASSPATH=$(realpath .)/src/lib/antlr-3.2.jar:$(HOME)/.ant/lib/ant-antlr3.jar +CLASSPATH=$(realpath .)/src/lib/antlr-3.2.jar:$(realpath .)/ant-antlr3.jar + +SKJAR_FILE = SpatialKappa-v2.1.5.jar +ANTLRJAR_FILE = ant-antlr-3.2.jar +PYTHON_SHARE_DIR = python/SpatialKappa/share/SpatialKappa all: cd build && ant cd build && CLASSPATH=$(CLASSPATH) ant compile-all - cd build && ant -f buildSpatialKappa.xml \ No newline at end of file + cd build && ant -f buildSpatialKappa.xml + mkdir -p $(PYTHON_SHARE_DIR) + cp $(SKJAR_FILE) $(PYTHON_SHARE_DIR) + cp $(ANTLRJAR_FILE) $(PYTHON_SHARE_DIR) diff --git a/README.md b/README.md index 2624cdd..bc38f33 100644 --- a/README.md +++ b/README.md @@ -4,22 +4,48 @@ SpatialKappa

14 September 2012

Copyright © 2012 DemonSoft.org -<http://www.demonsoft.org/>

+<http://www.demonsoft.org/>
+Copyright © 2014-2019 David C Sterratt +

-

INTRODUCTION

+INTRODUCTION +------------ -

This project contains an implementation of a syntactic extension -of Kappa to allow expression of location and transport in models. A user manual is available [here][manual-link].

+This project contains an implementation of a syntactic extension +of Kappa to allow expression of location and transport in models. A user manual is available [here][manual-link]. -Feel free to add bug reports or feature requests [here][bug-link].

+Feel free to add bug reports or feature requests [here][bug-link]. -

LICENSE

+INSTALLATION +------------ -

This project is licenced under the GNU Lesser General Public -Licence Version 3 (LGPLv3), included as <LICENSE.html>. +Either + +1. Run the standalone jar file (SpatialKappa-v2.1.3.jar) +2. Install the python interface using pip: +```pip install SpatialKappa``` and then test using ```python -m unittest SpatialKappa.tests```. Within python ```help(SpatialKappa)``` gives the list of model-building and control functions. + + +COMPILATION +----------- + +To make the `SpatialKappa.jar` file either: + +1. Import this code as an Ecclipse project; or +2. Install ant (`sudo apt install ant openjdk-7-jdk` on Debian/Ubuntu) and run `make all` + +To install the python interface, cd into the python directory, and +follow the instructions in [python/INSTALL.md](python/INSTALL.md) + +LICENSE +------- + +This project is licenced under the GNU Lesser General Public +Licence Version 3 (LGPLv3), included as [LICENSE.html](LICENSE.html). Included third party libraries are released under their respective -licences, listed below:

+licences, listed below: @@ -76,27 +102,28 @@ licences, listed below:

-

ACKNOWLEDGEMENTS

+ACKNOWLEDGEMENTS +---------------- -The development of Spatial Kappa was funded in part by SynthSys. SynthSys is a Centre for -Integrative Systems Biology (CISB) funded by BBSRC and EPSRC, reference BB/D019621/1. +The development of Spatial Kappa was funded in part by SynthSys. SynthSys is a Centre for Integrative Systems Biology (CISB) funded by BBSRC and EPSRC, reference BB/D019621/1. +EU flag -

DISCLAIMER

+This open source software code was developed with funding from the +European Union Seventh Framework Programme (FP7/2007-2013) under grant +agreement nos. 241498 (EUROSPIN project), 242167 (SynSys-project) and +604102 (Human Brain Project), and in the Human Brain Project, funded +from the European Union’s Horizon 2020 Framework Programme for +Research and Innovation under Specific Grant Agreements No. 720270 and +No. 785907 (Human Brain Project SGA1 and SGA2). -

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND -CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, -BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND -FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF -USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+DISCLAIMER +---------- + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, +BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. [bug-link]: https://github.com/lptolik/SpatialKappa/issues [manual-link]: https://github.com/lptolik/SpatialKappa/raw/master/docs/manual/SpatialKappaManual-v2.1.0.pdf - +[1]: python/INSTALL.md diff --git a/SpatialKappa-v2.1.1.jar b/SpatialKappa-v2.1.5.jar similarity index 92% rename from SpatialKappa-v2.1.1.jar rename to SpatialKappa-v2.1.5.jar index 22281a7..140ce30 100755 Binary files a/SpatialKappa-v2.1.1.jar and b/SpatialKappa-v2.1.5.jar differ diff --git a/ant-antlr3.jar b/ant-antlr3.jar new file mode 100644 index 0000000..eed4e72 Binary files /dev/null and b/ant-antlr3.jar differ diff --git a/build/buildSpatialKappa.xml b/build/buildSpatialKappa.xml index ffad881..a50d088 100644 --- a/build/buildSpatialKappa.xml +++ b/build/buildSpatialKappa.xml @@ -3,7 +3,7 @@ - + diff --git a/docs/figs/flag_yellow_low.jpg b/docs/figs/flag_yellow_low.jpg new file mode 100644 index 0000000..673be92 Binary files /dev/null and b/docs/figs/flag_yellow_low.jpg differ diff --git a/docs/manual/KaSim_manual_v3.5.pdf b/docs/manual/KaSim_manual_v3.5.pdf new file mode 100644 index 0000000..0ab1ddb Binary files /dev/null and b/docs/manual/KaSim_manual_v3.5.pdf differ diff --git a/docs/manual/README.md b/docs/manual/README.md new file mode 100644 index 0000000..2906313 --- /dev/null +++ b/docs/manual/README.md @@ -0,0 +1,10 @@ +# SpatialKappa manuals + +Documents + +* [The main SpatialKappa manual](SpatialKappaManual-v2.1.0.pdf) + +* [Manual for tranalating SpatialKappa files into files compatible with v2.01 of KaSim](SpatialKappaTranslatorManual-v2.0.1.pdf) + +* [KaSim v3.5 manual, for fuller description of Kappa Language](KaSim_manual_v3.5.pdf) - + Note that SpatialKappa does not implement the full spec. diff --git a/python/INSTALL b/python/INSTALL deleted file mode 100644 index bb80f8d..0000000 --- a/python/INSTALL +++ /dev/null @@ -1,22 +0,0 @@ -0. Make sure that the SpatialKappa jar file has been built, either - using Eclipse, or using the Makefile in the directory above. - -1. Install py4j, either (a) in standard system location: - - sudo easy_install-2.7 py4j - - or (b) in a user directory, e.g.: - - easy_install-2.7 --install-dir=${HOME}/lib/python2.7/site-packages/ py4j - -2. Install SpatialKappa python files - - sudo python2.7 setup.py install - # python2.7 setup.py install --prefix=${HOME} - -3. Run the test: - - cd test/launch-SpatialKappa - python2.7 launch-SpatialKappa.py - - This gives an indication of what methods are exposed to python. diff --git a/python/INSTALL.md b/python/INSTALL.md new file mode 100644 index 0000000..b655a51 --- /dev/null +++ b/python/INSTALL.md @@ -0,0 +1,41 @@ +Installing the Python interface to SpatialKappa +=============================================== + +0. Build the SpatialKappa jar as described in [../README.md][1] + +1. Either: + + a. Install py4j using `pip`: + + sudo apt-get install python-pip + sudo pip install py4j --upgrade + + To install in a non-standard location, use the `-t` flag, e.g.: + + pip install py4j --upgrade -t ${HOME}/lib/python2.7/site-packages/ + + b. Install py4j with python 2.7 from source: + + git clone https://github.com/davidcsterratt/py4j + cd py4j + cd py4j-java + ant jar + cd ../py4j/py4j-python + sudo python2.7 setup.py install + + +2. Install SpatialKappa python files + + sudo python2.7 setup.py install + + An alternative prefix can be specified like this: + + python2.7 setup.py install --prefix=${HOME} + +3. Run the tests: + + python2.7 -m unittest test + + This gives an indication of what methods are exposed to python. + +[1]: ../README.md diff --git a/python/LICENSE b/python/LICENSE new file mode 100644 index 0000000..6795733 --- /dev/null +++ b/python/LICENSE @@ -0,0 +1,16 @@ +This project is licenced under the GNU Lesser General Public Licence +Version 3 (LGPLv3, http://www.gnu.org/licenses/lgpl.html). + +Included third party libraries are released under their respective +licences, listed below: + +Library Home site License +Antlr v3.2 http://www.antlr.org http://www.antlr.org/license.html +jlfgr v1.0 http://java.sun.com/developer/techDocs/hi/repository/ Included in archive +commons io v1.4 http://commons.apache.org/io/ http://commons.apache.org/io/license.html +jcommon v1.0.16 http://www.jfree.org/jcommon/ http://www.gnu.org/licenses/lgpl.html +jfreechart v1.0.13 http://www.jfree.org/jfreechart/ http://www.gnu.org/licenses/lgpl.html +EasyMock v3.0 http://easymock.org/ http://easymock.org/License.html +CgLib v2.2 http://cglib.sourceforge.net/ http://www.apache.org/foundation/licence-FAQ.html +Objenesis v1.2 http://objenesis.googlecode.com/svn/docs/index.html http://objenesis.googlecode.com/svn/docs/license.html +ASM v3.3.1 http://asm.ow2.org/index.html http://asm.ow2.org/license.html \ No newline at end of file diff --git a/python/Makefile b/python/Makefile new file mode 100644 index 0000000..04e270e --- /dev/null +++ b/python/Makefile @@ -0,0 +1,12 @@ +PREFIX=/usr/local + +build_py: + cd .. ; make + python2.7 setup.py build + +test: build_py + python2.7 -m unittest tests + +install: build_py + python2.7 setup.py install --prefix=$(PREFIX) + diff --git a/python/NOTES.md b/python/NOTES.md new file mode 100644 index 0000000..14bb890 --- /dev/null +++ b/python/NOTES.md @@ -0,0 +1,54 @@ +Steps to distribute SpatialKappa python package +=============================================== + +Testing in Test PyPi repository +------------------------------- + +1. Test package +``` +python2 setup.py test +``` + +2. Build package +``` +rm -Rf build/* +rm -Rf dist/* +python setup.py sdist bdist_wheel +``` + +3. Upload to the Test PyPi repository +``` +twine upload --repository-url https://test.pypi.org/legacy/ dist/* +``` + +4. Create virtual environment https://packaging.python.org/tutorials/installing-packages/#creating-virtual-environments +``` +virtualenv SpatialKappa +source SpatialKappa/bin/activate +PYTHONPATH= +``` + +5. Test in virtual environment +``` +python -m pip install --index-url https://test.pypi.org/simple/ --extra-index-url=https://pypi.org/simple/ SpatialKappa==2.1.5 +python -m unittest SpatialKappa.tests + +``` + +Distributing in main PyPi repository +------------------------------------ + +1. Change `POSTFIX` in `setup.py` to `.rc1` (say) + +2. Test and rebuild + +``` +python2 setup.py test +rm -f dist/* +python setup.py sdist bdist_wheel +``` + +3. Upload +``` +twine upload dist/* +``` diff --git a/python/README.md b/python/README.md new file mode 100644 index 0000000..40ad092 --- /dev/null +++ b/python/README.md @@ -0,0 +1,29 @@ +SpatialKappa +============ + +14 September 2012 + +Copyright © 2012 DemonSoft.org [http://www.demonsoft.org/](http://www.demonsoft.org) + +Copyright © 2014 David C Sterratt + +INTRODUCTION +------------ + +This project contains an implementation of a syntactic extension of Kappa to allow expression of location and transport in models. A user manual is available [here][manual-link]. + +Feel free to add bug reports or feature requests [here][bug-link]. + +ACKNOWLEDGEMENTS +---------------- + +The development of Spatial Kappa was funded in part by SynthSys. SynthSys is a Centre for Integrative Systems Biology (CISB) funded by BBSRC and EPSRC, reference BB/D019621/1. + +DISCLAIMER +---------- + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +[bug-link]: https://github.com/lptolik/SpatialKappa/issues +[manual-link]: https://github.com/lptolik/SpatialKappa/raw/master/docs/manual/SpatialKappaManual-v2.1.0.pdf +[1]: python/INSTALL.md diff --git a/python/SpatialKappa/__init__.py b/python/SpatialKappa/__init__.py index 35a45bf..54695b7 100644 --- a/python/SpatialKappa/__init__.py +++ b/python/SpatialKappa/__init__.py @@ -1,26 +1,151 @@ +name = "SpatialKappa" + import py4j.java_gateway as jg import os class SpatialKappa: - """Runs gateway to SpatialKappa""" + """Runs Java gateway to SpatialKappa""" port = None gateway = None gateway_client = None - def __init__(self, classpath=''): - skjar_file = 'SpatialKappa-v2.1.1.jar' + count = 0 + "Count of SpatialKappa objects" + + def __init__(self, classpath='', redirect_stdout=None): + """Create and return a gateway to a Java object with SpatialKappa in + the classpath using py4j + + The distribution of SpatialKappa should contain the + SpatialKappa jar file, so there should be no need to download + it separately. + + Keyword arguments: + classpath -- classpath to override the default values + redirect_stdout -- Redirect stdout to this connection - see + py4j.java_gateway.launch_gateway() + + """ + + skjar_file = 'SpatialKappa-v2.1.5.jar' antlrjar_file = 'ant-antlr-3.2.jar' skjar_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'share', 'SpatialKappa', skjar_file) antlrjar_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'share', 'SpatialKappa', antlrjar_file) classpath = os.pathsep.join((classpath, skjar_path, antlrjar_path)) - print classpath - self.port = jg.launch_gateway(classpath=classpath, die_on_exit=True) + self.id = SpatialKappa.count + SpatialKappa.count = SpatialKappa.count + 1 + self.redirect_stdout = redirect_stdout + self.port = jg.launch_gateway(classpath=classpath, die_on_exit=False, redirect_stdout=self.redirect_stdout) self.gateway_client = jg.GatewayClient(port=self.port) - self.gateway = jg.JavaGateway(self.gateway_client) + self.gateway = jg.JavaGateway(self.gateway_client, auto_convert=True) + + def kappa_sim(self, time_units, verbose, seed=None): + """Create and return a SpatialKappa simulation. This is actually a Java object. + + Keyword arguments: + time_units -- Time units of the simulation. Can be "s" (seconds) + or "ms" (milliseconds) + verbose -- If True, more output is printed to the connection + specified by redirect_stdout of the parent SpatialKappa object + seed -- Random seed + + The object created has the following methods: + + MODEL BUILDING METHODS + + * loadFile(String kappaFileName) -- Load a SpatialKappa file "kappaFileName" into the simulation + + * addAgentDeclaration(String name, dict sites) -- Declare the + sites and states of an agent with name. Sites is in the format: + {'site1': ['state1', 'state2', ...], 'site2': ['state1', 'state2', ...]} + + * isAgent(String name) -- Return True if "name" has been declared as an agent + + * getAgentDeclaration(String name) -- Return agent delcaration + in same format as addAgentDeclaration() + + * addInitialValue(dict complex, int|double value) -- Set the + intial value of a complex specified using the following dict + structure in python: + + {agent_name1: {site_name1: {"l": link_name, "s": state_name}, + site_name2: {"l": link_name, "s": state_name}, ...}, + agent_name2: {site_name1: {"l": link_name, "s": state_name}, ...}, ...} + + This is equivalent to + %init: complex value + + in SpatialKappa. This function preseves the behaviour of the + effect of %init lines summing. For example + + addInitialValue({'ca': {'x': {}}}, 20) + addInitialValue({'ca': {'x': {}}}, 20) + + has the effect of adding 40 ca(x), just as would: + + %init: ca(x) 20 + %init: ca(x) 20 + + * overrideInitialValue(dict complex, int|double value) -- + The same as addInitialValue(), but overrides any existing + initial value for an complex. + + * addTransition(String label, dict leftSideAgents, dict rightSideAgents, float rate) -- + Create unidirectional transition labelled "label" with + "leftSideAgents" and "rightSideAgents" specified using format + described in setInitialValue() and propensity "rate" + + * addVariable(String name, dict complex) -- Create a variable + "name" referencing "complex" using the syntax as described + in addInitialValue() + + * addVariable(String name, float input) -- Create a + variable "name" with value "input" + + * isVariable(String name) -- Return True if "name" has been + declared as a variable + + RUNTIME METHODS + + * initialiseSim() -- Initialise the simulation + + * runUntilTime(float stepEndTime, boolean progress) -- Run + simulation from current simulation time to "stepEndTime" + (specified in "time_units" given when creating kappa_sim. + Time progress shown if "progress" is True. Initialises if + not already initialised. + + * runForTime(float dt, boolean progress) -- Run simulation + from current simulation time for another "dt" (specified in + "time_units" given when creating kappa_sim. Time progress + shown if "progress" is True. Initialises if not already + initialised. + + * getTime() -- Return time of SpatialKappa simulation + + * getVariable(String name) -- Get value of "name" + + * getVariables() -- Return variables. If "kappa_sim.verbose" + is True, print values + + * getObservation(String name) -- get Observation "name" + + * setTransitionRateOrVariable(String label, float value) -- Set rate of + Transition "label" or of Variable "label" to "value" + + * addAgent(String name, int value) -- Add "value" units of + agent "name" in its default configuration + + * addAgent(String name, double value) -- Same as the addAgent + with an int "value", but throws an error if non-int value + given - def kappa_sim(self, time_units, verbose): - ks = self.gateway.jvm.org.demonsoft.spatialkappa.api.SpatialKappaSim(time_units, verbose) + """ + ks = self.gateway.jvm.org.demonsoft.spatialkappa.api.SpatialKappaSim(time_units, verbose, seed) return ks + def __del__(self): + self.gateway_client.shutdown_gateway() + SpatialKappa.count = SpatialKappa.count - 1 diff --git a/python/SpatialKappa/share/SpatialKappa/SpatialKappa-v2.1.5.jar b/python/SpatialKappa/share/SpatialKappa/SpatialKappa-v2.1.5.jar new file mode 100755 index 0000000..140ce30 Binary files /dev/null and b/python/SpatialKappa/share/SpatialKappa/SpatialKappa-v2.1.5.jar differ diff --git a/python/SpatialKappa/share/SpatialKappa/ant-antlr-3.2.jar b/python/SpatialKappa/share/SpatialKappa/ant-antlr-3.2.jar new file mode 100644 index 0000000..171dc2f Binary files /dev/null and b/python/SpatialKappa/share/SpatialKappa/ant-antlr-3.2.jar differ diff --git a/python/SpatialKappa/tests/__init__.py b/python/SpatialKappa/tests/__init__.py new file mode 100644 index 0000000..fe9d9b9 --- /dev/null +++ b/python/SpatialKappa/tests/__init__.py @@ -0,0 +1,195 @@ +import SpatialKappa +import os +import unittest +from py4j.protocol import * + +class TestSpatialKappa(unittest.TestCase): + + def setUp(self): + self.flog = open("/tmp/SpatialKappa-test.log", "w") + self.sk = SpatialKappa.SpatialKappa(redirect_stdout=self.flog) + self.sim = self.sk.kappa_sim("ms", True) + + def test_setSeed(self): + flog1 = open("/tmp/SpatialKappa-seed-1.log", "w") + sk = SpatialKappa.SpatialKappa(redirect_stdout=flog1) + sim1 = sk.kappa_sim("ms", True, 1) + sim1.loadFile(os.path.dirname(SpatialKappa.__file__) + "/tests/caPump.ka") + sim1.runForTime(5.0, True) + + flog2 = open("/tmp/SpatialKappa-seed-2.log", "w") + sk2 = SpatialKappa.SpatialKappa(redirect_stdout=flog2) + sim2 = sk2.kappa_sim("ms", True, 1) + sim2.loadFile(os.path.dirname(SpatialKappa.__file__) + "/tests/caPump.ka") + sim2.runForTime(5.0, True) + + self.assertEqual(sim1.getVariable('ca'), sim2.getVariable('ca')) + self.assertEqual(sim1.getVariable('P-Ca'), sim2.getVariable('P-Ca')) + self.assertEqual(sim1.getVariable('P'), sim2.getVariable('P')) + + sim1 = [] + sk1 = [] + sim2 = [] + sk2 = [] + + flog3 = open("/tmp/SpatialKappa-seed-3.log", "w") + sk = SpatialKappa.SpatialKappa(redirect_stdout=flog3) + sim = sk.kappa_sim("ms", True) + sim.loadFile(os.path.dirname(SpatialKappa.__file__) + "/tests/caPump.ka") + sim.runForTime(5.0, True) + sim = [] + sk = [] + + ## flog1.close() + ## flog2.close() + ## flog3.close() + + def test_createSpatialKappa(self): + self.sk = SpatialKappa.SpatialKappa() + + def test_runForTime(self): + self.sim.loadFile(os.path.dirname(SpatialKappa.__file__) + "/tests/caPump.ka") + self.sim.runForTime(100.0, False) + self.assertEqual(self.sim.getTime(), 100.0) + + def test_addTransition(self): + self.sim.loadFile(os.path.dirname(SpatialKappa.__file__) + "/tests/caPump.ka") + self.sim.addTransition("TEST", {}, {"ca": {"x": {}}}, 0.0) + self.sim.initialiseSim() + tran = self.sim.getTransition("TEST") + self.assertEqual(str(tran), "'TEST' : [] -> [[ca(x)]] @ 0.0") + self.sim.setTransitionRateOrVariable("TEST", 100.0) + self.assertEqual(str(tran), "'TEST' : [] -> [[ca(x)]] @ 100.0") + self.sim.addTransition("TEST2", {}, {"ca": {}}, 66.0) + self.sim.initialiseSim() + tran2 = self.sim.getTransition("TEST2") + self.assertEqual(str(tran2), "'TEST2' : [] -> [[ca()]] @ 66.0") + self.sim.addTransition("TEST3", {"P": {}}, {}, 77.0) + self.sim.initialiseSim() + tran3 = self.sim.getTransition("TEST3") + self.assertEqual(str(tran3), "'TEST3' : [[P()]] -> [] @ 77.0") + + + def test_addAgent(self): + self.sim.loadFile(os.path.dirname(SpatialKappa.__file__) + "/tests/caPump.ka") + self.sim.initialiseSim() + obs = self.sim.getObservation("ca") + self.assertTrue(isinstance(obs, float)) + ## Add an integer number of Ca ions specified as a float + self.sim.addAgent('ca', 11.0) + obs2 = self.sim.getObservation("ca") + self.assertEqual(obs2, obs + 11.0) + ## Add an integer number of Ca ions specified as an integer + self.sim.addAgent('ca', 10) + obs3 = self.sim.getObservation("ca") + self.assertEqual(obs3, obs2 + 10) + + ## Somthing like this should work, but it doesn't. + ## self.assertRaises(TypeError, random.shuffle, (1,2,3)) + ## Hence the hack below + error = False + try: + self.sim.addAgent("ca", 11.5) + except Py4JJavaError: + error = True + self.assertTrue(error) + + def test_addVariable(self): + self.sim.loadFile(os.path.dirname(SpatialKappa.__file__) + "/tests/caPump.ka") + ## 1. Add agent expression + ## Create an error due to nonexistent site name + error = False + try: + self.sim.addVariable("test", {"ca": {"nonexistent_site_name": {"l": "?"}}}) + except Py4JJavaError: + error = True + self.assertTrue(error) + + ## Create an error due to nonexistent site attribute + error = False + try: + self.sim.addVariable("test", {"ca": {"x": {"nonexistent_site_attribute": "?"}}}) + except Py4JJavaError: + error = True + self.assertTrue(error) + + self.sim.addVariable('TotCa2', {"ca": {"x": {"l": "?"}}}) + self.sim.initialiseSim() + TotCa1 = self.sim.getVariable("TotCa") + TotCa2 = self.sim.getVariable("TotCa2") + self.assertEqual(TotCa1, TotCa2) + self.sim.addVariable('P-Ca2', {"ca": {"x": {"l": "1"}}, "P": {"x": {"l": "1"}}}) + PCa1 = self.sim.getVariable("P-Ca") + PCa2 = self.sim.getVariable("P-Ca2") + self.assertEqual(PCa1, PCa2) + + ## print PCa1, PCa2 + + ## 2. Add float variable + self.sim.addVariable("V", 9.0) + + # Test exception thrown with faulty Kappa file + def test_noInit(self): + self.sk = SpatialKappa.SpatialKappa() + self.sim = self.sk.kappa_sim("ms", True) + self.sim.loadFile(os.path.dirname(SpatialKappa.__file__) + "/tests/no_init.ka") + self.assertTrue(self.sim.isAgent('ca')) + self.assertFalse(self.sim.isInitialised()) + self.sim.initialiseSim() + self.assertTrue(self.sim.isInitialised()) + # error = False + # try: + # self.sim.getAgentDeclaration('ca') + # except Py4JJavaError: + # error = True + # self.assertTrue(error) + + def test_agentDeclaration(self): + self.sim.addAgentDeclaration('A', {'x': []}) + self.sim.addAgentDeclaration('B', {'x': ['a', 'b', 'c']}) + self.sim.addAgentDeclaration('C', {'x': ['a', 'b', 'c'], 'y': ['d', 'e', 'f']}) + + ## This doesn't work becuase of the type of the returned objects being Java maps + ## self.assertEqual({u'x': []}, {u'x': []}) + ## self.assertDictContainsSubset(dict(self.sim.getAgentDeclaration('A')), {u'x': []}) + self.assertEqual(list(self.sim.getAgentDeclaration('A')['x']), []) + self.assertEqual(list(self.sim.getAgentDeclaration('B')['x']), ['a', 'b', 'c']) + self.assertEqual(list(self.sim.getAgentDeclaration('C')['x']), ['a', 'b', 'c']) + self.assertEqual(list(self.sim.getAgentDeclaration('C')['y']), ['d', 'e', 'f']) + ## Expect error, since no intialisation + # try: + # self.sim.getAgentMap('A') + # except Py4JJavaError: + # error = True + # self.assertTrue(error) + + def test_getAgentDeclaration(self): + self.sim.loadFile(os.path.dirname(SpatialKappa.__file__) + "/tests/caPump.ka") + self.assertEqual(list(self.sim.getAgentDeclaration("ca")['x']), []) + + def test_overrideInitialValue(self): + self.sim.addAgentDeclaration('A', {'x': []}) + self.sim.overrideInitialValue({'A': {'x': {}}}, 1) + ## self.sim.overrideInitialValue({"ca": {"nonexistent_site_name": {"l": "?"}}}, 2) + + def test_getVariableComplex(self): + self.sim.loadFile(os.path.dirname(SpatialKappa.__file__) + "/tests/caPump.ka") + + try: + self.sim.getVariableComplex('gamma1') + except Py4JJavaError: + error = True + self.assertTrue(error) + self.sim.getVariableComplex('P-Ca') + self.sim.agentList(self.sim.getVariableComplex('P-Ca')) + self.sim.overrideInitialValue(self.sim.agentList(self.sim.getVariableComplex('P-Ca')), 10) + self.sim.initialiseSim() + self.assertEqual(self.sim.getVariable('P-Ca'), 10) + + def tearDown(self): + self.sim = [] + self.sk = [] + self.flog.close() + +if __name__ == '__main__': + unittest.main() diff --git a/python/test/launch-SpatialKappa/caPump.ka b/python/SpatialKappa/tests/caPump.ka similarity index 93% rename from python/test/launch-SpatialKappa/caPump.ka rename to python/SpatialKappa/tests/caPump.ka index 408445a..4211b20 100644 --- a/python/test/launch-SpatialKappa/caPump.ka +++ b/python/SpatialKappa/tests/caPump.ka @@ -16,4 +16,5 @@ %obs: 'ca' ca(x) %obs: 'P-Ca' ca(x!1), P(x!1) %obs: 'P' P(x) +%obs: 'TotCa' ca(x?) diff --git a/python/SpatialKappa/tests/launch-SpatialKappa/launch-SpatialKappa.py b/python/SpatialKappa/tests/launch-SpatialKappa/launch-SpatialKappa.py new file mode 100644 index 0000000..55f3693 --- /dev/null +++ b/python/SpatialKappa/tests/launch-SpatialKappa/launch-SpatialKappa.py @@ -0,0 +1,29 @@ +import SpatialKappa +import os + +sk = SpatialKappa.SpatialKappa() +sim = sk.kappa_sim("ms", True) +sim.loadFile("caPump.ka") + +sim.runForTime(0.01) +print('Time: %4.3f. %5.0f molecules of P %5.0f molecules of Ca' % (sim.getTime(), sim.getObservation("P"), sim.getObservation("ca"))) + +## Add an integer number of Ca ions (but given as a float +sim.addAgent('ca', 11.0) +print('Time: %4.3f. %5.0f molecules of P %5.0f molecules of Ca, 11 more than before' % (sim.getTime(), sim.getObservation("P"), sim.getObservation("ca"))) + +## Adding a non-integer number of ions should throw an error: +try: + sim.addAgent('ca', 11.5) +except: + print "Error was raised correctly" + +sim.runUntilTime(0.1) +print('Time: %4.3f. %5.0f molecules of P %5.0f molecules of Ca' % (sim.getTime(), sim.getObservation("P"), sim.getObservation("ca"))) + +## Add an integer number of Ca ions specified as an integer +sim.addAgent('ca', 10) + +sim.runForTime(0.01) +print('Time: %4.3f. %5.0f molecules of P %5.0f molecules of Ca' % (sim.getTime(), sim.getObservation("P"), sim.getObservation("ca"))) + diff --git a/python/test/launch-SpatialKappa/launch-SpatialKappa.py b/python/SpatialKappa/tests/launch-SpatialKappa/transition-creation.py similarity index 63% rename from python/test/launch-SpatialKappa/launch-SpatialKappa.py rename to python/SpatialKappa/tests/launch-SpatialKappa/transition-creation.py index a3a14e5..c4a2bdd 100644 --- a/python/test/launch-SpatialKappa/launch-SpatialKappa.py +++ b/python/SpatialKappa/tests/launch-SpatialKappa/transition-creation.py @@ -5,11 +5,16 @@ sim = sk.kappa_sim("ms", True) sim.loadFile("caPump.ka") -sim.runForTime(0.01) -print('Time: %4.3f. %5.0f molecules of P %5.0f molecules of Ca' % (sim.getTime(), sim.getObservation("P"), sim.getObservation("ca"))) +ca_agent = sim.getAgent("ca") +print(ca_agent) +tran = sim.addTransition("TEST", ca_agent, 0.0) -sim.runUntilTime(0.1) +sim.runForTime(100.0, False) print('Time: %4.3f. %5.0f molecules of P %5.0f molecules of Ca' % (sim.getTime(), sim.getObservation("P"), sim.getObservation("ca"))) -sim.runForTime(0.01) +sim.setTransitionRate("TEST", 100.0) +print(sim.getTransition("TEST")) + +sim.runForTime(100.0, False) print('Time: %4.3f. %5.0f molecules of P %5.0f molecules of Ca' % (sim.getTime(), sim.getObservation("P"), sim.getObservation("ca"))) + diff --git a/python/SpatialKappa/tests/no_init.ka b/python/SpatialKappa/tests/no_init.ka new file mode 100644 index 0000000..5f438cd --- /dev/null +++ b/python/SpatialKappa/tests/no_init.ka @@ -0,0 +1,2 @@ +%agent: ca(x) +%obs: 'ca' ca(x) diff --git a/python/setup.py b/python/setup.py index 338344e..ef97fd8 100644 --- a/python/setup.py +++ b/python/setup.py @@ -1,25 +1,28 @@ -from distutils.core import setup -import os -import shutil -VERSION = '2.1.1' -SKJAR_FILE = 'SpatialKappa-v' + VERSION + '.jar' -SKJAR_FILE_PATH = os.path.join('..', SKJAR_FILE) -ANTLRJAR_FILE = 'ant-antlr-3.2.jar' -ANTLRJAR_FILE_PATH = os.path.join('..', ANTLRJAR_FILE) -SHARE_DIR = 'SpatialKappa/share/SpatialKappa' -try: - os.makedirs(SHARE_DIR) -except: - pass +import setuptools -shutil.copyfile(ANTLRJAR_FILE_PATH, os.path.join(SHARE_DIR, ANTLRJAR_FILE)) -shutil.copyfile(SKJAR_FILE_PATH, os.path.join(SHARE_DIR, SKJAR_FILE)) +VERSION = '2.1.5' +POSTVERSION = '' -setup(name='SpatialKappa', - version=VERSION, - packages=['SpatialKappa'], - package_dir={'SpatialKappa': 'SpatialKappa'}, - package_data={'SpatialKappa': ['share/SpatialKappa/*']}) +with open("README.md", "r") as fh: + long_description = fh.read() -os.unlink(os.path.join(SHARE_DIR, ANTLRJAR_FILE)) -os.unlink(os.path.join(SHARE_DIR, SKJAR_FILE)) +setuptools.setup( + name="SpatialKappa", + version=VERSION + POSTVERSION, + author="Donal Stewart, Anatoly Sorokin, David C Sterratt", + author_email="david.c.sterratt@ed.ac.uk", + description="Implementation of syntactic extension of Kappa to allow expression of location and transport in models", + long_description=long_description, + long_description_content_type="text/markdown", + url="https://github.com/davidcsterratt/SpatialKappa", + packages=setuptools.find_packages(), + install_requires=['py4j'], + classifiers=( + "Programming Language :: Python :: 2", + "Programming Language :: Java", + "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", + "Operating System :: OS Independent", + ), + python_requires='<3', + package_data={'SpatialKappa': ['share/SpatialKappa/*', 'tests/*.ka']}, + test_suite='SpatialKappa.tests') diff --git a/src/java/org/demonsoft/spatialkappa/api/SpatialKappaSim.java b/src/java/org/demonsoft/spatialkappa/api/SpatialKappaSim.java index 90cb42b..9ed9fc1 100644 --- a/src/java/org/demonsoft/spatialkappa/api/SpatialKappaSim.java +++ b/src/java/org/demonsoft/spatialkappa/api/SpatialKappaSim.java @@ -7,10 +7,15 @@ import org.demonsoft.spatialkappa.tools.Simulation; import org.demonsoft.spatialkappa.model.SimulationState; import org.demonsoft.spatialkappa.model.Agent; +import org.demonsoft.spatialkappa.model.AgentLink; +import org.demonsoft.spatialkappa.model.AgentSite; +import org.demonsoft.spatialkappa.model.AggregateSite; import org.demonsoft.spatialkappa.model.AgentDeclaration; import org.demonsoft.spatialkappa.model.Observation; +import org.demonsoft.spatialkappa.model.ObservationElement; import org.demonsoft.spatialkappa.model.Complex; import org.demonsoft.spatialkappa.model.Variable; +import org.demonsoft.spatialkappa.model.Transition; import org.demonsoft.spatialkappa.model.VariableExpression; // import org.antlr.runtime.CharStream; @@ -20,19 +25,29 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.Collection; import java.util.HashMap; +import java.util.HashSet; import java.lang.IllegalArgumentException; +import java.lang.Exception; public class SpatialKappaSim { private IKappaModel kappaModel; private TransitionMatchingSimulation simulation; + private File kappaFile; private boolean verbose; public double timeMult; - public SpatialKappaSim(String timeUnits, boolean verbose) { + // Constructors + public SpatialKappaSim(String timeUnits, boolean verbose, Long seed) { + report("SpatialKappaSim(%s, %d, %d)", timeUnits, verbose, seed); + if (seed != null) { + Utils.setSeed(seed.longValue()); + } this.verbose = verbose; - Map allowedTimeUnits = new HashMap(); + Map allowedTimeUnits = new HashMap(); allowedTimeUnits.put("s" , new Double(1E-3)); allowedTimeUnits.put("ms", new Double(1.0)); if (!allowedTimeUnits.containsKey(timeUnits)) { @@ -40,29 +55,238 @@ public SpatialKappaSim(String timeUnits, boolean verbose) { throw(new IllegalArgumentException(error)); } this.timeMult = (double)allowedTimeUnits.get(timeUnits); + this.kappaModel = new KappaModel(); + this.simulation = null; } + public SpatialKappaSim(String timeUnits, boolean verbose) { + this(timeUnits, verbose, null); + } + public SpatialKappaSim() { this("ms", false); } - public void loadFile(String kappaFile) throws Exception { - File f = new File(kappaFile); - kappaModel = Utils.createKappaModel(f); - initialiseSim(); + ////////////////////////////////////////////////////////////////////// + // Helpers + ////////////////////////////////////////////////////////////////////// + + private void report(String format, Object... args) { + if (verbose) System.out.printf(format + "\n", args); } + + ////////////////////////////////////////////////////////////////////// + // Model specification + ////////////////////////////////////////////////////////////////////// + + // Agent creation + // e.g. addAgentDeclaration('Ca', {'x': ['a', 'b', 'c'], 'y': {'g', 'f', 'e'}}) is equivalent to + // Ca(x~a~b~c, y~e~f~g) - private void initialiseSim() { - try { - System.out.println("initialiseSim()"); - simulation = new TransitionMatchingSimulation(kappaModel); - } catch (Exception e) { - System.out.println("Error in initialiseSim()"); + public void addAgentDeclaration(String name, Map> sites) { + List sitesList = new ArrayList(); + for (Map.Entry> site: sites.entrySet()) { + sitesList.add(new AggregateSite(site.getKey(), site.getValue(), null)); + } + this.kappaModel.addAgentDeclaration(new AgentDeclaration(name, sitesList)); + } + + // Test if an agent is declared + public boolean isAgent(String name) { + report("isAgent(%s)", name); + return(this.kappaModel.getAgentDeclarationMap().containsKey(name)); + } + + // Get agent declaration + public Map> getAgentDeclaration(String name) { + report("getAgentDeclaration(%s)", name); + if (!isAgent(name)) { + String error = "Agent " + name + " not defined in " + this.kappaFile + "\nMake sure there are both %agent: and %init: declarations."; + throw(new IllegalArgumentException(error)); + } + AgentDeclaration ad = this.kappaModel.getAgentDeclarationMap().get(name); + Map> sitesMap = new HashMap>(); + List sites = ad.getSites(); + for (AggregateSite site: sites) { + sitesMap.put(site.getName(), site.getStates()); + } + return(sitesMap); + } + + // Initialisation methods + // Equivalent to %init: This version adds to the list of init lines + public void addInitialValue(Map>> agents, int value) { + kappaModel.addInitialValue(agentList(agents), Integer.toString(value), NOT_LOCATED); + }; + public void addInitialValue(Map>> agents, double value) { + addInitialValue(agents, (int)value); + }; + + // Not equivalent to %init: This version overrides existing init lines with matching agents + public void overrideInitialValue(List agents, int value) { + report("overrideInitialValue(, %d)", value); + kappaModel.overrideInitialValue(agents, Integer.toString(value), NOT_LOCATED); + }; + + public void overrideInitialValue(List agents, float value) { + overrideInitialValue(agents, (int)value); + }; + + + public void overrideInitialValue(Map>> agents, int value) { + overrideInitialValue(agentList(agents), value); + }; + public void overrideInitialValue(Map>> agents, double value) { + overrideInitialValue(agents, (int)value); + }; + + // Variables interface + public void addVariable(String label, float input) { + report("addVariable(%s, %f)", label, input); + kappaModel.addVariable(new VariableExpression(input), label); + } + + // This allows a variable to be set using the syntax as described in agentList() + public void addVariable(String label, Map>> agentsMap) { + report("addVariable(%s, )", label); + List agents = agentList(agentsMap); + kappaModel.addVariable(agents, label, NOT_LOCATED, false); + } + + // Test if a variable exists + public boolean isVariable(String name) { + report("isVariable(%s)", name); + return(kappaModel.getVariables().containsKey(name)); + } + + public Complex getVariableComplex(String name) throws Exception { + report("getVariableComplex(%s)", name); + if (!isVariable(name)) { + throw(new Exception(name + " is not a variable")); + } + Variable var = kappaModel.getVariables().get(name); + if (var.type != Variable.Type.KAPPA_EXPRESSION) { + throw(new Exception("Variable " + name + " is not Kappa expression")); + } + return(var.complex); + } + + // Limited version of addTransition(), just enough to make a creation rule + public void addTransition(String label, + Map>> leftSideAgents, + Map>> rightSideAgents, + float rate) { + report("addTransition(%s, , , %f)", label, rate); + List transitions = kappaModel.getTransitions(); + for (Transition transition: transitions) { + if (label.equals(transition.label)) { + String error = "Transition label \"" + label + "\" already exists"; + throw(new IllegalArgumentException(error)); + } } + kappaModel.addTransition(label, + NOT_LOCATED, agentList(leftSideAgents), + null, + NOT_LOCATED, agentList(rightSideAgents), + new VariableExpression(rate)); } - public void runUntilTime(float stepEndTime) { - simulation.runByTime2(stepEndTime*(float)timeMult); + // General function to allow an AgentList to be created programmtically. + // This allows a variable to be set using the following syntax in python: + // {agent_name1: {site_name1: {"l": link_name, "s": state_name}, site_name2: {"l": link_name, "s": state_name}, ...}, agent_name2: {site_name1: {"l": link_name, "s": state_name}, ...}, ...} + // + // e.g.: + // {"ca": {"x": {"l": "1"}}, "P": {"x": {"l": "1"}}} + public List agentList(Map>> agentsMap) { + List agents = new ArrayList(); + for (Map.Entry>> entry: agentsMap.entrySet()) { + String agentName = entry.getKey(); + Map> sites = entry.getValue(); + AgentDeclaration agentDeclaration = kappaModel.getAgentDeclarationMap().get(agentName); + Map agentDeclarationSites = new HashMap(); + for (AggregateSite ads: agentDeclaration.getSites()) { + agentDeclarationSites.put(ads.getName(), ads); + } + Agent agent = new Agent(agentDeclaration.getName()); + for (Map.Entry> site: sites.entrySet()) { + String siteName = site.getKey(); + if (!agentDeclarationSites.containsKey(siteName)) { + String error = "Agent \"" + agentName + "\" does not have a site \"" + siteName + "\""; + throw(new IllegalArgumentException(error)); + } + Map siteLinkState = site.getValue(); + for (String key: siteLinkState.keySet()) { + Set validKeys = new HashSet() {{add("l"); add("s");}}; + if (!validKeys.contains(key)) { + String error = "Agent \"" + agentName + "\" site name \"" + siteName + "\" cannot have a \" + key + \" attribute; only \"l\" and \"s\" are valid"; + throw(new IllegalArgumentException(error)); + } + } + String linkName = null; + if (siteLinkState.keySet().contains("l")) { + linkName = site.getValue().get("l"); + } + String state = null; + if (siteLinkState.keySet().contains("s")) { + state = siteLinkState.get("s"); + if (!agentDeclarationSites.get(siteName).getStates().contains(state)) { + String error = "Agent \"" + agentName + "\" site name \"" + siteName + "\" does not have state " + state; + throw(new IllegalArgumentException(error)); + + } + } + AgentSite agentSite = new AgentSite(siteName, state, linkName); + agent.addSite(agentSite); + + } + agents.add(agent); + } + return(agents); + } + + public List agentList(Complex complex) { + return(complex.agents); + } + + public void loadFile(String kappaFileName) throws Exception { + report("loadFile(%s)", kappaFileName); + kappaFile = new File(kappaFileName); + kappaModel = Utils.createKappaModel(kappaFile); + } + + public void initialiseSim() { + report("initialiseSim()"); + simulation = new TransitionMatchingSimulation(kappaModel); + } + + public boolean isInitialised() { + report("isIntialised()"); + return(simulation != null); + } + + // General Accessor methods + + // Get the time in user units + public double getTime() { + report("getTime()"); + return(simulation.getTime()/timeMult); + } + + + ////////////////////////////////////////////////////////////////////// + // Model simulation + ////////////////////////////////////////////////////////////////////// + + // Run methods + // stepEndTime is provided in user units + public void runUntilTime(double stepEndTime, boolean progress) throws Exception { + if (simulation == null) { initialiseSim(); }; + try { + simulation.runByTime2(stepEndTime*timeMult, progress); + } + catch (Exception ex) { + throw new Exception("Problem running simulation", ex); + } if (verbose) { // This allows us to get the value of a particular observable Observation observation = simulation.getCurrentObservation(); @@ -70,83 +294,181 @@ public void runUntilTime(float stepEndTime) { } } - public void runForTime(float dt) { - float stepEndTime = getTime() + dt; - runUntilTime(stepEndTime); + // test_runForTime + // dt is provided in user units + public void runForTime(double dt, boolean progress) throws Exception { + report("runForTime(%f)", dt); + if (simulation == null) { initialiseSim(); }; + double stepEndTime = getTime() + dt; + try { + runUntilTime(stepEndTime, progress); + } catch (Exception ex) { + throw(new Exception("Problem running simulation", ex)); + } } + public double getVariable(String variableName) throws Exception { + report("getVariable(%s)", variableName); + if (simulation == null) { + throw(new Exception("Simulation is not initialised, so not possble to get value of variable '" + variableName + "'")); + } + Variable variable = kappaModel.getVariables().get(variableName); + ObservationElement observable = variable.evaluate(simulation); + return(observable.value); + } + public Map getVariables() { Map variables = kappaModel.getVariables(); - for (Map.Entry variable : variables.entrySet()) { - System.out.println("Key = " + variable.getKey() + ", Value = " + variable.getValue()); - } return(variables); } - - public void setVariable(float input, String label) { - kappaModel.addVariable(new VariableExpression(input), label); - initialiseSim(); - } - - public float getTime() { - return(simulation.getTime()/(float)timeMult); - } - + + // Observation interface public double getObservation(String key) { Observation observation = simulation.getCurrentObservation(); return(observation.observables.get(key).value); } - - // value can be negative - public void addAgent(String key, double value) { - List agents = new ArrayList(); - SimulationState state = (SimulationState) simulation; - for (Complex complex : kappaModel.getFixedLocatedInitialValuesMap().keySet()) { - for (Agent currentAgent : complex.agents) { - // if (verbose) { System.out.println(currentAgent.name); } - if (key.equals(currentAgent.name)) { - // if (verbose) { System.out.println("ADD STUFF"); } - agents.add(currentAgent); - state.addComplexInstances(agents, (int)value); - agents.clear(); + + + // Transition interface + public Transition getTransition(String label) { + if (simulation == null) { + for (Transition transition: kappaModel.getTransitions()) { + if (label.equals(transition.label)) { + return transition; } } + return (Transition) null; } + return simulation.getTransition(label); } - - public void setAgentInitialValue(String key, double value) { - List agents = new ArrayList(); - for (Complex complex : kappaModel.getFixedLocatedInitialValuesMap().keySet()) { - for (Agent currentAgent : complex.agents) { - if (key.equals(currentAgent.name)) { - if (verbose) { - System.out.println("Set number of " + currentAgent.name + " to " + value); - } - agents.add(currentAgent); - kappaModel.overrideInitialValue(agents, Integer.toString((int)value), NOT_LOCATED); - agents.clear(); - } - } + + public void setTransitionRateOrVariable(String label, float rate) throws Exception { + report("setTransitionRateOrVariable(%s, %f)", label, rate); + if (!isInitialised()) { + throw(new Exception("Simulation not initalised. Initialise using initialSim()")); } - initialiseSim(); - System.out.println("Number of " + key + " is " + getObservation(key)); + simulation.setTransitionRateOrVariable(label, new VariableExpression(rate)); } - private void getFixedLocatedInitialValuesMap() { - for (Map.Entry result : kappaModel.getFixedLocatedInitialValuesMap().entrySet()) { - System.out.println("Key = " + result.getKey() + ", Value = " + result.getValue()); + // Agent interface + // value can be negative + public void addAgent(String key, int value) { + List agents = new ArrayList(); + SimulationState state = (SimulationState) simulation; + Agent agent = getAgent(key); + agents.add(agent); + state.addComplexInstances(agents, value); + agents.clear(); + } + + public void addAgent(String key, double value) { + int ivalue = (int)value; + if (ivalue != value) { + String error = "Trying to add non-integer number (" + value + ") of \'" + key + "\' agents"; + throw(new IllegalArgumentException(error)); } + addAgent(key, (int)value); } + + // Printing methods @Override public String toString() { return(kappaModel.toString()); } + public void printFixedLocatedInitialValuesMap() { + for (Map.Entry result : kappaModel.getFixedLocatedInitialValuesMap().entrySet()) { + System.out.println("Key = " + result.getKey() + ", Value = " + result.getValue()); + } + } + public void printAgentNames() { List agentNames = new ArrayList(kappaModel.getAgentDeclarationMap().keySet()); for(String agentName : agentNames) { System.out.println(agentName + " "); } } + + public String printAgentAgentInteractions() { + Map> interactions = new HashMap>(); + for (Transition transition : kappaModel.getTransitions()) { + List allComplexes = new ArrayList(); + allComplexes.addAll(transition.sourceComplexes); + allComplexes.addAll(transition.targetComplexes); + for (Complex complex: allComplexes) { + for (Agent agent: complex.agents) { + if (!interactions.containsKey(agent.name)) { + interactions.put(agent.name, new HashSet()); + } + for (Agent coagent: complex.agents) { + if (agent.name != coagent.name) { + interactions.get(agent.name).add(coagent.name); + } + } + } + } + } + return interactions.toString(); + } + + public String getDebugOutput() { + return(simulation.getDebugOutput()); + } + + // FIXME: Pending removal + // Get an agent + private Agent getAgent(String name) { + for (Complex complex : kappaModel.getFixedLocatedInitialValuesMap().keySet()) { + for (Agent currentAgent : complex.agents) { + if (name.equals(currentAgent.name)) { + return(currentAgent); + } + } + } + String error = "Agent " + name + " not defined in " + this.kappaFile + "\nMake sure there are both %agent: and %init: declarations."; + throw(new IllegalArgumentException(error)); + } + + private Map>> getAgentMap(Agent agent) { + Map>> agentMap = new HashMap>>(); + Map> siteMap = new HashMap>(); + Collection sites = agent.getSites(); + for (AgentSite site : sites) { + Map siteLinkState = new HashMap(); + if (site.getLinkName() != null) { + siteLinkState.put("l", site.getLinkName()); + } + if (site.getState() != null) { + siteLinkState.put("s", site.getState()); + } + siteMap.put(site.name, siteLinkState); + } + agentMap.put(agent.name, siteMap); + return(agentMap); + } + + public void setAgentInitialValue(String key, int value) { + List agents = new ArrayList(); + Agent agent = getAgent(key); + if (agent != null) { + + agents.add(agent); + overrideInitialValue(agents, value); + agents.clear(); + } + } + + public void setAgentInitialValue(String key, double value) { + setAgentInitialValue(key, (int)value); + } + + private Map>> getAgentMap(String agentName) { + return(getAgentMap(getAgent(agentName))); + } + + public void setSeed(long seed) throws Exception { + String error = "setSeed() is deprecated. Instead call SpatialKappaSim(String timeUnits, boolean verbose, Long seed) with the seed argument" ; + throw(new Exception(error)); + } } diff --git a/src/java/org/demonsoft/spatialkappa/model/SimulationState.java b/src/java/org/demonsoft/spatialkappa/model/SimulationState.java index bb4d674..4e6b01f 100644 --- a/src/java/org/demonsoft/spatialkappa/model/SimulationState.java +++ b/src/java/org/demonsoft/spatialkappa/model/SimulationState.java @@ -5,9 +5,9 @@ public interface SimulationState { - float getTime(); - float getElapsedTime(); - float getMaximumTime(); + double getTime(); + double getElapsedTime(); + double getMaximumTime(); int getEventCount(); int getMaximumEventCount(); diff --git a/src/java/org/demonsoft/spatialkappa/model/Transition.java b/src/java/org/demonsoft/spatialkappa/model/Transition.java index 2b4f867..3e193bd 100644 --- a/src/java/org/demonsoft/spatialkappa/model/Transition.java +++ b/src/java/org/demonsoft/spatialkappa/model/Transition.java @@ -747,6 +747,12 @@ public int getApplicationCount(List sourceComplexMappings, List< } } } + + if (bestPrimitives == null) { + System.out.println("Transition.getApplicationCount(): bestPrimitives == null; Transition:"); + System.out.println(this.toString()); + return 0; + } int applicationCount = 1; for (TransitionPrimitive primitive : bestPrimitives) { diff --git a/src/java/org/demonsoft/spatialkappa/model/TransitionPrimitive.java b/src/java/org/demonsoft/spatialkappa/model/TransitionPrimitive.java index c1fa472..9cf59cc 100644 --- a/src/java/org/demonsoft/spatialkappa/model/TransitionPrimitive.java +++ b/src/java/org/demonsoft/spatialkappa/model/TransitionPrimitive.java @@ -2,6 +2,7 @@ import static org.demonsoft.spatialkappa.model.Utils.getChannel; import static org.demonsoft.spatialkappa.model.Utils.getFlatString; +import org.demonsoft.spatialkappa.model.Utils; import java.util.ArrayList; import java.util.HashSet; @@ -374,7 +375,7 @@ public boolean apply(Map transformMap, List targetComplex newLocation = newLocations.get(0); } else { - int item = (int) (newLocations.size() * Math.random()); + int item = (int) (newLocations.size() * Utils.random()); newLocation = newLocations.get(item); } @@ -413,7 +414,7 @@ else if (newLocationLists.size() == 1) { newLocations = newLocationLists.get(0); } else { - int item = (int) (newLocationLists.size() * Math.random()); + int item = (int) (newLocationLists.size() * Utils.random()); newLocations = newLocationLists.get(item); } @@ -498,4 +499,4 @@ public int getApplicationCount(Map transformMap, List tar return 1; } -} \ No newline at end of file +} diff --git a/src/java/org/demonsoft/spatialkappa/model/Utils.java b/src/java/org/demonsoft/spatialkappa/model/Utils.java index a55a39b..02921b6 100644 --- a/src/java/org/demonsoft/spatialkappa/model/Utils.java +++ b/src/java/org/demonsoft/spatialkappa/model/Utils.java @@ -13,6 +13,7 @@ import java.util.ListIterator; import java.util.Map; import java.util.Stack; +import java.util.Random; import org.antlr.runtime.ANTLRInputStream; import org.antlr.runtime.CommonTokenStream; @@ -296,4 +297,17 @@ private static IKappaModel createKappaModel(InputStream inputStream, String erro return walker.prog(); } + private static Random r = new Random(); + + public static double random() { + double n = r.nextDouble(); + // FIXME: Random seed not always working? + // System.out.println(n); + return(n); + } + + public static void setSeed(long seed) { + r.setSeed(seed); + } + } diff --git a/src/java/org/demonsoft/spatialkappa/model/VariableExpression.java b/src/java/org/demonsoft/spatialkappa/model/VariableExpression.java index b1b1c56..cae798a 100644 --- a/src/java/org/demonsoft/spatialkappa/model/VariableExpression.java +++ b/src/java/org/demonsoft/spatialkappa/model/VariableExpression.java @@ -487,16 +487,16 @@ public ObservationElement evaluate(SimulationState state) { return new ObservationElement(state.getEventCount()); case TIME: - return new ObservationElement(state.getTime()); + return new ObservationElement((float)state.getTime()); case MAX_TIME: - return new ObservationElement(state.getMaximumTime()); + return new ObservationElement((float)state.getMaximumTime()); case MAX_EVENTS: return new ObservationElement(state.getMaximumEventCount()); case ELAPSED_TIME: - return new ObservationElement(state.getElapsedTime()); + return new ObservationElement((float)state.getElapsedTime()); } break; diff --git a/src/java/org/demonsoft/spatialkappa/tools/RecordSimulation.java b/src/java/org/demonsoft/spatialkappa/tools/RecordSimulation.java index 7e3f304..f66bccc 100644 --- a/src/java/org/demonsoft/spatialkappa/tools/RecordSimulation.java +++ b/src/java/org/demonsoft/spatialkappa/tools/RecordSimulation.java @@ -42,12 +42,12 @@ public void runByEvent(int steps, int stepSize) { simulation.runByEvent(steps, stepSize); } - public void runByTime(float steps, float stepSize) { + public void runByTime(double steps, double stepSize) { simulation.runByTime(steps, stepSize); } - public void runByTime2(float stepEndTime) { - simulation.runByTime2(stepEndTime); + public void runByTime2(double stepEndTime, boolean progress) throws Exception { + simulation.runByTime2(stepEndTime, progress); } diff --git a/src/java/org/demonsoft/spatialkappa/tools/Simulation.java b/src/java/org/demonsoft/spatialkappa/tools/Simulation.java index 2bfd8aa..542b041 100644 --- a/src/java/org/demonsoft/spatialkappa/tools/Simulation.java +++ b/src/java/org/demonsoft/spatialkappa/tools/Simulation.java @@ -13,9 +13,9 @@ public interface Simulation { void runByEvent(int steps, int stepSize); - void runByTime(float steps, float stepSize); + void runByTime(double steps, double stepSize); - void runByTime2(float stepEndTime); + void runByTime2(double stepEndTime, boolean progress) throws Exception; void stop(); diff --git a/src/java/org/demonsoft/spatialkappa/tools/TransitionMatchingSimulation.java b/src/java/org/demonsoft/spatialkappa/tools/TransitionMatchingSimulation.java index 84620f9..3627848 100644 --- a/src/java/org/demonsoft/spatialkappa/tools/TransitionMatchingSimulation.java +++ b/src/java/org/demonsoft/spatialkappa/tools/TransitionMatchingSimulation.java @@ -5,6 +5,7 @@ import static org.demonsoft.spatialkappa.model.Utils.getCompartment; import static org.demonsoft.spatialkappa.model.Utils.getComplexes; import static org.demonsoft.spatialkappa.model.Utils.getList; +import org.demonsoft.spatialkappa.model.Utils; import java.io.File; import java.io.IOException; @@ -67,14 +68,14 @@ public class TransitionMatchingSimulation implements Simulation, SimulationState private boolean stop = false; private boolean noTransitionsPossible = false; - private float time = 0; + private double time = 0; private long startTime; int eventCount = 0; private final IKappaModel kappaModel; private final List observationListeners = new ArrayList(); private final ComplexMatcher matcher = new ComplexMatcher(); - private float maximumTime; + private double maximumTime; private int maximumEventCount; private boolean verbose = false; @@ -176,7 +177,7 @@ public void runByEvent(int steps, int eventsPerStep) { notifyObservationListeners(true, 1); } - public void runByTime(float totalTime, float timePerStep) { + public void runByTime(double totalTime, double timePerStep) { startTime = Calendar.getInstance().getTimeInMillis(); stop = false; maximumTime = totalTime; @@ -184,7 +185,7 @@ public void runByTime(float totalTime, float timePerStep) { do { resetTransitionsFiredCount(); - float stepEndTime = getNextEndTime(time, timePerStep); + double stepEndTime = getNextEndTime(time, timePerStep); while (time < stepEndTime && !noTransitionsPossible && !stop) { int clashes = 0; while (!runSingleEvent() && clashes < 1000 && !noTransitionsPossible && !stop) { @@ -196,42 +197,61 @@ public void runByTime(float totalTime, float timePerStep) { System.out.println("Aborted timepoint"); } } - notifyObservationListeners(false, time / totalTime); + notifyObservationListeners(false, (float)(time / totalTime)); } while (!noTransitionsPossible && !stop && time < totalTime); notifyObservationListeners(true, 1); } - - public void runByTime2(float stepEndTime) { + + public void runByTime2(double stepEndTime, boolean progress) throws Exception { if (verbose) { - System.out.println("runByTime2. time = " + time + " ; stepEndTime = " + stepEndTime); + System.out.println("\rTime = " + time + " / " + stepEndTime + " [" + time/stepEndTime*100 + "%]"); } startTime = Calendar.getInstance().getTimeInMillis(); stop = false; maximumTime = stepEndTime; maximumEventCount = 0; - + + // Time at start of step + double time0 = time; + // Local time in step + double ltime = 0; + // Local step end time + double lStepEndTime = stepEndTime - time0; do { - if (verbose) { - System.out.println("runByTime2: resetTransitionsFiredCount()"); - } - resetTransitionsFiredCount(); - while (time < stepEndTime && !noTransitionsPossible && !stop) { - float nextEventTime = time + getTimeDelta(); + // DCS: 2019-05-05: This appears to be soley for reporting + // if (verbose) { + // System.out.println("runByTime2: resetTransitionsFiredCount()"); + // } + // resetTransitionsFiredCount(); + while (ltime < lStepEndTime && !noTransitionsPossible && !stop) { + if (progress) { + System.out.format("\rTime = %12.5f/%5.5f [%3.3f%%]", time, stepEndTime, time/stepEndTime*100); + } + double dt = getTimeDelta(); + if ((Math.log10(ltime) - Math.log10(dt)) > 12.0) { + throw new Exception("dt is more than 12 orders of magnitude smaller than local time."); + } + double lNextEventTime = ltime + dt; if (verbose) { - System.out.println("runByTime2: nextEventTime = " + nextEventTime); + System.out.printf("runByTime2: local nextEventTime = %f; t1 -t0 = %f\n", lNextEventTime, lNextEventTime - ltime); } - if (nextEventTime > stepEndTime) { + if (lNextEventTime > lStepEndTime) { time = stepEndTime; notifyObservationListeners(true, 1); + if (progress) { + System.out.format("\rTime = %12.5f/%5.5f [%3.3f%%]\n", time, stepEndTime, 100.0); + System.out.flush(); + } return; } int clashes = 0; - while (!runSingleEvent2(nextEventTime) && clashes < 1000 && !noTransitionsPossible && !stop) { + while (!runSingleEvent2(time0, lNextEventTime) && clashes < 1000 && !noTransitionsPossible && !stop) { // repeat clashes++; Thread.yield(); } + ltime = ltime + dt; if (verbose) { System.out.println("Event happened. time = " + time); } @@ -244,8 +264,8 @@ public void runByTime2(float stepEndTime) { notifyObservationListeners(true, 1); } - float getNextEndTime(float currentTime, float timePerStep) { - int eventsSoFar = Math.round(currentTime / timePerStep); + double getNextEndTime(double currentTime, double timePerStep) { + long eventsSoFar = Math.round(currentTime / timePerStep); return timePerStep * (eventsSoFar + 1); } @@ -273,18 +293,18 @@ public int getEventCount() { return eventCount; } - public float getTime() { + public double getTime() { return time; } - public float getElapsedTime() { + public double getElapsedTime() { if (startTime == 0) { return 0; } return (Calendar.getInstance().getTimeInMillis() - startTime) / 1000f; } - public float getMaximumTime() { + public double getMaximumTime() { return maximumTime; } @@ -306,7 +326,7 @@ private Observation getCurrentObservation(boolean finalEvent, float progress) { } long elapsedTime = Calendar.getInstance().getTimeInMillis() - startTime; long estimatedRemainingTime = ((long) (elapsedTime / progress)) - elapsedTime; - return new Observation(time, eventCount, kappaModel.getPlottedVariables(), result, finalEvent, elapsedTime, estimatedRemainingTime); + return new Observation((float)time, eventCount, kappaModel.getPlottedVariables(), result, finalEvent, elapsedTime, estimatedRemainingTime); } private boolean runSingleEvent() { @@ -317,7 +337,7 @@ private boolean runSingleEvent() { return applyFiniteRateTransition(); } - private boolean runSingleEvent2(float transitionTime) { + private boolean runSingleEvent2(double time0, double transitionTime) { if (verbose) { System.out.println("runSingleEvent2: transitionTime = " + transitionTime); } @@ -331,9 +351,10 @@ private boolean runSingleEvent2(float transitionTime) { return false; } if (applyTransition(transition, false)) { - time = transitionTime; + time = time0 + transitionTime; if (verbose) { - System.out.print("runSingleEvent2: Applying transition. time = " + time + "\n"); + System.out.print("runSingleEvent2: Applying transition. local time = " + transitionTime + "\n"); + System.out.print("runSingleEvent2: Applying transition. global time = " + time + "\n"); } return true; } else { @@ -380,15 +401,16 @@ private void applyPerturbations() { } } - private float getTimeDelta() { + private double getTimeDelta() { float totalQuantity = 0; for (Float current : finiteRateTransitionActivityMap.values()) { totalQuantity += current; } + double dt = -Math.log(Utils.random()) / totalQuantity; if (verbose) { - System.out.println("getTimeDelta: totalQuantity = " + totalQuantity); + System.out.printf("getTimeDelta: totalQuantity = %f; dt = %f\n", totalQuantity, dt); } - return (float) -Math.log(Math.random()) / totalQuantity; + return dt; } private Transition pickFiniteRateTransition() { @@ -400,7 +422,7 @@ private Transition pickFiniteRateTransition() { totalQuantity += entry.getValue(); } Transition lastTransition = null; - float item = (float) (totalQuantity * Math.random()); + float item = (float) (totalQuantity * Utils.random()); for (Map.Entry entry : finiteRateTransitionActivityMap.entrySet()) { if (entry.getValue() > 0) { lastTransition = entry.getKey(); @@ -425,7 +447,7 @@ private Transition pickInfiniteRateTransition() { totalCount++; } } - float item = (float) (totalCount * Math.random()); + float item = (float) (totalCount * Utils.random()); for (Map.Entry entry : infiniteRateTransitionActivityMap.entrySet()) { if (entry.getValue() && item <= 1) { return entry.getKey(); @@ -616,7 +638,7 @@ private Map getCountsPerAgent() { return result; } - private Transition getTransition(String label) { + public Transition getTransition(String label) { for (Transition transition : getAllTransitions()) { if (label.equals(transition.label)) { return transition; @@ -768,6 +790,10 @@ public String getDebugOutput() { for (Map.Entry entry : getCountsPerAgent().entrySet()) { builder.append(entry.getValue() + "\t" + entry.getKey() + "\n"); } + builder.append("\nTransitions fired:" + "\n"); + for (Map.Entry entry : transitionsFiredMap.entrySet()) { + builder.append(entry.getKey().toString() + "\t" + entry.getValue() + "\n"); + } return builder.toString(); } @@ -878,7 +904,7 @@ TransitionInstance pickTransitionInstance(Transition transition) { } TransitionInstance lastInstance = null; - float randomValue = (float) (totalTransitionRate * Math.random()); + float randomValue = (float) (totalTransitionRate * Utils.random()); for (TransitionInstance transitionInstance : transitionInstances) { float rate = infiniteRate ? transitionInstance.activity : transitionInstance.totalRate; if (rate > 0) { diff --git a/src/java/org/demonsoft/spatialkappa/tools/Version.java b/src/java/org/demonsoft/spatialkappa/tools/Version.java index 287e7fe..4f361ce 100644 --- a/src/java/org/demonsoft/spatialkappa/tools/Version.java +++ b/src/java/org/demonsoft/spatialkappa/tools/Version.java @@ -2,7 +2,7 @@ public class Version { - public static final String VERSION = "2.1.1"; - public static final String BUILD_NUMBER = "20121210"; + public static final String VERSION = "2.1.4"; + public static final String BUILD_NUMBER = "20190409"; } diff --git a/src/java/org/demonsoft/spatialkappa/ui/SpatialKappaSimulator.java b/src/java/org/demonsoft/spatialkappa/ui/SpatialKappaSimulator.java index e19c6da..435db9c 100644 --- a/src/java/org/demonsoft/spatialkappa/ui/SpatialKappaSimulator.java +++ b/src/java/org/demonsoft/spatialkappa/ui/SpatialKappaSimulator.java @@ -430,8 +430,8 @@ public void run() { simulation.runByEvent(steps, stepSize); } else { - float steps = ((Double) toolbarSpinnerModelSteps.getValue()).floatValue(); - float stepSize = ((Double) toolbarSpinnerModelStepSize.getValue()).floatValue(); + double steps = ((Double) toolbarSpinnerModelSteps.getValue()).doubleValue(); + double stepSize = ((Double) toolbarSpinnerModelStepSize.getValue()).doubleValue(); simulation.runByTime(steps, stepSize); } } diff --git a/test/java/org/demonsoft/spatialkappa/tools/RecordSimulationTest.java b/test/java/org/demonsoft/spatialkappa/tools/RecordSimulationTest.java index 3d3e1f4..8cadcba 100644 --- a/test/java/org/demonsoft/spatialkappa/tools/RecordSimulationTest.java +++ b/test/java/org/demonsoft/spatialkappa/tools/RecordSimulationTest.java @@ -174,6 +174,11 @@ public void stop() { public String getDebugOutput() { return null; } + + public void runByTime2(float stepEndTime) { + // TODO Auto-generated method stub + + } }