Initial commit

This commit is contained in:
Yentl Van Tendeloo 2016-08-04 17:38:43 +02:00
commit 66a6860316
407 changed files with 1254365 additions and 0 deletions

10
.gitignore vendored Normal file
View file

@ -0,0 +1,10 @@
*.aux
*.bbl
*.blg
*.log
*.out
*.tmp
*-converted-to.pdf
*.dms
*.pyc
*build*

203
LICENSE Normal file
View file

@ -0,0 +1,203 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

14
NOTICE Normal file
View file

@ -0,0 +1,14 @@
Copyright 2014 Modelling, Simulation and Design Lab (MSDL) at
McGill University and the University of Antwerp (http://msdl.cs.mcgill.ca/)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

39
build.sh Executable file
View file

@ -0,0 +1,39 @@
#!/bin/bash
###########
# Creates a package of PyPDEVS: strips of most useless data and tars it all up
###########
mkdir pypdevs
cp notes.txt pypdevs/releasenotes.txt
cp install_mpi4py.sh pypdevs/install_mpi4py.sh
cd doc/sphinx
rm -r _build/html
make html
./rewrite_documentation.sh
cd ../..
cp -R doc/sphinx/_build/html/ pypdevs/doc
cp -R src/ pypdevs/
cp -R examples/ pypdevs/
rm test/output/*
cp -R test/ pypdevs/
cp LICENSE pypdevs/
cp NOTICE pypdevs/
mkdir pypdevs/tests/output
rm pypdevs/src/pypdevs/*.pyc
rm pypdevs/examples/*/*.pyc
rm pypdevs/examples/*/*.pyo
rm pypdevs/src/pypdevs/*.pyo
rm pypdevs/src/pypdevs/*/*.pyc
rm pypdevs/src/pypdevs/*/*.pyo
rm pypdevs/tests/tests/*.pyc
rm pypdevs/tests/tests/*.pyo
rm pypdevs/tests/expected/normal_long
rm pypdevs/tests/expected/checkpoint
rm pypdevs/*.pyc
rm pypdevs/*.pyo
rm -R pypdevs/src/build
rm -R pypdevs/src/__pycache__
rm -R pypdevs/src/pypdevs/__pycache__
tar -czf pypdevs.tgz pypdevs
rm -R pypdevs

22
doc/ADEVS.rst Normal file
View file

@ -0,0 +1,22 @@
..
Copyright 2014 Modelling, Simulation and Design Lab (MSDL) at
McGill University and the University of Antwerp (http://msdl.cs.mcgill.ca/)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
AtomicDEVS User Interface
=========================
.. autoclass:: DEVS.AtomicDEVS
:members: __init__, extTransition, intTransition, confTransition, outputFnc, timeAdvance, simSettings, addInPort, addOutPort, removePort, modelTransition, preActivityCalculation, postActivityCalculation
:noindex:

20
doc/ADEVS_int.rst Normal file
View file

@ -0,0 +1,20 @@
..
Copyright 2014 Modelling, Simulation and Design Lab (MSDL) at
McGill University and the University of Antwerp (http://msdl.cs.mcgill.ca/)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
AtomicDEVS
==========
.. autoclass:: DEVS.AtomicDEVS
:members:

21
doc/BaseDEVS_int.rst Normal file
View file

@ -0,0 +1,21 @@
..
Copyright 2014 Modelling, Simulation and Design Lab (MSDL) at
McGill University and the University of Antwerp (http://msdl.cs.mcgill.ca/)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
BaseDEVS
========
.. autoclass:: DEVS.BaseDEVS
:members:

22
doc/CDEVS.rst Normal file
View file

@ -0,0 +1,22 @@
..
Copyright 2014 Modelling, Simulation and Design Lab (MSDL) at
McGill University and the University of Antwerp (http://msdl.cs.mcgill.ca/)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
CoupledDEVS User Interface
==========================
.. autoclass:: DEVS.CoupledDEVS
:members: __init__, simSettings, addInPort, addOutPort, select, removePort, addSubModel, removeSubModel, connectPorts, disconnectPorts, modelTransition
:noindex:

21
doc/CDEVS_int.rst Normal file
View file

@ -0,0 +1,21 @@
..
Copyright 2014 Modelling, Simulation and Design Lab (MSDL) at
McGill University and the University of Antwerp (http://msdl.cs.mcgill.ca/)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
CoupledDEVS
===========
.. autoclass:: DEVS.CoupledDEVS
:members:

25
doc/DEVS.rst Normal file
View file

@ -0,0 +1,25 @@
..
Copyright 2014 Modelling, Simulation and Design Lab (MSDL) at
McGill University and the University of Antwerp (http://msdl.cs.mcgill.ca/)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
DEVS User Interface
===================
.. automodule:: DEVS
.. toctree::
Atomic DEVS <ADEVS>
Coupled DEVS <CDEVS>

27
doc/DEVS_int.rst Normal file
View file

@ -0,0 +1,27 @@
..
Copyright 2014 Modelling, Simulation and Design Lab (MSDL) at
McGill University and the University of Antwerp (http://msdl.cs.mcgill.ca/)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
DEVS models
===========
.. automodule:: DEVS
.. toctree::
Base DEVS <BaseDEVS_int>
Atomic DEVS <ADEVS_int>
Coupled DEVS <CDEVS_int>
Root DEVS <RootDEVS_int>

154
doc/Makefile Normal file
View file

@ -0,0 +1,154 @@
# Makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
#SPHINXBUILD = sphinx-build
SPHINXBUILD = python -m sphinx
PAPER =
BUILDDIR = _build
# Internal variables.
PAPEROPT_a4 = -D latex_paper_size=a4
PAPEROPT_letter = -D latex_paper_size=letter
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
# the i18n builder cannot share the environment and doctrees with the others
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
help:
@echo "Please use \`make <target>' where <target> is one of"
@echo " html to make standalone HTML files"
@echo " dirhtml to make HTML files named index.html in directories"
@echo " singlehtml to make a single large HTML file"
@echo " pickle to make pickle files"
@echo " json to make JSON files"
@echo " htmlhelp to make HTML files and a HTML help project"
@echo " qthelp to make HTML files and a qthelp project"
@echo " devhelp to make HTML files and a Devhelp project"
@echo " epub to make an epub"
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
@echo " latexpdf to make LaTeX files and run them through pdflatex"
@echo " text to make text files"
@echo " man to make manual pages"
@echo " texinfo to make Texinfo files"
@echo " info to make Texinfo files and run them through makeinfo"
@echo " gettext to make PO message catalogs"
@echo " changes to make an overview of all changed/added/deprecated items"
@echo " linkcheck to check all external links for integrity"
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
clean:
-rm -rf $(BUILDDIR)/*
html:
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
dirhtml:
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
singlehtml:
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
@echo
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
pickle:
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
@echo
@echo "Build finished; now you can process the pickle files."
json:
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
@echo
@echo "Build finished; now you can process the JSON files."
htmlhelp:
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
@echo
@echo "Build finished; now you can run HTML Help Workshop with the" \
".hhp project file in $(BUILDDIR)/htmlhelp."
qthelp:
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
@echo
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/PythonPDEVS.qhcp"
@echo "To view the help file:"
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/PythonPDEVS.qhc"
devhelp:
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
@echo
@echo "Build finished."
@echo "To view the help file:"
@echo "# mkdir -p $$HOME/.local/share/devhelp/PythonPDEVS"
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/PythonPDEVS"
@echo "# devhelp"
epub:
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
@echo
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
latex:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
@echo "Run \`make' in that directory to run these through (pdf)latex" \
"(use \`make latexpdf' here to do that automatically)."
latexpdf:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo "Running LaTeX files through pdflatex..."
$(MAKE) -C $(BUILDDIR)/latex all-pdf
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
text:
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
@echo
@echo "Build finished. The text files are in $(BUILDDIR)/text."
man:
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
@echo
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
texinfo:
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@echo
@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
@echo "Run \`make' in that directory to run these through makeinfo" \
"(use \`make info' here to do that automatically)."
info:
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@echo "Running Texinfo files through makeinfo..."
make -C $(BUILDDIR)/texinfo info
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
gettext:
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
@echo
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
changes:
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
@echo
@echo "The overview file is in $(BUILDDIR)/changes."
linkcheck:
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
@echo
@echo "Link check complete; look for any errors in the above output " \
"or in $(BUILDDIR)/linkcheck/output.txt."
doctest:
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
@echo "Testing of doctests in the sources finished, look at the " \
"results in $(BUILDDIR)/doctest/output.txt."

20
doc/RootDEVS_int.rst Normal file
View file

@ -0,0 +1,20 @@
..
Copyright 2014 Modelling, Simulation and Design Lab (MSDL) at
McGill University and the University of Antwerp (http://msdl.cs.mcgill.ca/)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
RootDEVS
=========
.. autoclass:: DEVS.RootDEVS
:members:

BIN
doc/activity.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

100
doc/activity.rst Normal file
View file

@ -0,0 +1,100 @@
..
Copyright 2014 Modelling, Simulation and Design Lab (MSDL) at
McGill University and the University of Antwerp (http://msdl.cs.mcgill.ca/)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Activity Tracking
=================
.. note:: This feature is still being worked on and all information is therefore prone to change.
*Activity Tracking* is a feature that will have the simulator time the invocation of each user-defined function, being the *intTransition*, *extTransition*, *confTransition*, *outputFnc* and *timeAdvance* functions. At the end, all this timing information is gathered and a list of all timings is shown to the user. In its current state, there is not really a big use for *Activity Tracking* apart from the user being aware of the load of every seperate model.
Two different output variants are currently supported:
* List view: this will simply show a flat list of the name of the model, followed by its activity (in seconds). Depending on the configuration options, this will either be sorted on model name or on activity.
* Cell view: this present the same information as the list view, only in a visual way. It will create a matrix-style file that contains the activity for that specific cell. This file can then be visualised using for example *Gnuplot*.
The following configuration options are related to this functionality:
* *setActivityTracking(at, sortOnActivity)*: enables or disables the printing of activity tracking information. Note that activity tracking is **always** performed internally, so disabling this will not increase performance.
* *setActivityTrackingCellMap(cellmap, x, y)*: when activity tracking is enabled, setting this to *True* will result in a matrix-style file instead of a (printed) flat list. The resulting file will be called *activity*.
In this example, we will create an activity Cell view, as this is a lot nicer than the other (more statistical) variant. Our model will be something more complex as the previous examples, but the model itself isn't really that important. A *fire spread* model was chosen, as this nicely reduces to a map and is thus perfect for Cell view.
.. note:: The Cell view furthermore requires models that are to be plotted have an *x* and *y* attribute. This value will determine their location in the map. Models without such attributes will simply be ignored.
This image was created by using the *experiment* file containing::
x, y = 20, 20
model = FireSpread(x, y)
sim = Simulator(model)
sim.setTerminationTime(1000.0)
sim.setActivityTrackingVisualisation(True, x, y)
sim.simulate()
After simulation, a file called *activity* was created. This file was plotted using *gnuplot* with the commands
.. code-block:: gnuplot
set view map
splot 'activity' matrix with image
.. image:: activity.png
:alt: Activity Tracking cell view
:width: 100%
.. note:: In order to make some more visually pleasing maps, the computation in the transition functions was severely increased. This is due to our transition function being rather small by default, which doesn't provide very accurate timings.
.. warning:: Don't forget to take the word of caution (see below) into account when analyzing the results.
Word of caution
---------------
The *Activity Tracking* feature uses the *time.time()* function from the Python *time* library. This means that it measures *wall clock* time instead of actual *CPU* time. Should the CPU be overloaded with work, these timings will thus be inaccurate due to possible interruptions of the simulation. Most of the time however, such interrupts should arrive either outside of the timed code. Otherwise, the interrupted models should be somewhat evenly spread out, thus reducing the impact.
Of course, Python provides functions to fetch the actual *CPU* time spend. These alternatives were checked, but were *insufficient* for our purpose for several reasons. The alternatives are mentioned below:
* Python *time.clock()* function: this has a very low granularity on Linux, which would show total nonsense in case the transition functions are small.
* Python *resource* library: this has the same problem as the *time.clock()* approach and furthermore is a lot slower
* External *psutil* library: this alternative has the same problems as the above alternatives, with the additional disadvantage that it is **extremely** slow.
Since *Activity Tracking* is done at every model, in every simulation step, performance of this call is critical. To show the difference between these alternatives, a *timeit* comparison is shown below:
.. code-block:: bash
yentl ~ $ python -m timeit -s "import time" -- "time.time()"
10000000 loops, best of 3: 0.0801 usec per loop
yentl ~ $ python -m timeit -s "import time" -- "time.clock()"
10000000 loops, best of 3: 0.199 usec per loop
yentl ~ $ python -m timeit -s "import resource" -- "resource.getrusage(resource.RUSAGE_SELF).ru_utime"
1000000 loops, best of 3: 0.642 usec per loop
yentl ~ $ python -m timeit -s "import psutil" -- "psutil.cpu_times().user"
10000 loops, best of 3: 25.9 usec per loop
As can be seen from this comparison, we have the following performance statistics:
+----------+---------------+----------------------------+
| method | time per loop | times slower than *time()* |
+----------+---------------+----------------------------+
| time() | 0.08 usec | 1x |
+----------+---------------+----------------------------+
| clock() | 0.199 usec | 2.5x |
+----------+---------------+----------------------------+
| resource | 0.642 usec | 8x |
+----------+---------------+----------------------------+
| psutil | 25.9 usec | 324x |
+----------+---------------+----------------------------+
Since this function will be called twice for every transition that happens, using one of the slower methods would have an immense difference on the actual simulation time. The main purpose of *Activity Tracking* is to increase performance, but when when e.g. *psutil* is used, the simulation is already slowed down by a massive factor, removing any chance for improvement in general situations.

View file

@ -0,0 +1,30 @@
..
Copyright 2014 Modelling, Simulation and Design Lab (MSDL) at
McGill University and the University of Antwerp (http://msdl.cs.mcgill.ca/)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Automatic activity-based relocation
===================================
As was mentioned in the previous section, manual relocation is possible in PyPDEVS. However, the knowledge of the modeler about which models need to be moved and at what time exactly, might be rather vague or even non-existent. Clearly, slightly altering the models parameters could completely alter the time at which relocation would be ideal. Additionally, such manual relocation is time consuming to write and benchmark.
In order to automate the approach, PyPDEVS can use *activity* to guide it into making autonomous decisions. Since activity is a rather broad concept, several options are possible, most of them also offering the modeller the possibility to plug in its own relocators or activity definitions. Together with custom modules, comes also the possibility for inserting domain specific knowledge.
We distinguish several different situations:
.. toctree::
Activity Tracking <activitytracking>
Custom Activity Tracking <customactivitytracking>
Custom Activity Prediction <activityprediction>

View file

@ -0,0 +1,40 @@
..
Copyright 2014 Modelling, Simulation and Design Lab (MSDL) at
McGill University and the University of Antwerp (http://msdl.cs.mcgill.ca/)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Activity prediction
===================
One of the remaining problems with the previous two solutions is that they use a general relocator, instead of a custom one. A custom relocator has several advantages, of which the most important is addition of domain specific information. A relocator might not only know how to perform the best relocations (instead of simply mutating the border), but might also be able to predict how the activity will evolve in the future and already take some measures to speed up the future.
A custom relocator also allows us to have a radically different concept of activity than was previously possible. As a simple example, models might return their energy consumption as their activity and the relocator then tries to minimise this value by performing some (domain-specific) relocations.
An example of how to use a user-defined relocator (located in the file "relocatorFile", with classname "Relocator")::
sim = Simulator(MyModel())
# Note that additional parameters can be passed to the method
# these will then be passed to the constructor of the provided relocator
sim.setActivityRelocatorCustom("relocatorFile", "Relocator")
Writing a relocator
-------------------
Writing a relocator yourself is somewhat more difficult, as it offers a lot of possibilities. There are 2 important methods to define: *getRelocations(GVT, activities, horizon)* and *useLastStateOnly()*.
The *useLastStateOnly()* is simple and should return a boolean. If the boolean is *False*, all activities within the passed time will be accumulated into a single value, which indicates the activity of this model. This is required if the activity is e.g. the total time taken by the transition functions.
Should the boolean be *True*, only the final state will be used to determine the activity. This will effectively drop all information that was gathered during the simulation and only return the activity that was determined at the final simulation step (up to the GVT). While it does not show complete behaviour of the model, it provides a (consistent!) snapshot of the complete model. A simple use case could be when modelling a road, where the amount of cars on the road determine the activity. If every road then returns 1 if there is a car on it and 0 otherwise, the activity information can be used to fetch all models that contained a car.
TODO: *getRelocations()*

31
doc/activitytracking.rst Normal file
View file

@ -0,0 +1,31 @@
..
Copyright 2014 Modelling, Simulation and Design Lab (MSDL) at
McGill University and the University of Antwerp (http://msdl.cs.mcgill.ca/)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Activity Tracking
=================
Activity tracking will perform the most generic approach available: measuring the time every transition function takes, accumulating all these values and calculating the complete load of the node.
When using the *basic boundary relocator*, the current allocation will be mutated in such a way that every node gets approximately the same load. Sometimes this will not yield decent results, since the approach is too general and uses a greedy algorithm. It should therefore only be used in very simple situations where the number of possible mutations is rather limited. A simple example of this is a queue which has only one input and one output port.
The *basic boundary relocator* takes a single argument: the swappiness. This simply defines what threshold to use for 'unacceptable load distribution'. A swappiness of 2 for example, will only try to offload nodes that have an activity twice as big as the average activity. Setting a too low swappiness will cause many (often unnecessary) relocations, while setting a too high swappiness will prevent relocations completely.
To start a distributed simulation with *general activity tracking*, the configuration is::
sim = Simulator(CQueue())
swappiness = 1.3
sim.setActivityRelocatorBasicBoundary(swappiness)
sim.simulate()

View file

@ -0,0 +1,21 @@
..
Copyright 2014 Modelling, Simulation and Design Lab (MSDL) at
McGill University and the University of Antwerp (http://msdl.cs.mcgill.ca/)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Activity-aware utility functions
================================
.. automodule:: activityVisualisation
:members:

5
doc/addAllDocs.sh Normal file
View file

@ -0,0 +1,5 @@
#!/bin/bash
svn add *.rst
svn add _build/html/*.html
svn add _build/html/sources/*.txt
svn add _build/html/modules/*.html

48
doc/advanced.rst Normal file
View file

@ -0,0 +1,48 @@
..
Copyright 2014 Modelling, Simulation and Design Lab (MSDL) at
McGill University and the University of Antwerp (http://msdl.cs.mcgill.ca/)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Advanced examples
=================
In this section, we will continue using the model from the previous section wherever possible. We will extend it in several ways, each demonstrating another feature of the PyPDEVS simulator that will first be briefly explained. All examples are sorted based on complexity of both usage and implementation, but also with dependencies in mind.
.. toctree::
:maxdepth: 1
Reinitialisation <reinitialisation>
Continuing a simulation <continuing>
Minimal simulation kernel <minimal>
Multiple Simulations <multisim>
Transfer Functions <transferfunction>
Dynamic Structure DEVS <dynamicstructure>
Nesting <nesting>
Realtime simulation <realtime>
Listening to realtime simulation <listener>
Cell tracing <celltracing>
Custom scheduler <customscheduler>
Distribution <distribution>
Location-specific scheduler <locationscheduler>
Random number generation <random>
Distributed termination condition <distributedtermination>
Checkpointing <checkpoint>
Location tracking <location>
Memoization <memoization>
Manual relocation <relocation>
Activity tracking <activity>
Automatic relocation <activity_relocation>
Activity visualisation <visualisation>
Static allocator <staticallocator>
Dynamic allocator <dynamicallocator>

23
doc/allocators_int.rst Normal file
View file

@ -0,0 +1,23 @@
..
Copyright 2014 Modelling, Simulation and Design Lab (MSDL) at
McGill University and the University of Antwerp (http://msdl.cs.mcgill.ca/)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Allocators
==========
.. toctree::
Auto Allocator <autoallocator_int>
Greedy Allocator <greedyallocator_int>

View file

@ -0,0 +1,21 @@
..
Copyright 2014 Modelling, Simulation and Design Lab (MSDL) at
McGill University and the University of Antwerp (http://msdl.cs.mcgill.ca/)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Asynchronous Combo Generator
============================
.. autoclass:: realtime.asynchronousComboGenerator.AsynchronousComboGenerator
:members:

21
doc/autoallocator_int.rst Normal file
View file

@ -0,0 +1,21 @@
..
Copyright 2014 Modelling, Simulation and Design Lab (MSDL) at
McGill University and the University of Antwerp (http://msdl.cs.mcgill.ca/)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
AutoAllocator
=============
.. autoclass:: allocators.autoAllocator.AutoAllocator
:members:

45
doc/base_dsdevs.py Normal file
View file

@ -0,0 +1,45 @@
from pypdevs.DEVS import AtomicDEVS, CoupledDEVS
from pypdevs.simulator import Simulator
class Root(CoupledDEVS):
def __init__(self):
CoupledDEVS.__init__(self, "Root")
self.models = []
# First model
self.models.append(self.addSubModel(Generator()))
# Second model
self.models.append(self.addSubModel(Consumer(0)))
# And connect them
self.connectPorts(self.models[0].outport, self.models[1].inport)
class Generator(AtomicDEVS):
def __init__(self):
AtomicDEVS.__init__(self, "Generator")
# Keep a counter of how many events were sent
self.outport = self.addOutPort("outport")
self.state = 0
def intTransition(self):
# Increment counter
return self.state + 1
def outputFnc(self):
# Send the amount of messages sent on the output port
return {self.outport: [self.state]}
def timeAdvance(self):
# Fixed 1.0
return 1.0
class Consumer(AtomicDEVS):
def __init__(self, count):
AtomicDEVS.__init__(self, "Consumer_%i" % count)
self.inport = self.addInPort("inport")
def extTransition(self, inputs):
for inp in inputs[self.inport]:
print("Got input %i on model %s" % (inp, self.name))
sim = Simulator(Root())
sim.setTerminationTime(5)
sim.simulate()

21
doc/basesimulator_int.rst Normal file
View file

@ -0,0 +1,21 @@
..
Copyright 2014 Modelling, Simulation and Design Lab (MSDL) at
McGill University and the University of Antwerp (http://msdl.cs.mcgill.ca/)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
BaseSimulator: the DEVS kernel
==============================
.. autoclass:: basesimulator.BaseSimulator
:members:

View file

@ -0,0 +1,21 @@
..
Copyright 2014 Modelling, Simulation and Design Lab (MSDL) at
McGill University and the University of Antwerp (http://msdl.cs.mcgill.ca/)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Basic Boundary Relocator
========================
.. automodule:: relocators.basicBoundaryRelocator
:members:

View file

@ -0,0 +1,21 @@
..
Copyright 2014 Modelling, Simulation and Design Lab (MSDL) at
McGill University and the University of Antwerp (http://msdl.cs.mcgill.ca/)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Boundary Relocator
==================
.. automodule:: relocators.boundaryRelocator
:members:

BIN
doc/celldevs.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 MiB

BIN
doc/celldevs.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

111
doc/celltracing.rst Normal file
View file

@ -0,0 +1,111 @@
..
Copyright 2014 Modelling, Simulation and Design Lab (MSDL) at
McGill University and the University of Antwerp (http://msdl.cs.mcgill.ca/)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Cell Tracing
============
Cell tracing is somewhat different from the *verbose* and *XML* tracers, in that it is only applicable for several models. The models that support it however, can profit from it, as this is a very visual and easy to understand tracer.
.. note:: For those familiar with Cell DEVS (and particularly CD++): this tracer aims at providing the same tracing functionality for 2D DEVS models as CD++ provides for them. This does introduce parts of Cell DEVS in Parallel DEVS, but of course the only affected parts are the actual tracing and not model construction and simulation.
There are only 2 requirements on the model for Cell tracing:
* The models have an *x* and *y* attribute, which signify the location where the cell will be drawn.
* The states have an *toCellState()* method, which should return the value to be shown in the matrix.
.. note:: If two models have unique coordinates, only one of them will be considered in the tracing. There is no support for dimensions above 2D.
Thus a very simple cell would look like this::
class CellState(object):
def __init__(self, value):
self.value = value
def toCellState(self):
# Simply return the value, but could also first
# perform some operations on the value
return value
class Cell(AtomicDEVS):
def __init__(self, x, y):
AtomicDEVS.__init__(self, 'Cell(%i, %i)' % (x, y))
self.x = x
self.y = y
self.state = CellState()
The coupled model would then be responsible for assigning unique coordinates to every cell. The following configurations now still need to be set in the experiment file::
# Save the coordinates of the model somewhere, as we need them later
x, y = 20, 20
model = MyCoupledCellModel(x, y)
sim = Simulator(model)
sim.setCell(True, x, y, cell_file="celltrace", multifile=False)
sim.simulate()
This will then generate a file with a matrix of the current state at every timestep. The matrices are simply appended with a delimiter between them. Sadly, this kind of data is not directly usable in gnuplot, which is why the *multifile* option comes in handy.
When setting *multifile=True*, every timestep will have its own file, which contains only the matrix and can be directly drawn using *gnuplot*. Simulator-defined files are not that handy, because you probably want it in a slightly different format. For this reason, the *cell_file* parameter will be interpreted as a *format string* when *multifile* is set to *True*. An example invocation of this could be::
sim.setCell(True, x, y, cell_file="celltrace-%05d", multifile=True)
This will then create the files *celltrace-00001*, *celltrace-00002*, ... until the termination time is reached.
.. note:: Remember that the *cell_file* parameter will be used as a format string if *multifile* is *True*!
After these files are generated, we can simply plot them with e.g. *gnuplot* as follows:
.. code-block:: gnuplot
set pm3d map
set pm3d interpolate 0, 0
splot 'celltrace-00001' matrix
This will generate in interpolated version (which is slightly nicer than the original version). Naturally, scripting immediately comes to mind when a lot of files with similar data are created. It is now even possible to create an animation of the simulation, thus visualizing the change over time. This can be done in e.g. bash as follows:
.. code-block:: bash
for f in `ls -1 celltrace-*`
do
echo "set view map" > plot
echo "set terminal png size 400, 300 enhanced" >> plot
echo "set pm3d map" >> plot
echo "set pm3d interpolate 0, 0" >> plot
echo "set output '$f.png'" >> plot
echo "splot '$f' matrix" >> plot
gnuplot plot
rm plot
done
avconv -i celltrace-%5d.png out.avi
Which will create an animation visualizing e.g. a *fire spread* model. Since *gnuplot* will automatically determine the range of colors to use, it might be interesting to provide this range yourself, as to keep it consistent between all different images. This can be done by adding the following line right before the *splot* line:
.. code-block:: bash
echo "set cbrange [27:800]" >> plot
Each individual file will then look something like:
.. image:: celldevs.png
:alt: Cell view
:width: 100%
And an animated version looks like:
.. image:: celldevs.gif
:alt: Cell view animation
:width: 100%
.. note:: It is technically possible to visualize this data in (semi)-realtime. If local simulation is done, each trace file will be written as soon as possible. It would require some additional lines of scripting to actually poll for these files and render them of course. In distributed simulation, visualisation at run time is not so simple, as these files will only be written at GVT boundaries.

21
doc/changelog.rst Normal file
View file

@ -0,0 +1,21 @@
..
Copyright 2014 Modelling, Simulation and Design Lab (MSDL) at
McGill University and the University of Antwerp (http://msdl.cs.mcgill.ca/)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Changelog
=========
.. literalinclude:: ../../notes.txt

51
doc/checkpoint.rst Normal file
View file

@ -0,0 +1,51 @@
..
Copyright 2014 Modelling, Simulation and Design Lab (MSDL) at
McGill University and the University of Antwerp (http://msdl.cs.mcgill.ca/)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Checkpointing
=============
.. note:: Checkpointing is only possible in distributed simulation and only if the MPI backend is used.
Checkpointing offers the user the possibility to resume a computation from a previous simulation run. This previous simulation run might have been interrupted, with only a partial simulation as a result. Furthermore, all possible tracers will only have parts of their actual output being written. Restarting the simulation from scratch might be unacceptable due to the long time that was already spent on simulation. Checkpointing offers a solution to this problem, because it will save the current simulation state to a file after a fixed amount of GVT computations.
The checkpointing algorithm is closely linked to the GVT algorithm, as this allows for several optimisations. At the GVT, it is possible to know that no message will arrive from before, so all states from before can be removed. Since after a checkpoint recovery all nodes will revert to the GVT, no future state needs to be saved too.
The only data that is thus stored is the model itself. To allow for somewhat easier implementation, some other data is also stored, such as configuration options. Basically it boils down to a selective *pickle* of the kernels at every location.
Now how do you actually use checkpointing? The first step is of course to enable it in the configuration options, like this::
sim = Simulator(DQueue())
sim.setCheckpointing("myQueue", 1)
sim.simulate()
The *setCheckpointing* function takes a name as its first parameter, which is simply used to identify the checkpoints and it will be used as a filename. The second parameter is the amount of GVT computations that should pass before a checkpoint is made. It might be possible to calculate the GVT frequently (e.g. after 10 seconds of simulation), but only create a checkpoint after a few minutes of simulation. This is because the GVT calculation frees up memory and might therefore be necessary. On the other hand, creating checkpoints very often is I/O intensive and when restoring a checkpoint, it will probably not be a matter of seconds.
.. warning:: The first parameter of the *setCheckpointing* function is used as a filename, so make sure that this would create a valid file name.
When simulation is running with these options, files will be created at every checkpoint step that are placed in the current directory. The created files will have the PDC extension, which stands for PythonDEVS Checkpoint. There will be as many files as there are nodes running: one for each kernel. Furthermore, a basic file will be created at the start, which contains the simulator that oversees the simulation. This file doesn't change with simulation, so it is not altered during simulation itself.
Now that we have our checkpoints, we only need to be able to recover from them. This is again as simple as running the *loadCheckpoint* function **before** recreating a simulator and model. It is not completely necessary to do this before, though the work would be useless... This *loadCheckpoint* call will automatically resume simulation as soon as all nodes are recovered. The call will return *None* in case no recovery is possible (e.g. when there are no checkpoint files), or will return a simulator object when simulation has finished. It is therefore **only** necessary to create a new model and simulator if this fails. This gives the following code::
sim = loadCheckpoint("myQueue")
if sim is None:
sim = Simulator(DQueue())
sim.setCheckpointing("myQueue", 1)
sim.simulate()
# Here, the simulation is finished and the Simulator object can be used as normally in both cases
The *loadCheckpoint* will automatically search for the latest available checkpoint that is completely valid. If certain files are missing, then the next available option will be tried until a usable one is found. Note that it is possible for a checkpoint file to be corrupt, for example when the simulation crashes while writing the checkpoint file. This will be seen by the user as a seemingly random *PicklingError*. In this case it is necessary to remove at least one of these files and retry. For this reason, older checkpoints are still kept.
.. note:: On-the-fly recovery of a crashed node is not possible, all nodes will have to stop and restart the simulation anew.

View file

@ -0,0 +1,22 @@
..
Copyright 2014 Modelling, Simulation and Design Lab (MSDL) at
McGill University and the University of Antwerp (http://msdl.cs.mcgill.ca/)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Classic DEVS Wrapper
====================
.. automodule:: classicDEVSWrapper
:members:
:special-members:

23
doc/colors_int.rst Normal file
View file

@ -0,0 +1,23 @@
..
Copyright 2014 Modelling, Simulation and Design Lab (MSDL) at
McGill University and the University of Antwerp (http://msdl.cs.mcgill.ca/)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Colors
=========
.. automodule:: colors
:members:
.. autodata:: colors.colors

252
doc/conf.py Normal file
View file

@ -0,0 +1,252 @@
# -*- coding: utf-8 -*-
#
# PythonPDEVS documentation build configuration file, created by
# sphinx-quickstart on Sat Sep 28 20:20:40 2013.
#
# This file is execfile()d with the current directory set to its containing dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.
import sys, os
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
sys.path.insert(0, os.path.abspath('../../src/pypdevs/'))
# -- General configuration -----------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
#needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
#extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.todo', 'sphinx.ext.coverage', 'sphinx.ext.pngmath', 'sphinx.ext.ifconfig', 'sphinx.ext.viewcode']
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode']
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# The suffix of source filenames.
source_suffix = '.rst'
# The encoding of source files.
#source_encoding = 'utf-8-sig'
# The master toctree document.
master_doc = 'index'
# General information about the project.
project = u'PythonPDEVS'
copyright = u'2016, Yentl Van Tendeloo'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
version = '2.4'
# The full version, including alpha/beta/rc tags.
release = '2.4.0'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#language = None
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
#today = ''
# Else, today_fmt is used as the format for a strftime call.
#today_fmt = '%B %d, %Y'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
exclude_patterns = ['_build']
# The reST default role (used for this markup: `text`) to use for all documents.
#default_role = None
# If true, '()' will be appended to :func: etc. cross-reference text.
#add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
#add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
#show_authors = False
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
# A list of ignored prefixes for module index sorting.
#modindex_common_prefix = []
# -- Options for HTML output ---------------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
html_theme = 'default'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
#html_theme_options = {}
# Add any paths that contain custom themes here, relative to this directory.
#html_theme_path = []
# The name for this set of Sphinx documents. If None, it defaults to
# "<project> v<release> documentation".
#html_title = None
# A shorter title for the navigation bar. Default is the same as html_title.
#html_short_title = None
# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
#html_logo = None
# The name of an image file (within the static path) to use as favicon of the
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
#html_favicon = None
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
htmlstatic_path = ['static']
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
#html_last_updated_fmt = '%b %d, %Y'
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
#html_use_smartypants = True
# Custom sidebar templates, maps document names to template names.
#html_sidebars = {}
# Additional templates that should be rendered to pages, maps page names to
# template names.
#html_additional_pages = {}
# If false, no module index is generated.
#html_domain_indices = True
# If false, no index is generated.
#html_use_index = True
# If true, the index is split into individual pages for each letter.
#html_split_index = False
# If true, links to the reST sources are added to the pages.
#html_show_sourcelink = True
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
#html_show_sphinx = True
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
#html_show_copyright = True
# If true, an OpenSearch description file will be output, and all pages will
# contain a <link> tag referring to it. The value of this option must be the
# base URL from which the finished HTML is served.
#html_use_opensearch = ''
# This is the file name suffix for HTML files (e.g. ".xhtml").
#html_file_suffix = None
# Output file base name for HTML help builder.
htmlhelp_basename = 'PythonPDEVSdoc'
# -- Options for LaTeX output --------------------------------------------------
latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
#'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
#'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
#'preamble': '',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass [howto/manual]).
latex_documents = [
('index', 'PythonPDEVS.tex', u'PythonPDEVS Documentation',
u'Yentl Van Tendeloo', 'manual'),
]
# The name of an image file (relative to this directory) to place at the top of
# the title page.
#latex_logo = None
# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
#latex_use_parts = False
# If true, show page references after internal links.
#latex_show_pagerefs = False
# If true, show URL addresses after external links.
#latex_show_urls = False
# Documents to append as an appendix to all manuals.
#latex_appendices = []
# If false, no module index is generated.
#latex_domain_indices = True
# -- Options for manual page output --------------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
('index', 'pythonpdevs', u'PythonPDEVS Documentation',
[u'Yentl Van Tendeloo'], 1)
]
# If true, show URL addresses after external links.
#man_show_urls = False
# -- Options for Texinfo output ------------------------------------------------
# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
('index', 'PythonPDEVS', u'PythonPDEVS Documentation',
u'Yentl Van Tendeloo', 'PythonPDEVS', 'One line description of project.',
'Miscellaneous'),
]
# Documents to append as an appendix to all manuals.
#texinfo_appendices = []
# If false, no module index is generated.
#texinfo_domain_indices = True
# How to display URL addresses: 'footnote', 'no', or 'inline'.
#texinfo_show_urls = 'footnote'
# Include the __init__ function (http://stackoverflow.com/questions/5599254/how-to-use-sphinxs-autodoc-to-document-a-classs-init-self-method)
def skip(app, what, name, obj, skip, options):
if name == "__init__":
return False
return skip
def setup(app):
app.connect("autodoc-skip-member", skip)

47
doc/continuing.rst Normal file
View file

@ -0,0 +1,47 @@
..
Copyright 2014 Modelling, Simulation and Design Lab (MSDL) at
McGill University and the University of Antwerp (http://msdl.cs.mcgill.ca/)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Continuing a stopped simulation
===============================
It is possible to continue a terminated simulation from the current state. An example use case of this is splitting the simulation in two 'phases'. The first phase is a kind of warmup period, in which tracing might not be necessary. If this phase only takes until a certain time, the termination time can be set to this time. As soon as the *simulate()* call returns, it is possible to perform some alterations to the model and to the simulation methods.
The syntax is as simple as recalling the *simulate()* method. Of course, if the termination time (or condition) is not altered, simulation will halt immediately. If this is changed, simulation will run until this condition is satisfied. All tracers from the original run will still be in effect and possible new tracers will be added. These new tracers wil only contain the data from the simulation run that happens after they are created.
A small example, in which a model is contructed and runs until simulation time 100 is reached. After this, a tracer is set and simulation will runn up until simulation time 200::
sim = Simulator(MyModel())
sim.setTerminationTime(100)
sim.simulate() # First simulation run; no tracers
# We are at simulation time 100 now
sim.setTerminationTime(200)
sim.setVerbose() # Now add a tracer at time 100
sim.simulate() # Simulate it for a second time; using the tracer
Altering the state
------------------
It is also possible to alter the state in between two calls to the simulate method. This allows you to e.g. enable internal logging only after a certain time, or clear all gathered statistics for the warm-up period. This uses the exact same syntax (and internally, it uses exactly the same methods) as the reinitialisation with the exception that no *reinit()* is called.
The available methods are:
* *setModelState(model, newState)*: modify the state of *model* and set it to *newState*. Use this to set a completely new state for the model. This is an optimized version of *setModelAttribute*.
* *setModelStateAttr(model, attr, value)*: modify the attribute *attr* of the state of *model* and set it to *value*. This will keep the original initialisation state, but alters only a single attribute.
* *setModelAttribute(model, attr, value)*: modify the attribute *attr* of the *model* and set it to *value*. This can be done to modify read-only attributes of the simulation model.
Such alterations will be visible in the *Verbose* logger as 'user events', signifying the attribute that was altered and to which value. This is done to prevent inconsistent trace files.
.. warning:: The time advance is **not** recalculated after a change to the state. This is because if no significant change happens and the timeAdvance returns the same value (as it should), it would signify a different absolute time due to the time advance function returning a relative file.

21
doc/controller_int.rst Normal file
View file

@ -0,0 +1,21 @@
..
Copyright 2014 Modelling, Simulation and Design Lab (MSDL) at
McGill University and the University of Antwerp (http://msdl.cs.mcgill.ca/)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Controller
==========
.. automodule:: controller
:members:

View file

@ -0,0 +1,39 @@
..
Copyright 2014 Modelling, Simulation and Design Lab (MSDL) at
McGill University and the University of Antwerp (http://msdl.cs.mcgill.ca/)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Custom Activity Prediction
==========================
The general activity tracking methodology has the disadvantage that it only looks at the time spent within a transition function. It is therefore unable to return stable values for a model. Calls to the Python time library might also have a too big overhead. Custom activity tracking will offer a solution to this, by offering the user a custom way of defining activity.
These functions are the *preActivityCalculation()* and *postActivityCalculation(prevalue)* methods. Right before the transition, the *preActivityCalculation()* method will be executed. This method can return a value that should be passed to the *postActivityCalculation(prevalue)* method, as the prevalue. The duality of these methods is necessary, since otherwise the custom activity function would only have access to the new state of the model, while it might be possible that activity is defined in terms of the difference between two states.
To give an idea of a simple implementation, the general activity tracking is defined as follows::
def preActivityCalculation(self):
return time.time()
def postActivityCalculation(self, prevalue):
return time.time() - prevalue
Of course, some activity definitions dont care about the previous state, so they can simply write an empty *preActivityCalculation()* method and ignore the prevalue in the *postActivityCalculation(prevalue)* method.
While we have only elaborated on the time definition of activity, activity can also be defined in several different ways, all of which are possible with this custom activity function. However, due to the use of simple activity tracking, relocations will still try to balance the activity over the nodes. The next sections will provide solutions to that problem.
No special simulation options are necessary to use the custom *preActivityCalculation()* and *postActivityCalculation(prevalue)* methods, since this is handled by polymorphism already.
.. note:: All atomic models should have these two methods defined, as otherwise they will simply fall back to the general activity tracking. This might be problematic in situations where the values get mixed unknowingly.

105
doc/customscheduler.rst Normal file
View file

@ -0,0 +1,105 @@
..
Copyright 2014 Modelling, Simulation and Design Lab (MSDL) at
McGill University and the University of Antwerp (http://msdl.cs.mcgill.ca/)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Writing a custom scheduler
==========================
.. warning:: Writing a custom scheduler is potentially dangerous, as you could easily violate the DEVS formalism. Only trust your own schedulers after rigorous testing **and** (if you violate DEVS formalisms in the general case) make sure that such violating situations never happen.
A scheduler requires a simple interface, which will be explained here. If you require the scheduler to work distributed, you should allow for some kind of rollback to happen. This should be handled by the *massReschedule* method automatically.
Interface
---------
The interface, and thus the methods that need to be implemented, is rather small. Some of them might be skipped if you only want your scheduler to work for static structure without relocation. However, it is advised to implement all of them for future compliance.
.. function:: __init__(models, epsilon, totalModels)
The constructor to be used for the scheduler. The argument *models* will contain a list of all models that are local to this scheduler. Argument *epsilon* contains the allowed floating point deviation when searching for imminent children. The *totalModels* argument can be ignored most of the time and is only useful if the scheduler needs some global information about all models, even those running remotely.
.. function:: schedule(model)
Add a new model to the scheduler. The provided model will **not** have been passed in the constructor. It is only used in dynamic structure (when creating a new model) and distributed simulation with relocation (where a model is relocated to our node).
.. function:: unschedule(model)
Remove a model from the scheduler. The provided model will have been either passed in the constructor or by the *schedule* method. Unscheduling a model should have the effect that it will never be returned by the *getImminent* method unless it is scheduled again.
.. function:: massReschedule(reschedule_set)
Reschedules all models in the reschedule_set. This method is used to notify the scheduler that there is the possibility that the *timeNext* value of these models has been changed. Models that are not mentioned in the reschedule_set are guaranteed to have the same *timeNext* value. All models that are provided are guaranteed to either be passed in the constructor, or have the schedule method called for them. Performance wise, this is one of the most time-critical pieces of code.
.. function:: readFirst()
Returns the time of the first model that is scheduled, as a tuple of the form (simulationtime, age). Since this is a read operation, nothing should change to the scheduled models and their order.
.. function:: getImminent(time)
Return an iterable containing all models that are scheduled for this specific time, with an allowed deviation of *epsilon* (passed in the constructor). It is possible that there will be no imminent models! The internal state of the returned models is irrelevant, as they will afterwards have the *massReschedule* method called with (among others) them in the iterable.
.. note:: The time should agree in both parts of the tuple: the simulation time (up to an *epsilon*) and the age field (*exact* equality only)
Example: sorted list scheduler
------------------------------
As an example, the sorted list scheduler is shown below. It simply contains an internal list of all models it has to take into account, sorts the list based on timeNext and returns the first elements that match.
.. code-block:: python
class SchedulerSL(object):
def __init__(self, models, epsilon, totalModels):
self.models = list(models)
self.epsilon = epsilon
def schedule(self, model):
self.models.append(model)
self.models.sort(key=lambda i: i.timeNext)
def unschedule(self, model):
self.models.remove(model)
def massReschedule(self, reschedule_set):
self.models.sort(key=lambda i: i.timeNext)
def readFirst(self):
return self.models[0].timeNext
def getImminent(self, time):
immChildren = []
t, age = time
try:
# Age must be exactly the same
count = 0
while (abs(self.models[count].timeNext[0] - t) < self.epsilon) and (self.models[count].timeNext[1] == age):
# Don't pop, as we want to keep all models in the list
immChildren.append(self.models[count])
count += 1
except IndexError:
pass
return immChildren
Using your own scheduler
------------------------
Since the name of your custom scheduler is not known by me, there is no simple utility function like *setSchedulerSortedList()* provided, but you will have to use the more advanced interface. Note that all of these *setSchedulerX()* methods are simply utility functions which make exactly the same calls as you will be making. They are only provided to make the life of most users simpler.
Setting the custom scheduler requires 2 bits of information: the filename in which the class is defined and the name of the class. Take for example that we created the '*CustomScheduler*' class in the file '*myFirstScheduler*'. Using the scheduler is then as simple as::
sim = Simulator(Queue())
# Internally, this is evaluated as an import statement of the form
# from myFirstScheduler import CustomScheduler
sim.setSchedulerCustom('myFirstScheduler', 'CustomScheduler')
sim.simulate()

162
doc/differences.rst Normal file
View file

@ -0,0 +1,162 @@
..
Copyright 2014 Modelling, Simulation and Design Lab (MSDL) at
McGill University and the University of Antwerp (http://msdl.cs.mcgill.ca/)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Differences from PyDEVS
=======================
This section is aimed at people used to the API as presented in PyDEVS up to version 1.1. Since this version, the aim was shifted to performance and efficient simulation, together with several specific (and hopefully useful) features.
Users not familiar with PyDEVS do not need to read this section, as it contains parts of the old API.
Parallel DEVS
-------------
The major shift in PyPDEVS is the switch to Parallel DEVS, which is also where the additional *'P'* in the name comes from.
For the modeller, the main difference is in the content of the messages that are being passed. Previously, only a single message was put on every port. In the new version, a message is *always* a list containing the actual messages. As specified in the PDEVS standard, this is actually to be interpreted as a *bag* and therefore there is **no guaranteed order** to the elements in the list.
Another change is in the removal of the *select()* function, which is no longer needed due to all transitions being processed simultaneously.
There is also the addition of the *confTransition(inputs)* function, which will be called in situations where both an internal and external transition happen together. This function is set to a combination of the *extTransition(inputs)* and *intTransition()* function by default.
Even though the default formalism was shifted to Parallel DEVS (and with it, all internal algorithms), Classic DEVS is still supported by using the *setClassicDEVS()* configuration option. Classic DEVS is a lot slower though, due to the *select()* function, so it is advised to use Parallel DEVS whenever possible.
Different API
-------------
The old interface of the *Simulator* class was very small and all options were passed to the *simulate()* method. Due to the explosive amount of new configurations, this became unmanageable and it required a lot of checking. These configuration settings are now done using setters, which are all documented under the *Simulator* interface.
Not only the *Simulator* interface has changed, but also the methods to access the input in the *extTransition* and *confTransition* functions. Previously, this was done using the *peek* and *poke* functions. This became unnatural for Parallel DEVS (as it is unknown whether to overwrite or append the output) and also incurred a significant overhead due to the enormous amount of function calls. In the new interface, the methods receive a dictionary containing all ports on which input was received and the corresponding values. Ports without input are not mentioned in the dictionary. The *poke* message is similarly replaced by having the *outputFnc* return a similar dictionary.
Allow for distribution and parallellisation
-------------------------------------------
The main improvement in version 2.1 is the possibility to distribute the model and run it in parallel. For the modeller, the only difference is the addition of several API calls (which are optional, as some sane default values are chosen).
Additionally, the *addSubModel()* function takes an optional parameter *location* which specifies the number of the node on which the simulation of this model and its submodels should run. It is not necessary to specify this parameter manually, as there is a configuration option for automatic distribution of the model.
All these extensions do not change the interface for the local simulations or for local model construction.
Additional tracers
------------------
The tracing framework has been extensively rewritten and now supports both *verbose*, *VCD* and *XML*. These options themselves were already available in previous versions (through extended versions of PyDEVS), though the traced data is now flushed to disk immediately instead of being saved in memory. This was a severe limitation in big simulation runs, as they were unable to be traced.
Additionally, multiple tracers of the same kind are now also possible from within PyPDEVS itself. Though it should be noted that this will not perform any optimisations and literally run the same code twice. If such situation is really desired in combination with performance, please look at the *tee* UNIX command.
.. versionadded:: 2.1.4 A new Cell DEVS style tracer is also added, which prints the output to a file that can be used for e.g. visualisation.
Custom copy method for messages
-------------------------------
The DEVS formalism states that messages should be copies of the original message that is being send, in order to prevent different models from influencing each others. This was rather inefficient and PyDEVS was actually (one of) the only simulators that actually did this, though copying is the correct behaviour.
In order to allow for fair comparisons to other simulators, such as ADEVS, the modeller now has the possibility to define a custom *copy()* method on the message being sent. This method should return a *deep* copy of the original message.
While this might seem bothersome for the modeller, even a simple data class can be copied up to 10 times faster by only defining a *copy()* method which makes a copy itself instead of relying on the default *pickle* behaviour. Furthermore, the modeller might have some further knowledge about the model and thus know that a message will not be used for such unacceptable purposes.
In the most extreme case, it is even possible to disable message copies completely. This is the only behaviour in most simulators, so it is mainly implemented to allow for fair performance comparisons.
.. warning::
While it would be possible to violate the DEVS formalism by using these shortcuts, they are **not** supported in any way. In local simulation, such alterations might probably work in some versions, though this behaviour is not guaranteed to stay the same between several versions. On distributed simulation, the situation gets worse, since it will probably **never** work and might lead to seemingly random state modifications due to state saving.
Realtime simulation
-------------------
Realtime simulation is now possible using the *Simulator* configuration parameters. The model itself does not need to be modified, though it is necessary to pass a dictionary containing the ports on which input can be passed. File input is also possible and can be interleaved with input from stdin.
Simulation can be halted by using the standard termination options, or by sending the empty string as input.
Checkpointing added
-------------------
Distributed simulation has an increased possibility for failure and therefore checkpointing support was added. Checkpoints are made using the *pickle* modules present in Python and therefore require the states and attributes of the models to be picklable themself. This should not be a problem, as it would be unnatural to have unpicklable attributes there. Furthermore, distributed simulation already requires the states to be picklable for state saving.
To restore from a checkpoint, the *load_checkpoint* function can be used, which will automatically search for the latest (complete) checkpoint and use it. In case none is found, this function will return *None*. In case a suitable checkpoint was found, it will return the original simulator object to be used in further code. It is therefore important to **not** call *simulate()* on this returned simulator and the *load_checkpoint* call should be (one of) the first lines of code.
Checkpoints are *disabled* by default, as they take some time and require disk access.
Logging support
---------------
Logging support is present using a Syslog server (by default, the one at *localhost* will be used). This is not really useful for normal users, though it provides great debugging for the simulation kernel. To disable this logging (as it increases simulation time by a noticable factor), it is possible to either run the *disableAsserts.sh* script, or run *python* with the *-O* flag. This logger is also used for messages from the simulator itself, though fatal and important errors are always thrown using the exception system (as a *DEVSException*).
Due to the huge performance impact, logging is disabled by default at the *source* level. If you require logging support, use the *enableAsserts.sh* shell script.
Model visualisation
-------------------
It is possible to export the model in a format that is understandable for *Graphviz*, as to visualize the model. The primary advantage of this is that it also visualizes the location where the node runs (at least at the start of the simulation). It furthermore allows the modeller to see what kind of model is being simulated and whether or not some connections are wrong.
.. note:: Huge models might have problems being visualized due to *Graphviz* drawing the edge labels by default. This causes a lot of collision checking to be done and probably results in an unreadable file. For this reason, the *hideEdgeLabels* is present and will hide these edge labels.
Progress bar added
------------------
Some nice *'eye candy'* that is present in PyPDEVS is the progress bar. While its primary purpose is to visualize the progress of the different nodes, it is also usable in local simulation.
An example output is::
0 |######################== | 35%
1 |######################=================================================|DONE
2 |######################== | 35%
In this situation, a simulation runs over 3 nodes, where node 1 is already finished. Node 0 and 2 are both at 35% of their simulation. This percentage is calculated based on the current simulation time and the termination time provided during configuration.
The *'#'* symbols indicate the parts of the simulation that are already confirmed to be definitive by the GVT. The *'='* symbols indicate pending parts of the simulation that are not yet confirmed and are still possible to be reverted.
Clearly, simulation finishes as soon as all nodes are *'DONE'*.
.. note:: It is certainly possible for progress bars to fall back, even before the lowest of them all (though not below the GVT). This is due to messages that might still be in the network and are not taken into account by the progress bars. Of course, these messages are still taken into account by the GVT algorithm.
.. note:: Because these progress bars are updated using terminal escape codes, it is possible that they only work in specific terminals. Additionally, the progress bars will always overwrite the last few lines, which could contain your own prints or verbose tracing output.
Different schedulers
--------------------
In PyDEVS, the scheduler was fixed, whereas PyPDEVS now allows the user to choose which kind of scheduler to use. The current version supports 7 different schedulers: Sorted List, Minimal List, Activity Heap, HeapSet, NoAge, Discrete and Polymorphic. This modularised approach allows for domain-specific schedulers to be used if one can be implemented more efficiently.
If you do not care about performance, simply use the default scheduler.
Python 3 compliant
------------------
With Python 3 being the new version of Python, the PythonPDEVS simulator was also written with Python 3 compatibility in mind. The main development still happens on Python 2, though tests are sporadically also ran using Python 3.
Several possibilities for Python 3 compliance exist, though the method implemented in PythonPDEVS is the use of a restricted subset of Python 2, which is still present in Python 3 (with the same semantics of course).
.. note:: Python 2 and Python 3 have modifications to several key aspects of the language, such as the use of iterators for the *range* function. For this reason, there might be a performance gap between using either of the interpreters.
.. warning:: Even though the semantics of most core elements stay the same, several other parts of the language might behave slightly different and produce different results. It can therefore not be guaranteed that Python 3 is perfectly supported, though it should be considered a bug if there is a deviation.
Reinitialisation
----------------
Previous versions of PythonDEVS did not have the possibility to reinitialize the model and simply restart simulation (with possibly slightly different configuration options). Now it is possible to simply call the *simulate()* function multiple times, which will reinitialize the model on subsequent calls.
For local simulation, this requires a non-default simulation configuration. This decision was made for two reasons:
1 Reinitialisation was previously not supported, so there would be no change for normal simulation
2 Resetting the model requires to create a copy of the model in the original state, which takes some time to pickle the model
3 Saving a copy of the original model will take some additional memory, which can be rather high for large models
Distributed simulation has this option enabled by default, because the pickled representation of the model is already made and distributed in the initial setup. Furthermore, in local simulation it is much easier to reconstruct the model and simply restart the simulation on it. On the other hand, distributed simulation would require model distribution all over again, which can cause a high network load.
.. note:: When changing model attributes after the first call of the *simulate()* method, this should happen using the interface provided by the *Simulator*. This is because the model that is visible in the experiment file, is **not** the version of the model that is still present on the remote nodes. Changing attributes on this model will thus have no effect.
Activity Tracking
-----------------
Together with the possibility for distributed simulation, load balancing based on activity information was added. This is a huge feature and can allow for severe speedups in several cases. All of default activity tracking, custom activity tracking and custom activity prediction are possible. For more details on this feature, we refer to its own section.

View file

@ -0,0 +1,103 @@
..
Copyright 2014 Modelling, Simulation and Design Lab (MSDL) at
McGill University and the University of Antwerp (http://msdl.cs.mcgill.ca/)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Distributed Termination
=======================
Termination was simple in local, sequential simulation. All that had to be done was placing a simple check before each step in the simulation and check whether or not this simulation step should still be executed.
Distributed simulation on the other hand, should only terminate as soon as all nodes are able to quit. But due to the use of time warp, even if a node has stopped running, it might have to start again some time later. Clearly a global termination time is still relatively simple to use, as all nodes just compare their clocks to it, but a termination condition is a lot more difficult.
In this section, the two different ways of termination in a distributed setting will be discussed.
Termination time
----------------
A global termination time is clearly the most efficient solution. All nodes will receive this time at startup and will simply compare to it. It requires no inter-node communication whatsoever because every node can determine for itself whether or not simulation is finished. If at all possible, this approach is highly recommended over the other option.
Due to its simplicity, the exact same methods and semantics can be used as in sequential simulation::
sim = Simulator(Model())
sim.setTerminationTime(50.0)
sim.simulate()
Termination condition: frequent state checks
--------------------------------------------
.. warning:: Due to the use of time warp, the *time* parameter of the function now returns a **tuple** instead of a **float**. Most often, only the first value of the tuple is used, which contains the actual simulation time. The second value is the so called *age* field, which indicates how often this exact same simulation time has already happened.
A termination condition is still possible in distributed simulation, albeit in a reduced form. First of all, the granularity of a termination condition is not guaranteed in distributed simulation. Since DEVS takes non-fixed timesteps, they are depending on the models that are present on the current node. This means that the termination condition will also be checked only at these steps. Generally, this should not be a big problem, though it is something to keep in mind.
Only a single node can be responsible for the termination condition, due to the model being completely distributed. This node is always the controller. The controller should thus have all models involved in the termination condition running, as otherwise invalid states will be read.
.. note:: PyPDEVS will **not** complain when reading an invalid state, as such readings are done behind the back of PyPDEVS. If you want certainty that the state you are accessing is local, check whether or not the *location* attribute is equal to zero.
To be able to cope with allocation and relocation, the simulator should have its *setTerminationModel(model)* method be called. This will mark the model as being used in the termination condition and will guarantee that this model stays on the controller, whatever allocation or relocation is given. Note though, that this could potentially move a single atomic model from a remote coupled model, causing many revertions.
As soon as the termination condition triggers the end of the simulation, the controller will send a termination message to all other nodes, which will then keep running until they have reached the same time as passed in the termination condition. Should the controller be reverted, its termination messages will also be cancelled.
If we want the generator atomic model of the queue to be used in the termination condition, we could write::
def generatedEnough(time, model):
return model.generator.state.generated > 5
myQueue = CQueue()
sim = Simulator(myQueue)
sim.setTerminationCondition(generatedEnough)
sim.setTerminationModel(myQueue.generator)
sim.simulate()
Termination condition: sporadic state checks
--------------------------------------------
.. warning:: The complete *time* tuple should be passed to the *getState(time)* method!
If the model state is only required sporadically, it would be wasteful to run the model at the controller, simply for this one access. To cope with this, a *pull based* method is possible. Every atomic DEVS model will have a *getState(time)* method. If the model is non-local, this method can be called to retrieve the state of the model at that specific time. If this approach is used, the model need not be marked as a termination model.
.. note:: This time attribute is necessary due to time warp being used: it is very unlikely that the remote model is at exactly the same time in simulated time, so the time at which this call was made should be passed.
Such *getState(time)* calls are blocking, meaning that they will not return a result if the remote model is not yet at the simulated time that was requested. Furthermore, such requests cause artificial dependencies between different models. This pull based approach is thus only recommended if it is done (very) sporadically. It goes without saying that these remote calls also incur a latency due to the network delay.
To write the same as the previous example, we can write::
def generatedEnough(time, model):
return model.generator.getState(time).generated > 5
myQueue = CQueue()
sim = Simulator(myQueue)
sim.setTerminationCondition(generatedEnough)
sim.simulate()
Termination condition: mixed state checks
-----------------------------------------
Both approaches could be mixed if it is required, for example if the generator is checked at every iteration (and is running local). If the generator passed a certain check, then other remote models need to be checked, which will only be done very sporadically. This could give::
def generatedEnough(time, model):
# First a local check
if model.generator.state.generated <= 5:
return False
else:
# Now a remote check, but we know that this will only be called rarely
return model.processor2.getState(time).processed > 5
myQueue = CQueue()
sim = Simulator(myQueue)
sim.setTerminationCondition(generatedEnough)
# Only mark the generator as a termination model
sim.setTerminationModel(myQueue.generator)
sim.simulate()

102
doc/distribution.rst Normal file
View file

@ -0,0 +1,102 @@
..
Copyright 2014 Modelling, Simulation and Design Lab (MSDL) at
McGill University and the University of Antwerp (http://msdl.cs.mcgill.ca/)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Distribution
============
For the modeller, distribution of models is as simple as providing a model (either atomic or coupled) with the location where it should run and afterwards running it as a distributed simulation (as mentioned in :doc:`howto`).
.. note:: Since version 2.1, model distribution was simplified and a lot of restrictions on the model were dropped. Every model that was valid in local simulation, should now also be valid in distributed simulation.
Distributing the queue
----------------------
Let's extend the *CQueue* example from previous sections and add some distribution to it. Of course, this model only has two atomic models, which is clearly not an ideal model to distribute. Therefore, we will add some *Queue* atomic models in parallel, all taking the output from the single generator. Our model should look something like:
.. image:: distribution_local_model.png
:alt: The model to distribute
We will start of with implementing this model locally. This implementation is simple and shouldn't be more difficult than the model in :doc:`examples`. It should look something like::
class DQueue(CoupledDEVS):
def __init__(self):
CoupledDEVS.__init__(self, "DQueue")
self.generator = self.addSubModel(Generator())
self.queue1 = self.addSubModel(Queue())
self.queue2 = self.addSubModel(Queue())
self.connectPorts(self.generator.outport, self.queue1.inport)
self.connectPorts(self.generator.outport, self.queue2.inport)
.. note:: Our original *Queue* atomic model was not written with multiple instances in mind. Therefore, the model name will **not** be unique in this simulation. In later versions of PyPDEVS, this doesn't pose a problem apart from possibly confusing trace output.
Now all that is left is performing the actual distribution. Suppose we run 3 different nodes, with every atomic model on a seperate node. Thus the *Generator* runs on node 0, the first *Queue* runs on node 1 and the final *Queue* runs on node 2. This is as simple as altering the *addSubModel* method calls and add the desired node of the model being added. This results in::
class DQueue(CoupledDEVS):
def __init__(self):
CoupledDEVS.__init__(self, "DQueue")
self.generator = self.addSubModel(Generator(), 0)
self.queue1 = self.addSubModel(Queue(), 1)
self.queue2 = self.addSubModel(Queue(), 2)
self.connectPorts(self.generator.outport, self.queue1.inport)
self.connectPorts(self.generator.outport, self.queue2.inport)
Setting the location of a model, will automatically set the location of all its submodels with an unspecified location to the same location.
.. note:: Models for location 0 do not necessarily need to be specified, as the default is node 0. Leaving this option open (or specifying the location as *None*) means that it should take the location of the parent.
Additional options
------------------
A *Simulator* object that is initialized with a distributed model has several extra options that can be configured.
More advanced options are elaborated further on in their own :doc:`advanced` subsection.
+------------------------------------+-------------------------------------------------------+
|*setTerminationModel(model)* | Marks *model* as being used in termination condition |
+------------------------------------+-------------------------------------------------------+
|*registerState(variable, model)* | Register a state to be fetched after simulation |
+------------------------------------+-------------------------------------------------------+
|*setFetchAllAfterSimulation(fetch)* | Completely update the model after simulation |
+------------------------------------+-------------------------------------------------------+
|*setGVTInterval(gvt_int)* | Calculate the GVT after every *gvt_int* seconds |
+------------------------------------+-------------------------------------------------------+
|*setCheckpointing(name, chk_int)* | Create a checkpoint after every *chk_int* GVT creation|
+------------------------------------+-------------------------------------------------------+
|*setStateSaving(state_saving)* | Change the method for state saving to *state_saving* |
+------------------------------------+-------------------------------------------------------+
.. note:: If any of these options are set during a local simulation, they will either throw a Exception or will simply have no effect.
Automatic allocation
--------------------
Some models are very simple, but tedious, to distribute. As long as the actual allocation has no real importance, there is an additional option *setAutoAllocation(autoAllocate)* which will have the simulator distribute the models automatically. This distribution is rather efficient, though it is often suboptimal. It will simply try to balance the amount of atomic models at every node, not taking into account the actual activity of these models. Furthermore, it only works at the root level.
Even though it doesn't perform intelligent distribution, it will work in situations where root models can work in parallel without influencing each other. More information can be found in :doc:`Static Allocators <staticallocator>`
General tips
------------
The distributed simulation algorithm that is being used is Time Warp. This means that every node will simulate *optimistically* in the assumption that other models have progressed as far as itself. As soon as a message from the past arrives, simulation at that node will be rolled back. This implies that nodes should have an equal load and that as few as messages should be exchanged between different nodes.
Therefore, the following rules should be taken into account to maximize the performance in distributed simulation:
* Models that exchange lots of messages should be on the same node
* Balance the load between nodes, so that the deviation is minimal
* Use homogeneous nodes
* Use quantums where possible, thus reducing the amount of messages
.. note:: Due to time warp's property of saving (nearly) everything, it is possible to quickly run out of memory. It is therefore adviced to set the GVT calculation time to a reasonable number. Running the GVT algorithm frequently yields slightly worse performance, though it will clean up a lot of memory.

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

44
doc/dynamicallocator.rst Normal file
View file

@ -0,0 +1,44 @@
..
Copyright 2014 Modelling, Simulation and Design Lab (MSDL) at
McGill University and the University of Antwerp (http://msdl.cs.mcgill.ca/)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Dynamic Allocator
=================
Dynamic allocators are almost the same as the static allocators, with the exception that they make their allocation at a GVT calculation step instead of at the beginning.
Before the dynamic allocator gets to run, the model will have already simulated for some time. Due to this simulation, which will have happened completely locally, the simulator will have already collected activity information.
Since the simulation ran locally and with the pure intention of finding a decent allocation, the simulator will gather a lot more statistics such as the amount of transferred messages over a connection.
In dynamic allocation mode, the model drawing functionality will also mention the load on the connections.
Writing a custom allocator
--------------------------
Writing a dynamic allocator is the same as writing a static allocator, with the exception that the 2 arguments which were always *None* will now be filled in.
The *edges* argument will contain a dictionary with another dictionary in it. The value in it will be a message counter. This means that the value returned by *edges[a][b]* will be the amount of messages between *a* and *b*.
The *totalActivities* argument will be a dictionary containing the accumulated activity of every model in the complete simulation.
Which of this information is used (and how) is of course up to you.
The *getTerminationTime()* method will now be used to indicate how long a *warm-up* period takes for the simulator. This will not be done perfectly, but up to the first GVT calculation that goes beyond this termination time. If this method returns *0*, it becomes a static allocator and will not be able to use the 2 parameters that are dependent on the simulation run.
Using the allocator
-------------------
Using a dynamic allocator is identical to using a static one, so please refer to the explanation in the :doc:`previous section <staticallocator>`

76
doc/dynamicstructure.rst Normal file
View file

@ -0,0 +1,76 @@
..
Copyright 2014 Modelling, Simulation and Design Lab (MSDL) at
McGill University and the University of Antwerp (http://msdl.cs.mcgill.ca/)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Dynamic Structure
=================
.. note:: Currently, Dynamic Structure DEVS is an *option* for local simulation only. It is not enabled by default due to the additional (relatively costly) check for whether or not to transition.
Dynamic Structure DEVS (DSDEVS) is not actually an 'extension' to DEVS, though it offers some more advanced features in a simpler way. The basic idea is that a model (both coupled and atomic) can now cause a change in the structure of the model. This means adding submodels, deleting submodels, adding ports, removing ports, disconnecting ports and connecting ports. Such changes happen at run-time and cause a slight overhead due to the model being saved internally in a slightly different way.
The way DSDEVS works in PyPDEVS is similar to how it works in adevs. After each transition function, the transitioned model will have its *modelTransition(state)* method called. If it returns *True*, the model will signal its parent model that it requests a structural change. The model will then have its *modelTransition(state)* called too, which is allowed to make structural changes. If it requires changes at a higher level, it should return *True* again, to have the structural change request being propagated upwards. If it returns *False*, no change is requested.
All structural changes should happen in a *coupled model*, as this is kind of model has a structural role. The **only** structural change that is allowed in the *modelTransition(state)* method is the addition or removal of ports. Removing a port will also remove all of its current connections. Adding a port will not yet have an effect on the model itself, as it is not yet connected and requires further structural changes at a higher level. The *modelTransition(state)* method is **NOT** allowed to perform any changes to the model state, only to the state that is provided as an argument (which contains a dictionary).
The *modelTransition* method takes a *state* parameter, which is simply a dictionary that will be passed everytime. If you require modularity, it is possible to use e.g. the fully qualified name of the modelas the key and store another dictionary in here. This functionality can be used to support structural changes that require some kind of memory before deciding whether or not to perform such a change.
Interface
---------
Several methods are added or altered to support DSDEVS. These functions are:
* *removePort(port)*: remove the *port* argument from the ports. Any further usage of the port, such as saved references, should no longer be used as they result in simulation errors. It is best practice to manually delete the port by invoking the *del* Python instruction.
* *addInPort(name)*: similar to the normal usage, but now extended to allow changes at run-time.
* *addOutPort(name)*: similar to the normal usage, but now extended to allow changes at run-time.
* *addSubModel(model)*: similar to the normal usage, but now extended to allow changes at run-time.
* *removeSubModel(model)*: remove the *model* as a child, including all its submodels (if applicable). All of the relevant ports are also removed, thus breaking all these connections. After a model is removed, there is no way to get it back.
* *connectPorts(port1, port2)*: similar to the normal usage, but now extended to allow changes at run-time.
* *disconnectPorts(port1, port2)*: removes the connection between the ports. These ports should correspond to the parameters previously passed to the *connectPorts* method.
DSDEVS should also be enabled as a configuration parameter, using the *setDSDEVS(dsdevs)* method of the simulator. The default is *False* due to the simulation overhead.
Example
-------
A complete example will not be provided here, but instead only the relevant *modelTransition(state)* methods will be shown.
Enabling the use of DSDEVS is done as follows::
sim = Simulator(MyDSModel())
sim.setDSDEVS(True)
sim.simulate()
The actual model can be something like::
class MyAtomicDSModel(AtomicDEVS):
...
def modelTransition(self, state):
if self.state.currenttime == 5.0:
self.removePort(self.outport) # Remove the output port
del self.outport # Remove our own reference to the port
return True # We need a structural change from the parent too
else:
return False # Nothing to do
...
class MyDSModel(CoupledDEVS):
...
def modelTransition(self, state):
self.removeModel(self.atomic) # Remove the model stored in 'self.atomic' from the simulation
return False # No need to propagate the change
...

17
doc/elapsed_time.rst Normal file
View file

@ -0,0 +1,17 @@
.. _elapsed_time:
Elapsed time
============
In PythonPDEVS, the elapsed time is present in the attribute *elapsed*.
While the attribute is always there, its value is only guaranteed to be correct during the execution of an external transition.
For compatibility reasons, in Parallel DEVS, the confluent transition will also set the elapsed time to 0.
It might seem strange to put the elapsed time as an attribute, instead of a parameter.
The reason for this is that, during model initialization, it is possible to set the elapsed time to some value.
The time at which that specific atomic model was initialized, will then be set to the negative elapsed time.
This implies that the initial time advance is larger than the configured elapsed time.
For example, if a model sets its elapsed time to -1.5 during model initialization, and has a time advance of 2, the first transition will happen at simulated time 0.5.
Note, however, that for the model it will seem as if the actual 2 units of simulated time have passed.
It thus merely allows for a fixed deviation between the starting point of different models.

296
doc/examples.rst Normal file
View file

@ -0,0 +1,296 @@
..
Copyright 2014 Modelling, Simulation and Design Lab (MSDL) at
McGill University and the University of Antwerp (http://msdl.cs.mcgill.ca/)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Examples for Parallel DEVS
==========================
A small *trafficModel* and corresponding *trafficExperiment* file is included in the *examples* folder of the PyPDEVS distribution. This (completely working) example is slightly too big to use as a first introduction to PyPDEVS and therefore this page will start with a very simple example.
For this, we will first introduce a simplified queue model, which will be used as the basis of all our examples. The complete model can be downloaded: :download:`queue_example.py <queue_example.py>`.
This section should provide you with all necessary information to get you started with creating your very own PyPDEVS simulation. More advanced features are presented in the next section.
Generator
---------
Somewhat simpler than a queue even, is a generator. It will simply create a message to send after a certain delay and then it will stop doing anything.
Informally, this would result in a DEVS specification as:
* Time advance function returns the waiting time to generate the message, infinity after the message was created
* Output function outputs the generated message
* Internal transition function marks the generator as done
* External transition function will never happen (as there are no inputs)
* Confluent transition function will never happen (as no external transition will ever happen)
In PythonPDEVS, this simply becomes the class::
class Generator(AtomicDEVS):
def __init__(self):
AtomicDEVS.__init__(self, "Generator")
self.state = True
self.outport = self.addOutPort("outport")
def timeAdvance(self):
if self.state:
return 1.0
else:
return INFINITY
def outputFnc(self):
# Our message is simply the integer 5, though this could be anything
# It is mandatory to output lists (which signify the 'bag')
return {self.outport: [5]}
def intTransition(self):
self.state = False
return self.state
Note that all functions have a *default* behaviour, which is sufficient if the function will never be called. In most situations, the *confTransition* function is sufficient and rarely requires to be overwritten.
It is possible to simulate this model, though nothing spectacular will happen. For this reason, we will postpone actual simulation examples.
Simple queue
------------
To couple the *Generator* model up to something useful, we will now create a simple queue model. It doesn't do any real computation and just forwards the message after a certain (fixed) time delay. For simplicity, we allow the queue to *drop* the previous message if a message was already being processed.
Informally, this would result in a DEVS specification as:
* Time advance function returns the processing time if a message is being processed, or INFINITY otherwise
* Output function outputs the message
* Internal transition function removes the message from the queue
* External transition function adds the message to the queue
* Confluent transition function can simply default to the internal transition function, immeditaly followed by the external transition function
To implement this in PythonPDEVS, one simply has to write::
class Queue(AtomicDEVS):
def __init__(self):
AtomicDEVS.__init__(self, "Queue")
self.state = None
self.processing_time = 1.0
self.inport = self.addInPort("input")
self.outport = self.addOutPort("output")
def timeAdvance(self):
if self.state is None:
return INFINITY
else:
return self.processing_time
def outputFnc(self):
return {self.outport: [self.state]}
def extTransition(self, inputs):
# To access them, you will need to access each element seperately
# In this case, we only use the first element from the bag as we
# know that it will only contain one element
self.state = inputs[self.inport][0]
return self.state
def intTransition(self):
self.state = None
return self.state
And we are done with our basic queue model.
However, there is currently no means of testing it, as simply simulating this model will have no effect, due to no messages arriving. We will thus have to couple it with the *Generator* we previously made.
Coupling
--------
To couple up the *Generator* to the *Queue*, all we have to do is create a *CoupledDEVS* class and simulate this class::
class CQueue(CoupledDEVS):
def __init__(self):
CoupledDEVS.__init__(self, "CQueue")
self.generator = self.addSubModel(Generator())
self.queue = self.addSubModel(Queue())
self.connectPorts(self.generator.outport, self.queue.inport)
That is all for the coupled model. Note that it is not required for every port of a model to be connected to another port. For example the *outport* of the *Queue* is not connected. Any output that is put on this port is thus discarded.
It is perfectly allowed to do model construction and connection in e.g. a loop or conditionally, as long as the required functions are called.
.. note:: The DEVS formalism allows for an input-to-output translation function, but this is not implemented in PythonPDEVS.
Simulation
----------
Now that we have an actual coupled model that does something remotely useful, it is time to simulate it. Simulation is as simple as constructing a *Simulator* object with the model and calling *simulate()* on the simulator::
model = CQueue()
sim = Simulator(model)
sim.simulate()
Sadly, nothing seems to happen because no tracers are enabled. Note that it is possible to access the attributes of the model and see that they are actually changed as directed by the simulation::
model = CQueue()
print(model.generator.state)
sim = Simulator(model)
sim.simulate()
print(model.generator.state)
This code will simply print *True* in the beginning and *False* at the end, since the model is updated in-place in this situation. The model will **not** be simulated in-place if either simulation is distributed, or reinitialisation is enabled.
Tracing
-------
To actually see some results from the simulation, it is advised to enable certain tracers. The simplest tracer is the *verbose* tracer, which will output some details in a human-readable format. Enabling the verbose tracer is as simple as setting the *setVerbose()* configuration to a destination file. For the verbose tracer, it is also possible to trace to stdout by using the *None* argument::
model = CQueue()
sim = Simulator(model)
sim.setVerbose(None)
sim.simulate()
Saving the output to a file can de done by passing the file name as a string. Note that a file handle does **not** work::
model = CQueue()
sim = Simulator(model)
sim.setVerbose("myOutputFile")
sim.simulate()
Multiple tracers can be defined simultaneously, all of which will be used. So to trace to the files *myOutputFile* and *myOutputFile* and simultaneously output to stdout, you could use::
model = CQueue()
sim = Simulator(model)
sim.setVerbose("myOutputFile")
sim.setVerbose(None)
sim.setVerbose("myOutputFile2")
sim.simulate()
.. note:: There is no way to unset a single tracer. There is however a way to remove all currently registered tracers: *setRemoveTracers()*, though it is generally only useful in reinitialized simulations.
An example output of the *verbose* tracer is::
__ Current Time: 0.00 __________________________________________
INITIAL CONDITIONS in model <CQueue.Generator>
Initial State: True
Next scheduled internal transition at time 1.00
INITIAL CONDITIONS in model <CQueue.Queue>
Initial State: None
Next scheduled internal transition at time inf
__ Current Time: 1.00 __________________________________________
EXTERNAL TRANSITION in model <CQueue.Queue>
Input Port Configuration:
port <input>:
5
New State: 5
Next scheduled internal transition at time 2.00
INTERNAL TRANSITION in model <CQueue.Generator>
New State: False
Output Port Configuration:
port <outport>:
5
Next scheduled internal transition at time inf
__ Current Time: 2.00 __________________________________________
INTERNAL TRANSITION in model <CQueue.Queue>
New State: None
Output Port Configuration:
port <output>:
5
Next scheduled internal transition at time inf
.. note:: Several other tracers are available, such as *VCD*, *XML* and *Cell*. Their usage is very similar and is only useful in several situations. Only the *Cell* tracer requires further explanation and is mentioned in the *Advanced examples* section.
Termination
-----------
Our previous example stopped simulation automatically, since both models returned a time advance equal to infinity.
In several cases, it is desired to stop simulation after a certain period. The simplest example of this is when the *Generator* would keep generating messages after a certain delay. Without a termination condition, the simulation will keep going forever.
Adding a termination time is as simple as setting one additional configuration option::
sim.setTerminationTime(5.0)
This will make the simulation stop as soon as simulation time 5.0 is reached.
A termination time is sufficient in most situations, though it is possible to use a more advanced approach: using a termination function. Using the option::
sim.setTerminationCondition(termFunc)
With this additional option, the function *termFunc* will be evaluated at every timestep. If the function returns *True*, simulation will stop. The function will receive 2 parameters from the simulator: the model being simulated and the current simulation time.
Should our generator save the number of messages it has generated, an example of such a termination function could be::
def termFunc(clock, model):
if model.generator.generated > 5:
# The generator has generated more than 5 events
# So stop
return True
elif clock[0] > 10:
# Or if the clock has progressed past simulation time 10
return True
else:
# Otherwise, we simply continue
return False
The *clock* parameter in the termination condition will be a **tuple** instead of a simple floating point number. The first field of the tuple is the current simulation time (and can be used as such). The second field is a so-called *age* field, containing the number of times the same simulation time has occured. This is passed on in the termination condition as it is required in some cases for distributed simulation.
.. note:: Using a termination function is a lot slower than simply using a termination time. This option should therefore be avoided if at all possible.
.. warning:: It is **only** allowed to read from the model in the termination function. Performing write actions to the model has unpredictable consequences!
.. warning:: Running a termination function in a distributed simulation is slightly different, so please refer to the advanced section for this!
Simulation time
---------------
Accessing the global simulation time is a frequent operation, though it is not supported by DEVS out-of-the-box. Of course, the simulator internally keeps such a clock, though this is not meant to be accessed by the user directly as it is an implementation detail of PyPDEVS (and it might even change between releases!).
If you require access to the simulation time, e.g. to put a timestamp on a message, this can be done by writing some additional code in the model that requires this time as follows::
class MyModelState():
def __init__(self):
self.actual_state = ...
self.current_time = 0.0
class MyModel(AtomicDEVS):
def __init__(self, ...):
AtomicDEVS.__init__(self, "ExampleModel")
self.state = MyModelState()
...
def extTransition(self, inputs):
self.state.current_time += self.elapsed
...
return self.state
def intTransition(self):
self.state.current_time += self.timeAdvance()
...
return self.state
def confTransition(self, inputs):
self.state.current_time += self.timeAdvance()
...
return self.state
In the *extTransition* method, we use the *elapsed* attribute to determine the time between the last transition and the current transition. However, in the *intTransition* we are **not** allowed to access it.
A more detailed explanation can be found at :ref:`elapsed_time`.
You are allowed to call the *timeAdvance* method again, as this is the time that was waited before calling the internal transition function (as defined in the DEVS formalism).
This requires, however, that your timeAdvance is deterministic (as it should be).
Deterministic timeAdvance functions are not trivial if you use random numbers, for which you should read up on :ref:`random_numbers` in PythonPDEVS.

297
doc/examples_classic.rst Normal file
View file

@ -0,0 +1,297 @@
..
Copyright 2014 Modelling, Simulation and Design Lab (MSDL) at
McGill University and the University of Antwerp (http://msdl.cs.mcgill.ca/)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Examples
========
A small *trafficModel* and corresponding *trafficExperiment* file is included in the *examples* folder of the PyPDEVS distribution. This (completely working) example is slightly too big to use as a first introduction to PyPDEVS and therefore this page will start with a very simple example.
For this, we will first introduce a simplified queue model, which will be used as the basis of all our examples. The complete model can be downloaded: :download:`queue_example_classic.py <queue_example_classic.py>`.
This section should provide you with all necessary information to get you started with creating your very own PyPDEVS simulation. More advanced features are presented in the next section.
Generator
---------
Somewhat simpler than a queue even, is a generator. It will simply create a message to send after a certain delay and then it will stop doing anything.
Informally, this would result in a DEVS specification as:
* Time advance function returns the waiting time to generate the message, infinity after the message was created
* Output function outputs the generated message
* Internal transition function marks the generator as done
* External transition function will never happen (as there are no inputs)
In PythonPDEVS, this simply becomes the class::
class Generator(AtomicDEVS):
def __init__(self):
AtomicDEVS.__init__(self, "Generator")
self.state = True
self.outport = self.addOutPort("outport")
def timeAdvance(self):
if self.state:
return 1.0
else:
return INFINITY
def outputFnc(self):
# Our message is simply the integer 5, though this could be anything
return {self.outport: 5}
def intTransition(self):
self.state = False
return self.state
Note that all functions have a *default* behaviour, which is sufficient if the function will never be called.
It is possible to simulate this model, though nothing spectacular will happen. For this reason, we will postpone actual simulation examples.
Simple queue
------------
To couple the *Generator* model up to something useful, we will now create a simple queue model. It doesn't do any real computation and just forwards the message after a certain (fixed) time delay. For simplicity, we allow the queue to *drop* the previous message if a message was already being processed.
Informally, this would result in a DEVS specification as:
* Time advance function returns the processing time if a message is being processed, or INFINITY otherwise
* Output function outputs the message
* Internal transition function removes the message from the queue
* External transition function adds the message to the queue
To implement this in PythonPDEVS, one simply has to write::
class Queue(AtomicDEVS):
def __init__(self):
AtomicDEVS.__init__(self, "Queue")
self.state = None
self.processing_time = 1.0
self.inport = self.addInPort("input")
self.outport = self.addOutPort("output")
def timeAdvance(self):
if self.state is None:
return INFINITY
else:
return self.processing_time
def outputFnc(self):
return {self.outport: self.state}
def extTransition(self, inputs):
self.state = inputs[self.inport]
return self.state
def intTransition(self):
self.state = None
return self.state
And we are done with our basic queue model.
However, there is currently no means of testing it, as simply simulating this model will have no effect, due to no messages arriving. We will thus have to couple it with the *Generator* we previously made.
Coupling
--------
To couple up the *Generator* to the *Queue*, all we have to do is create a *CoupledDEVS* class and simulate this class::
class CQueue(CoupledDEVS):
def __init__(self):
CoupledDEVS.__init__(self, "CQueue")
self.generator = self.addSubModel(Generator())
self.queue = self.addSubModel(Queue())
self.connectPorts(self.generator.outport, self.queue.inport)
That is all for the coupled model. Note that it is not required for every port of a model to be connected to another port. For example the *outport* of the *Queue* is not connected. Any output that is put on this port is thus discarded.
It is perfectly allowed to do model construction and connection in e.g. a loop or conditionally, as long as the required functions are called.
.. note:: The DEVS formalism allows for an input-to-output translation function, but this is not implemented in PythonPDEVS.
Simulation
----------
Now that we have an actual coupled model that does something remotely useful, it is time to simulate it. Simulation is as simple as constructing a *Simulator* object with the model and calling *simulate()* on the simulator::
model = CQueue()
sim = Simulator(model)
# Required to set Classic DEVS, as we simulate in Parallel DEVS otherwise
sim.setClassicDEVS()
sim.simulate()
Be sure not to forget to call the *setClassicDEVS()* method, as otherwise your model will be simulated using Parallel DEVS (likely resulting into errors).
Sadly, nothing seems to happen because no tracers are enabled. Note that it is possible to access the attributes of the model and see that they are actually changed as directed by the simulation::
model = CQueue()
print(model.generator.state)
sim = Simulator(model)
# Required to set Classic DEVS, as we simulate in Parallel DEVS otherwise
sim.setClassicDEVS()
sim.simulate()
print(model.generator.state)
This code will simply print *True* in the beginning and *False* at the end, since the model is updated in-place in this situation. The model will **not** be simulated in-place if reinitialisation is enabled.
Tracing
-------
To actually see some results from the simulation, it is advised to enable certain tracers. The simplest tracer is the *verbose* tracer, which will output some details in a human-readable format. Enabling the verbose tracer is as simple as setting the *setVerbose()* configuration to a destination file. For the verbose tracer, it is also possible to trace to stdout by using the *None* argument::
model = CQueue()
sim = Simulator(model)
sim.setVerbose(None)
# Required to set Classic DEVS, as we simulate in Parallel DEVS otherwise
sim.setClassicDEVS()
sim.simulate()
Saving the output to a file can de done by passing the file name as a string. Note that a file handle does **not** work::
model = CQueue()
sim = Simulator(model)
sim.setVerbose("myOutputFile")
# Required to set Classic DEVS, as we simulate in Parallel DEVS otherwise
sim.setClassicDEVS()
sim.simulate()
Multiple tracers can be defined simultaneously, all of which will be used. So to trace to the files *myOutputFile* and *myOutputFile* and simultaneously output to stdout, you could use::
model = CQueue()
sim = Simulator(model)
sim.setVerbose("myOutputFile")
sim.setVerbose(None)
sim.setVerbose("myOutputFile2")
# Required to set Classic DEVS, as we simulate in Parallel DEVS otherwise
sim.setClassicDEVS()
sim.simulate()
.. note:: There is no way to unset a single tracer. There is however a way to remove all currently registered tracers: *setRemoveTracers()*, though it is generally only useful in reinitialized simulations.
An example output of the *verbose* tracer is::
__ Current Time: 0.00 __________________________________________
INITIAL CONDITIONS in model <CQueue.Generator>
Initial State: True
Next scheduled internal transition at time 1.00
INITIAL CONDITIONS in model <CQueue.Queue>
Initial State: None
Next scheduled internal transition at time inf
__ Current Time: 1.00 __________________________________________
EXTERNAL TRANSITION in model <CQueue.Queue>
Input Port Configuration:
port <input>:
5
New State: 5
Next scheduled internal transition at time 2.00
INTERNAL TRANSITION in model <CQueue.Generator>
New State: False
Output Port Configuration:
port <outport>:
5
Next scheduled internal transition at time inf
__ Current Time: 2.00 __________________________________________
INTERNAL TRANSITION in model <CQueue.Queue>
New State: None
Output Port Configuration:
port <output>:
5
Next scheduled internal transition at time inf
.. note:: Several other tracers are available, such as *VCD*, *XML* and *Cell*. Their usage is very similar and is only useful in several situations. Only the *Cell* tracer requires further explanation and is mentioned in the *Advanced examples* section.
Termination
-----------
Our previous example stopped simulation automatically, since both models returned a time advance equal to infinity.
In several cases, it is desired to stop simulation after a certain period. The simplest example of this is when the *Generator* would keep generating messages after a certain delay. Without a termination condition, the simulation will keep going forever.
Adding a termination time is as simple as setting one additional configuration option::
sim.setTerminationTime(5.0)
This will make the simulation stop as soon as simulation time 5.0 is reached.
A termination time is sufficient in most situations, though it is possible to use a more advanced approach: using a termination function. Using the option::
sim.setTerminationCondition(termFunc)
With this additional option, the function *termFunc* will be evaluated at every timestep. If the function returns *True*, simulation will stop. The function will receive 2 parameters from the simulator: the model being simulated and the current simulation time.
Should our generator save the number of messages it has generated, an example of such a termination function could be::
def termFunc(clock, model):
if model.generator.generated > 5:
# The generator has generated more than 5 events
# So stop
return True
elif clock[0] > 10:
# Or if the clock has progressed past simulation time 10
return True
else:
# Otherwise, we simply continue
return False
The *clock* parameter in the termination condition will be a **tuple** instead of a simple floating point number. The first field of the tuple is the current simulation time (and can be used as such). The second field is a so-called *age* field, containing the number of times the same simulation time has occured. This is passed on in the termination condition as it is required in some cases for distributed simulation.
.. note:: Using a termination function is a lot slower than simply using a termination time. This option should therefore be avoided if at all possible.
.. warning:: It is **only** allowed to read from the model in the termination function. Performing write actions to the model has unpredictable consequences!
.. warning:: Running a termination function in a distributed simulation is slightly different, so please refer to the advanced section for this!
Simulation time
---------------
Accessing the global simulation time is a frequent operation, though it is not supported by DEVS out-of-the-box. Of course, the simulator internally keeps such a clock, though this is not meant to be accessed by the user directly as it is an implementation detail of PyPDEVS (and it might even change between releases!).
If you require access to the simulation time, e.g. to put a timestamp on a message, this can be done by writing some additional code in the model that requires this time as follows::
class MyModelState():
def __init__(self):
self.actual_state = ...
self.current_time = 0.0
class MyModel(AtomicDEVS):
def __init__(self, ...):
AtomicDEVS.__init__(self, "ExampleModel")
self.state = MyModelState()
...
def extTransition(self, inputs):
self.state.current_time += self.elapsed
...
return self.state
def intTransition(self):
self.state.current_time += self.timeAdvance()
...
return self.state
In the *extTransition* method, we use the *elapsed* attribute to determine the time between the last transition and the current transition. However, in the *intTransition* we are **not** allowed to access it.
A more detailed explanation can be found at :ref:`elapsed_time`.
You are allowed to call the *timeAdvance* method again, as this is the time that was waited before calling the internal transition function (as defined in the DEVS formalism).
This requires, however, that your timeAdvance is deterministic (as it should be).
Deterministic timeAdvance functions are not trivial if you use random numbers, for which you should read up on :ref:`random_numbers` in PythonPDEVS.

223
doc/examples_ds.rst Normal file
View file

@ -0,0 +1,223 @@
..
Copyright 2014 Modelling, Simulation and Design Lab (MSDL) at
McGill University and the University of Antwerp (http://msdl.cs.mcgill.ca/)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Examples for Dynamic Structure DEVS
===================================
We used the same approach to Dynamic Structure as adevs, meaning that a *modelTransition* method will be called at every step in simulated time on every model that transitioned.
A small *trafficLight* model is included in the *examples* folder of the PyPDEVS distribution. In this section, we introduce the use of the new constructs.
Dynamic structure is possible for both Classic and Parallel DEVS, but only for local simulation.
Starting point
--------------
We will start from a very simple Coupled DEVS model, which contains 2 Atomic DEVS models.
The first model sends messages on its output port, which are then routed to the second model.
Note that, for compactness, we include the experiment part in the same file as the model.
This can be expressed as follows (:download:`base_dsdevs.py <base_dsdevs.py>`)::
from pypdevs.DEVS import AtomicDEVS, CoupledDEVS
from pypdevs.simulator import Simulator
class Root(CoupledDEVS):
def __init__(self):
CoupledDEVS.__init__(self, "Root")
self.models = []
# First model
self.models.append(self.addSubModel(Generator()))
# Second model
self.models.append(self.addSubModel(Consumer(0)))
# And connect them
self.connectPorts(self.models[0].outport, self.models[1].inport)
class Generator(AtomicDEVS):
def __init__(self):
AtomicDEVS.__init__(self, "Generator")
# Keep a counter of how many events were sent
self.outport = self.addOutPort("outport")
self.state = 0
def intTransition(self):
# Increment counter
return self.state + 1
def outputFnc(self):
# Send the amount of messages sent on the output port
return {self.outport: [self.state]}
def timeAdvance(self):
# Fixed 1.0
return 1.0
class Consumer(AtomicDEVS):
def __init__(self, count):
AtomicDEVS.__init__(self, "Consumer_%i" % count)
self.inport = self.addInPort("inport")
def extTransition(self, inputs):
for inp in inputs[self.inport]:
print("Got input %i on model %s" % (inp, self.name))
sim = Simulator(Root())
sim.setTerminationTime(5)
sim.simulate()
All undefined functions will just use the default implementation.
As such, this model will simply print messages::
Got input 0 on model Consumer_0
Got input 1 on model Consumer_0
Got input 2 on model Consumer_0
Got input 3 on model Consumer_0
Got input 4 on model Consumer_0
We will now extend this model to include dynamic structure.
Dynamic Structure
-----------------
To allow for dynamic structure, we need to augment both our experiment and our model.
Experiment
^^^^^^^^^^
First, the dynamic structure configuration parameter should be enabled in the experiment.
For performance reasons, this feature is disabled by default. It can be enabled as follows::
sim = Simulator(Root())
sim.setTerminationTime(5)
sim.setDSDEVS(True)
sim.simulate()
Model
^^^^^
With dynamic structure enabled, models can define a new method: *modelTransition(state)*.
This method is called on all Atomic DEVS models that executed a transition in that time step.
This method is invoked on the model, and can thus access the state of the model.
For now, ignore the *state* parameter.
In this method, all modifications on the model are allowed, as it was during model construction.
That is, it is possible to invoke the following methods::
setInPort(portname)
setOutPort(portname)
addSubModel(model)
connectPorts(output, input)
But apart from these known methods, it is also possible to delete existing constructs, with the operations::
removePort(port)
removeSubModel(model)
disconnectPorts(output, input)
On Atomic DEVS models, only the port operations are available. On Coupled DEVS models, all of these are available.
Removing a port or submodel will automatically disconnect all its connections.
The method will also return a boolean, indicating whether or not to propagate the structural changes on to the parent model.
If it is *True*, the method is invoked on the parent as well. Note that the root model should not return *True*.
Propagation is necessary, as models are only allowed to change the structure of their subtree.
.. NOTE::
In the latest implementation, modifying the structure outside of your own subtree has no negative consequences. However, it should be seen as a best practice to only modify yourself.
For example, to create a second receiver as soon as the generator has output 3 messages, you can modify the following methods (:download:`simple_dsdevs.py <simple_dsdevs.py>`)::
class Generator(AtomicDEVS):
...
def modelTransition(self, state):
# Notify parent of structural change if state equals 3
return self.state == 3
class Root(CoupledDEVS):
...
def modelTransition(self, state):
# We are notified, so are required to add a new model and link it
self.models.append(self.addSubModel(Consumer(1)))
self.connectPorts(self.models[0].outport, self.models[-1].inport)
## Optionally, we could also remove the Consumer(0) instance as follows:
# self.removeSubModel(self.models[1])
# Always returns False, as this is top-level
return False
This would give the following output (or similar, due to concurrency)::
Got input 0 on model Consumer_0
Got input 1 on model Consumer_0
Got input 2 on model Consumer_0
Got input 3 on model Consumer_0
Got input 3 on model Consumer_1
Got input 4 on model Consumer_0
Got input 4 on model Consumer_1
.. NOTE::
As structural changes are not a common operation, their performance is not optimized extensively. To make matters worse, many structural optimizations done by PythonPDEVS will automatically be redone after each structural change.
Passing state
^^^^^^^^^^^^^
Finally, we come to the *state* parameter of the modelTransition call.
In some cases, it will be necessary to pass arguments to the parent, to notify it of how the structure should change.
This is useful if the child knows information that is vital to the change.
Since Coupled DEVS models cannot hold state, and should not directly access the state of their children, we can use the *state* parameter for this.
The *state* parameter is simply a dictionary object, which is passed between all the different *modelTransition* calls.
Simply put, it is an object shared by all calls.
For example, if we would want the structural change from before to create a new consumer every time, with an ID provided by the Generator, this can be done as follows (:download:`state_dsdevs.py <state_dsdevs.py>`)::
class Generator(AtomicDEVS):
...
def modelTransition(self, state):
# We pass on the ID that we would like to create, which is equal to our counter
state["ID"] = self.state
# Always create a new element
return True
class Root(CoupledDEVS):
...
def modelTransition(self, state):
# We are notified, so are required to add a new model and link it
# We can use the ID provided by the model below us
self.models.append(self.addSubModel(Consumer(state["ID"])))
self.connectPorts(self.models[0].outport, self.models[-1].inport)
# Always returns False, as this is top-level
return False
This would then create the output (or similar, due to concurrency)::
Got input 0 on model Consumer_0
Got input 1 on model Consumer_0
Got input 1 on model Consumer_1
Got input 2 on model Consumer_0
Got input 2 on model Consumer_1
Got input 2 on model Consumer_2
Got input 3 on model Consumer_0
Got input 3 on model Consumer_1
Got input 3 on model Consumer_2
Got input 3 on model Consumer_3
More complex example
====================
In the PyPDEVS distribution, a more complex example is provided.
That example provides a model of two traffic lights, with a policeman who periodically changes the traffic light he is interrupting.

22
doc/experiment_fast.py Normal file
View file

@ -0,0 +1,22 @@
# Copyright 2014 Modelling, Simulation and Design Lab (MSDL) at
# McGill University and the University of Antwerp (http://msdl.cs.mcgill.ca/)
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from pypdevs.simulator import Simulator
from trafficLightModel import *
model = TrafficLight(name="trafficLight")
sim = Simulator(model)
sim.setVerbose(None)
sim.simulate()

34
doc/experiment_loop.py Normal file
View file

@ -0,0 +1,34 @@
# Copyright 2014 Modelling, Simulation and Design Lab (MSDL) at
# McGill University and the University of Antwerp (http://msdl.cs.mcgill.ca/)
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from pypdevs.simulator import Simulator
from trafficLightModel import *
model = TrafficLight(name="trafficLight")
refs = {"INTERRUPT": model.INTERRUPT}
sim = Simulator(model)
sim.setRealTime(True)
sim.setRealTimeInputFile(None)
sim.setRealTimePorts(refs)
sim.setVerbose(None)
sim.setRealTimePlatformGameLoop()
sim.simulate()
import time
while 1:
before = time.time()
sim.realtime_loop_call()
time.sleep(0.1 - (before - time.time()))
print("Current state: " + str(model.state.get()))

33
doc/experiment_threads.py Normal file
View file

@ -0,0 +1,33 @@
# Copyright 2014 Modelling, Simulation and Design Lab (MSDL) at
# McGill University and the University of Antwerp (http://msdl.cs.mcgill.ca/)
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from pypdevs.simulator import Simulator
from trafficLightModel import *
model = TrafficLight(name="trafficLight")
refs = {"INTERRUPT": model.INTERRUPT}
sim = Simulator(model)
sim.setRealTime(True)
sim.setRealTimeInputFile(None)
sim.setRealTimePorts(refs)
sim.setVerbose(None)
sim.setRealTimePlatformThreads()
sim.simulate()
# If we get here, simulation will also end, as the sleep calls are daemon threads
# (otherwise, they would make the simulation unkillable)
while 1:
sim.realtime_interrupt(raw_input())

90
doc/experiment_tk.py Normal file
View file

@ -0,0 +1,90 @@
# Copyright 2014 Modelling, Simulation and Design Lab (MSDL) at
# McGill University and the University of Antwerp (http://msdl.cs.mcgill.ca/)
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from pypdevs.simulator import Simulator
from Tkinter import *
from trafficLightModel import *
isBlinking = None
model = TrafficLight(name="trafficLight")
refs = {"INTERRUPT": model.INTERRUPT}
root = Tk()
sim = Simulator(model)
sim.setRealTime(True)
sim.setRealTimeInputFile(None)
sim.setRealTimePorts(refs)
sim.setVerbose(None)
sim.setRealTimePlatformTk(root)
def toManual():
global isBlinking
isBlinking = False
sim.realtime_interrupt("INTERRUPT toManual")
def toAutonomous():
global isBlinking
isBlinking = None
sim.realtime_interrupt("INTERRUPT toAutonomous")
size = 50
xbase = 10
ybase = 10
frame = Frame(root)
canvas = Canvas(frame)
canvas.create_oval(xbase, ybase, xbase+size, ybase+size, fill="black", tags="red_light")
canvas.create_oval(xbase, ybase+size, xbase+size, ybase+2*size, fill="black", tags="yellow_light")
canvas.create_oval(xbase, ybase+2*size, xbase+size, ybase+3*size, fill="black", tags="green_light")
canvas.pack()
frame.pack()
def updateLights():
state = model.state.get()
if state == "red":
canvas.itemconfig("red_light", fill="red")
canvas.itemconfig("yellow_light", fill="black")
canvas.itemconfig("green_light", fill="black")
elif state == "yellow":
canvas.itemconfig("red_light", fill="black")
canvas.itemconfig("yellow_light", fill="yellow")
canvas.itemconfig("green_light", fill="black")
elif state == "green":
canvas.itemconfig("red_light", fill="black")
canvas.itemconfig("yellow_light", fill="black")
canvas.itemconfig("green_light", fill="green")
elif state == "manual":
canvas.itemconfig("red_light", fill="black")
global isBlinking
if isBlinking:
canvas.itemconfig("yellow_light", fill="yellow")
isBlinking = False
else:
canvas.itemconfig("yellow_light", fill="black")
isBlinking = True
canvas.itemconfig("green_light", fill="black")
root.after(500, updateLights)
b = Button(root, text="toManual", command=toManual)
b.pack()
c = Button(root, text="toAutonomous", command=toAutonomous)
c.pack()
root.after(100, updateLights)
sim.simulate()
root.mainloop()

View file

@ -0,0 +1,21 @@
..
Copyright 2014 Modelling, Simulation and Design Lab (MSDL) at
McGill University and the University of Antwerp (http://msdl.cs.mcgill.ca/)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Greedy Allocator
================
.. autoclass:: allocators.greedyAllocator.GreedyAllocator
:members:

74
doc/howto.rst Normal file
View file

@ -0,0 +1,74 @@
..
Copyright 2014 Modelling, Simulation and Design Lab (MSDL) at
McGill University and the University of Antwerp (http://msdl.cs.mcgill.ca/)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
How to run PyPDEVS
==================
There are several ways to start up a PythonPDEVS simulation. The simplest of them all is simply by running the experiment file, as discussed in the :doc:`examples` section. For completeness, this method will also be mentioned as the first option.
Local simulation
----------------
The simplest method is simply by running the experiment file, which looks something like::
model = MyModel()
sim = Simulator(model)
sim.simulate()
For a more elaborate approach, please see the :doc:`examples` section.
Distributed simulation with MPI
-------------------------------
Running the MPI version requires the *mpi4py* Python module.
Since version 2.1, running the MPI version was completely redesigned and severely simplified. PyPDEVS will now automatically detect the usage of MPI when the correct invocation is done.
.. code-block:: bash
mpirun -np 3 python experiment.py
Depending on the MPI backend that is used, several additional options might be possible. Please consult the documentation of your MPI implementation for more information.
Distributed simulation with PyRO
--------------------------------
.. versionchanged:: 2.2.0
PyRO support was dropped.
.. note:: This method is no longer advised or actively developed. Most special features are **only** supported on MPI due to limitations in PyRO. Furthermore, PyRO is dramatically slow in comparison to MPI.
Running the PyRO version requires the *PyRO4* Python module.
Starting a simulation in PyRO is almost as simple as starting it as if it was a local simulation. The only difference is that we require both a nameserver and (possibly multiple) simulation node(s).
A basic version of the nameserver can be started running:
.. code-block:: bash
python -m Pyro4.naming
As soon as this nameserver is started up, each simulation node still needs to be set up. This can be done by running the :doc:`server` file with the name of the server as a parameter. For example:
.. code-block:: bash
user@node-1$ python server.py 1
user@node-2$ python server.py 2
user@node-3$ python server.py 3
The name of the server should be incremental numbers, starting from 1. The server with name 0 is reserved for the controller, as is the common naming in MPI.
.. warning:: PyRO simulation is possibly started from several different folders, which might cause import problems. PyRO transfers models as pickles, so the user should make sure that the **exact** same file structure is visible for the referenced files.

44
doc/index.rst Normal file
View file

@ -0,0 +1,44 @@
..
Copyright 2014 Modelling, Simulation and Design Lab (MSDL) at
McGill University and the University of Antwerp (http://msdl.cs.mcgill.ca/)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
.. PythonPDEVS documentation master file, created by
sphinx-quickstart on Sat Sep 28 20:20:40 2013.
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.
Welcome to PythonPDEVS's documentation!
=======================================
The Interface section contains all information about the functions and methods that should be used by the modeller. Internal documentation shows the complete interface that is only supposed to be used by the simulator itself.
.. warning:: Only the methods mentioned in the Interface subsection should be used by the user, all others can have unexpected side effects due to the use of Time-Warp!
Contents:
.. toctree::
:maxdepth: 2
Installation <installation>
How to run <howto>
Changelog <changelog>
Interface <interface>
Differences from PyDEVS <differences>
Examples (Classic DEVS) <examples_classic>
Examples (Parallel DEVS) <examples>
Examples (Dynamic Structure DEVS) <examples_ds>
Advanced examples <advanced>
Common problems and solutions <problems>
Internal documentation <internal>

20
doc/infinity.rst Normal file
View file

@ -0,0 +1,20 @@
..
Copyright 2014 Modelling, Simulation and Design Lab (MSDL) at
McGill University and the University of Antwerp (http://msdl.cs.mcgill.ca/)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Infinity object
===============
.. autodata:: infinity.INFINITY

192
doc/injection.py Normal file
View file

@ -0,0 +1,192 @@
# Copyright 2014 Modelling, Simulation and Design Lab (MSDL) at
# McGill University and the University of Antwerp (http://msdl.cs.mcgill.ca/)
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Import code for DEVS model representation:
from pypdevs.infinity import *
from pypdevs.DEVS import *
# ====================================================================== #
class TrafficLightMode:
"""Encapsulates the system's state
"""
###
def __init__(self, current="red"):
"""Constructor (parameterizable).
"""
self.set(current)
def set(self, value="red"):
self.__colour=value
def get(self):
return self.__colour
def __str__(self):
return self.get()
class TrafficLight(AtomicDEVS):
"""A traffic light
"""
###
def __init__(self, name=None):
"""Constructor (parameterizable).
"""
# Always call parent class' constructor FIRST:
AtomicDEVS.__init__(self, name)
# STATE:
# Define 'state' attribute (initial sate):
self.state = TrafficLightMode("red")
# PORTS:
# Declare as many input and output ports as desired
# (usually store returned references in local variables):
self.INTERRUPT = self.addInPort(name="INTERRUPT")
self.OBSERVED = self.addOutPort(name="OBSERVED")
###
def extTransition(self, inputs):
"""External Transition Function."""
# Compute the new state 'Snew' based (typically) on current
# State, Elapsed time parameters and calls to 'self.peek(self.IN)'.
#input = self.peek(self.INTERRUPT)
input = inputs[self.INTERRUPT][0]
state = self.state.get()
if input == "toManual":
if state == "manual":
# staying in manual mode
return TrafficLightMode("manual")
if state in ("red", "green", "yellow"):
return TrafficLightMode("manual")
else:
raise DEVSException(\
"unknown state <%s> in TrafficLight external transition function"\
% state)
if input == "toAutonomous":
if state == "manual":
return TrafficLightMode("red")
else:
raise DEVSException(\
"unknown state <%s> in TrafficLight external transition function"\
% state)
raise DEVSException(\
"unknown input <%s> in TrafficLight external transition function"\
% input)
###
def intTransition(self):
"""Internal Transition Function.
"""
state = self.state.get()
if state == "red":
return TrafficLightMode("green")
elif state == "green":
return TrafficLightMode("yellow")
elif state == "yellow":
return TrafficLightMode("red")
else:
raise DEVSException(\
"unknown state <%s> in TrafficLight internal transition function"\
% state)
###
def outputFnc(self):
"""Output Funtion.
"""
# A colourblind observer sees "grey" instead of "red" or "green".
# BEWARE: ouput is based on the OLD state
# and is produced BEFORE making the transition.
# We'll encode an "observation" of the state the
# system will transition to !
# Send messages (events) to a subset of the atomic-DEVS'
# output ports by means of the 'poke' method, i.e.:
# The content of the messages is based (typically) on current State.
state = self.state.get()
if state == "red":
return {self.OBSERVED: ["grey"]}
elif state == "green":
return {self.OBSERVED: ["yellow"]}
# NOT return {self.OBSERVED: ["grey"]}
elif state == "yellow":
return {self.OBSERVED: ["grey"]}
# NOT return {self.OBSERVED: ["yellow"]}
else:
raise DEVSException(\
"unknown state <%s> in TrafficLight external transition function"\
% state)
###
def timeAdvance(self):
"""Time-Advance Function.
"""
# Compute 'ta', the time to the next scheduled internal transition,
# based (typically) on current State.
state = self.state.get()
if state == "red":
return 3
elif state == "green":
return 2
elif state == "yellow":
return 1
elif state == "manual":
return INFINITY
else:
raise DEVSException(\
"unknown state <%s> in TrafficLight time advance transition function"\
% state)
# ====================================================================== #
class TrafficLightSystem(CoupledDEVS):
def __init__(self):
CoupledDEVS.__init__(self, "System")
self.light = self.addSubModel(TrafficLight("Light"))
self.observed = self.addOutPort(name="observed")
self.connectPorts(self.light.OBSERVED, self.observed)
def my_function(event):
print("Observed the following event: " + str(event))
from pypdevs.simulator import Simulator
model = TrafficLightSystem()
sim = Simulator(model)
sim.setRealTime()
sim.setListenPorts(model.observed, my_function)
sim.simulate()
import time
while 1:
time.sleep(0.1)

49
doc/install_mpi4py.sh Executable file
View file

@ -0,0 +1,49 @@
#!/bin/bash
set -e
rm -r build-mpi || true
mkdir build-mpi
cd build-mpi
mkdir mpich-build
mkdir mpich
base=`pwd`
cd mpich-build
wget http://www.mpich.org/static/downloads/3.1.2/mpich-3.1.2.tar.gz
tar -xvzf mpich-3.1.2.tar.gz
cd mpich-3.1.2
./configure --prefix=$base/mpich --with-device=ch3:sock --disable-fortran
make -j5
make install
export PATH=$base/mpich/bin:$PATH
cd ../..
mkdir mpi4py
cd mpi4py
wget https://pypi.python.org/packages/source/m/mpi4py/mpi4py-1.3.1.tar.gz
tar -xvzf mpi4py-1.3.1.tar.gz
cd mpi4py-1.3.1
# Force the correct MPI distribution
rm mpi.cfg || true
echo "[mpi]" >> mpi.cfg
echo "" >> mpi.cfg
echo "include_dirs = $base/mpich/include" >> mpi.cfg
echo "libraries = mpi" >> mpi.cfg
echo "library_dirs = $base/mpich/lib" >> mpi.cfg
echo "runtime_library_dirs = $base/mpich/lib" >> mpi.cfg
echo "mpicc = $base/mpich/bin/mpicc" >> mpi.cfg
echo "mpicxx = $base/mpich/bin/mpicxx" >> mpi.cfg
python setup.py build --mpi=mpi
python setup.py install --user
echo "=============================="
echo "== All done =="
echo "=============================="
echo ""
echo "Please add $base/mpich/bin to your PATH"
echo "This is done by adding the line"
echo " export PATH=$base/mpich/bin:\$PATH"
echo "to a file parsed at system startup, e.g. ~/.bashrc"
echo "For example for Ubuntu: http://askubuntu.com/questions/60218/how-to-add-a-directory-to-my-path"
echo ""
echo "Alternative:"
echo "A symbolic link can be made from /usr/local/bin/mpirun to $base/mpich/bin/mpirun (probably requires root)"
echo "This is done with the command:"
echo " sudo ln -s $base/mpich/bin/mpirun /usr/local/bin/mpirun"

95
doc/installation.rst Normal file
View file

@ -0,0 +1,95 @@
..
Copyright 2014 Modelling, Simulation and Design Lab (MSDL) at
McGill University and the University of Antwerp (http://msdl.cs.mcgill.ca/)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Installation
============
This section describes the necessary steps for installing PyPDEVS.
Dependencies
------------
The following dependencies are mandatory:
* python 2.7
For parallel and distributed simulation, the following additional dependencies are required:
* MPICH3 with socket device
* mpi4py
Installation instructions are given for these two dependencies further in this section.
Realtime simulation using the Tk backend, obviously requires Tk.
PyPDEVS Installation
--------------------
Execute the following command in the 'src' folder::
python setup.py install --user
Afterwards, PyPDEVS should be installed. This can easily be checked with the command::
python -c "import pypdevs"
If this returns without errors, PyPDEVS is sucessfully installed.
Parallel and distributed simulation with mpi4py
-----------------------------------------------
.. note:: An installation script for mpi4py and MPICH3 is provided in :download:`install_mpi4py.sh <install_mpi4py.sh>`. At the end, you will still need to add mpi to your PATH though, as explained by the script.
First of all, an MPI middleware has to be installed, for which I recommend MPICH3.
Due to some non-standard configuration options, it is required to install MPICH manually instead of using the one from the repositories.
You can use either the official installation guide, or follow the steps below.
Just make sure that the correct configuration options are used.
The following commands should work on most systems, just replace the '/home/you' part with a location of your choosing::
mkdir mpich-build
mkdir mpich
base=`pwd`
cd mpich-build
wget http://www.mpich.org/static/downloads/3.1.2/mpich-3.1.2.tar.gz
tar -xvzf mpich-3.1.2.tar.gz
cd mpich-3.1.2
./configure --prefix=$base/mpich --with-device=ch3:sock --disable-fortran
make
make install
export PATH=$base/mpich/bin:$PATH
cd ../..
You will probably want to put this final export of PATH to your .bashrc file, to make sure that mpi is found in new terminals too.
After that, make sure that the following command does not cause any errors and simply prints your hostname 4 times::
mpirun -np 4 hostname
Now you just need to install mpi4py, which is easy if you have MPICH installed correctly::
mkdir mpi4py
cd mpi4py
wget https://pypi.python.org/packages/source/m/mpi4py/mpi4py-1.3.1.tar.gz
tar -xvzf mpi4py-1.3.1.tar.gz
cd mpi4py-1.3.1
python setup.py build --mpicc=../../mpich/bin/mpicc
python setup.py install --user
cd ../..
Testing whether or not everything works can be done by making sure that the following command prints '4' four times::
mpirun -np 4 python -c "from mpi4py import MPI; print(MPI.COMM_WORLD.Get_size())"

29
doc/interface.rst Normal file
View file

@ -0,0 +1,29 @@
..
Copyright 2014 Modelling, Simulation and Design Lab (MSDL) at
McGill University and the University of Antwerp (http://msdl.cs.mcgill.ca/)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Interface
=========
This section contains all constructors and methods necessary to setup, configure and simulate a DEVS model. See the next section for examples on usage.
.. toctree::
Infinity <infinity>
Models <DEVS>
Simulator <simulator>
Server <server>
Random Number Generator <randomgenerator>
Elapsed Time <elapsed_time>

49
doc/internal.rst Normal file
View file

@ -0,0 +1,49 @@
..
Copyright 2014 Modelling, Simulation and Design Lab (MSDL) at
McGill University and the University of Antwerp (http://msdl.cs.mcgill.ca/)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Internal documentation
======================
.. warning:: This section contains every function of PyPDEVS, most of which are **not** suited to be used by the modeller. This section should therefore only be used to understand the backend of the simulator, or as reference to developers wishing to modify the source code.
.. toctree::
Activity Visualisation <activityvisualisation_int>
Allocators <allocators_int>
AsynchronousComboGenerator <asynchronouscombogenerator_int>
BaseSimulator <basesimulator_int>
Classic DEVS Wrapper <classicdevswrapper_int>
Colors <colors_int>
Controller <controller_int>
DEVS <DEVS_int>
Logger <logger_int>
Message <message_int>
Message Scheduler <messagescheduler_int>
Middleware Detection <middleware_int>
Minimal Simulation kernal <minimal_int>
MPIRedirect <mpiredirect_int>
Random number generator <randomgenerator_int>
Relocators <relocators_int>
Schedulers <schedulers>
Server <server_int>
Simulator <simulator_int>
SimulatorConfigurator <simconfig_int>
Solver <solver_int>
State Savers <statesavers_int>
Threading subsystems <threading>
Threadpool <threadpool_int>
Tracers <tracers>
Util <util_int>

42
doc/listener.rst Normal file
View file

@ -0,0 +1,42 @@
..
Copyright 2014 Modelling, Simulation and Design Lab (MSDL) at
McGill University and the University of Antwerp (http://msdl.cs.mcgill.ca/)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Listening to realtime simulation
================================
During realtime simulation, there frequently exists the need to listen to specific ports, monitoring the events passing over them. In PythonPDEVS, it is possible to register a function that is invoked for each event passing over that port. This is called a *listener* in PythonPDEVS.
Example model
-------------
We can simply reuse the traffic light model from the previous section on realtime simulation (:download:`trafficLightModel.py <trafficLightModel.py>`). All that needs to be changed now, is adding a listener on an output port of a *coupled* DEVS model. First and foremost, therefore, we must put our traffic light in a container::
class TrafficLightSystem(CoupledDEVS):
def __init__(self):
CoupledDEVS.__init__(self, "System")
self.light = self.addSubModel(TrafficLight("Light"))
self.observed = self.addOutPort(name="observed")
In the experiment file, it is then possible to register a listener on this port as follows (:download:`injection.py <injection.py>`)::
def my_function(event):
print("Observed the following event: " + str(event))
sim.setListenPorts(model.observed, my_function)
Since this function is invoked with the complete bag, the received event will contain a list of events, exactly as how it would be invoked in the external transition function.
.. note:: Listeners are only supported on output ports of Coupled DEVS models. This is because the listeners are invoked upon event routing, for which the source port is not checked.

BIN
doc/location.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

47
doc/location.rst Normal file
View file

@ -0,0 +1,47 @@
..
Copyright 2014 Modelling, Simulation and Design Lab (MSDL) at
McGill University and the University of Antwerp (http://msdl.cs.mcgill.ca/)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Location tracing
================
.. note:: This feature is still being worked on and all information is therefore prone to change.
Location tracing is closely linked to activity tracing. The only major difference is when the files are generated: an activity trace is only created at the end of the simulation run, whereas an location trace is created at the start of the simulation and at every GVT boundary where at least one migration happens. Location traces will always contain the time at which the location as it is presented actually went into effect.
Enabling location tracing is as simple as::
x, y = 20, 20
model = FireSpread(x, y)
sim = Simulator(model)
sim.setTerminationTime(1000.0)
sim.setLocationCellMap(True, x, y)
sim.simulate()
Note that this again shows the location in a Cell view. If this kind of visualisation is not desirable (or possible), you are advised to use standard :doc:`visualisation` using Graphviz.
An example output of location tracing is given below. It isn't really spectacular, as it only shows you your allocations again.
.. image:: location.png
:alt: Location cell view
:align: center
:width: 50%
Cell view visualisation is completely different from the regular :doc:`visualisation`, as it contains some domain specific information and thus more closely resembles your interpretation of the model. Drawing a 6x6 grid with the generic visualisation, would generate something non-intuitive like the 'curly' graph below.
.. image:: location_normal.png
:alt: Location normal view
:height: 2000px
:align: center

BIN
doc/location_normal.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

20
doc/locationscheduler.rst Normal file
View file

@ -0,0 +1,20 @@
..
Copyright 2014 Modelling, Simulation and Design Lab (MSDL) at
McGill University and the University of Antwerp (http://msdl.cs.mcgill.ca/)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Location-specific scheduler
===========================
TODO

21
doc/logger_int.rst Normal file
View file

@ -0,0 +1,21 @@
..
Copyright 2014 Modelling, Simulation and Design Lab (MSDL) at
McGill University and the University of Antwerp (http://msdl.cs.mcgill.ca/)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Logger
======
.. automodule:: logger
:members:

View file

@ -0,0 +1,21 @@
..
Copyright 2014 Modelling, Simulation and Design Lab (MSDL) at
McGill University and the University of Antwerp (http://msdl.cs.mcgill.ca/)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Manual Relocator
================
.. automodule:: relocators.manualRelocator
:members:

113
doc/memoization.rst Normal file
View file

@ -0,0 +1,113 @@
..
Copyright 2014 Modelling, Simulation and Design Lab (MSDL) at
McGill University and the University of Antwerp (http://msdl.cs.mcgill.ca/)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Memoization
===========
PyPDEVS supports memoization for the most heavyweight functions that are called during simulation.
What is memoization?
--------------------
Memoization simply means that the return values of a function call will be cached.
As soon as the function is called again with exactly the same parameters, the cached value will be
returned instead of the function being reevaluated again.
The advantage is clearly that it has the potential to speed up computation in situations where
the value is likely to be cached **and** if the function takes a relatively long time.
How does it apply to PyPDEVS?
-----------------------------
The PyPDEVS code is significantly optimized, though a certain part of the code is inoptimizable by
the simulator itself. This code is the *user code*, which defines e.g. the transition functions of
the model. These transition functions have the potential to computationally intensive. For example,
most distributed simulation benchmarks have a transition function which takes in the terms of milliseconds.
The only remaining requirement is then that the value is *likely* to be cached. For this reason, memoization
is only used in distributed simulation. In distributed simulation, a complete node might have to revert
all of its computation due to another (unrelated) model requesting such a revertion. Most of the time,
this model is not influenced by the change directly, therefore the input parameters of the function are
likely to be identical.
It is therefore possible to assume that distributed simulation is likely to profit from this optimization,
certainly in the case of relocations. When a relocation happens, the node is reverted to the current GVT,
even though no real causality violation happened. These transitions can then be recalculated immediately with
the use of memoization.
Why not enable it by default?
-----------------------------
Even though memoization seems a way to quickly increase performance, it also has several downsides. The most
important downside is the high space complexity that it incurs. Time warp simulation is already extremely
space consuming, so also caching the inputs and their response is not going to be of much help to that.
This problem is partially mitigated by having time warp and memoization refer to the same state in memory,
though this still requires additional lists, input dictionaries, ...
Another problem is the datastructure management. As soon as a revertion happens, the list of old states is
reverted and used to check for equality. Without memoization, this list would be discarded, freeing up lots
of space. Therefore, this problem again relates to space complexity.
A final problem is the requirement to check the states for equality. These checks can take arbitrarily long,
depending on how the user defined the equality method. In the worst case, the user might not have defined such
a method, causing every comparison to result in *False*. This is clearly problematic, as the memoization speedup
will then never be visible. Furthermore, memoization is unlikely to have an impact in simulations where nearly
no revertions happen.
For these reasons, memoization is not enabled by default, but only when the user enables it explicitly.
Implementation hints
--------------------
Due to the way memoization is implemented in PyPDEVS, some considerations apply:
1. As soon as an inequal state is found, memoization code is aborted because the chance of further equality becomes too small.
2. Memoization code is only triggered after a revertion happened.
3. Due to memoization, side-effects of the transition function are not performed. This includes printing, random number generation, ... Note that transition functions with side effects are already a bad idea in time warp simulationn.
Requirements
------------
Two requirements exist to use memoization. The first one is simply to enable it in the configuration, the second one
requires a little more explanation.
By default, Python provides equality methods on two objects, but they always return *False* if the objects are different
(even though their content might be equal).
The user should thus add the *__eq__(self, other)* and *__hash__(self)* function, to provide user-defined equality.
Technically, it is required that the output is **exactly** the same when the current state (and input message, in case
of *external* and *confluent transitions*) are equal according to these methods.
Example
-------
Simply enabling memoization is not that difficult and is simply::
sim = Simulator(MyModel())
sim.setMemoization(True)
sim.simulate()
Defining the equality method on a state can be::
class MyState(object):
def __init__(self, var1, var2):
self.var1 = var1
self.var2 = var2
def __eq__(self, other):
return self.var1 == other.var1 and self.var2 == other.var2
def __hash__(self):
return hash(hash(self.var1) + hash(self.var2))

21
doc/message_int.rst Normal file
View file

@ -0,0 +1,21 @@
..
Copyright 2014 Modelling, Simulation and Design Lab (MSDL) at
McGill University and the University of Antwerp (http://msdl.cs.mcgill.ca/)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Message object
==============
.. automodule:: message
:members:
:special-members:

View file

@ -0,0 +1,20 @@
..
Copyright 2014 Modelling, Simulation and Design Lab (MSDL) at
McGill University and the University of Antwerp (http://msdl.cs.mcgill.ca/)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
MessageScheduler
================
.. automodule:: messageScheduler
:members:

21
doc/middleware_int.rst Normal file
View file

@ -0,0 +1,21 @@
..
Copyright 2014 Modelling, Simulation and Design Lab (MSDL) at
McGill University and the University of Antwerp (http://msdl.cs.mcgill.ca/)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Middleware Detection
====================
.. automodule:: middleware
:members:

39
doc/minimal.rst Normal file
View file

@ -0,0 +1,39 @@
..
Copyright 2014 Modelling, Simulation and Design Lab (MSDL) at
McGill University and the University of Antwerp (http://msdl.cs.mcgill.ca/)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Minimal Simulation Kernel
=========================
Due to the many features that PythonPDEVS supports, the code is quite big and many additional steps need to be taken during simulation.
Certainly with Python, this causes problems due to the lack of compilation (and consequently optimization).
For this reason, but also to show the minimal simulation algorithm of PythonPDEVS, a minimal simulation kernel is added.
Such a minimal kernel, even without any special features, is useful for benchmarking different algorithms, without needing to worry about the supported features.
It can also be useful for end-users, if the user is only interested in the state of the simulated model.
Its use is identical to the normal simulation kernel, but the import is different.
Instead of importing *Simulator* from the file *simulator.py*, it needs to be imported from *minimal.py*.
So the only change is the import, which becomes something like::
from pypdevs.minimal import Simulator
All other code remains exactly the same.
Unless configuration options (apart from *setTerminationTime*) are used, as these will no longer be possible.
The only supported option is *setTerminationTime*, which works exactly the same as in the normal simulation kernel.
While it is still possible to define transfer functions or define allocations in the model construction, these are ignored during simulation.
The polymorphic scheduler is used automatically, so the performance will automatically be optimized to the different access patterns.
There is thus no need to manually specify the scheduler.

21
doc/minimal_int.rst Normal file
View file

@ -0,0 +1,21 @@
..
Copyright 2014 Modelling, Simulation and Design Lab (MSDL) at
McGill University and the University of Antwerp (http://msdl.cs.mcgill.ca/)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Minimal Simulation Kernel
=========================
.. autofunction:: minimal.directConnect
.. autoclass:: minimal.Simulator
:members:

BIN
doc/model.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3 KiB

22
doc/mpiredirect_int.rst Normal file
View file

@ -0,0 +1,22 @@
..
Copyright 2014 Modelling, Simulation and Design Lab (MSDL) at
McGill University and the University of Antwerp (http://msdl.cs.mcgill.ca/)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
MPIRedirect class
=================
.. automodule:: MPIRedirect
:members:
:special-members:

40
doc/multisim.rst Normal file
View file

@ -0,0 +1,40 @@
..
Copyright 2014 Modelling, Simulation and Design Lab (MSDL) at
McGill University and the University of Antwerp (http://msdl.cs.mcgill.ca/)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Multiple Simulators
===================
In some situations, having multiple models (and their respective simulator) in the same Python script is useful for comparing the performance.
Local simulation
----------------
In local simulation, it is possible to create multiple Simulator instances without any problems::
sim1 = Simulator(Model1())
# Any configuration you want on sim1
sim1.simulate()
sim2 = Simulator(Model2())
# Any configuration you want on sim2
sim2.simulate()
Distributed simulation
----------------------
Starting up multiple Simulator classes is not supported in distributed simulation. This is because starting a simulator will also impose the construction of different MPI servers, resulting in an inconsistency.
It is supported in distributed simulation to use :doc:`reinitialisation <reinitialisation>`, simply because it is the same model and is coordinated by the same simulator.

72
doc/nesting.rst Normal file
View file

@ -0,0 +1,72 @@
..
Copyright 2014 Modelling, Simulation and Design Lab (MSDL) at
McGill University and the University of Antwerp (http://msdl.cs.mcgill.ca/)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Nested simulation
=================
.. versionchanged:: 2.1.3
Nested simulation is only possible if **both** the nested simulation and the invoking simulation are **local** simulations.
.. versionchanged:: 2.2.0
Allow nested local simulations in distributed simulations.
Nested simulation allows a simulation to be influenced by the results of another simulation. This functionality is very simple to use, as it just works as expected. In one of the user-defined functions of the model, it is thus possible to create another model and another simulator and simulate that new simulator. The only difference is that some specific configurations do not work on the nested simulation, such as using a logging server.
An example of nesting is provided in the remainder of this subsection.
Suppose we want to create a *Processor* which has a state of how many messages it has already processed. This amount of messages is then used to determine the *timeAdvance*, but in a very specific way that is determined by another DEVS simulation. This example is somewhat contrived, though the example is supposed to be as small as possible to only show how it works, not the utility of the feature.
The following code implements such behaviour::
class State(object):
def __init__(self):
self.processed = 0
self.processing = False
class NestedProcessor(AtomicDEVS):
def __init__(self):
AtomicDEVS.__init__(self, "Nested")
self.state = State()
self.inport = self.addInPort("inport")
self.outport = self.addOutPort("outport")
def extTransition(self, inputs):
self.state.processing = True
return self.state
def intTransition(self):
self.state.processed += 1
return self.state
def outputFnc(self):
return {self.outport: [1]}
def timeAdvance(self):
if self.state.processing:
# Determine the time based on another simulation
from simulator import Simulator
from myqueue import CQueue
model = CQueue()
# The processed attribute of the state is used to determine the processing time in our example
model.queue.processing_time = self.state.processed
sim = Simulator(model)
sim.setTerminationTime(5.0)
sim.simulate()
return max(1, model.queue.state)
else:
return INFINITY
In our example, we only used nested simulation in the *timeAdvance* function, though it is possible everywhere.

113
doc/problems.rst Normal file
View file

@ -0,0 +1,113 @@
..
Copyright 2014 Modelling, Simulation and Design Lab (MSDL) at
McGill University and the University of Antwerp (http://msdl.cs.mcgill.ca/)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Common problems and their solution
==================================
The most important parts in PyPDEVS are guarded by a *DEVSException*. Such an exception being thrown is probably due to a modelling error (or more likely: a bug in the simulator). These exceptions should contain enough information about what went wrong and should normally only be seen if violations of DEVS were written (or a simulator bug).
Other problems are often caused due to an ommission of the modeller. This section tries to provide an overview of common problems for first-time modellers using PyPDEVS.
ImportError: No module named X
------------------------------
This indicates that PyPDEVS isn't imported correctly. Make sure that you have installed PyPDEVS, and all its dependencies, correctly.
AttributeError: 'X' object has no attribute 'IPorts' (or 'OPorts' or 'componentSet')
------------------------------------------------------------------------------------
This problem indicates that you forgot to initialize the superclass of your own DEVS models as the **first** instruction. For example::
class MyAtomicDEVSModel(AtomicDEVS):
def __init__(self):
AtomicDEVS.__init__(self, 'name') # <-- you probably forgot this line
# Remainder of your initialisation
The same should happen for CoupledDEVS models::
class MyCoupledDEVSModel(CoupledDEVS):
def __init__(self):
CoupledDEVS.__init__(self, 'name') # <-- you probably forgot this line
# Remainder of your initialisation
TypeError: 'NoneType' object is not iterable
--------------------------------------------
This most likely indicates that you forgot to return a dictionary in the *outputFnc* function. Even if no output is generated, it is mandatory to return a dictionary::
def outputFnc(self):
return {} # <-- This is required
TypeError: 'X' object is not iterable
-------------------------------------
.. note:: This solution is not valid in Classic DEVS simulation, as here it is allowed to be a simple value.
Probably, you forgot to return the values in your dicationary as a list, as is required in parallel DEVS. For example::
def outputFnc(self):
# return {self.outport: myMessage} <-- WRONG
return {self.outport: [myMessage]}
AttributeError: 'List' object has no attribute 'X'
--------------------------------------------------
.. note:: This solution is not valid in Classic DEVS simulation, as here it is allowed to be a simple value.
This is equivalent to the previous error: the values of the inputs dictionary are lists instead of the actual values::
def extTransition(self, inputs):
# processMessage(inputs[self.inport]) <-- WRONG
for msg in inputs[self.inport]:
processMessage(msg)
.. warning:: To be complete, it is not sufficient to just take the first element from the list, as there might be more elements. Always using [0] carelessly is therefore discouraged.
AttributeError: 'NoneType' has no attribute 'X'
-----------------------------------------------
You probably forgot to return the new state in one of the transition functions. Transition functions should always return the new state. In case only state attributes are updated, it is necessary to return the *self.state* variable::
def intTransition(self):
self.state.message = 5 # <-- OK, state is updated
return self.state # <-- Don't forget this
Alternatively::
def intTransition(self):
return State(5) # <-- Simply create a completely new state
New state: <X object at 0xXXXXXXX>
----------------------------------
Not actually a problem, though it is worth noting that you can have custom string output by defining a *__str__(self)* function for your state in case it is a class.
AttributeError: 'module' object has no attribute 'YourCoupledDEVSClass'
-----------------------------------------------------------------------
This means that you have a wrong import order. Due to the way MPI runs, it starts up the same file multiple times. To get around this problem, PyPDEVS will stop execution at the server nodes as soon as they are started up. However, they still need to have the actual model to be simulated loaded. For this reason, the import of the *simulator* file should happend after the import of the models. In case you have your model and experiment in the same file, you should add the import to simulator right before creating the model, preferably even in a conditional::
class MyCoupledDEVSModel(CoupledDEVS):
def __init__(self):
...
if __name__ == "__main__":
from simulator import Simulator
model = MyCoupledDEVSModel()
sim = Simulator(model)
sim.simulate()
The execution of the file will stop as soon as the *simulator* file is imported, so make sure that all your models are imported by that time. The simplest way to solve this problem is by creating seperate model and experiment files.

69
doc/queue_example.py Normal file
View file

@ -0,0 +1,69 @@
from pypdevs.DEVS import AtomicDEVS, CoupledDEVS
from pypdevs.simulator import Simulator
from pypdevs.infinity import INFINITY
class Generator(AtomicDEVS):
def __init__(self):
AtomicDEVS.__init__(self, "Generator")
self.state = True
self.outport = self.addOutPort("outport")
def timeAdvance(self):
if self.state:
return 1.0
else:
return INFINITY
def outputFnc(self):
# Our message is simply the integer 5, though this could be anything
return {self.outport: [5]}
def intTransition(self):
self.state = False
return self.state
class Queue(AtomicDEVS):
def __init__(self):
AtomicDEVS.__init__(self, "Queue")
self.state = None
self.processing_time = 1.0
self.inport = self.addInPort("input")
self.outport = self.addOutPort("output")
def timeAdvance(self):
if self.state is None:
return INFINITY
else:
return self.processing_time
def outputFnc(self):
return {self.outport: [self.state]}
def intTransition(self):
self.state = None
return self.state
def extTransition(self, inputs):
self.state = inputs[self.inport][0]
return self.state
class CQueue(CoupledDEVS):
def __init__(self):
CoupledDEVS.__init__(self, "CQueue")
self.generator = self.addSubModel(Generator())
self.queue = self.addSubModel(Queue())
self.connectPorts(self.generator.outport, self.queue.inport)
class DQueue(CoupledDEVS):
def __init__(self):
CoupledDEVS.__init__(self, "DQueue")
self.generator = self.addSubModel(Generator())
self.queue1 = self.addSubModel(Queue())
self.queue2 = self.addSubModel(Queue())
self.connectPorts(self.generator.outport, self.queue1.inport)
self.connectPorts(self.generator.outport, self.queue2.inport)
model = CQueue()
sim = Simulator(model)
sim.setVerbose()
sim.simulate()

View file

@ -0,0 +1,71 @@
from pypdevs.DEVS import AtomicDEVS, CoupledDEVS
from pypdevs.simulator import Simulator
from pypdevs.infinity import INFINITY
class Generator(AtomicDEVS):
def __init__(self):
AtomicDEVS.__init__(self, "Generator")
self.state = True
self.outport = self.addOutPort("outport")
def timeAdvance(self):
if self.state:
return 1.0
else:
return INFINITY
def outputFnc(self):
# Our message is simply the integer 5, though this could be anything
return {self.outport: 5}
def intTransition(self):
self.state = False
return self.state
class Queue(AtomicDEVS):
def __init__(self):
AtomicDEVS.__init__(self, "Queue")
self.state = None
self.processing_time = 1.0
self.inport = self.addInPort("input")
self.outport = self.addOutPort("output")
def timeAdvance(self):
if self.state is None:
return INFINITY
else:
return self.processing_time
def outputFnc(self):
return {self.outport: self.state}
def intTransition(self):
self.state = None
return self.state
def extTransition(self, inputs):
self.state = inputs[self.inport]
return self.state
class CQueue(CoupledDEVS):
def __init__(self):
CoupledDEVS.__init__(self, "CQueue")
self.generator = self.addSubModel(Generator())
self.queue = self.addSubModel(Queue())
self.connectPorts(self.generator.outport, self.queue.inport)
class DQueue(CoupledDEVS):
def __init__(self):
CoupledDEVS.__init__(self, "DQueue")
self.generator = self.addSubModel(Generator())
self.queue1 = self.addSubModel(Queue())
self.queue2 = self.addSubModel(Queue())
self.connectPorts(self.generator.outport, self.queue1.inport)
self.connectPorts(self.generator.outport, self.queue2.inport)
model = CQueue()
sim = Simulator(model)
sim.setClassicDEVS()
sim.setTerminationTime(5.0)
sim.setVerbose()
sim.simulate()

81
doc/random.rst Normal file
View file

@ -0,0 +1,81 @@
..
Copyright 2014 Modelling, Simulation and Design Lab (MSDL) at
McGill University and the University of Antwerp (http://msdl.cs.mcgill.ca/)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
.. _random_numbers:
Random Number Generation
========================
Using the normal *random* library during simulation is not recommended in situations where repeatability is desired. Even though the *random* library uses a seed to guarantee determinism, this seed is part of a global state and is thus **not** allowed in pure DEVS. In sequential classic DEVS, this most likely doesn't pose a real problem, though even then it is possible that it might go wrong.
To solve these problems, a very basic *random number generator* is included in the simulator. The model that needs random numbers should create an instantiation of this generator and save it **in its state**. All random numbers should afterwards be requested from this object. The random number generator class will take special measures to ensure that the seed is actually encapsulated in the object itself and doesn't rely on the global seed.
.. note:: Even though the random number generator classes are not influenced by calls to the *random* library, the reverse is true. This is due to the class simply being a wrapper, which still redirects its calls to the library function. This should not pose a problem, as the use of the library function in its pure form should be avoided at all costs.
.. note:: While the random number generator class simply redirects its calls to the *random* library, the returned values will **not** be equal to those provided by the *random* library through direct access. This is due to the fact that another random number is generated to be used as a seed.
.. warning:: Making calls to the random number generator will alter its state. This means that it is only allowed to make calls on the object from within the *intTransition*, *extTransition*, *confTransition* and *__init__* methods!
Overridable
-----------
Like many parts of the simulator, this random number generator is only provided for convenience. The user might want to have its own special random number generator, one with more functionality, ... This is all possible, though if this is done, it should be similar to the one provided with the simulator. At least, it should implement the *__eq__*, *__hash__* and *copy* methods, with the expected behaviour. Furthermore, such implementations should also encapsulate the seed in one way or the other. Simply relying on the *random* library to have a correct seed is **unacceptable**.
Example
-------
A simple example is provided by this processor, which takes a random time to process a message. While this code is not that spectacular, the importance is that this code will **always** return the same traces, independent of the use of distribution, the presence of revertions, indeterminism in execution order of transition functions, ...
Such a processor and its state look like::
class RandomProcessorState(object):
def __init__(self, seed):
from randomGenerator import RandomGenerator
self.randomGenerator = RandomGenerator(seed)
self.queue = []
self.proctime = self.randomGenerator.uniform(0.3, 3.0)
def __str__(self):
return "Random Processor State -- " + str(self.proctime)
class RandomProcessor(AtomicDEVS):
def __init__(self, seed):
AtomicDEVS.__init__(self, "RandomProcessor_" + str(seed))
self.inport = self.addInPort("inport")
self.outport = self.addOutPort("outport")
self.state = RandomProcessorState(seed)
def intTransition(self):
self.state.queue = self.state.queue[1:]
self.state.proctime = self.state.randomGenerator.uniform(0.3, 3.0)
return self.state
def extTransition(self, inputs):
if self.state.queue:
self.state.proctime -= self.elapsed
self.state.queue.extend(inputs[self.inport])
return self.state
def outputFnc(self):
return {self.outport: [self.state.queue[0]]}
def timeAdvance(self):
if self.state.queue:
return self.state.proctime
else:
return INFINITY
As long as the same seed is passed, the results will always be the same.

22
doc/randomgenerator.rst Normal file
View file

@ -0,0 +1,22 @@
..
Copyright 2014 Modelling, Simulation and Design Lab (MSDL) at
McGill University and the University of Antwerp (http://msdl.cs.mcgill.ca/)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Random Number Generator
=======================
.. automodule:: randomGenerator
:members:
:noindex:

View file

@ -0,0 +1,22 @@
..
Copyright 2014 Modelling, Simulation and Design Lab (MSDL) at
McGill University and the University of Antwerp (http://msdl.cs.mcgill.ca/)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Random Number Generator
=======================
.. automodule:: randomGenerator
:members:
:special-members:

309
doc/realtime.rst Normal file
View file

@ -0,0 +1,309 @@
..
Copyright 2014 Modelling, Simulation and Design Lab (MSDL) at
McGill University and the University of Antwerp (http://msdl.cs.mcgill.ca/)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Realtime simulation
===================
Realtime simulation is closely linked to normal simulation, with the exception that simulation will not progress as fast as possible. The value returned by the time advance will be interpreted in seconds and the simulator will actually wait (not busy loop) until the requested time has passed. Several realtime backends are supported in PyPDEVS and are mentioned below.
Example model
-------------
The example model will be something else than the *queue* from before, as this isn't really that interesting for realtime simulation. We will instead use the *trafficLight* model. It has a *trafficLight* that is either running autonomous or is in a manual mode. Normally, the traffic light will work autonomously, though it is possible to interrupt the traffic light and switch it to manual mode and back to autonomous again.
This complete model is (:download:`trafficLightModel.py <trafficLightModel.py>`)::
class TrafficLightMode:
def __init__(self, current="red"):
self.set(current)
def set(self, value="red"):
self.__colour=value
def get(self):
return self.__colour
def __str__(self):
return self.get()
class TrafficLight(AtomicDEVS):
def __init__(self, name):
AtomicDEVS.__init__(self, name)
self.state = TrafficLightMode("red")
self.INTERRUPT = self.addInPort(name="INTERRUPT")
self.OBSERVED = self.addOutPort(name="OBSERVED")
def extTransition(self, inputs):
input = inputs[self.INTERRUPT][0]
if input == "toManual":
if state == "manual":
# staying in manual mode
return TrafficLightMode("manual")
if state in ("red", "green", "yellow"):
return TrafficLightMode("manual")
elif input == "toAutonomous":
if state == "manual":
return TrafficLightMode("red")
raise DEVSException("Unkown input in TrafficLight")
def intTransition(self):
state = self.state.get()
if state == "red":
return TrafficLightMode("green")
elif state == "green":
return TrafficLightMode("yellow")
elif state == "yellow":
return TrafficLightMode("red")
else:
raise DEVSException("Unkown state in TrafficLight")
def outputFnc(self):
state = self.state.get()
if state == "red":
return {self.OBSERVED: ["grey"]}
elif state == "green":
return {self.OBSERVED: ["yellow"]}
elif state == "yellow":
return {self.OBSERVED: ["grey"]}
else:
raise DEVSException("Unknown state in TrafficLight")
def timeAdvance(self):
if state == "red":
return 60
elif state == "green":
return 50
elif state == "yellow":
return 10
elif state == "manual":
return INFINITY
else:
raise DEVSException("Unknown state in TrafficLight")
With our model being set up, we could run it as-fast-as-possible by starting it like::
model = TrafficLight("trafficLight")
sim = Simulator(model)
sim.simulate()
To make it run in real time, we only need to do some minor changes. First, we need to define on which port we want to put some external input. We can choose a way to address this port, but lets assume that we choose the same name as the name of the port. This gives us::
refs = {"INTERRUPT": model.INTERRUPT}
Now we only need to pass this mapping to the simulator, together with the choice for realtime simulation. This is done as follows::
refs = {"INTERRUPT": model.INTERRUPT}
sim.setRealTime(True)
sim.setRealTimePorts(refs)
That is all extra configuration that is required for real time simulation.
As soon as the *simulate()* method is called, the simulation will be started as usual, though now several additional options are enabled. Specifically, the user can now input external data on the declared ports. This input should be of the form *portname data*.
In our example, the model will respond to both *toManual* and *toAutonomous* and we chose *INTERRUPT* as portname in our mapping. So our model will react on the input *INTERRUPT toManual*. This input can then be given through the invocation of the *realtime_interrupt(string)* call as follows::
sim.realtime_interrupt("INTERRUPT toManual")
Malformed input will cause an exception and simulation will be halted.
.. note:: All input that is injected will be passed to the model as a *string*. If the model is thus supposed to process integers, a string to integer processing step should happen in the model itself.
Input files
-----------
PyPDEVS also supports the use of input files together with input provided at run time. The input file will be parsed at startup and should be of the form *time port value*, with time being the simulation time at which this input should be injected. Again, this input will always be interpreted as a string. If a syntax error is detected while reading through this file, the error will immediately be shown.
.. note:: The file input closely resembles the usual prompt, though it is not possible to define a termination at a certain time by simply stating the time at the end. For this, you should use the termination time as provided by the standard interface.
An example input file for our example could be::
10 INTERRUPT toManual
20 INTERRUPT toAutonomous
30 INTERRUPT toManual
Backends
--------
Several backends are provided for the realtime simulation, each serving a different purpose. The default backend is the best for most people who just want to simulate in realtime. Other options are for when PyPDEVS is coupled to TkInter, or used in the context of a game loop system.
The following backends are currently supported:
* Python threads: the default, provides simple threading and doesn't require any other programs. Activated with *setRealTimePlatformThreads()*.
* TkInter: uses Tk for all of its waiting and delays (using the Tk event list). Activated with *setRealTimePlatformTk()*.
* Game loop: requires an external program to call the simulator after a certain delay. Activated with *setRealTimePlatformGameLoop()*.
For each of these backends, an example is given on how to use and invoke it, using the traffic light model presented above.
Python Threads
^^^^^^^^^^^^^^
This is the simplest platform to use, and is used by default. After the invocation of *sim.simulate()*, simulation will happen in the background of the currently running application. The call to *sim.simulate()* will return immediately. Afterwards, users can do some other operations. Most interestingly, users can provide input to the running simulation by invoking the *realtime_interrupt(string)* method.
Simulation runs as a daemon thread, so exiting the main thread will automatically terminate the simulation.
.. warning:: Python threads can sometimes have a rather low granularity in CPython 2. So while we are simulating in soft realtime anyway, it is important to note that delays could potentially become significant.
An example is given below (:download:`experiment_threads.py <experiment_threads.py>`)::
from pypdevs.simulator import Simulator
from trafficLightModel import *
model = TrafficLight(name="trafficLight")
refs = {"INTERRUPT": model.INTERRUPT}
sim = Simulator(model)
sim.setRealTime(True)
sim.setRealTimeInputFile(None)
sim.setRealTimePorts(refs)
sim.setVerbose(None)
sim.setRealTimePlatformThreads()
sim.simulate()
while 1:
sim.realtime_interrupt(raw_input())
In this example, users are presented with a prompt where they can inject events in the simulation, for example by typing *INTERRUPT toManual* during simulation. Sending an empty input (*i.e.*, malformed), simulation will also terminate.
TkInter
^^^^^^^
The TkInter event loop can be considered the most difficult one to master, as you will also need to interface with TkInter.
Luckily, PythonPDEVS hides most of this complexity for you. You will, however, still need to define your GUI application and start PythonPDEVS. Upon configuration of PythonPDEVS, a reference to the root window needs to be passed to PythonPDEVS, such that it knows to which GUI to couple.
Upon termination of the GUI, PythonPDEVS will automatically terminate simulation as well.
The following example will create a simple TkInter GUI of a traffic light, visualizing the current state of the traffic light, and providing two buttons to send specific events. Despite the addition of TkInter code, the PythonPDEVS interface is still very similar.
The experiment file is as follows (:download:`experiment_tk.py <experiment_tk.py>`)::
from pypdevs.simulator import Simulator
from Tkinter import *
from trafficLightModel import *
isBlinking = None
model = TrafficLight(name="trafficLight")
refs = {"INTERRUPT": model.INTERRUPT}
root = Tk()
sim = Simulator(model)
sim.setRealTime(True)
sim.setRealTimeInputFile(None)
sim.setRealTimePorts(refs)
sim.setVerbose(None)
sim.setRealTimePlatformTk(root)
def toManual():
global isBlinking
isBlinking = False
sim.realtime_interrupt("INTERRUPT toManual")
def toAutonomous():
global isBlinking
isBlinking = None
sim.realtime_interrupt("INTERRUPT toAutonomous")
size = 50
xbase = 10
ybase = 10
frame = Frame(root)
canvas = Canvas(frame)
canvas.create_oval(xbase, ybase, xbase+size, ybase+size, fill="black", tags="red_light")
canvas.create_oval(xbase, ybase+size, xbase+size, ybase+2*size, fill="black", tags="yellow_light")
canvas.create_oval(xbase, ybase+2*size, xbase+size, ybase+3*size, fill="black", tags="green_light")
canvas.pack()
frame.pack()
def updateLights():
state = model.state.get()
if state == "red":
canvas.itemconfig("red_light", fill="red")
canvas.itemconfig("yellow_light", fill="black")
canvas.itemconfig("green_light", fill="black")
elif state == "yellow":
canvas.itemconfig("red_light", fill="black")
canvas.itemconfig("yellow_light", fill="yellow")
canvas.itemconfig("green_light", fill="black")
elif state == "green":
canvas.itemconfig("red_light", fill="black")
canvas.itemconfig("yellow_light", fill="black")
canvas.itemconfig("green_light", fill="green")
elif state == "manual":
canvas.itemconfig("red_light", fill="black")
global isBlinking
if isBlinking:
canvas.itemconfig("yellow_light", fill="yellow")
isBlinking = False
else:
canvas.itemconfig("yellow_light", fill="black")
isBlinking = True
canvas.itemconfig("green_light", fill="black")
root.after(500, updateLights)
b = Button(root, text="toManual", command=toManual)
b.pack()
c = Button(root, text="toAutonomous", command=toAutonomous)
c.pack()
root.after(100, updateLights)
sim.simulate()
root.mainloop()
Game Loop
^^^^^^^^^
This mechanism will not block the main thread and if the main thread stops, so will the simulation. The caller can, after the invocation of *simulate()*, give control to PythonPDEVS to process all outstanding events. This methods is called *realtime_loop_call()*. A simple game loop would thus look like::
sim = Simulator(Model())
sim.simulate()
sim.setRealTimePlatformGameLoop()
while (True):
# Do rendering and such
...
# Advance the state of the DEVS model, processing all input events
sim.realtime_loop_call()
The game loop mechanism is thus closely linked to the invoker. The calls to the *realtime_loop_call()* function and the initializer are the only concept of time that this mechanism uses. Newer versions of PythonPDEVS will automatically detect the number of Frames per Second (FPS), so there is no longer any need to do this manually.
An example is presented below (:download:`experiment_loop.py <experiment_loop.py>`)::
from pypdevs.simulator import Simulator
from trafficLightModel import *
model = TrafficLight(name="trafficLight")
refs = {"INTERRUPT": model.INTERRUPT}
sim = Simulator(model)
sim.setRealTime(True)
sim.setRealTimeInputFile(None)
sim.setRealTimePorts(refs)
sim.setVerbose(None)
sim.setRealTimePlatformGameLoop()
sim.simulate()
import time
while 1:
before = time.time()
sim.realtime_loop_call()
time.sleep(0.1 - (before - time.time()))
print("Current state: " + str(model.state.get()))
It is important to remark here, that the time management (*i.e.*, invoking sleep and computing how long to sleep), is the responsibility of the invoking code, instead of PythonPDEVS. PythonPDEVS will simply poll for the current wall clock time when it is invoked, and progress simulated time up to that point in time (depending on the scale factor).

62
doc/reinitialisation.rst Normal file
View file

@ -0,0 +1,62 @@
..
Copyright 2014 Modelling, Simulation and Design Lab (MSDL) at
McGill University and the University of Antwerp (http://msdl.cs.mcgill.ca/)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Reinitialisation
================
Starting from PyPDEVS 2.1.4, it is possible to run the *simulate()* method multiple times. If the *reinit()* method was called, the model will be restored to its initial state. This is done by completely saving the model in memory right before the actual simulation starts. Main problem with this approach is that it will require additional memory and for local simulation, it also increases the initialisation time as the copy will have to be made.
For these reasons, local simulation will have reinitialisation disabled by default and calling the *reinit()* method will result in a *DEVSException* stating this fact (and how to resolve it). Distributed simulation does not have this additional overhead and therefore it is always enabled.
So for remote simulation, it is as simple as::
sim = Simulator(DQueue())
sim.simulate() # Run it for the first time
sim.reinit() # Reinitialize
sim.simulate() # 'Continue' the simulation run (which was reset)
In local simulation, the option to allow reinitialisation needs to be set first, which simply gives::
sim = Simulator(CQueue())
sim.setAllowLocalReinit(True)
sim.simulate() # Run it for the first time
sim.reinit() # Reinitialize
sim.simulate() # 'Continue' the simulation run (which was reset)
Altering the model
------------------
Of course, simply rerunning the simulation is not really useful. Most of the time, reinitialisation is done to try the exact same simulation, but with a slightly different configuration. As long as the model structure is not altered, simply reinitializing is the best option. Note that these alterations should happen **before** the reinitialize call is made, as it implies network communication that is best done in a batch.
After a simulation run, the model will naturally still be in the post-simulation state and the model states will be the ones at the end of the simulation. Altering them has no effect on subsequent simulation runs, as the model will be reinitialised in a single step. Simply altering the model after a simulation run is not a viable option.
For these reasons, the only way to alter a model after simulation is through three methods of the *Simulator* object. These methods all serve a similar goal, though they are optimized for specific goals. They are:
* *setModelState(model, newState)*: modify the state of *model* and set it to *newState*. Use this to set a completely new state for the model. This is an optimized version of *setModelAttribute*.
* *setModelStateAttr(model, attr, value)*: modify the attribute *attr* of the state of *model* and set it to *value*. This will keep the original initialisation state, but alters only a single attribute.
* *setModelAttribute(model, attr, value)*: modify the attribute *attr* of the *model* and set it to *value*. This can be done to modify read-only attributes of the simulation model.
For example, if you want to change the *processing_time* attribute of the queue, you can simply::
model = CQueue()
sim = Simulator(model)
sim.setAllowLocalReinit(True)
sim.simulate() # <-- Initial run with processing_time = 1.0
sim.reinit() # <-- Perform reinitialisation and perform all changes
sim.setModelAttribute(model.queue, "processing_time", 2.0) # <-- Set it to 2.0
sim.simulate() # <-- Now run with processing_time = 2.0
.. warning:: Altering the state should happen after reinitialisation, as otherwise your changes will be reverted too.

40
doc/relocation.rst Normal file
View file

@ -0,0 +1,40 @@
..
Copyright 2014 Modelling, Simulation and Design Lab (MSDL) at
McGill University and the University of Antwerp (http://msdl.cs.mcgill.ca/)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Relocation directives
=====================
If your model is distributed, there is the possibility to move models to a different node. Model relocations only happen at the GVT boundaries, so the GVT interval that was configured previously will also be the interval for checking for relocation directives and actually performing them.
Setting a relocation directive is as simple as using the configuration option *setRelocationDirective(time, model, destination)*. At the first GVT boundary where *time* is reached, the *model* will be transfered to node *destination*. The *model* can be both the internal *model_id*, or simply the model itself. The *destination* should be the integer specifying the node to send the model to.
Since the model relocation directives are only checked sporadically, it is possible for several relocation directives to be in conflict. In that case, the latest relocation directive (in terms of requested time) will be used for that specific model.
The actual sending of a model is not that time consuming, but mainly the locking and unlocking cost of both models (and the subsequent revert). To maximize performance, transfer as many models simultaneously as possible, because the algorithm is optimised for such situations.
A simple example to swap the location of the *generator* and the first *queue* from our previous example is::
model = DQueue()
sim = Simulator(model)
sim.setRelocationDirective(20, model.generator, 1)
sim.setRelocationDirective(20, model.queue1, 0)
sim.simulate()
Of course, the GVT algorithm will probably never run in this small example and thus the relocation will also never happen.
Relocating a model to the node where it is currently running will not impose a revertion to the GVT. Such directives will simply be ignored.
.. note:: Executing a relocation causes a revertion to the GVT on both nodes that are involved. This is to avoid transferring the complete state history and sent messages.

24
doc/relocators_int.rst Normal file
View file

@ -0,0 +1,24 @@
..
Copyright 2014 Modelling, Simulation and Design Lab (MSDL) at
McGill University and the University of Antwerp (http://msdl.cs.mcgill.ca/)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Relocators
==========
.. toctree::
ManualRelocator <manualrelocator_int>
BoundaryRelocator <boundaryrelocator_int>
BasicBoundaryRelocator <basicboundaryrelocator_int>

32
doc/rewrite_documentation.sh Executable file
View file

@ -0,0 +1,32 @@
#!/bin/bash
function rewrite {
echo $1
sed -i.bak -e s/_downloads/downloads/g $1
sed -i.bak -e s/_static/static/g $1
sed -i.bak -e s/_images/images/g $1
sed -i.bak -e s/_modules/modules/g $1
sed -i.bak -e s/_sources/sources/g $1
rm ${1}.bak
}
echo `pwd`
cd _build/html
echo `pwd`
rm -r downloads || true
rm -r images || true
rm -r modules || true
rm -r sources || true
rm -r static || true
mv _downloads downloads 2> /dev/null
mv _static static 2> /dev/null
mv _images images 2> /dev/null
mv _modules modules 2> /dev/null
mv _sources sources 2> /dev/null
export -f rewrite
for f in `find . -type f`
do
rewrite $f
done

21
doc/schedulerAH_int.rst Normal file
View file

@ -0,0 +1,21 @@
..
Copyright 2014 Modelling, Simulation and Design Lab (MSDL) at
McGill University and the University of Antwerp (http://msdl.cs.mcgill.ca/)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Activity Heap scheduler
=======================
.. automodule:: schedulers.schedulerAH
:members:

21
doc/schedulerAuto_int.rst Normal file
View file

@ -0,0 +1,21 @@
..
Copyright 2014 Modelling, Simulation and Design Lab (MSDL) at
McGill University and the University of Antwerp (http://msdl.cs.mcgill.ca/)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Polymorphic scheduler
=====================
.. automodule:: schedulers.schedulerAuto
:members:

21
doc/schedulerDH_int.rst Normal file
View file

@ -0,0 +1,21 @@
..
Copyright 2014 Modelling, Simulation and Design Lab (MSDL) at
McGill University and the University of Antwerp (http://msdl.cs.mcgill.ca/)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Dirty Heap scheduler
=======================
.. automodule:: schedulers.schedulerDH
:members:

21
doc/schedulerDT_int.rst Normal file
View file

@ -0,0 +1,21 @@
..
Copyright 2014 Modelling, Simulation and Design Lab (MSDL) at
McGill University and the University of Antwerp (http://msdl.cs.mcgill.ca/)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
'Discrete Time' scheduler
=========================
.. automodule:: schedulers.schedulerDT
:members:

21
doc/schedulerHS_int.rst Normal file
View file

@ -0,0 +1,21 @@
..
Copyright 2014 Modelling, Simulation and Design Lab (MSDL) at
McGill University and the University of Antwerp (http://msdl.cs.mcgill.ca/)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Heapset scheduler
=================
.. automodule:: schedulers.schedulerHS
:members:

21
doc/schedulerML_int.rst Normal file
View file

@ -0,0 +1,21 @@
..
Copyright 2014 Modelling, Simulation and Design Lab (MSDL) at
McGill University and the University of Antwerp (http://msdl.cs.mcgill.ca/)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Minimal List scheduler
======================
.. automodule:: schedulers.schedulerML
:members:

21
doc/schedulerNA_int.rst Normal file
View file

@ -0,0 +1,21 @@
..
Copyright 2014 Modelling, Simulation and Design Lab (MSDL) at
McGill University and the University of Antwerp (http://msdl.cs.mcgill.ca/)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
No Age scheduler
================
.. automodule:: schedulers.schedulerNA
:members:

21
doc/schedulerSL_int.rst Normal file
View file

@ -0,0 +1,21 @@
..
Copyright 2014 Modelling, Simulation and Design Lab (MSDL) at
McGill University and the University of Antwerp (http://msdl.cs.mcgill.ca/)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Sorted List scheduler
=====================
.. automodule:: schedulers.schedulerSL
:members:

32
doc/schedulers.rst Normal file
View file

@ -0,0 +1,32 @@
..
Copyright 2014 Modelling, Simulation and Design Lab (MSDL) at
McGill University and the University of Antwerp (http://msdl.cs.mcgill.ca/)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Schedulers
==========
PythonPDEVS currently supports a variety of schedulers. Each of these schedulers has its own advantages and disadvantages, which are summarized in the docstrings of the Simulator.
The internal working of each scheduler is described in the corresponding documentation pages.
.. toctree::
Activity Heap <schedulerAH_int>
Dirty Heap <schedulerDH_int>
Heapset <schedulerHS_int>
No Age <schedulerNA_int>
Sorted List <schedulerSL_int>
Minimal List <schedulerML_int>
'Discrete Time' <schedulerDT_int>
Polymorphic <schedulerAuto_int>

Some files were not shown because too many files have changed in this diff Show more