From 8ee8c3a8232668ba4c6873a8d292abe5dd387878 Mon Sep 17 00:00:00 2001 From: Eloy Retamino <retamino@ugr.es> Date: Tue, 19 Nov 2019 11:50:26 +0000 Subject: [PATCH] Merged in NRRPLT-7739-remove-music (pull request #15) [NRRPLT-7739] removed music support. * [NRRPLT-7739] removed music support. music distributed packages kept as tar files. * [NRRPLT-7739] small fixes Approved-by: Ugo Albanese <ugo.albanese@santannapisa.it> Approved-by: Manos Angelidis <angelidis@fortiss.org> --- Makefile | 8 +- bitbucket-pipelines.yml | 17 +- hbp_nrp_distributed_nest/README.txt | 3 +- hbp_nrp_music_interface/MANIFEST.in | 1 - hbp_nrp_music_interface/README.txt | 2 - .../hbp_nrp_music_interface/__init__.py | 8 - .../hbp_nrp_music_interface/bibi/__init__.py | 4 - .../bibi/bibi_music_config.py | 245 ------ .../cle/MUSICBrainLoader.py | 112 --- .../cle/MUSICPyNNCommunicationAdapter.py | 255 ------ .../cle/MUSICPyNNControlAdapter.py | 74 -- .../hbp_nrp_music_interface/cle/__init__.py | 6 - .../launch/MUSICBrainProcess.py | 166 ---- .../launch/MUSICCLEProcess.py | 86 -- .../launch/MUSICLauncher.py | 118 --- .../launch/MUSICMPILauncher.py | 53 -- .../launch/__init__.py | 4 - .../launch/host/LocalLauncher.py | 88 --- .../launch/host/LuganoLauncher.py | 170 ---- .../launch/host/__init__.py | 3 - .../hbp_nrp_music_interface/tests/__init__.py | 5 - .../tests/bibi/__init__.py | 3 - .../tests/bibi/bibi.xml | 14 - .../tests/bibi/config.music | 16 - .../tests/bibi/config.xml | 137 ---- .../tests/bibi/test_bibi_music_config.py | 91 --- .../tests/cle/__init__.py | 5 - .../tests/cle/test_brain_loader.py | 67 -- .../tests/cle/test_communication_adapter.py | 89 --- .../tests/cle/test_control_adapter.py | 62 -- .../tests/launch/__init__.py | 3 - .../tests/launch/host/__init__.py | 4 - .../tests/launch/host/test_interface.py | 65 -- .../tests/launch/host/test_local.py | 105 --- .../tests/launch/host/test_lugano.py | 129 --- .../tests/launch/test_mpi_launcher.py | 197 ----- .../tests/launch/test_music_launcher.py | 109 --- .../hbp_nrp_music_interface/version.py | 2 - hbp_nrp_music_interface/requirements.txt | 3 - .../requirements_extension_tests.txt | 3 - hbp_nrp_music_interface/setup.py | 68 -- hbp_nrp_music_interface_deprec.tar.gz | Bin 0 -> 25324 bytes hbp_nrp_music_xml/MANIFEST.in | 1 - hbp_nrp_music_xml/README.txt | 5 - .../hbp_nrp_music_xml/__init__.py | 9 - .../hbp_nrp_music_xml/config/__init__.py | 6 - .../hbp_nrp_music_xml/config/music_config.py | 154 ---- .../hbp_nrp_music_xml/pynn/__init__.py | 6 - .../hbp_nrp_music_xml/pynn/cell_types.py | 58 -- .../pynn/connector_factory.py | 114 --- .../hbp_nrp_music_xml/pynn/factory.py | 188 ----- .../hbp_nrp_music_xml/pynn/utils.py | 70 -- .../hbp_nrp_music_xml/pynn/xml_factory.py | 249 ------ .../hbp_nrp_music_xml/schema/__init__.py | 5 - .../schema/generated/__init__.py | 5 - .../schema/generated/music_xml.py | 743 ------------------ .../hbp_nrp_music_xml/schema/music.xsd | 108 --- .../hbp_nrp_music_xml/tests/__init__.py | 5 - .../tests/config/__init__.py | 5 - .../tests/config/test_music_config.py | 75 -- .../hbp_nrp_music_xml/tests/pynn/__init__.py | 5 - .../tests/pynn/test_factory.py | 169 ---- .../tests/pynn/test_xml_factory.py | 178 ----- .../hbp_nrp_music_xml/version.py | 2 - hbp_nrp_music_xml/requirements.txt | 4 - .../requirements_extension_tests.txt | 3 - hbp_nrp_music_xml/setup.py | 71 -- hbp_nrp_music_xml_deprec.tar.gz | Bin 0 -> 19985 bytes verify.sh | 6 +- 69 files changed, 14 insertions(+), 4830 deletions(-) delete mode 100644 hbp_nrp_music_interface/MANIFEST.in delete mode 100644 hbp_nrp_music_interface/README.txt delete mode 100644 hbp_nrp_music_interface/hbp_nrp_music_interface/__init__.py delete mode 100644 hbp_nrp_music_interface/hbp_nrp_music_interface/bibi/__init__.py delete mode 100644 hbp_nrp_music_interface/hbp_nrp_music_interface/bibi/bibi_music_config.py delete mode 100644 hbp_nrp_music_interface/hbp_nrp_music_interface/cle/MUSICBrainLoader.py delete mode 100644 hbp_nrp_music_interface/hbp_nrp_music_interface/cle/MUSICPyNNCommunicationAdapter.py delete mode 100644 hbp_nrp_music_interface/hbp_nrp_music_interface/cle/MUSICPyNNControlAdapter.py delete mode 100644 hbp_nrp_music_interface/hbp_nrp_music_interface/cle/__init__.py delete mode 100644 hbp_nrp_music_interface/hbp_nrp_music_interface/launch/MUSICBrainProcess.py delete mode 100644 hbp_nrp_music_interface/hbp_nrp_music_interface/launch/MUSICCLEProcess.py delete mode 100644 hbp_nrp_music_interface/hbp_nrp_music_interface/launch/MUSICLauncher.py delete mode 100644 hbp_nrp_music_interface/hbp_nrp_music_interface/launch/MUSICMPILauncher.py delete mode 100644 hbp_nrp_music_interface/hbp_nrp_music_interface/launch/__init__.py delete mode 100644 hbp_nrp_music_interface/hbp_nrp_music_interface/launch/host/LocalLauncher.py delete mode 100644 hbp_nrp_music_interface/hbp_nrp_music_interface/launch/host/LuganoLauncher.py delete mode 100644 hbp_nrp_music_interface/hbp_nrp_music_interface/launch/host/__init__.py delete mode 100644 hbp_nrp_music_interface/hbp_nrp_music_interface/tests/__init__.py delete mode 100644 hbp_nrp_music_interface/hbp_nrp_music_interface/tests/bibi/__init__.py delete mode 100644 hbp_nrp_music_interface/hbp_nrp_music_interface/tests/bibi/bibi.xml delete mode 100644 hbp_nrp_music_interface/hbp_nrp_music_interface/tests/bibi/config.music delete mode 100644 hbp_nrp_music_interface/hbp_nrp_music_interface/tests/bibi/config.xml delete mode 100644 hbp_nrp_music_interface/hbp_nrp_music_interface/tests/bibi/test_bibi_music_config.py delete mode 100644 hbp_nrp_music_interface/hbp_nrp_music_interface/tests/cle/__init__.py delete mode 100644 hbp_nrp_music_interface/hbp_nrp_music_interface/tests/cle/test_brain_loader.py delete mode 100644 hbp_nrp_music_interface/hbp_nrp_music_interface/tests/cle/test_communication_adapter.py delete mode 100644 hbp_nrp_music_interface/hbp_nrp_music_interface/tests/cle/test_control_adapter.py delete mode 100644 hbp_nrp_music_interface/hbp_nrp_music_interface/tests/launch/__init__.py delete mode 100644 hbp_nrp_music_interface/hbp_nrp_music_interface/tests/launch/host/__init__.py delete mode 100644 hbp_nrp_music_interface/hbp_nrp_music_interface/tests/launch/host/test_interface.py delete mode 100644 hbp_nrp_music_interface/hbp_nrp_music_interface/tests/launch/host/test_local.py delete mode 100644 hbp_nrp_music_interface/hbp_nrp_music_interface/tests/launch/host/test_lugano.py delete mode 100644 hbp_nrp_music_interface/hbp_nrp_music_interface/tests/launch/test_mpi_launcher.py delete mode 100644 hbp_nrp_music_interface/hbp_nrp_music_interface/tests/launch/test_music_launcher.py delete mode 100644 hbp_nrp_music_interface/hbp_nrp_music_interface/version.py delete mode 100644 hbp_nrp_music_interface/requirements.txt delete mode 100644 hbp_nrp_music_interface/requirements_extension_tests.txt delete mode 100644 hbp_nrp_music_interface/setup.py create mode 100644 hbp_nrp_music_interface_deprec.tar.gz delete mode 100644 hbp_nrp_music_xml/MANIFEST.in delete mode 100644 hbp_nrp_music_xml/README.txt delete mode 100644 hbp_nrp_music_xml/hbp_nrp_music_xml/__init__.py delete mode 100644 hbp_nrp_music_xml/hbp_nrp_music_xml/config/__init__.py delete mode 100644 hbp_nrp_music_xml/hbp_nrp_music_xml/config/music_config.py delete mode 100644 hbp_nrp_music_xml/hbp_nrp_music_xml/pynn/__init__.py delete mode 100644 hbp_nrp_music_xml/hbp_nrp_music_xml/pynn/cell_types.py delete mode 100644 hbp_nrp_music_xml/hbp_nrp_music_xml/pynn/connector_factory.py delete mode 100644 hbp_nrp_music_xml/hbp_nrp_music_xml/pynn/factory.py delete mode 100644 hbp_nrp_music_xml/hbp_nrp_music_xml/pynn/utils.py delete mode 100644 hbp_nrp_music_xml/hbp_nrp_music_xml/pynn/xml_factory.py delete mode 100644 hbp_nrp_music_xml/hbp_nrp_music_xml/schema/__init__.py delete mode 100644 hbp_nrp_music_xml/hbp_nrp_music_xml/schema/generated/__init__.py delete mode 100644 hbp_nrp_music_xml/hbp_nrp_music_xml/schema/generated/music_xml.py delete mode 100644 hbp_nrp_music_xml/hbp_nrp_music_xml/schema/music.xsd delete mode 100644 hbp_nrp_music_xml/hbp_nrp_music_xml/tests/__init__.py delete mode 100644 hbp_nrp_music_xml/hbp_nrp_music_xml/tests/config/__init__.py delete mode 100644 hbp_nrp_music_xml/hbp_nrp_music_xml/tests/config/test_music_config.py delete mode 100644 hbp_nrp_music_xml/hbp_nrp_music_xml/tests/pynn/__init__.py delete mode 100644 hbp_nrp_music_xml/hbp_nrp_music_xml/tests/pynn/test_factory.py delete mode 100644 hbp_nrp_music_xml/hbp_nrp_music_xml/tests/pynn/test_xml_factory.py delete mode 100644 hbp_nrp_music_xml/hbp_nrp_music_xml/version.py delete mode 100644 hbp_nrp_music_xml/requirements.txt delete mode 100644 hbp_nrp_music_xml/requirements_extension_tests.txt delete mode 100644 hbp_nrp_music_xml/setup.py create mode 100644 hbp_nrp_music_xml_deprec.tar.gz diff --git a/Makefile b/Makefile index f2d4b3e..91af839 100644 --- a/Makefile +++ b/Makefile @@ -1,18 +1,14 @@ #modules that have tests -#TEST_MODULES=hbp_nrp_music_xml/hbp_nrp_music_xml hbp_nrp_music_interface/hbp_nrp_music_interface hbp_nrp_distributed_nest/hbp_nrp_distributed_nest -# HOTFIX: as import music fails in tests (NRRPLT-6949), remove music packages from the tests TEST_MODULES=hbp_nrp_distributed_nest/hbp_nrp_distributed_nest #modules that are installable (ie: ones w/ setup.py) -INSTALL_MODULES=hbp_nrp_music_xml hbp_nrp_music_interface hbp_nrp_distributed_nest +INSTALL_MODULES=hbp_nrp_distributed_nest #packages to cover -#COVER_PACKAGES=hbp_nrp_music_xml hbp_nrp_music_interface hbp_nrp_distributed_nest -# HOTFIX: as import music fails in tests (NRRPLT-6949), remove music packages from the tests COVER_PACKAGES=hbp_nrp_distributed_nest #documentation to build -#DOC_MODULES=hbp_nrp_music_xml/doc hbp_nrp_music_interface/doc hbp_nrp_distributed_nest/doc +#DOC_MODULES=hbp_nrp_distributed_nest/doc PYTHON_PIP_VERSION?=pip==9.0.3 diff --git a/bitbucket-pipelines.yml b/bitbucket-pipelines.yml index 063cb87..d62b366 100644 --- a/bitbucket-pipelines.yml +++ b/bitbucket-pipelines.yml @@ -19,12 +19,12 @@ pipelines: # Configure build has to be placed before make devinstall - export VIRTUAL_ENV_PATH=$VIRTUAL_ENV - export NRP_INSTALL_MODE=dev - - export PYTHONPATH=hbp_nrp_music_xml:hbp_nrp_music_interface:$VIRTUAL_ENV_PATH/lib/python2.7/site-packages:$PYTHONPATH + - export PYTHONPATH=$VIRTUAL_ENV_PATH/lib/python2.7/site-packages:$PYTHONPATH # Concatenate all build requirements, ensure newline in between - - (echo; cat $HBP/ExperimentControl/hbp_nrp_excontrol/requirements.txt) >> hbp_nrp_music_interface/requirements.txt - - (echo; cat $HBP/CLE/hbp_nrp_cle/requirements.txt) >> hbp_nrp_music_interface/requirements.txt - - (echo; cat $HBP/ExDBackend/hbp_nrp_commons/requirements.txt) >> hbp_nrp_music_interface/requirements.txt + - (echo; cat $HBP/ExperimentControl/hbp_nrp_excontrol/requirements.txt) >> hbp_nrp_distributed_nest/requirements.txt + - (echo; cat $HBP/CLE/hbp_nrp_cle/requirements.txt) >> hbp_nrp_distributed_nest/requirements.txt + - (echo; cat $HBP/ExDBackend/hbp_nrp_commons/requirements.txt) >> hbp_nrp_distributed_nest/requirements.txt # Checkout config.ini.sample from user-scripts - cp $HBP/user-scripts/config_files/CLE/config.ini.sample $HBP/CLE/hbp_nrp_cle/hbp_nrp_cle/config.ini @@ -34,7 +34,7 @@ pipelines: # Generate schemas # Egg-links have to be removed because make devinstall set them up wrongly - - pushd $VIRTUAL_ENV_PATH/lib/python2.7/site-packages && rm -f hbp-nrp-music-interface.egg-link hbp-nrp-music-xml.egg-link hbp-nrp-distributed-nest.egg-link && popd + - pushd $VIRTUAL_ENV_PATH/lib/python2.7/site-packages && rm -f hbp-nrp-distributed-nest.egg-link && popd - make devinstall # Otherwise it can't find pyxbgen - export pyxb_version=`grep "pyxb" $HBP/ExDBackend/hbp_nrp_commons/requirements.txt` - . $VIRTUAL_ENV_PATH/bin/activate && pip install ${pyxb_version} && pyxbgen -u $HBP/Experiments/bibi_configuration.xsd -m bibi_api_gen && pyxbgen -u $HBP/Experiments/ExDConfFile.xsd -m exp_conf_api_gen && pyxbgen -u $HBP/Models/robot_model_configuration.xsd -m robot_conf_api_gen && pyxbgen -u $HBP/Models/environment_model_configuration.xsd -m environment_conf_api_gen @@ -43,11 +43,10 @@ pipelines: - deactivate # Run tests - # HOTFIX: Exclude music packages from pylint because of NRRPLT-6949 - - export IGNORE_LINT='platform_venv|hbp_nrp_music_xml|hbp_nrp_music_interface|hbp_nrp_music_xml/hbp_nrp_music_xml/schema/generated|migrations|nest' + - export IGNORE_LINT='platform_venv|migrations|nest' # Egg-links have to be removed because make devinstall set them up wrongly - - pushd $VIRTUAL_ENV_PATH/lib/python2.7/site-packages && rm -f hbp-nrp-music-interface.egg-link hbp-nrp-music-xml.egg-link hbp-nrp-distributed-nest.egg-link && popd - - . $VIRTUAL_ENV_PATH/bin/activate && source /opt/ros/kinetic/setup.$CURR_SHELL && echo "PYTHONPATH $PYTHONPATH" && make verify_base || { if [ -f pylint.txt ]; then echo "----------"; echo "PYLINT.TXT"; echo "----------";cat pylint.txt; fi; if [ -f pep8.txt ]; then echo "----------"; echo "PEP8.TXT"; echo "----------";cat pep8.txt; fi; exit 1; } + - pushd $VIRTUAL_ENV_PATH/lib/python2.7/site-packages && rm -f hbp-nrp-distributed-nest.egg-link && popd + - . $VIRTUAL_ENV_PATH/bin/activate && source /opt/ros/melodic/setup.$CURR_SHELL && echo "PYTHONPATH $PYTHONPATH" && make verify_base || { if [ -f pylint.txt ]; then echo "----------"; echo "PYLINT.TXT"; echo "----------";cat pylint.txt; fi; if [ -f pep8.txt ]; then echo "----------"; echo "PEP8.TXT"; echo "----------";cat pep8.txt; fi; exit 1; } # Coverage check - $HBP/admin-scripts/nrp_cobertura_check coverage.xml diff --git a/hbp_nrp_distributed_nest/README.txt b/hbp_nrp_distributed_nest/README.txt index 0fb5718..b9e05ae 100644 --- a/hbp_nrp_distributed_nest/README.txt +++ b/hbp_nrp_distributed_nest/README.txt @@ -1,2 +1 @@ -This package provides interfaces for standalone distributed Nest simulation without -any MUSIC dependencies for the CLE/ExDBackend. \ No newline at end of file +This package provides interfaces for standalone distributed Nest simulation for the CLE/ExDBackend. \ No newline at end of file diff --git a/hbp_nrp_music_interface/MANIFEST.in b/hbp_nrp_music_interface/MANIFEST.in deleted file mode 100644 index 540b720..0000000 --- a/hbp_nrp_music_interface/MANIFEST.in +++ /dev/null @@ -1 +0,0 @@ -include requirements.txt \ No newline at end of file diff --git a/hbp_nrp_music_interface/README.txt b/hbp_nrp_music_interface/README.txt deleted file mode 100644 index 532ebdf..0000000 --- a/hbp_nrp_music_interface/README.txt +++ /dev/null @@ -1,2 +0,0 @@ -This package provides interfaces between the generic MUSIC/PyNN hbp_nrp_music_xml -package and the CLE/ExDBackend by implementing and overriding required interfaces. diff --git a/hbp_nrp_music_interface/hbp_nrp_music_interface/__init__.py b/hbp_nrp_music_interface/hbp_nrp_music_interface/__init__.py deleted file mode 100644 index c87a881..0000000 --- a/hbp_nrp_music_interface/hbp_nrp_music_interface/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -""" -This package contains NRP specific implementations utilizing the MUSIC XML configuration -and PyNN packages. -""" - -from hbp_nrp_music_interface.version import VERSION as __version__ # pylint: disable=W0611 - -__author__ = 'Martin Schulze' diff --git a/hbp_nrp_music_interface/hbp_nrp_music_interface/bibi/__init__.py b/hbp_nrp_music_interface/hbp_nrp_music_interface/bibi/__init__.py deleted file mode 100644 index 227a3db..0000000 --- a/hbp_nrp_music_interface/hbp_nrp_music_interface/bibi/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -""" -This package contains classes to translate from a parsed BIBI specification to -a MUSIC launch file and XML connectivity specification. -""" diff --git a/hbp_nrp_music_interface/hbp_nrp_music_interface/bibi/bibi_music_config.py b/hbp_nrp_music_interface/hbp_nrp_music_interface/bibi/bibi_music_config.py deleted file mode 100644 index be3a2de..0000000 --- a/hbp_nrp_music_interface/hbp_nrp_music_interface/bibi/bibi_music_config.py +++ /dev/null @@ -1,245 +0,0 @@ -# ---LICENSE-BEGIN - DO NOT CHANGE OR MOVE THIS HEADER -# This file is part of the Neurorobotics Platform software -# Copyright (C) 2014,2015,2016,2017 Human Brain Project -# https://www.humanbrainproject.eu -# -# The Human Brain Project is a European Commission funded project -# in the frame of the Horizon2020 FET Flagship plan. -# http://ec.europa.eu/programmes/horizon2020/en/h2020-section/fet-flagships -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# ---LICENSE-END -""" -Provdes a class to generate a MUSIC configuration from a BIBI configuration. -""" - -from hbp_nrp_commons.generated import bibi_api_gen # pylint:disable=no-name-in-module -from hbp_nrp_music_xml.schema.generated import music_xml -from hbp_nrp_music_xml.config.music_config import Application, MusicConfigWriter, MusicConfigPort - -import os -import logging - -logger = logging.getLogger(__name__) -logger.setLevel(logging.DEBUG) - - -class MUSICConfiguration(object): - """ - This class offers a high-level API for assembling MUSIC configuration that contains - ports between the Closed-Loop-Engine (CLE) and MUSIC-enabled neuro-simulators. - - The MUSIC configuration is assembled by converting a BIBI specification into a - synapse/neuron specification MUSIC XML representation and also produces a formatted - MUSIC script that can be used to launch the distributed simulation components. - """ - - # FIXME: [NRRPLT-4722] fudge factor for faux dynamic ports - FAUX_DYNAMIC_FUDGE_FACTOR = 5 - - def __init__(self, bibi): - """ - Convert the given BIBI configuration into a MUSIC XML configuration. - - :param bibi: A parsed BIBI file to convert. - """ - assert isinstance(bibi, bibi_api_gen.BIBIConfiguration) - - #TODO: The BIBI schema currently does not require population definitions, - # but they are required to properly generate a BIBI->MUSIC mapping. - # This check should be removed after the BIBI is updated and consistent - # for Python and h5 brains. - if bibi.brainModel.populations is None or len(bibi.brainModel.populations) == 0: - raise Exception("This BIBI file cannot be used for multi-process simulations " - "as it does not specify neuron populations in the brainModel " - "section. Please contact neurorobotics@humanbrainproject.eu if " - "you require assistance to update this file or restrict this " - "experiment to single process mode.") - - self.__conf_xml = music_xml.root() - self.__conf_script = None - - # construct the bidirectional xml port/synapse definitions - for bibi_population in bibi.brainModel.populations: - name = bibi_population.population - width = MUSICConfiguration.get_neuron_count(bibi_population) - music_population = self.__bibi_to_music_population(bibi_population) - - # FIXME: [NRRPLT-4722] Workaround for lack of dynamic MUSIC ports, allow the user - # <fudge> the size of the population for devices (the neurons will be frozen - # by default, which should not impact performance (too much)) - faux_w = width * MUSICConfiguration.FAUX_DYNAMIC_FUDGE_FACTOR - self.__add_port('%s_to_brain' % name, faux_w, 'CLE', 'BRAIN', name, music_population) - self.__add_port('%s_to_cle' % name, width, 'BRAIN', 'CLE', name, music_population) - - # empty dict of applications, CLE and BRAIN definitions are required - self.__applications = {} - - @staticmethod - def get_neuron_count(neurons): - """ - Gets the amount of neurons connected - - :param neurons: The neuron selector - :return: The amount of neurons as int - """ - if isinstance(neurons, bibi_api_gen.Index): - return 1 - elif isinstance(neurons, bibi_api_gen.Range): - if neurons.step is None: - return neurons.to - neurons.from_ - return (neurons.to - neurons.from_) / neurons.step - elif isinstance(neurons, bibi_api_gen.List): - return len(neurons.element) - elif isinstance(neurons, bibi_api_gen.Population): - return neurons.count - raise Exception("Neuron Count: Don't know how to process neuron selector " - + str(type(neurons))) - - def add_application(self, name, binary, args, processes): - """ - Add a sending or receiving MUSIC application definition. - - :param name: The application name, currently only CLE and BRAIN are supported. - :param binary: The binary to execute for this application. - :param args: A list of arguments to python. - :param processes: The number of processes to allocate for this application. - """ - if name not in ['CLE', 'BRAIN']: - raise Exception("Invalid application name {name} specified, must be " - "either CLE or BRAIN.".format(name=name)) - - if name in self.__applications: - raise Exception("Duplicate application definition for {name}, this does not " - "appear to be a valid request.".format(name=name)) - - self.__applications[name] = Application(name, binary, args, int(processes)) - - def save(self, path): - """ - Save the MUSIC XML and application configuration files to the specified directory. - - :patam path: The path of the directory to save to. - """ - - if 'CLE' not in self.__applications or 'BRAIN' not in self.__applications: - raise Exception('Missing CLE or BRAIN application definitions, cannote create MUSIC' - 'configuration script!') - - # write the xml connectivity specification to proxy.xml - proxy_file = os.path.join(path, 'proxy.xml') - with open(proxy_file, 'w') as f: - f.write(self.__conf_xml.toxml()) - - # write the actual music launching script to cle.music - music_file = os.path.join(path, 'cle.music') - with open(music_file, 'w') as f: - ports = [MusicConfigPort(p) for p in self.__conf_xml.port] - applications = [self.__applications['CLE'], self.__applications['BRAIN']] - self.__conf_script = MusicConfigWriter(None, applications, ports) - self.__conf_script.write(f) - - @staticmethod # required to avoid "no self use" pylint error - def __bibi_to_music_population(bibi_population): - """ - This function converts BIBI NeuronSelector object (population specification) into - a MUSIC-XML SynapticSelector objects. Currently only Range types are supported in - population definitions, but this function supports all potential types. - - :param bibi_population: A BIBI MultiNeuronSelector object - """ - - def make_list_selector(iterable): - """ - Helper function to create a list of neurons from multiple BIBI selector types. - - :param iterable: A list of neuron ids to add to this list selector. - """ - neuron_list = music_xml.ListSelector() - for element in iterable: - neuron_list.append(element) - return neuron_list - - if isinstance(bibi_population, bibi_api_gen.Range): - neuron_slice = music_xml.SliceSelector() - neuron_slice.start = bibi_population.from_ - neuron_slice.stop = bibi_population.to - step = getattr(bibi_population, 'step', 1) - if not step: - step = 1 - neuron_slice.step = step - return neuron_slice - - elif isinstance(bibi_population, bibi_api_gen.List): - return make_list_selector(bibi_population.element) - - elif isinstance(bibi_population, bibi_api_gen.Population): - return make_list_selector(list(range(0, bibi_population.count))) - - else: - raise Exception("The deduction from type {} (for population declaration " - "'{}') to the MUSIC equivalent population declaration " - "is not implemented".format(type(bibi_population), - bibi_population.population)) - - def __add_port(self, name, width, sender, receiver, target, selector): - """ - Generates a MUSIC XML spike event port for the named population with given width between - the specified source and target. - - :param name: Base name of the MUSIC port (NOT the population name). - :param width: The MUSIC port width (number of neurons to - :param sender: The name of the producing MUSIC application (e.g. CLE, BRAIN) - :param receiver: The name of the receiving MUSIC application (e.g. CLE, BRAIN) - :param target: The neuron population name to connect to/from. - :param selector: The MUSIC XML population selector (e.g. slice/etc.) - """ - - if sender not in ['CLE', 'BRAIN'] or receiver not in ['CLE', 'BRAIN']: - raise Exception("Invalid sender {sender} or receiver {receiver} specified, must be " - "either CLE or BRAIN.".format(sender=sender, receiver=receiver)) - - port = music_xml.Port() - port.type = music_xml.PortType.Event - port.name = name - port.width = width - port.sender = music_xml.Peer(name=sender) - port.receiver.append(music_xml.Peer(name=receiver)) - - synapse = music_xml.SynapticConnection() - synapse.type = music_xml.ConnectionType.one_to_one - synapse.target = target - synapse.selector = selector - port.receiver[0].synapse = [synapse] - - logger.debug("Created MUSIC port for spike event proxy: " - "{sender_name}.{port_name} ---/port_width:{port_width}/---> " - "{receiver_name}.{port_name}" - .format(sender_name=port.sender.name, port_width=port.width, - receiver_name=port.receiver[0].name, port_name=port.name)) - - self.__conf_xml.port.append(port) - - def __str__(self): - """ - Returns the formatted MUSIC configuration script as a string. - """ - return str(self.__conf_script) - - def xml(self): - """ - Returns the XML string representation of the underlying generated configuration. - """ - return str(self.__conf_xml.toxml()) diff --git a/hbp_nrp_music_interface/hbp_nrp_music_interface/cle/MUSICBrainLoader.py b/hbp_nrp_music_interface/hbp_nrp_music_interface/cle/MUSICBrainLoader.py deleted file mode 100644 index fd43c6d..0000000 --- a/hbp_nrp_music_interface/hbp_nrp_music_interface/cle/MUSICBrainLoader.py +++ /dev/null @@ -1,112 +0,0 @@ -# ---LICENSE-BEGIN - DO NOT CHANGE OR MOVE THIS HEADER -# This file is part of the Neurorobotics Platform software -# Copyright (C) 2014,2015,2016,2017 Human Brain Project -# https://www.humanbrainproject.eu -# -# The Human Brain Project is a European Commission funded project -# in the frame of the Horizon2020 FET Flagship plan. -# http://ec.europa.eu/programmes/horizon2020/en/h2020-section/fet-flagships -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# ---LICENSE-END -"""` -Load a brain network's MUSIC proxies -""" -import pyNN.nest as sim -import hbp_nrp_cle.tf_framework.config as tf_config - -from hbp_nrp_music_xml.pynn.factory import PyNNProxyFactory -from hbp_nrp_music_xml.pynn.connector_factory import PyNNConnectorFactory -from hbp_nrp_music_xml.pynn.xml_factory import XmlFactory - -import imp - -__brainIndex = 0 - - -# pylint: disable=global-statement -def load_proxies_from_xml(xml_file, **populations): - """ - Load neuron population proxy devices from the specified music_xml file and create a - mapping between population names and their proxy devices. This will allow the - communications adapter or any other components to lookup the appropriate input or - output proxy for a given named population. - - :param xml_file: Path to the music_xml ile containing proxy population definitions. - :param populations: A named list of population proxies to create - """ - - # ensure a Python brain module has not been loaded or set - if tf_config.brain_root is not None: - raise Exception("A brain module has been loaded in this process, no brain should be" - "present when loading MUSIC proxies. Invalid configuration, aborting.") - - # attempt to read the XML file, abort if doesn't exist or cannot be read - try: - with open(xml_file, 'r') as f: - xml = f.read() - except IOError: - raise Exception("Cannot create MUSIC proxy devices for simulation, unable to access " - "XML specification file {}. Aborting!".format(xml_file)) - - # simulator specific configuration (not through PyNN, make sure simulator is supported) - if sim.simulator.name != "NEST": - raise Exception("Unable to load MUSIC proxy devices for simulator {}, currently only " - "NEST is supported. Aborting!".format(sim.simulator.name)) - - # build proxy factories and store a dictionary of built proxies - proxy_model_factory = PyNNProxyFactory(sim) - connector_factory = PyNNConnectorFactory(sim) - xml_factory = XmlFactory("CLE", connector_factory, proxy_model_factory, {}) - proxy_dict = xml_factory.create_proxies(xml) - - # create a surrogate python brain module in place of loading the actual brain module - global __brainIndex - tf_config.brain_root = imp.new_module('__brain_model{}'.format(__brainIndex)) - __brainIndex += 1 - - # the CLE will only be able to access named proxy populations that were specified in the BIBI - # create a population -> proxy dictionary in the tf config so that the communications adapter - # can appropriately locate and use named input or output proxies as need - # store a proxy population in the root brain module for each population so that the existing - # tf framework can create population views without modification - understanding that the - # communications adapter will replace the base proxy with the input/output population as needed - try: - tf_config.music_proxies = {} - - for p in populations: - - # create population views to mimic how normal neuron populations are created - proxy_to_brain = proxy_dict['{}_to_brain'.format(p)] - proxy_to_brain_view = sim.PopulationView(proxy_to_brain, - slice(0, len(proxy_to_brain), None), - label=p) - proxy_to_cle = proxy_dict['{}_to_cle'.format(p)] - proxy_to_cle_view = sim.PopulationView(proxy_to_cle, - slice(0, len(proxy_to_cle), None), - label=p) - - # default named proxy population, a named population for p must be in the brain_root - # dictionary for transfer functions to create slices, but the correct population for - # a source or sink will be properly selected in the communication adapter, so this - # specification is arbitrary but definitely required - tf_config.brain_root.__dict__[p] = proxy_to_brain_view - - # store the named proxies by direction - tf_config.music_proxies[p] = {'source': proxy_to_brain_view, 'sink': proxy_to_cle_view} - - except KeyError: - raise Exception("Could not find proxy-specification of population {} in MUSIC " - "XML specification file: {}".format(p, xml_file)) diff --git a/hbp_nrp_music_interface/hbp_nrp_music_interface/cle/MUSICPyNNCommunicationAdapter.py b/hbp_nrp_music_interface/hbp_nrp_music_interface/cle/MUSICPyNNCommunicationAdapter.py deleted file mode 100644 index 6588a93..0000000 --- a/hbp_nrp_music_interface/hbp_nrp_music_interface/cle/MUSICPyNNCommunicationAdapter.py +++ /dev/null @@ -1,255 +0,0 @@ -# ---LICENSE-BEGIN - DO NOT CHANGE OR MOVE THIS HEADER -# This file is part of the Neurorobotics Platform software -# Copyright (C) 2014,2015,2016,2017 Human Brain Project -# https://www.humanbrainproject.eu -# -# The Human Brain Project is a European Commission funded project -# in the frame of the Horizon2020 FET Flagship plan. -# http://ec.europa.eu/programmes/horizon2020/en/h2020-section/fet-flagships -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# ---LICENSE-END -""" -Extensions of the base CLE PyNNCommunicationAdapter to use MUSIC specific proxies -instead of direct population access. Maxmimum code reuse and minimal duplication -where possible. -""" -from hbp_nrp_cle.brainsim.pynn_nest.PyNNNestCommunicationAdapter import PyNNNestCommunicationAdapter -import pyNN.nest as sim -import hbp_nrp_cle.tf_framework.config as tf_config - -from hbp_nrp_music_interface.bibi.bibi_music_config import MUSICConfiguration -from hbp_nrp_distributed_nest.launch.NestBrainProcess import NestBrainProcess - -from mpi4py import MPI - -import logging - -logger = logging.getLogger(__name__) - - -class MUSICPyNNCommunicationAdapter(PyNNNestCommunicationAdapter): - """ - Represents a MUSIC/PyNN proxy communication adapter for the neuronal simulation - """ - - def initialize(self): - """ - Marks the MUSICPyNN adapter as initialized. - """ - logger.info("MUSICPyNN communication adapter initialized") - super(MUSICPyNNCommunicationAdapter, self).initialize() - - def register_spike_source(self, populations, spike_generator_type, **params): - """ - Intercepts default PyNNNestCommunicationAdapter request and replaces specified - population with a new set of proxy neurons for each individual TF. This is required - to ensure weighting between the TF and target node is maintained properly as multiple - TFs can target a single neuron and we must weight their inputs separately. - - This function is over-complicated due to backwards comaptibility for the single process - CLE, it can be simplified in the future if we merge these repositories and especially - when MUSIC supports dynamic ports. - - :param populations: A reference to the populations to which the spike generator - should be connected - :param spike_generator_type: A spike generator type (see documentation - or a list of allowed values) - :param params: A dictionary of configuration parameters - :return: A communication object or a group of objects - """ - # get the next set of free parrot neurons with real connectivity to proxy_out neuron - proxies = self.__get_population_proxy(populations, 'source') - - # connect the device generator to the new proxy neurons, this will create synapses with - # given parameters that we will need to duplicate from proxy->real neuron (parrot neurons - # ignore their input synapses, but output synapses are respected) - device = super(MUSICPyNNCommunicationAdapter, self). \ - register_spike_source(proxies, spike_generator_type, **params) - - # notify the remote brain processes that they need to setup the other side of this proxy - self.__notify_brain_processes(populations, proxies) - - # return the new generator device - return device - - def register_spike_sink(self, populations, spike_detector_type, **params): - """ - Requests a communication object with the given spike detector type - for the given set of neurons - - :param populations: A reference to the populations which should be connected - to the spike detector - :param spike_detector_type: A spike detector type (see documentation - for a list of allowed values) - :param params: A dictionary of configuration parameters - :return: A Communication object or a group of objects - """ - populations = self.__get_population_proxy(populations, 'sink') - return super(MUSICPyNNCommunicationAdapter, self). \ - register_spike_sink(populations, spike_detector_type, **params) - - @staticmethod - def __notify_brain_processes(populations, proxies): - """ - Notify remote MPI Brain Processes that they must complete this transfer function - connection by duplicating the parameters for this device connection with the output - proxy neurons. This is the only way to ensure a TF is actually connected with the - right parameters. - - :param populations The target population to connect to on the other side. - :param proxies The proxy/parrot neurons that the device is actually connected to. - """ - - # nest is the only supported simulator in the hbp_nrp_music packages, but assert here - # in case this changes, import here to avoid starting nest by accident earlier - assert sim.simulator.name == "NEST", "NEST is currently required to reconfigure MUSIC ports" - import nest - - # synapse details to pass to other MPI clients, the synapse parameters should all be - # the same, but in the future a device may have variable/randomly parameterized - # synapses so just support them now - synapse_params = [] - - # for each proxy/reference pair, extract input connectivity from the device, the real - # reference output targets, and finally duplicate the input connectivity to the targets - for p in map(int, proxies.all_cells): - - # the reference connection from the parrot neuron to proxy out, this contains music - # channel information - ref_c = nest.GetStatus(nest.GetConnections(source=[p]))[0] - channel = ref_c['receptor'] - - # the connection from device to parrot neuron, our desired synapse - dev_c = nest.GetStatus(nest.GetConnections(target=[p]))[0] - model = str(dev_c['synapse_model']) - - # remove specific connection information, only leave synapse params since - # otherwise nest will complain about unused values - for k in ['receptor', 'source', 'target', 'synapse_label', 'synapse_model', 'sizeof']: - if k in dev_c: - dev_c.pop(k) - - # override the original dummy weighted parrot->proxy synapse, we can't create a - # new synapse because MUSIC will complain about duplicate use of music_channels - nest.SetStatus(nest.GetConnections(source=[p]), dev_c) - - # store the connection parameters for remote clients - dev_c['model'] = model - dev_c['music_channel'] = channel - synapse_params.append(dev_c) - - # population information to send to the remote MPI nodes, we can't pickle Populations - # directly and those references wouldn't be valid on the remote nodes anyway - label = populations.parent.label if populations.parent else populations.label - - # [NRRPLT-4722] workaround, if there is no population mask, then we need to use the size - # of the proxies to target the real size of the neuron population instead of the inflated - # proxy size - mask = populations.mask if populations.mask else slice(0, len(proxies), 1) - - # propagate the synapse creation parameters to all remote notes, they will create the - # other side of the connections for this type - for rank in xrange(MPI.COMM_WORLD.Get_size()): - if rank == MPI.COMM_WORLD.Get_rank(): - continue - - MPI.COMM_WORLD.send({'command': 'ConnectTF', 'label': label, 'mask': mask, - 'synapses': synapse_params}, - dest=rank, tag=NestBrainProcess.MPI_MSG_TAG) - - def __get_population_proxy(self, population, proxy_type): - """ - Retrieves the first <size> free proxy neurons of the specified population proxy type - for a given population. - - :param population The population to find/create a proxy for. - :param proxy_type string The type of proxy device to retrieve. - :return: An equivalend population list or view containing proxies. - """ - if proxy_type not in ['sink', 'source']: - raise TypeError("Cannot retrieve unknown population proxy type: {}" % proxy_type) - - # lists are explicitly allowed by __register_device, support even if they are unused - if isinstance(population, list): - return [self.__get_population_proxy(p, proxy_type) for p in population] - - assert isinstance(population, sim.PopulationView) - try: - - # for sinks we must use the same few neurons that are allocated at MUSIC launch - # otherwise the frontend will not properly fill out the neuron monitor and things - if proxy_type == 'sink': - # top level population view with no subslicing (e.g. sensors, actors, etc.) - if isinstance(population.parent, sim.Population): - # tf_config.music_proxies is set in - # hbp_nrp_music_interface/hbp_nrp_music_interface/cle/MUSICBrainLoader.py - # and not in hbp_nrp_cle.tf_framework.config - # which confuses pylint - # pylint: disable=no-member - return tf_config.music_proxies[population.label][proxy_type] - # pylint: enable=no-member - - # otherwise, this is a view of a top level named population view - # pylint: disable=no-member - parent = tf_config.music_proxies[population.parent.label][proxy_type] - # pylint: enable=no-member - return sim.PopulationView(parent, population.mask, population.label) - - # [NRRPLT-4722] workaround, simply get the first <size> proxy neurons that are - # frozen for a population, unfreeze and return them for connection, for full - # population requests only get the real population size - import nest - # top level population view with no subslicing (e.g. sensors, actors, etc.) - # pylint: disable=no-member - if isinstance(population.parent, sim.Population): - proxies = tf_config.music_proxies[population.label][proxy_type] - size = proxies.size / MUSICConfiguration.FAUX_DYNAMIC_FUDGE_FACTOR - - # otherwise, this is a view of a top level named population view - else: - proxies = tf_config.music_proxies[population.parent.label][proxy_type] - size = population.size - # pylint: disable=no-member - # find all of the free/frozen proxy neurons - mask = [] - for i, p in enumerate(map(int, proxies.all_cells)): - - # if the neuron is frozen, unfreeze it and add to our list - if nest.GetStatus([p], 'frozen')[0]: - nest.SetStatus([p], 'frozen', False) - mask.append(i) - - # stop looping when we have enough free neurons - if len(mask) == size: - break - - # make sure we have enough free proxy neurons - if len(mask) != size: - raise Exception("Not enough free proxy neurons to connect transfer functions!\n" - "Please contact neurorobotics@humanbrainproject.eu with details of " - "this experiment and associated BIBI file if you require support.") - - # return a view of the free proxy neurons for connection - return sim.PopulationView(proxies, mask, population.label) - - except KeyError: - raise Exception("Unable to locate distriuted MUSIC neuron population proxies for {}.\n" - "This likely means that a transfer function is utilizing a neuron " - "population that is not specified at the top of the BIBI file or is" - "attempting to directly access the underlying \"circuit\" population." - "Please contact neurorobotics@humanbrainproject.eu with details of " - "this experiment and associated BIBI file if you require support." - .format(population)) diff --git a/hbp_nrp_music_interface/hbp_nrp_music_interface/cle/MUSICPyNNControlAdapter.py b/hbp_nrp_music_interface/hbp_nrp_music_interface/cle/MUSICPyNNControlAdapter.py deleted file mode 100644 index 2d003a6..0000000 --- a/hbp_nrp_music_interface/hbp_nrp_music_interface/cle/MUSICPyNNControlAdapter.py +++ /dev/null @@ -1,74 +0,0 @@ -# ---LICENSE-BEGIN - DO NOT CHANGE OR MOVE THIS HEADER -# This file is part of the Neurorobotics Platform software -# Copyright (C) 2014,2015,2016,2017 Human Brain Project -# https://www.humanbrainproject.eu -# -# The Human Brain Project is a European Commission funded project -# in the frame of the Horizon2020 FET Flagship plan. -# http://ec.europa.eu/programmes/horizon2020/en/h2020-section/fet-flagships -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# ---LICENSE-END -""" -Extensions of the base CLE PyNNControlAdapter to use MUSIC specific proxies -instead of direct population access. Maxmimum code reuse and minimal duplication -where possible. -""" -from hbp_nrp_distributed_nest.cle.DistributedPyNNControlAdapter import DistributedPyNNControlAdapter -from hbp_nrp_music_interface.cle import MUSICBrainLoader - -import music -import logging -import os - -logger = logging.getLogger(__name__) - - -class MUSICPyNNControlAdapter(DistributedPyNNControlAdapter): - """ - Represents a MUSIC/PyNN proxy controller object for the neuronal simulator - """ - - def load_brain(self, network_file, **populations): - """ - Load MUSIC/PyNN brain proxy populations rather than loading and instantiating - the brain - overrides functionality for both python and h5 brains. - - :param network_file: The path to the python file containing the network - :param populations: A named list of populations to create - """ - self.__load_music_brain_proxies(**populations) - - # load the brain source for the frontend to display, copied from parent class - logger.info("Saving brain source") - import hbp_nrp_cle.tf_framework.config as tf_config - with open(network_file) as source: - tf_config.brain_source = source.read() - - @staticmethod - def __load_music_brain_proxies(**populations): - """ - Load MUSIC proxy devices for the given brain network specification and create - mappings from populations to input/output proxy devices. - - :param populations: A named list of populations to create - """ - - # initialize MUSIC and load proxies from the MUSIC proxy xml - logger.info("Loading MUSIC proxy brain devices.") - music.Setup() - music_path = os.environ.get('NRP_MUSIC_DIRECTORY') - proxy_file = os.path.join(music_path, 'proxy.xml') - MUSICBrainLoader.load_proxies_from_xml(proxy_file, **populations) diff --git a/hbp_nrp_music_interface/hbp_nrp_music_interface/cle/__init__.py b/hbp_nrp_music_interface/hbp_nrp_music_interface/cle/__init__.py deleted file mode 100644 index fb359f2..0000000 --- a/hbp_nrp_music_interface/hbp_nrp_music_interface/cle/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -""" -This package contains an extended implementation of the CLE PyNN Brain Control Adapter -and Brain Loader that use MUSIC proxy interfaces rather than direct access. -""" - -__author__ = 'Martin Schulze' diff --git a/hbp_nrp_music_interface/hbp_nrp_music_interface/launch/MUSICBrainProcess.py b/hbp_nrp_music_interface/hbp_nrp_music_interface/launch/MUSICBrainProcess.py deleted file mode 100644 index 1def9d8..0000000 --- a/hbp_nrp_music_interface/hbp_nrp_music_interface/launch/MUSICBrainProcess.py +++ /dev/null @@ -1,166 +0,0 @@ -# ---LICENSE-BEGIN - DO NOT CHANGE OR MOVE THIS HEADER -# This file is part of the Neurorobotics Platform software -# Copyright (C) 2014,2015,2016,2017 Human Brain Project -# https://www.humanbrainproject.eu -# -# The Human Brain Project is a European Commission funded project -# in the frame of the Horizon2020 FET Flagship plan. -# http://ec.europa.eu/programmes/horizon2020/en/h2020-section/fet-flagships -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# ---LICENSE-END -""" -A distributed MUSIC brain process that can be launched standalone on remote hosts. -""" -import hbp_nrp_cle.brainsim.pynn_nest # pylint: disable=unused-import - -from hbp_nrp_distributed_nest.launch.NestBrainProcess import NestBrainProcess - -import pyNN.nest as sim -import hbp_nrp_cle.tf_framework.config as tf_config - -from hbp_nrp_music_xml.pynn.factory import PyNNProxyFactory -from hbp_nrp_music_xml.pynn.connector_factory import PyNNConnectorFactory -from hbp_nrp_music_xml.pynn.xml_factory import XmlFactory - -import argparse -import os -import music - -from mpi4py import MPI -import traceback - - -class MUSICBrainProcess(NestBrainProcess): - """ - A distributed MUSIC brain process that can be launched standalone on remote hosts. - """ - - def __init__(self, bibi_file, rng_seed): - """ - Load the distributed brain and construct MUSIC proxies for communication - to/from the CLE. Nest will automatically allocate the brain in a round-robin - fashion under the hood, we do not need to do anything explicitly. - - :param bibi_file The absolute path to the BIBI file for this experiment. - """ - - # setup MUSIC before any brain loading attempt - self._music_setup = music.Setup() - - # load the brain and set parameters - super(MUSICBrainProcess, self).__init__(bibi_file, rng_seed) - - # load the MUSIC proxies for the spawned brain - music_path = os.environ.get('NRP_MUSIC_DIRECTORY') - proxy_file = os.path.join(music_path, 'proxy.xml') - with open(proxy_file, 'r') as f: - music_xml = f.read() - - proxy_model_factory = PyNNProxyFactory(sim) - connector_factory = PyNNConnectorFactory(sim) - xml_factory = XmlFactory("BRAIN", - connector_factory, - proxy_model_factory, - tf_config.brain_root.__dict__) - - self._proxies = xml_factory.create_proxies(music_xml) - - def _connect_tf(self, params): - """ - Reflect a transfer function connection made on the CLE side by connecting proxy neurons - to real brain neurons using the same parameters and connectivity as the CLE. This is the - only way to guarantee both sides share the same connectivity using static port allocation. - - :param params The connectivity/synapse parameters passed by the CLE. - """ - - # connections only supported during simulation construction - if self._ready: - raise Exception("The distributed MUSIC-Nest implementation does not dynamic TFs!") - - # get the population of neurons from our dictionary, we can guarantee this is a valid - # population that has been declared in the BIBI at this point as the CLE will validate - # before sending us the create message - brain_pop = sim.PopulationView(tf_config.brain_root.__dict__[params['label']], - selector=params['mask']) - - # get the whole population of proxy neurons for this port - port_name = '%s_to_brain' % params['label'] - proxies = self._proxies[port_name] - - # this is Nest specific code, but so is the rest of the pipeline - assert sim.simulator.name == "NEST", "Currently only NEST is supported" - import nest - - # iterate through synapses and connect the specific proxy neuron (via music channel) to real - # brain neuron with given parameters - for synapse, brain_neuron in zip(params['synapses'], map(int, brain_pop.all_cells)): - - # get the proxy neuron at the channel index - proxy = proxies[synapse['music_channel']] - synapse.pop('music_channel') - - # thaw the proxy neuron so we actually get data - nest.SetStatus([proxy], 'frozen', False) - - # for a source, we only need to connect the input proxy to the real neuron - nest.Connect([proxy], [brain_neuron], syn_spec=synapse) - - def _delete_tf(self, params): - """ - Currently unsupported, unable to dynamically create or destroy MUSIC ports. - """ - - # ignore any commands during simulation construction - if not self._ready: - return - - raise Exception("The distributed MUSIC-Nest implementation does not support TF deletion!") - - def _load_brain(self, params): - """ - Currently unsupported, unable to dynamically create or destroy MUSIC ports. - """ - - # ignore any commands during simulation construction - if not self._ready: - return - - raise Exception("The distributed MUSIC-Nest implementation does not support brain changes!") - - -if __name__ == '__main__': # pragma: no cover - - try: - parser = argparse.ArgumentParser() - parser.add_argument('--bibi-file', dest='bibi_file', - help='the bibi file path to load', required=True) - parser.add_argument('--rng-seed', dest='rng_seed', - help='the global experiment RNG seed', required=True) - args = parser.parse_args() - - # construct brain and proxies (expand environment variables in paths) - brain = MUSICBrainProcess(os.path.expandvars(args.bibi_file), args.rng_seed) - - # run the brain until terminated, this is a blocking call - brain.run() - - except Exception: # pylint: disable=broad-except - # print the traceback which should go back to the remote logger - traceback.print_exc() - - # for any failures, terminate all other brain processes and the CLE - MPI.COMM_WORLD.Abort(-1) diff --git a/hbp_nrp_music_interface/hbp_nrp_music_interface/launch/MUSICCLEProcess.py b/hbp_nrp_music_interface/hbp_nrp_music_interface/launch/MUSICCLEProcess.py deleted file mode 100644 index cb149af..0000000 --- a/hbp_nrp_music_interface/hbp_nrp_music_interface/launch/MUSICCLEProcess.py +++ /dev/null @@ -1,86 +0,0 @@ -# ---LICENSE-BEGIN - DO NOT CHANGE OR MOVE THIS HEADER -# This file is part of the Neurorobotics Platform software -# Copyright (C) 2014,2015,2016,2017 Human Brain Project -# https://www.humanbrainproject.eu -# -# The Human Brain Project is a European Commission funded project -# in the frame of the Horizon2020 FET Flagship plan. -# http://ec.europa.eu/programmes/horizon2020/en/h2020-section/fet-flagships -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# ---LICENSE-END -""" -This module contains the CLE process logic for the simulation assembly using MUSIC -""" - -import logging -import sys - -from hbp_nrp_cleserver.server.CLEGazeboSimulationAssembly import CLEGazeboSimulationAssembly -from hbp_nrp_cleserver.server.ServerConfigurations import robot_gazebo_ros_adapters -from hbp_nrp_music_interface.cle.MUSICPyNNCommunicationAdapter import MUSICPyNNCommunicationAdapter -from hbp_nrp_music_interface.cle.MUSICPyNNControlAdapter import MUSICPyNNControlAdapter - -from hbp_nrp_distributed_nest.launch.DistributedCLEProcess import launch_cle - -# maintain this import order and both must be done before mpi4py -import hbp_nrp_cle.brainsim.pynn_nest # pylint: disable=unused-import -import pyNN.nest as nestsim -import music - -logger = logging.getLogger(__name__) - - -class MusicCLESimulationAssembly(CLEGazeboSimulationAssembly): - """ - Defines the assembly of a simulation using MUSIC - """ - - def __init__(self, sim_id, exc, bibi_model, **par): - """ - Creates a new simulation assembly to simulate an experiment using the CLE and Gazebo - :param sim_id: The simulation id - :param exc: The experiment configuration - :param bibi_model: The BIBI configuration - """ - super(MusicCLESimulationAssembly, self).__init__(sim_id, exc, bibi_model, **par) - - def _create_robot_adapters(self): - """ - Creates the adapter components for the robot side - - :return: A tuple of the communication and control adapter for the robot side - """ - return robot_gazebo_ros_adapters() - - def _create_brain_adapters(self): - """ - Creates the adapter components for the neural simulator - - :return: A tuple of the communication and control adapter for the neural simulator - """ - logger.info('Using MUSIC configuration and adapters for CLE') - - # initialize music and set the CLE to use MUSIC adapters - music.Setup() - - braincomm = MUSICPyNNCommunicationAdapter() - braincontrol = MUSICPyNNControlAdapter(nestsim) - return braincomm, braincontrol - -if __name__ == '__main__': # pragma: no cover - - # guaranteed to only be launched in one process by MUSIC, launch the CLE with defined assembly - launch_cle(sys.argv[1:], MusicCLESimulationAssembly) diff --git a/hbp_nrp_music_interface/hbp_nrp_music_interface/launch/MUSICLauncher.py b/hbp_nrp_music_interface/hbp_nrp_music_interface/launch/MUSICLauncher.py deleted file mode 100644 index 5da6ead..0000000 --- a/hbp_nrp_music_interface/hbp_nrp_music_interface/launch/MUSICLauncher.py +++ /dev/null @@ -1,118 +0,0 @@ -# ---LICENSE-BEGIN - DO NOT CHANGE OR MOVE THIS HEADER -# This file is part of the Neurorobotics Platform software -# Copyright (C) 2014,2015,2016,2017 Human Brain Project -# https://www.humanbrainproject.eu -# -# The Human Brain Project is a European Commission funded project -# in the frame of the Horizon2020 FET Flagship plan. -# http://ec.europa.eu/programmes/horizon2020/en/h2020-section/fet-flagships -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# ---LICENSE-END -""" -Setup, build, and launch a distributed MUSIC instance that will spawn the CLE and -requested brain processes. -""" -from hbp_nrp_distributed_nest.launch.NestLauncher import NestLauncher - -from hbp_nrp_music_interface.bibi import bibi_music_config -from hbp_nrp_music_interface.launch.MUSICMPILauncher import MUSICMPILauncher -from hbp_nrp_music_interface.launch.host.LocalLauncher import LocalLauncher -from hbp_nrp_music_interface.launch.host.LuganoLauncher import LuganoLauncher - -import os - - -# This class intentionally does not inherit SimulationServer (even though it is an implementation of -# it) in order to avoid duplicate notificators -class MUSICLauncher(NestLauncher): - """ - Setup, build, and launch a distributed MUSIC instance that will spawn the CLE and - requested brain processes. - """ - - def __init__(self, sim_id, exc, bibi, **par): - """ - Store all experiment configuration parameters so that they can be propagated - to the remote hosts. - - :param exc: the experiment configuration - :param bibi: the BIBI configuration. - :param server_host Target Gazebo/brain process host (e.g. local or lugano) - :param reservation Reservation string for cluster backend (None is a valid option) - :param sim_id The id of the simulation/experiment to be launched. - :param timeout The default simulation timeout (time initially allocated). - """ - super(MUSICLauncher, self).__init__(sim_id, exc, bibi, **par) - - # we should call the except_hook when something goes wrong in the simulation, but currently - # we don't - # pylint: disable=unused-argument - def initialize(self, environment_file, except_hook): - """ - Construct the MUSIC launch configuration that will spawn CLE + brain processes - on distributed hosts. - """ - - # extract the environment file path - nrp_models_path = os.environ.get('NRP_MODELS_DIRECTORY').rstrip('/') - self._env_file = environment_file.replace(nrp_models_path, '$NRP_MODELS_DIRECTORY') - - # create a host specific launcher - if self._server_host == 'local': - self._launcher = LocalLauncher() - elif self._server_host == 'lugano': - self._launcher = LuganoLauncher(self._exc.bibiConf.processes + 1, - self._timeout, - self._reservation) - else: - raise Exception('Unknown server host {}, cannot configure and launch MUSIC!' - .format(self._server_host)) - - # create launch scripts for the CLE and brain processes - cle_launcher, brain_launcher = self._launcher.create_launch_scripts() - - # command line argument friendly versions of timeout and reservation arguments - # the receiving processes must understand how to convert these back - reservation_str = self._reservation if self._reservation else '' - timeout_str = str(self._timeout).replace(' ', '_') - - # build a MUSIC configuration script with correct brain ports, launchers and arugments - # save it to the host launcher temp directory, this is the same for every host - music_conf = bibi_music_config.MUSICConfiguration(self._bibi) - music_conf.add_application('CLE', - cle_launcher, - ['--exdconf={}'.format(self._exd_file), - '--bibi={}'.format(self._bibi_file), - '--environment={}'.format(self._env_file), - '--experiment-path={}'.format(self._exp_path), - '--gzserver-host={}'.format(self._server_host), - '--reservation={}'.format(reservation_str), - '--sim-id={}'.format(self._sim_id), - '--timeout={}'.format(timeout_str), - '--rng-seed={}'.format(self._rng_seed)], - 1) - music_conf.add_application('BRAIN', - brain_launcher, - ['--bibi-file={}'.format(self._bibi_file), - '--rng-seed={}'.format(self._rng_seed)], - self._exc.bibiConf.processes) - music_conf.save(self._launcher.local_tmpdir) - - # construct the actual MPI launcher - self.mpilauncher = MUSICMPILauncher('music cle.music') - - # build and deploy configuration - self._build() diff --git a/hbp_nrp_music_interface/hbp_nrp_music_interface/launch/MUSICMPILauncher.py b/hbp_nrp_music_interface/hbp_nrp_music_interface/launch/MUSICMPILauncher.py deleted file mode 100644 index 0bc240f..0000000 --- a/hbp_nrp_music_interface/hbp_nrp_music_interface/launch/MUSICMPILauncher.py +++ /dev/null @@ -1,53 +0,0 @@ -# ---LICENSE-BEGIN - DO NOT CHANGE OR MOVE THIS HEADER -# This file is part of the Neurorobotics Platform software -# Copyright (C) 2014,2015,2016,2017 Human Brain Project -# https://www.humanbrainproject.eu -# -# The Human Brain Project is a European Commission funded project -# in the frame of the Horizon2020 FET Flagship plan. -# http://ec.europa.eu/programmes/horizon2020/en/h2020-section/fet-flagships -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# ---LICENSE-END - -""" -Helper class to build and execute a formatted mpirun command for music in the format: - - mpirun -envlist <vars> -np <proc> -host <hostname/ip> -wdir <temporary work dir> <command> : ... - -where each of the hosts has a specific working directory with necessary config files already -in place. Also passes environment variables required for NRP/CLE execution. -""" -from hbp_nrp_distributed_nest.launch.MPILauncher import MPILauncher - - -class MUSICMPILauncher(MPILauncher): - """ - Class constructs and executes the MUSIC mpi launch command. - """ - - def __init__(self, executable): - super(MUSICMPILauncher, self).__init__(executable) - - def add_host(self, hostname, tmpdir, processes=1): - """ - Add a target host to the mpi launch configuration with MUSIC working directory set. - - :param hostname The remote host name or ip. - :param tmpdir A valid temporary directory on the remote host to launch in. - :param processes The number of processes for this host. - """ - self._hosts.append('-np {p} -host {h} -wdir {t} -genv NRP_MUSIC_DIRECTORY {t}' - .format(p=processes, h=hostname, t=tmpdir)) diff --git a/hbp_nrp_music_interface/hbp_nrp_music_interface/launch/__init__.py b/hbp_nrp_music_interface/hbp_nrp_music_interface/launch/__init__.py deleted file mode 100644 index e0b1e74..0000000 --- a/hbp_nrp_music_interface/hbp_nrp_music_interface/launch/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -""" -This package contains classes related to launching a distributed -MUSIC/PyNN/NEST simulation within the NRP. -""" diff --git a/hbp_nrp_music_interface/hbp_nrp_music_interface/launch/host/LocalLauncher.py b/hbp_nrp_music_interface/hbp_nrp_music_interface/launch/host/LocalLauncher.py deleted file mode 100644 index c383c02..0000000 --- a/hbp_nrp_music_interface/hbp_nrp_music_interface/launch/host/LocalLauncher.py +++ /dev/null @@ -1,88 +0,0 @@ -# ---LICENSE-BEGIN - DO NOT CHANGE OR MOVE THIS HEADER -# This file is part of the Neurorobotics Platform software -# Copyright (C) 2014,2015,2016,2017 Human Brain Project -# https://www.humanbrainproject.eu -# -# The Human Brain Project is a European Commission funded project -# in the frame of the Horizon2020 FET Flagship plan. -# http://ec.europa.eu/programmes/horizon2020/en/h2020-section/fet-flagships -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# ---LICENSE-END -""" -localhost launch target configuration. -""" - -from hbp_nrp_distributed_nest.launch.host.LocalLauncher import LocalLauncher as ILocalLauncher - -import os -import stat -import sys - - -class LocalLauncher(ILocalLauncher): - """ - This launch configuration targets the localhost for all processes and is suitable for local - installs or deployed installs where the newly spawned processes can run on the same host - as the REST backend. - """ - - def __init__(self): - """ - Create a local launcher to target localhost and create a local temporary directory to - write MUSIC configuration files. - """ - super(LocalLauncher, self).__init__() - - def create_launch_scripts(self): - """ - Create a set of launch scripts for the CLE and individual brain processes with specific - implementations required for each host. Write the launch scripts to the temporary directory - for this launcher. - - Returns a tuple (path to CLE launch script, path to BrainProcess launc script). - """ - # create .sh launcher scripts for the CLE and brain processes - cle_launcher = self._create_launch_script('music_cle_launcher.sh', - 'hbp_nrp_music_interface.launch.MUSICCLEProcess') - brain_launcher = self._create_launch_script('music_brain_launcher.sh', - 'hbp_nrp_music_interface.launch.' + - 'MUSICBrainProcess') - return (cle_launcher, brain_launcher) - - def _create_launch_script(self, name, module): - """ - Create an executable script in the working temporary folder to launch the specified - Python module. These will be used by the MUSIC runtime on each of the hosts since there - are some quirks in launching "python -m <module> with <args>" directly throug MUSIC. - - :param name The name for the launch script to create. - :param module The python module to launch with this script. - :return The absolute path to this launch script. - """ - - # absolute path to script in tmpdir - path = os.path.join(self._local_tmpdir, name) - - # use the absolute path of the Python interpreter for our current process, this current - # process will not be executed through uwsgi, so this will be the correct interpreter - with open(path, 'w') as f: - f.write('#!/bin/bash\n') - f.write('{python} -m {module}\n'.format(python=sys.executable, module=module)) - - os.chmod(path, stat.S_IRWXU) - - # return a relative path to the script, it's guaranteed to be run in the tmpdir - return './%s' % name diff --git a/hbp_nrp_music_interface/hbp_nrp_music_interface/launch/host/LuganoLauncher.py b/hbp_nrp_music_interface/hbp_nrp_music_interface/launch/host/LuganoLauncher.py deleted file mode 100644 index 86e6032..0000000 --- a/hbp_nrp_music_interface/hbp_nrp_music_interface/launch/host/LuganoLauncher.py +++ /dev/null @@ -1,170 +0,0 @@ -# ---LICENSE-BEGIN - DO NOT CHANGE OR MOVE THIS HEADER -# This file is part of the Neurorobotics Platform software -# Copyright (C) 2014,2015,2016,2017 Human Brain Project -# https://www.humanbrainproject.eu -# -# The Human Brain Project is a European Commission funded project -# in the frame of the Horizon2020 FET Flagship plan. -# http://ec.europa.eu/programmes/horizon2020/en/h2020-section/fet-flagships -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# ---LICENSE-END -""" -Lugano vizcluster launch target configuration. -""" - -from hbp_nrp_music_interface.launch.host.LocalLauncher import LocalLauncher - -from hbp_nrp_commons.cluster.LuganoVizCluster import LuganoVizCluster - -from hbp_nrp_cle.cle import config - -import netifaces -import os -import stat - - -class LuganoLauncher(LuganoVizCluster, LocalLauncher): - """ - This launch configuration targets the Lugano vizcluster and handles brain/CLE process - allocation and distribution. This is executed before the CLE is launched, so launching will - immediately terminate if there are not enough cluster resources. - """ - - def __init__(self, processes, timeout, reservation): - """ - Immediately attempt to allocate cluster resources for the brain processes. If this fails - or causes Exceptions, it will be propagated up to the user appropriately. - - :param processes The total number of processes (brain + CLE) to reserve. - :param timeout The simulation timeout required by the vizcluster launcher. - :param reservation Resource reservation string to access reserved nodes. - """ - - # ensure both constructors are called, raise Exception if allocation fails - try: - super(LuganoLauncher, self).__init__(processes, 0, timeout.tzinfo, reservation) - self._allocate_job(reuse_nodes=True) # multiple brains can run on the same node - finally: - LocalLauncher.__init__(self) - - # override hostname with allocated node, remote tmp does not exist until written - self._hostname = self._node - self._host_tmpdir = None - - def _create_launch_script(self, name, module): - """ - Create an executable script in the working temporary folder to launch the specified - Python module. These will be used by the MUSIC runtime on each of the hosts since there - are some quirks in launching "python -m <module> with <args>" directly throug MUSIC. - - Handle specific vizcluster configuration (modules and environment). - - :param name The name for the launch script to create. - :param module The python module to launch with this script. - :return The absolute path to this launch script on the remote host. - """ - - # absolute path to script in local tmpdir - path = os.path.join(self._local_tmpdir, name) - - # determine if we are running on a dev or staging environment - environment = os.environ.get('ENVIRONMENT') - - # set the ROS master uri to use an actual IP instead of localhost - ifaddress = netifaces.ifaddresses(config.config.get('network', 'main-interface')) - local_ip = ifaddress[netifaces.AF_INET][0]['addr'] - ros_master_uri = os.environ.get("ROS_MASTER_URI").replace('localhost', local_ip) - - # create a launch script that configures the vizcluster environment properly - with open(path, 'w') as f: - f.write('#!/bin/bash\n') - f.write('source /opt/rh/python27/enable\n') - - # set the terminal type to ensure we get the same behavior as from a backend VM - # this is also required in the Docker images - f.write('export TERM=linux\n') - - # set the environment version and any specific module versions to be used, then - # override the environment variable to the deployed location on the remote host - f.write('source $NRP_MUSIC_DIRECTORY/nrp-variables\n') - f.write('export NRP_VARIABLES_PATH=$NRP_MUSIC_DIRECTORY/nrp-variables\n') - - # load the environment modules based on the above configuration - proj_path = '/gpfs/bbp.cscs.ch/project/proj30/neurorobotics/%s/' % environment - f.write('source %s/server-scripts/nrp-services-modules.sh\n' % proj_path) - - # set paths to models/experiments directory on gpfs - f.write('export NRP_MODELS_DIRECTORY=%s/models\n' % proj_path) - f.write('export NRP_EXPERIMENTS_DIRECTORY=%s/experiments\n' % proj_path) - - # set the PYTHONPATH to add NRP modules on gpfs - venv_path = '%s/platform_venv/lib/python2.7/site-packages' % proj_path - f.write('export PYTHONPATH=%s:$PYTHONPATH\n' % venv_path) - - # configure ROS and source the ros_venv before launching - f.write('export ROS_MASTER_URI=%s\n' % ros_master_uri) - f.write('source $ROS_PYTHON_VENV/bin/activate\n') - - # actually launch the module - f.write('python -m {module}\n'.format(module=module)) - - # ensure the script is executable - os.chmod(path, stat.S_IRWXU) - - # return a relative path to the script, it's guaranteed to be run in the tmpdir - return './%s' % name - - def deploy(self): - """ - Copy all configuration files to a temp directory on the remote host. The remote directory - is created here, so use it to set the launcher interface host tmpdir value. - """ - - # generated configuration files - for f in os.listdir(self._local_tmpdir): - self._copy_to_remote(os.path.join(self._local_tmpdir, f)) - - # deploy any NRP specific variable/module version configuration - self._copy_to_remote(os.environ.get('NRP_VARIABLES_PATH')) - - # set the host tmpdir, slightly different naming convention between interfaces - self._host_tmpdir = self._tmp_dir - - def shutdown(self): - """ - Shutdown by trying to kill any running processes and deleting the temporary directory on the - remote allocated node and localhost. Both are guaranteed to exist by the constructor. - """ - - # try to terminate any processes running on the allocated node, they should already be - # terminated before we get here, catch any Exceptions and ensure we cleanup below - if self._host_tmpdir: - try: - # terminate any processes, remote directory will be deleted by parent cleanup - clean_process = self._spawn_ssh_node() - clean_process.sendline('for L in %s/*.lock ; do kill -9 `basename $L .lock`; done' % - self._host_tmpdir) - clean_process.terminate() - except Exception: # pylint: disable=broad-except - pass - finally: - self._host_tmpdir = None - - # delete the remote temp directory and deallocate the node - LuganoVizCluster.stop(self) - - # cleanup any local processes and delete the remote temp directory - LocalLauncher.shutdown(self) diff --git a/hbp_nrp_music_interface/hbp_nrp_music_interface/launch/host/__init__.py b/hbp_nrp_music_interface/hbp_nrp_music_interface/launch/host/__init__.py deleted file mode 100644 index 51aa846..0000000 --- a/hbp_nrp_music_interface/hbp_nrp_music_interface/launch/host/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -""" -This package contains host specific implementations for distributed MUSIC targets. -""" diff --git a/hbp_nrp_music_interface/hbp_nrp_music_interface/tests/__init__.py b/hbp_nrp_music_interface/hbp_nrp_music_interface/tests/__init__.py deleted file mode 100644 index ec4a4d0..0000000 --- a/hbp_nrp_music_interface/hbp_nrp_music_interface/tests/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -""" -This package contains all tests for the MUSIC interface package. -""" - -__author__ = 'Martin Schulze' diff --git a/hbp_nrp_music_interface/hbp_nrp_music_interface/tests/bibi/__init__.py b/hbp_nrp_music_interface/hbp_nrp_music_interface/tests/bibi/__init__.py deleted file mode 100644 index 7366ab6..0000000 --- a/hbp_nrp_music_interface/hbp_nrp_music_interface/tests/bibi/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -""" -This package contains all tests for the MUSIC BIBI interface package. -""" diff --git a/hbp_nrp_music_interface/hbp_nrp_music_interface/tests/bibi/bibi.xml b/hbp_nrp_music_interface/hbp_nrp_music_interface/tests/bibi/bibi.xml deleted file mode 100644 index 7416897..0000000 --- a/hbp_nrp_music_interface/hbp_nrp_music_interface/tests/bibi/bibi.xml +++ /dev/null @@ -1,14 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<bibi xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.humanbrainproject.eu/SP10/2014/BIBI" xsi:schemaLocation="http://schemas.humanbrainproject.eu/SP10/2014/BIBI bibi_configuration.xsd"> - <brainModel> - <file>fake_brain.py</file> - <populations population="range" xsi:type="Range" from="0" to="2"/> - <populations population="list" xsi:type="List"> - <element>3</element> - <element>4</element> - <element>5</element> - </populations> - <populations population="population" xsi:type="Population" count="10"/> - </brainModel> - <bodyModel>fake_model.sdf</bodyModel> -</bibi> diff --git a/hbp_nrp_music_interface/hbp_nrp_music_interface/tests/bibi/config.music b/hbp_nrp_music_interface/hbp_nrp_music_interface/tests/bibi/config.music deleted file mode 100644 index c3235d0..0000000 --- a/hbp_nrp_music_interface/hbp_nrp_music_interface/tests/bibi/config.music +++ /dev/null @@ -1,16 +0,0 @@ -[CLE] -np = 4 -binary = cle_binary -args = - -[BRAIN] -np = 2 -binary = brain_binary -args = "--brain --args" - -CLE.range_to_brain -> BRAIN.range_to_brain [10] -BRAIN.range_to_cle -> CLE.range_to_cle [2] -CLE.list_to_brain -> BRAIN.list_to_brain [15] -BRAIN.list_to_cle -> CLE.list_to_cle [3] -CLE.population_to_brain -> BRAIN.population_to_brain [50] -BRAIN.population_to_cle -> CLE.population_to_cle [10] diff --git a/hbp_nrp_music_interface/hbp_nrp_music_interface/tests/bibi/config.xml b/hbp_nrp_music_interface/hbp_nrp_music_interface/tests/bibi/config.xml deleted file mode 100644 index 89b9261..0000000 --- a/hbp_nrp_music_interface/hbp_nrp_music_interface/tests/bibi/config.xml +++ /dev/null @@ -1,137 +0,0 @@ -<?xml version="1.0"?> -<root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> - <port> - <type>Event</type> - <name>range_to_brain</name> - <width>10</width> - <sender> - <name>CLE</name> - </sender> - <receiver> - <name>BRAIN</name> - <synapse> - <target>range</target> - <type>one_to_one</type> - <selector xsi:type="SliceSelector"> - <start>0</start> - <stop>2</stop> - <step>1</step> - </selector> - </synapse> - </receiver> - </port> - <port> - <type>Event</type> - <name>range_to_cle</name> - <width>2</width> - <sender> - <name>BRAIN</name> - </sender> - <receiver> - <name>CLE</name> - <synapse> - <target>range</target> - <type>one_to_one</type> - <selector xsi:type="SliceSelector"> - <start>0</start> - <stop>2</stop> - <step>1</step> - </selector> - </synapse> - </receiver> - </port> - <port> - <type>Event</type> - <name>list_to_brain</name> - <width>15</width> - <sender> - <name>CLE</name> - </sender> - <receiver> - <name>BRAIN</name> - <synapse> - <target>list</target> - <type>one_to_one</type> - <selector xsi:type="ListSelector"> - <element>3</element> - <element>4</element> - <element>5</element> - </selector> - </synapse> - </receiver> - </port> - <port> - <type>Event</type> - <name>list_to_cle</name> - <width>3</width> - <sender> - <name>BRAIN</name> - </sender> - <receiver> - <name>CLE</name> - <synapse> - <target>list</target> - <type>one_to_one</type> - <selector xsi:type="ListSelector"> - <element>3</element> - <element>4</element> - <element>5</element> - </selector> - </synapse> - </receiver> - </port> - <port> - <type>Event</type> - <name>population_to_brain</name> - <width>50</width> - <sender> - <name>CLE</name> - </sender> - <receiver> - <name>BRAIN</name> - <synapse> - <target>population</target> - <type>one_to_one</type> - <selector xsi:type="ListSelector"> - <element>0</element> - <element>1</element> - <element>2</element> - <element>3</element> - <element>4</element> - <element>5</element> - <element>6</element> - <element>7</element> - <element>8</element> - <element>9</element> - </selector> - </synapse> - </receiver> - </port> - <port> - <type>Event</type> - <name>population_to_cle</name> - <width>10</width> - <sender> - <name>BRAIN</name> - </sender> - <receiver> - <name>CLE</name> - <synapse> - <target>population</target> - <type>one_to_one</type> - <selector xsi:type="ListSelector"> - <element>0</element> - <element>1</element> - <element>2</element> - <element>3</element> - <element>4</element> - <element>5</element> - <element>6</element> - <element>7</element> - <element>8</element> - <element>9</element> - </selector> - </synapse> - </receiver> - </port> -</root> diff --git a/hbp_nrp_music_interface/hbp_nrp_music_interface/tests/bibi/test_bibi_music_config.py b/hbp_nrp_music_interface/hbp_nrp_music_interface/tests/bibi/test_bibi_music_config.py deleted file mode 100644 index 55bf4fc..0000000 --- a/hbp_nrp_music_interface/hbp_nrp_music_interface/tests/bibi/test_bibi_music_config.py +++ /dev/null @@ -1,91 +0,0 @@ -# ---LICENSE-BEGIN - DO NOT CHANGE OR MOVE THIS HEADER -# This file is part of the Neurorobotics Platform software -# Copyright (C) 2014,2015,2016,2017 Human Brain Project -# https://www.humanbrainproject.eu -# -# The Human Brain Project is a European Commission funded project -# in the frame of the Horizon2020 FET Flagship plan. -# http://ec.europa.eu/programmes/horizon2020/en/h2020-section/fet-flagships -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# ---LICENSE-END -import unittest - -from hbp_nrp_music_interface.bibi.bibi_music_config import MUSICConfiguration -import hbp_nrp_commons.generated.bibi_api_gen as api - -from mock import Mock, patch -import os - - -class TestMusicConfiguration(unittest.TestCase): - - def setUp(self): - self._path = os.path.dirname(os.path.realpath(__file__)) - - # valid bibi with all supported population types - bibi_file = os.path.join(self._path, "bibi.xml") - with open(bibi_file) as f: - self._bibi = api.CreateFromDocument(f.read()) - - # expected xml generated - xml_file = os.path.join(self._path, "config.xml") - with open(xml_file) as f: - self._xml = "".join(f.read().split()) - - # expected script generated - script_file = os.path.join(self._path, "config.music") - with open(script_file) as f: - self._script = f.read() - - def test_invalid_bibi(self): - # not a BIBI file - self.assertRaises(Exception, MUSICConfiguration, 'foo') - - # brain populations set to none or empty - brain_file = "foo.py" - empty_bibi = api.BIBIConfiguration() - empty_bibi.brainModel = api.BrainModelWithPath(file=api.PythonFilename(brain_file), - populations=None) - self.assertRaises(Exception, MUSICConfiguration, empty_bibi) - empty_bibi.brainModel = api.BrainModelWithPath(file=api.PythonFilename(brain_file), - populations=[]) - self.assertRaises(Exception, MUSICConfiguration, empty_bibi) - - def test_xml(self): - conf = MUSICConfiguration(self._bibi) - self.assertEqual("".join(conf.xml().split()), self._xml) - - @patch('__builtin__.open', spec=open) - def test_str(self, mocked_open): - conf = MUSICConfiguration(self._bibi) - conf.add_application('CLE', 'cle_binary', None, 4) - - # missing BRAIN definition - self.assertRaises(Exception, conf.save, "") - - # invalid application name - self.assertRaises(Exception, conf.add_application, 'FOO', 'bar', None, 1) - - # add brain, invalid if added twice - conf.add_application('BRAIN', 'brain_binary', ['--brain', '--args'], 2) - self.assertRaises(Exception, conf.add_application, 'BRAIN', 'bar', None, 1) - - # compare generated music script - conf.save("") - self.assertEqual(str(conf), self._script) - -if __name__ == "__main__": - unittest.main() diff --git a/hbp_nrp_music_interface/hbp_nrp_music_interface/tests/cle/__init__.py b/hbp_nrp_music_interface/hbp_nrp_music_interface/tests/cle/__init__.py deleted file mode 100644 index 244bd59..0000000 --- a/hbp_nrp_music_interface/hbp_nrp_music_interface/tests/cle/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -""" -This package contains all tests for the MUSIC CLE interface package. -""" - -__author__ = 'Martin Schulze' diff --git a/hbp_nrp_music_interface/hbp_nrp_music_interface/tests/cle/test_brain_loader.py b/hbp_nrp_music_interface/hbp_nrp_music_interface/tests/cle/test_brain_loader.py deleted file mode 100644 index 7b51fb9..0000000 --- a/hbp_nrp_music_interface/hbp_nrp_music_interface/tests/cle/test_brain_loader.py +++ /dev/null @@ -1,67 +0,0 @@ -# ---LICENSE-BEGIN - DO NOT CHANGE OR MOVE THIS HEADER -# This file is part of the Neurorobotics Platform software -# Copyright (C) 2014,2015,2016,2017 Human Brain Project -# https://www.humanbrainproject.eu -# -# The Human Brain Project is a European Commission funded project -# in the frame of the Horizon2020 FET Flagship plan. -# http://ec.europa.eu/programmes/horizon2020/en/h2020-section/fet-flagships -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# ---LICENSE-END -import unittest - -from hbp_nrp_music_interface.cle.MUSICBrainLoader import load_proxies_from_xml -import hbp_nrp_cle.tf_framework.config as tf_config -import pyNN.nest as sim - -from mock import Mock, patch -import os - - -class TestBrainLoader(unittest.TestCase): - - def test_invalid_setup(self): - tf_config.brain_root = 'foo' - self.assertRaises(Exception, load_proxies_from_xml, 'foo.xml') - tf_config.brain_root = None - - def test_invalid_xml_file(self): - self.assertRaises(Exception, load_proxies_from_xml, 'foo.xml') - - @patch('__builtin__.open', spec=open) - def test_invalid_simulator(self, mock_open): - sim.simulator.name = 'FOO' - self.assertRaises(Exception, load_proxies_from_xml, 'foo.xml') - sim.simulator.name = 'NEST' - - @patch('__builtin__.open', spec=open) - @patch('hbp_nrp_music_xml.pynn.xml_factory.XmlFactory.create_proxies', new=Mock(return_value={})) - def test_proxy_dict_missing(self, mock_open): - self.assertRaises(Exception, load_proxies_from_xml, 'foo.xml', bar='foo') - - @patch('__builtin__.open', spec=open) - @patch('pyNN.nest.PopulationView', new=Mock(return_value='fake')) - @patch('hbp_nrp_music_xml.pynn.xml_factory.XmlFactory.create_proxies', - new=Mock(return_value={'bar_to_brain': 'foo', 'bar_to_cle': 'bar'})) - def test_proxy_dict_valid(self, mock_open): - tf_config.brain_root = None - load_proxies_from_xml('foo.xml', bar='foo') - self.assertEqual(tf_config.brain_root.__dict__['bar'], 'fake') - self.assertEqual(tf_config.music_proxies['bar'], {'source': 'fake', 'sink': 'fake'}) - - -if __name__ == "__main__": - unittest.main() diff --git a/hbp_nrp_music_interface/hbp_nrp_music_interface/tests/cle/test_communication_adapter.py b/hbp_nrp_music_interface/hbp_nrp_music_interface/tests/cle/test_communication_adapter.py deleted file mode 100644 index 14e345e..0000000 --- a/hbp_nrp_music_interface/hbp_nrp_music_interface/tests/cle/test_communication_adapter.py +++ /dev/null @@ -1,89 +0,0 @@ -# ---LICENSE-BEGIN - DO NOT CHANGE OR MOVE THIS HEADER -# This file is part of the Neurorobotics Platform software -# Copyright (C) 2014,2015,2016,2017 Human Brain Project -# https://www.humanbrainproject.eu -# -# The Human Brain Project is a European Commission funded project -# in the frame of the Horizon2020 FET Flagship plan. -# http://ec.europa.eu/programmes/horizon2020/en/h2020-section/fet-flagships -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# ---LICENSE-END -import unittest - -from hbp_nrp_music_interface.cle.MUSICPyNNCommunicationAdapter import MUSICPyNNCommunicationAdapter - -import hbp_nrp_cle.tf_framework.config as tf_config - -from mock import Mock, patch -import os - - -class MockPopulation(object): - - def __init__(self, label): - self.label = label - - -class MockPopulationView(object): - - def __init__(self, parent, label, mask): - self.parent = parent - self.label = label - self.mask = mask - self.size = 0 - self.all_cells = [] - - -@patch('hbp_nrp_cle.brainsim.pynn_nest.PyNNNestCommunicationAdapter.PyNNNestCommunicationAdapter.initialize') -@patch('hbp_nrp_cle.brainsim.pynn_nest.PyNNNestCommunicationAdapter.PyNNNestCommunicationAdapter.register_spike_source') -@patch('hbp_nrp_cle.brainsim.pynn_nest.PyNNNestCommunicationAdapter.PyNNNestCommunicationAdapter.register_spike_sink') -@patch('pyNN.nest.PopulationView', MockPopulationView) -@patch('pyNN.nest.Population', MockPopulation) -class TestCommunicationAdapter(unittest.TestCase): - - def setUp(self): - self._adapter = MUSICPyNNCommunicationAdapter() - - def test_initialize(self, mock_initialize, mock_source, mock_sink): - self._adapter.initialize() - - def test_invalid_population_type(self, mock_initialize, mock_source, mock_sink): - self.assertRaises(Exception, self._adapter.register_spike_sink, 'foo', 'bar') - - def test_parent_population(self, mock_initialize, mock_source, mock_sink): - pop = MockPopulationView(MockPopulation('parent'), 'population', 'mask') - tf_config.music_proxies = {'population':{'source':pop}} - self._adapter.register_spike_source(pop, 'foo') - - def test_parent_population_view(self, mock_initialize, mock_source, mock_sink): - pop = MockPopulationView(MockPopulationView(MockPopulation('grandparent'), 'parent', 'mask'), 'population', 'mask') - tf_config.music_proxies = {'parent':{'sink':pop}} - self._adapter.register_spike_sink(pop, 'foo') - - ''' - def test_population_list(self, mock_initialize, mock_source, mock_sink): - pop = MockPopulationView(MockPopulation('parent'), 'population', 'mask') - tf_config.music_proxies = {'population':{'source':pop}} - self._adapter.register_spike_source([pop, pop, pop], 'foo') - ''' - - def test_invalid_population(self, mock_initialize, mock_source, mock_sink): - pop = MockPopulationView(MockPopulation('parent'), 'population', 'mask') - tf_config.music_proxies = {} - self.assertRaises(Exception, self._adapter.register_spike_source, pop, 'foo') - -if __name__ == "__main__": - unittest.main() diff --git a/hbp_nrp_music_interface/hbp_nrp_music_interface/tests/cle/test_control_adapter.py b/hbp_nrp_music_interface/hbp_nrp_music_interface/tests/cle/test_control_adapter.py deleted file mode 100644 index 3d054fc..0000000 --- a/hbp_nrp_music_interface/hbp_nrp_music_interface/tests/cle/test_control_adapter.py +++ /dev/null @@ -1,62 +0,0 @@ -# ---LICENSE-BEGIN - DO NOT CHANGE OR MOVE THIS HEADER -# This file is part of the Neurorobotics Platform software -# Copyright (C) 2014,2015,2016,2017 Human Brain Project -# https://www.humanbrainproject.eu -# -# The Human Brain Project is a European Commission funded project -# in the frame of the Horizon2020 FET Flagship plan. -# http://ec.europa.eu/programmes/horizon2020/en/h2020-section/fet-flagships -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# ---LICENSE-END -import unittest - -from hbp_nrp_music_interface.cle.MUSICPyNNControlAdapter import MUSICPyNNControlAdapter -import hbp_nrp_cle.tf_framework.config as tf_config - -from mock import Mock, patch, mock_open -import os - - -class MockedMUSICSetup(object): - - def config(self, key): - return 'mocked.xml' - - -class TestControlAdapter(unittest.TestCase): - - def test_missing_xml(self): - mc = MUSICPyNNControlAdapter(Mock()) - self.assertRaises(Exception, mc.load_brain, 'foo.xml') - - @patch('music.Setup', name=MockedMUSICSetup()) - @patch('os.environ.get', return_value='') - @patch('hbp_nrp_music_interface.cle.MUSICBrainLoader.load_proxies_from_xml') - def test_valid_xml(self, mocked_setup, mocked_environ, mocked_load): - - m = mock_open() - m.return_value.read.return_value = 'mock brain source' - - with patch('__builtin__.open', m, create=True): - mc = MUSICPyNNControlAdapter(Mock()) - mc.load_brain('foo.xml') - - mocked_environ.assert_called_once_with('NRP_MUSIC_DIRECTORY') - m.assert_called_once_with('foo.xml') - self.assertEquals(tf_config.brain_source, 'mock brain source') - -if __name__ == "__main__": - unittest.main() diff --git a/hbp_nrp_music_interface/hbp_nrp_music_interface/tests/launch/__init__.py b/hbp_nrp_music_interface/hbp_nrp_music_interface/tests/launch/__init__.py deleted file mode 100644 index 77cd9a0..0000000 --- a/hbp_nrp_music_interface/hbp_nrp_music_interface/tests/launch/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -""" -This package contains all tests for the MUSIC interface launch package. -""" diff --git a/hbp_nrp_music_interface/hbp_nrp_music_interface/tests/launch/host/__init__.py b/hbp_nrp_music_interface/hbp_nrp_music_interface/tests/launch/host/__init__.py deleted file mode 100644 index d6a3227..0000000 --- a/hbp_nrp_music_interface/hbp_nrp_music_interface/tests/launch/host/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -""" -This package contains all tests for the MUSIC interface launch.host package for host specific -launch configurations. -""" diff --git a/hbp_nrp_music_interface/hbp_nrp_music_interface/tests/launch/host/test_interface.py b/hbp_nrp_music_interface/hbp_nrp_music_interface/tests/launch/host/test_interface.py deleted file mode 100644 index 36525a6..0000000 --- a/hbp_nrp_music_interface/hbp_nrp_music_interface/tests/launch/host/test_interface.py +++ /dev/null @@ -1,65 +0,0 @@ -# ---LICENSE-BEGIN - DO NOT CHANGE OR MOVE THIS HEADER -# This file is part of the Neurorobotics Platform software -# Copyright (C) 2014,2015,2016,2017 Human Brain Project -# https://www.humanbrainproject.eu -# -# The Human Brain Project is a European Commission funded project -# in the frame of the Horizon2020 FET Flagship plan. -# http://ec.europa.eu/programmes/horizon2020/en/h2020-section/fet-flagships -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# ---LICENSE-END -import unittest - -from hbp_nrp_distributed_nest.launch.host import IHostLauncher - -from mock import Mock, patch -import os - - -class TestHostLauncherInterface(unittest.TestCase): - - def setUp(self): - self.__launcher = IHostLauncher() - - def test_initial_values(self): - self.assertEqual(self.__launcher._hostname, None) - self.assertEqual(self.__launcher._local_tmpdir, None) - self.assertEqual(self.__launcher._host_tmpdir, None) - - def test_properties(self): - - # default values raise implementation errors - self.assertRaises(NotImplementedError, getattr, self.__launcher, 'hostname') - self.assertRaises(NotImplementedError, getattr, self.__launcher, 'local_tmpdir') - self.assertRaises(NotImplementedError, getattr, self.__launcher, 'host_tmpdir') - - # set some values to simulate an implementation - self.__launcher._hostname = 'foo' - self.__launcher._local_tmpdir = 'local' - self.__launcher._host_tmpdir = 'host' - self.assertEqual(self.__launcher.hostname, 'foo') - self.assertEqual(self.__launcher.local_tmpdir, 'local') - self.assertEqual(self.__launcher.host_tmpdir, 'host') - - def test_functions(self): - - # all functions raise implementation errors in the interface - self.assertRaises(NotImplementedError, self.__launcher.create_launch_scripts) - self.assertRaises(NotImplementedError, self.__launcher.deploy) - self.assertRaises(NotImplementedError, self.__launcher.shutdown) - -if __name__ == "__main__": - unittest.main() diff --git a/hbp_nrp_music_interface/hbp_nrp_music_interface/tests/launch/host/test_local.py b/hbp_nrp_music_interface/hbp_nrp_music_interface/tests/launch/host/test_local.py deleted file mode 100644 index 0615167..0000000 --- a/hbp_nrp_music_interface/hbp_nrp_music_interface/tests/launch/host/test_local.py +++ /dev/null @@ -1,105 +0,0 @@ -# ---LICENSE-BEGIN - DO NOT CHANGE OR MOVE THIS HEADER -# This file is part of the Neurorobotics Platform software -# Copyright (C) 2014,2015,2016,2017 Human Brain Project -# https://www.humanbrainproject.eu -# -# The Human Brain Project is a European Commission funded project -# in the frame of the Horizon2020 FET Flagship plan. -# http://ec.europa.eu/programmes/horizon2020/en/h2020-section/fet-flagships -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# ---LICENSE-END -import unittest - -from hbp_nrp_music_interface.launch.host.LocalLauncher import LocalLauncher - -from mock import Mock, patch, mock_open, call -import os -import sys -import stat -import signal - - -class TestLocalLauncherInterface(unittest.TestCase): - - def setUp(self): - with patch('tempfile.mkdtemp') as mkdtemp_mock: - mkdtemp_mock.return_value = '/mock_tmp' - self.__launcher = LocalLauncher() - - def test_properties(self): - - # implementation should set all property values - self.assertEqual(self.__launcher.hostname, 'localhost') - self.assertEqual(self.__launcher.local_tmpdir, '/mock_tmp') - self.assertEqual(self.__launcher.host_tmpdir, '/mock_tmp') - - def test_create_launch_scripts(self): - - # ensure that the calls to create the cle and brain launch scripts are made - def mock_creator(name, module): - return name - - with patch.object(self.__launcher, '_create_launch_script', mock_creator): - self.assertEqual(self.__launcher.create_launch_scripts(), - ('music_cle_launcher.sh', 'music_brain_launcher.sh')) - - @patch('os.chmod') - def test_internal_create_launch_script(self, mock_chmod): - - # mock the script open and write commands - m = mock_open() - with patch('__builtin__.open', m, create=True): - self.assertEqual(self.__launcher._create_launch_script('name.sh', 'module'), - './name.sh') - - # the open call - m.assert_called_once_with('/mock_tmp/name.sh', 'w') - - # the write calls for the actual script - handle = m() - self.assertEqual(handle.write.call_args_list, - [call('#!/bin/bash\n'), - call('{python} -m module\n'.format(python=sys.executable))]) - - # ensure the chmod was called to make the script executable - mock_chmod.assert_called_once_with('/mock_tmp/name.sh', stat.S_IRWXU) - - def test_deploy(self): - - # should just call pass, exception means failure - self.__launcher.deploy() - - @patch('os.path.exists', return_value=True) - @patch('glob.iglob', return_value=['1234.lock']) - @patch('os.kill') - @patch('shutil.rmtree') - def test_shutdown(self, mock_rmtree, mock_kill, mock_iglob, mock_exists): - - # mock the lockfile open/readline command - m = mock_open() - m.return_value.readline.return_value = '1234' - with patch('__builtin__.open', m, create=True): - self.__launcher.shutdown() - - # verify the rmtree is called to delete the tmpdir - mock_rmtree.assert_called_once_with('/mock_tmp') - - # verify the tmp dirs are unset - self.assertEqual(self.__launcher._local_tmpdir, None) - self.assertEqual(self.__launcher._host_tmpdir, None) - -if __name__ == "__main__": - unittest.main() diff --git a/hbp_nrp_music_interface/hbp_nrp_music_interface/tests/launch/host/test_lugano.py b/hbp_nrp_music_interface/hbp_nrp_music_interface/tests/launch/host/test_lugano.py deleted file mode 100644 index c548b35..0000000 --- a/hbp_nrp_music_interface/hbp_nrp_music_interface/tests/launch/host/test_lugano.py +++ /dev/null @@ -1,129 +0,0 @@ -# ---LICENSE-BEGIN - DO NOT CHANGE OR MOVE THIS HEADER -# This file is part of the Neurorobotics Platform software -# Copyright (C) 2014,2015,2016,2017 Human Brain Project -# https://www.humanbrainproject.eu -# -# The Human Brain Project is a European Commission funded project -# in the frame of the Horizon2020 FET Flagship plan. -# http://ec.europa.eu/programmes/horizon2020/en/h2020-section/fet-flagships -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# ---LICENSE-END -import unittest - -from hbp_nrp_music_interface.launch.host.LuganoLauncher import LuganoLauncher - -from mock import Mock, patch, mock_open, call -import os -import sys -import stat -import signal -import netifaces - - -class TestLuganoLauncherInterface(unittest.TestCase): - - def setUp(self): - - # mock the Lugano specific calls made in the constructor - def mock_init(self, processes, gpus, timeout, reservation): - self._node = 'mock_node' - - with patch('tempfile.mkdtemp', return_value='/mock_tmp') as mkdtemp_mock,\ - patch('hbp_nrp_commons.cluster.LuganoVizCluster.LuganoVizCluster.__init__', mock_init) as init_mock,\ - patch('hbp_nrp_commons.cluster.LuganoVizCluster.LuganoVizCluster._allocate_job') as allocate_mock: - - self.__launcher = LuganoLauncher(4, Mock(), None) - - def test_properties(self): - - # implementation should set all property values - self.assertEqual(self.__launcher.hostname, 'mock_node') - self.assertEqual(self.__launcher.local_tmpdir, '/mock_tmp') - - @patch('hbp_nrp_cle.cle.config.config.get') - @patch('netifaces.ifaddresses', return_value={netifaces.AF_INET: [{'addr': '1.2.3.4'}]}) - @patch('os.chmod') - def test_internal_create_launch_script(self, mock_chmod, mock_ifaddresses, mock_config): - - def mock_environ_get(key): - if key == 'ENVIRONMENT': - return 'dev' - elif key == 'ROS_MASTER_URI': - return 'http://localhost:11311' - - # mock the script open and write commands - m = mock_open() - with patch('__builtin__.open', m, create=True), patch('os.environ.get', mock_environ_get): - self.assertEqual(self.__launcher._create_launch_script('name.sh', 'module'), - './name.sh') - - # the open call - m.assert_called_once_with('/mock_tmp/name.sh', 'w') - - # the write calls for the actual script - handle = m() - self.assertEqual(handle.write.call_args_list, - [call('#!/bin/bash\n'), - call('source /opt/rh/python27/enable\n'), - call('export TERM=linux\n'), - call('source $NRP_MUSIC_DIRECTORY/nrp-variables\n'), - call('export NRP_VARIABLES_PATH=$NRP_MUSIC_DIRECTORY/nrp-variables\n'), - call('source /gpfs/bbp.cscs.ch/project/proj30/neurorobotics/dev//server-scripts/nrp-services-modules.sh\n'), - call('export NRP_MODELS_DIRECTORY=/gpfs/bbp.cscs.ch/project/proj30/neurorobotics/dev//models\n'), - call('export NRP_EXPERIMENTS_DIRECTORY=/gpfs/bbp.cscs.ch/project/proj30/neurorobotics/dev//experiments\n'), - call('export PYTHONPATH=/gpfs/bbp.cscs.ch/project/proj30/neurorobotics/dev//platform_venv/lib/python2.7/site-packages:$PYTHONPATH\n'), - call('export ROS_MASTER_URI=http://1.2.3.4:11311\n'), - call('source $ROS_PYTHON_VENV/bin/activate\n'), - call('python -m module\n')]) - - # ensure the chmod was called to make the script executable - mock_chmod.assert_called_once_with('/mock_tmp/name.sh', stat.S_IRWXU) - - def test_deploy(self): - - # set the remote temp dir name that is created by the copy command - def mock_copy(self, source): - self._tmp_dir = '/mock_remote_tmp' - - with patch('hbp_nrp_commons.cluster.LuganoVizCluster.LuganoVizCluster._copy_to_remote', mock_copy),\ - patch('os.listdir', return_value=['foo']): - # should just call pass, exception means failure - self.__launcher.deploy() - - # make sure the remote tmp variable is set correctly - self.assertEqual(self.__launcher.host_tmpdir, '/mock_remote_tmp') - - @patch('hbp_nrp_commons.cluster.LuganoVizCluster.LuganoVizCluster._spawn_ssh_node') - @patch('hbp_nrp_commons.cluster.LuganoVizCluster.LuganoVizCluster.stop') - @patch('hbp_nrp_music_interface.launch.host.LocalLauncher.LocalLauncher.shutdown') - def test_shutdown(self, mock_shutdown, mock_stop, mock_spawn): - - mock_ssh = Mock() - mock_spawn.return_value = mock_ssh - - # ensure deploy has been called first even if the test hasn't run - self.__launcher._host_tmpdir = '/mock_remote_tmp' - self.__launcher.shutdown() - - # verify the parent class shutdowns are called - mock_stop.assert_called_once() - mock_shutdown.assert_called_once() - - # verify the tmp dirs are unset - self.assertEqual(self.__launcher._host_tmpdir, None) - -if __name__ == "__main__": - unittest.main() diff --git a/hbp_nrp_music_interface/hbp_nrp_music_interface/tests/launch/test_mpi_launcher.py b/hbp_nrp_music_interface/hbp_nrp_music_interface/tests/launch/test_mpi_launcher.py deleted file mode 100644 index 8b55e82..0000000 --- a/hbp_nrp_music_interface/hbp_nrp_music_interface/tests/launch/test_mpi_launcher.py +++ /dev/null @@ -1,197 +0,0 @@ -# ---LICENSE-BEGIN - DO NOT CHANGE OR MOVE THIS HEADER -# This file is part of the Neurorobotics Platform software -# Copyright (C) 2014,2015,2016,2017 Human Brain Project -# https://www.humanbrainproject.eu -# -# The Human Brain Project is a European Commission funded project -# in the frame of the Horizon2020 FET Flagship plan. -# http://ec.europa.eu/programmes/horizon2020/en/h2020-section/fet-flagships -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# ---LICENSE-END -import unittest - -from hbp_nrp_music_interface.launch.MUSICMPILauncher import MUSICMPILauncher - -from mock import Mock, patch -import os -import json - -from std_msgs.msg import String - - -class MockProcessIO(object): - - def write(self, message): - pass - - def flush(self): - pass - - -class MockProcess(object): - - def __init__(self, cmd, preexec_fn, stdin, env): - self.stdin=MockProcessIO() - - def poll(self): - return None - - def wait(self): - return 0 - - def kill(self): - raise OSError('testing error') - - -class MockFailedProcess(MockProcess): - - def poll(self): - return 255 - - def wait(self): - return 255 - - -class TestControlAdapter(unittest.TestCase): - - def setUp(self): - self.__launcher = MUSICMPILauncher('music cle.music') - - def test_add_hosts(self): - self.__launcher.add_host('foo', '/tmp/foo') - self.__launcher.add_host('bar', '/tmp/bar', 3) - self.assertEqual(self.__launcher._hosts, - ['-np 1 -host foo -wdir /tmp/foo -genv NRP_MUSIC_DIRECTORY /tmp/foo', - '-np 3 -host bar -wdir /tmp/bar -genv NRP_MUSIC_DIRECTORY /tmp/bar']) - - @patch('os.environ.get', new=Mock(return_value='foo')) - def test_build(self): - self.__launcher._hosts = [] - self.assertRaises(Exception, self.__launcher.build) - - self.__launcher.add_host('foo', '/tmp/foo') - self.__launcher.add_host('bar', '/tmp/bar', 3) - self.__launcher.build() - self.assertEqual(self.__launcher._cmd, - 'mpirun -np 1 -host foo -wdir /tmp/foo -genv NRP_MUSIC_DIRECTORY /tmp/foo music cle.music : ' + - '-np 3 -host bar -wdir /tmp/bar -genv NRP_MUSIC_DIRECTORY /tmp/bar music cle.music') - - def test_on_status(self): - - # reset launched flag if another test was run prior - self.__launcher._launched = False - - # not a progress message, no update to launched - msg = String() - msg.data = json.dumps({'progress': {'task': 'Neurorobotics Closed Loop Engine'}}) - self.__launcher._on_status(msg) - self.assertEqual(self.__launcher._launched, False) - - # progress completion message - msg.data = json.dumps({'simulation': 'status'}) - self.__launcher._on_status(msg) - self.assertEqual(self.__launcher._launched, True) - - @patch('rosnode.get_api_uri') - @patch('rospy.get_master', return_value='foo') - @patch('os.environ', return_value={}) - @patch('hbp_nrp_cle.config.config.get', return_value='mock_gzbridge_cmd') - @patch('os.system') - def test_check_gzbridge(self, system_mock, config_mock, environ_mock, rospy_mock, rosnode_mock): - - # simulate a call when uri has already been set - self.__launcher._gazebo_master_uri = 'something' - self.__launcher._check_gzbridge() - self.assertEqual(rosnode_mock.call_count, 0) - self.assertEqual(environ_mock.call_count, 0) - self.assertEqual(system_mock.call_count, 0) - - # simulate node not yet started - self.__launcher._gazebo_master_uri = None - rosnode_mock.return_value = (-1, '', '') - self.__launcher._check_gzbridge() - rosnode_mock.assert_called_with('foo', '/gazebo', True) - self.assertEqual(environ_mock.call_count, 0) - self.assertEqual(system_mock.call_count, 0) - - # simulate started on localhost - self.__launcher._gazebo_master_uri = None - rosnode_mock.return_value = (1, '', 'http://127.0.0.1:54321') - self.__launcher._check_gzbridge() - rosnode_mock.assert_called_with('foo', '/gazebo', True) - self.assertEqual(self.__launcher._gazebo_master_uri, 'http://127.0.0.1:11345') - self.assertEqual(environ_mock.call_count, 0) - self.assertEqual(system_mock.call_count, 0) - - # simulate started on remote host - self.__launcher._gazebo_master_uri = None - rosnode_mock.return_value = (1, '', 'http://1.2.3.4:54321') - self.__launcher._check_gzbridge() - rosnode_mock.assert_called_with('foo', '/gazebo', True) - self.assertEqual(self.__launcher._gazebo_master_uri, 'http://1.2.3.4:11345') - system_mock.assert_called_once_with('mock_gzbridge_cmd') - - - @patch('rospy.Subscriber') - def test_launch(self, rospy_mock): - - # unset any variables from other tests - self.__launcher._cmd = None - self.__launcher._launched = None - - # not initialized, cannot run without a command - self.assertRaises(Exception, self.__launcher.launch) - - # patch gzbridge launch function to be tested separately - with patch.object(self.__launcher, '_check_gzbridge', Mock()): - - # initialize with a valid process - self.__launcher._cmd = 'foo' - with patch('subprocess.Popen', MockProcess): - self.__launcher._launched = True - self.__launcher.launch() - - # initialize with an invalid process - with patch('subprocess.Popen', MockFailedProcess): - self.__launcher._launched = False - self.assertRaises(Exception, self.__launcher.launch) - - def test_run(self): - - # not initialized or launched, failure expected - self.__launcher._process = None - self.__launcher._launched = False - self.assertRaises(Exception, self.__launcher.run) - - # successfully launched and completed - self.__launcher._launched = True - self.__launcher._process = MockProcess(None, None, None, None) - self.__launcher.run() - - # failed process run - self.__launcher._process = MockFailedProcess(None, None, None, None) - self.assertRaises(Exception, self.__launcher.run) - - @patch('hbp_nrp_cle.config.config.get', return_value='mock_gzbridge_cmd') - @patch('os.system') - def test_shutdown(self, system_mock, config_mock): - self.__launcher._process = MockProcess(None, None, None, None) - self.__launcher._gazebo_master_uri = 'http://1.2.3.4:11345' - self.__launcher.shutdown() - system_mock.assert_called_once_with('mock_gzbridge_cmd') - -if __name__ == "__main__": - unittest.main() diff --git a/hbp_nrp_music_interface/hbp_nrp_music_interface/tests/launch/test_music_launcher.py b/hbp_nrp_music_interface/hbp_nrp_music_interface/tests/launch/test_music_launcher.py deleted file mode 100644 index c94af48..0000000 --- a/hbp_nrp_music_interface/hbp_nrp_music_interface/tests/launch/test_music_launcher.py +++ /dev/null @@ -1,109 +0,0 @@ -# ---LICENSE-BEGIN - DO NOT CHANGE OR MOVE THIS HEADER -# This file is part of the Neurorobotics Platform software -# Copyright (C) 2014,2015,2016,2017 Human Brain Project -# https://www.humanbrainproject.eu -# -# The Human Brain Project is a European Commission funded project -# in the frame of the Horizon2020 FET Flagship plan. -# http://ec.europa.eu/programmes/horizon2020/en/h2020-section/fet-flagships -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# ---LICENSE-END -import unittest - -from hbp_nrp_music_interface.launch.MUSICLauncher import MUSICLauncher - -from mock import Mock, patch -import os - - -class MockLocalLauncher(Mock): - - @property - def hostname(self): - return 'localhost' - - @property - def local_tmpdir(self): - return '/mock_tmpdir' - - @property - def host_tmpdir(self): - return '/mock_host_tmpdir' - - def create_launch_scripts(self): - return ('cle.sh', 'brain.sh') - - -class TestMUSICLauncher(unittest.TestCase): - - @patch('os.environ.get', return_value='') - def setUp(self, mock_environ_get): - exc_mock = Mock() - exc_mock.path = 'exd_conf' - exc_mock.dir = '/exp_path' - exc_mock.rngSeed = 123456 - exc_mock.bibiConf.processes = 10 - bibi_mock = Mock() - bibi_mock.path = 'bibi_file' - self.__launcher = MUSICLauncher(1, exc_mock, bibi_mock, - gzserver_host='local', - reservation=None, - timeout=None) - - @patch('hbp_nrp_music_interface.launch.MUSICLauncher.LocalLauncher', MockLocalLauncher) - @patch('hbp_nrp_music_interface.launch.MUSICLauncher.bibi_music_config.MUSICConfiguration') - @patch('hbp_nrp_music_interface.launch.MUSICLauncher.MUSICMPILauncher') - def test_init(self, mock_mpi, mock_conf): - # mock all of the local launcher functionality - with patch('hbp_nrp_music_interface.launch.MUSICLauncher.os.environ.get', - return_value='/somewhere/over/the/rainbow'): - self.__launcher.initialize('env_file', None) - - # call to generate launch scripts - self.__launcher._launcher.deploy.assert_called_once() - - # assert that all of the MPI configuration/launch commands were called - self.__launcher.mpilauncher.add_host.assert_called_once_with('localhost', '/mock_host_tmpdir', 11) - self.__launcher.mpilauncher.build.assert_called_once() - self.__launcher.mpilauncher.launch.assert_called_once() - - @patch('os.system') - def test_shutdown(self, system_mock): - - # mock the cleserver and launcher - mock_cle_server = Mock() - mock_cle_server.shutdown = Mock() - mock_launcher = Mock() - mock_launcher.shutdown = Mock() - - # set values like in init() and shutdown - self.__launcher.mpilauncher = mock_cle_server - self.__launcher._launcher = mock_launcher - self.__launcher.shutdown() - - # make sure the mocked shutdowns are called - mock_cle_server.shutdown.assert_called_once() - mock_launcher.shutdown.assert_called_once() - - # verify the local variables are unset - self.assertEqual(self.__launcher.mpilauncher, None) - self.assertEqual(self.__launcher._launcher, None) - - # verify the ros cleanup command has been called - system_mock.assert_called_once() - -if __name__ == "__main__": - unittest.main() diff --git a/hbp_nrp_music_interface/hbp_nrp_music_interface/version.py b/hbp_nrp_music_interface/hbp_nrp_music_interface/version.py deleted file mode 100644 index cff3ffe..0000000 --- a/hbp_nrp_music_interface/hbp_nrp_music_interface/version.py +++ /dev/null @@ -1,2 +0,0 @@ -'''version string - generated by setVersion.sh''' -VERSION = '2.3.0' diff --git a/hbp_nrp_music_interface/requirements.txt b/hbp_nrp_music_interface/requirements.txt deleted file mode 100644 index fb7fe6f..0000000 --- a/hbp_nrp_music_interface/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -# third party requirements -mpi4py==2.0.0 -netifaces==0.8 diff --git a/hbp_nrp_music_interface/requirements_extension_tests.txt b/hbp_nrp_music_interface/requirements_extension_tests.txt deleted file mode 100644 index 031ce95..0000000 --- a/hbp_nrp_music_interface/requirements_extension_tests.txt +++ /dev/null @@ -1,3 +0,0 @@ -#the following is required for the unit testing -mock==1.0.1 -testfixtures==3.0.2 diff --git a/hbp_nrp_music_interface/setup.py b/hbp_nrp_music_interface/setup.py deleted file mode 100644 index a164b22..0000000 --- a/hbp_nrp_music_interface/setup.py +++ /dev/null @@ -1,68 +0,0 @@ -'''setup.py''' - -from setuptools import setup - -import hbp_nrp_music_interface -import pip - -from optparse import Option -options = Option('--workaround') -options.skip_requirements_regex = None -reqs_file = './requirements.txt' -if pip.__version__.startswith('10.'): - # Versions greater or equal to 10.x don't rely on pip.req.parse_requirements - install_reqs = list(val.strip() for val in open(reqs_file)) - reqs = install_reqs -elif pip.__version__.startswith('1.'): - # Versions 1.x rely on pip.req.parse_requirements - # but don't require a "session" parameter - from pip.req import parse_requirements # pylint:disable=no-name-in-module, import-error - install_reqs = parse_requirements(reqs_file, options=options) - reqs = [str(ir.req) for ir in install_reqs] -else: - # Versions greater than 1.x but smaller than 10.x rely on pip.req.parse_requirements - # and requires a "session" parameter - from pip.req import parse_requirements # pylint:disable=no-name-in-module, import-error - from pip.download import PipSession # pylint:disable=no-name-in-module, import-error - options.isolated_mode = False - install_reqs = parse_requirements( # pylint:disable=unexpected-keyword-arg - reqs_file, - session=PipSession, - options=options - ) - reqs = [str(ir.req) for ir in install_reqs] - -# ensure we install numpy before the main list of requirements, ignore -# failures if numpy/cython are not requirements and just proceed (future proof) -try: - cython_req = next(r for r in reqs if r.startswith('cython')) - numpy_req = next(r for r in reqs if r.startswith('numpy')) - if pip.__version__.startswith('10.'): - import subprocess - subprocess.check_call( - ["python", '-m', 'pip', 'install', "--no-clean", "--user", cython_req, numpy_req] - ) - else: - pip.main(['install', '--no-clean', cython_req, numpy_req]) # pylint:disable=no-member -# pylint: disable=bare-except -except: - pass - -config = { - 'description': 'MUSIC interface support for CLE/ExDBackend for HBP SP10', - 'author': 'HBP Neurorobotics', - 'url': 'http://neurorobotics.net', - 'author_email': 'neurorobotics@humanbrainproject.eu', - 'version': hbp_nrp_music_interface.__version__, - 'install_requires': reqs, - 'packages': ['hbp_nrp_music_interface', - 'hbp_nrp_music_interface.bibi', - 'hbp_nrp_music_interface.cle', - 'hbp_nrp_music_interface.launch', - 'hbp_nrp_music_interface.launch.host'], - 'scripts': [], - 'name': 'hbp-nrp-music-interface', - 'include_package_data': True, -} - -setup(**config) diff --git a/hbp_nrp_music_interface_deprec.tar.gz b/hbp_nrp_music_interface_deprec.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..ebc23b26413abeef4eff06dbf95f40bbb0f70044 GIT binary patch literal 25324 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 B=*a*8 literal 0 HcmV?d00001 diff --git a/hbp_nrp_music_xml/MANIFEST.in b/hbp_nrp_music_xml/MANIFEST.in deleted file mode 100644 index 540b720..0000000 --- a/hbp_nrp_music_xml/MANIFEST.in +++ /dev/null @@ -1 +0,0 @@ -include requirements.txt \ No newline at end of file diff --git a/hbp_nrp_music_xml/README.txt b/hbp_nrp_music_xml/README.txt deleted file mode 100644 index 4133956..0000000 --- a/hbp_nrp_music_xml/README.txt +++ /dev/null @@ -1,5 +0,0 @@ -This package provides an interface for creating and interacting with MUSIC XML configuration -files - including reading/writing to a neuron/synapse XML format, generating a MUSIC script -that can be used to instantiate MUSIC, and conversions to and from PyNN interfaces. - -This package does not depend on any other hbp_nrp dependencies and is loigically independent. diff --git a/hbp_nrp_music_xml/hbp_nrp_music_xml/__init__.py b/hbp_nrp_music_xml/hbp_nrp_music_xml/__init__.py deleted file mode 100644 index 762853e..0000000 --- a/hbp_nrp_music_xml/hbp_nrp_music_xml/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -""" -This package contains a XML schema and corresponding factories as well as -utilities in order to programatically create MUSIC ports and connect them to -PyNN populations. -""" - -from hbp_nrp_music_xml.version import VERSION as __version__ # pylint: disable=W0611 - -__author__ = 'Martin Schulze' diff --git a/hbp_nrp_music_xml/hbp_nrp_music_xml/config/__init__.py b/hbp_nrp_music_xml/hbp_nrp_music_xml/config/__init__.py deleted file mode 100644 index 9f1d4cc..0000000 --- a/hbp_nrp_music_xml/hbp_nrp_music_xml/config/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -""" -This package provides a programatic interface to assemble a MUSIC-XML and -MUSIC-config file -""" - -__author__ = 'Martin Schulze' diff --git a/hbp_nrp_music_xml/hbp_nrp_music_xml/config/music_config.py b/hbp_nrp_music_xml/hbp_nrp_music_xml/config/music_config.py deleted file mode 100644 index a77db57..0000000 --- a/hbp_nrp_music_xml/hbp_nrp_music_xml/config/music_config.py +++ /dev/null @@ -1,154 +0,0 @@ -# ---LICENSE-BEGIN - DO NOT CHANGE OR MOVE THIS HEADER -# This file is part of the Neurorobotics Platform software -# Copyright (C) 2014,2015,2016,2017 Human Brain Project -# https://www.humanbrainproject.eu -# -# The Human Brain Project is a European Commission funded project -# in the frame of the Horizon2020 FET Flagship plan. -# http://ec.europa.eu/programmes/horizon2020/en/h2020-section/fet-flagships -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# ---LICENSE-END -""" -Programmatic interfaces to assemble MUSIC XML and config files. -""" -from hbp_nrp_music_xml.schema.generated import music_xml - -from ConfigParser import ConfigParser -from types import NoneType -import StringIO - - -class Application(object): - """ - Class to represent an application in the MUSIC context - """ - - def __init__(self, name, binary, args=None, num_processes=1): - """ - :param name: A string to specify the application name - :param binary: A string specifiying the binary to execute for this - application process - :param args: An ordered list of arguments for the binary. - :param num_processes: The integer number of MPI instances for this process - """ - assert isinstance(name, str) - assert isinstance(binary, str) - assert isinstance(args, (NoneType, list)) - assert isinstance(num_processes, int) - - self.name = name - self.binary = binary - self.args = '"{}"'.format(' '.join(args)) if args is not None else '' - self.num_processes = num_processes - - -class MusicConfigWriter(object): - """ - This class helps to write a MUSIC-Config file - """ - - def __init__(self, header=None, applications=None, ports=None): - """ - :param header: A dictionary object - :param applications: A list of music_config.Application objects - :param ports: A list of music_config.MusicConfigPort objects - """ - assert isinstance(header, (NoneType, dict)) - assert isinstance(applications, (NoneType, list, tuple)) - assert isinstance(ports, (NoneType, list, tuple)) - - self.header = header - self.applications = applications - self.ports = ports - - def write(self, file_handle): - """ - Writes a string representing the MUSIC-Config to a file-like object - :param file_handle: A file-like object - """ - if self.header and len(self.header) > 0: - for key, val in self.header.items(): - file_handle.write(u'{key} = "{val}"\n'.format(key=key, val=val)) - file_handle.write(u'\n') - - if self.applications: - config = ConfigParser() - for application in self.applications: - config.add_section(application.name) - config.set(application.name, 'np', application.num_processes) - config.set(application.name, 'binary', application.binary) - config.set(application.name, 'args', application.args) - - config.write(file_handle) - - if self.ports: - for port in self.ports: - file_handle.write(u'{port}\n'.format(port=port)) - - def __str__(self): - """ - Returns the MUSIC-Config as a python string - """ - - try: - conf_io = StringIO.StringIO() - self.write(conf_io) - conf_str = conf_io.getvalue() - except (UnicodeError, ValueError): - raise Exception("Unable to encode MUSIC Configuration Script to String!") - finally: - conf_io.close() - - return conf_str - - -class MusicConfigPort(object): - """ - This class converts a MUSIC-XML Port object into a string - representation in order to assemble the MUSIC config file - """ - - def __init__(self, music_xml_port): - """ - :param music_xml_port: A music_xml.Port object - """ - assert isinstance(music_xml_port, music_xml.Port) - self.music_xml_port = music_xml_port - - def get_connection_strings(self): - """ - Returns a list of port connectivity representations. The format complies - with the format that is being used in the MUSIC-config files. - """ - connection_strs = [] - for receiver in self.music_xml_port.receiver: - connection_strs.append("{sender_name}.{port_name} -> " - "{receiver_name}.{port_name} [{port_width}]".format( - sender_name=self.music_xml_port.sender.name, - port_name=self.music_xml_port.name, - receiver_name=receiver.name, - port_width=self.music_xml_port.width)) - return connection_strs - - def __str__(self): - """ - Returns all connectivity representation strings as given by - MusicConfigPort.get_connection_strings() but in a concatenated - form. Different connectivity strings are seperated by new-line breaks. - """ - - connection_strs = self.get_connection_strings() - return "\n".join(connection_strs) diff --git a/hbp_nrp_music_xml/hbp_nrp_music_xml/pynn/__init__.py b/hbp_nrp_music_xml/hbp_nrp_music_xml/pynn/__init__.py deleted file mode 100644 index 679b1da..0000000 --- a/hbp_nrp_music_xml/hbp_nrp_music_xml/pynn/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -""" -This package provides a programmatic interface to create MUSIC PyNN proxy devices -from a MUSIC-XML specification. -""" - -__author__ = 'Martin Schulze' diff --git a/hbp_nrp_music_xml/hbp_nrp_music_xml/pynn/cell_types.py b/hbp_nrp_music_xml/hbp_nrp_music_xml/pynn/cell_types.py deleted file mode 100644 index 3af30bd..0000000 --- a/hbp_nrp_music_xml/hbp_nrp_music_xml/pynn/cell_types.py +++ /dev/null @@ -1,58 +0,0 @@ -# ---LICENSE-BEGIN - DO NOT CHANGE OR MOVE THIS HEADER -# This file is part of the Neurorobotics Platform software -# Copyright (C) 2014,2015,2016,2017 Human Brain Project -# https://www.humanbrainproject.eu -# -# The Human Brain Project is a European Commission funded project -# in the frame of the Horizon2020 FET Flagship plan. -# http://ec.europa.eu/programmes/horizon2020/en/h2020-section/fet-flagships -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# ---LICENSE-END -""" -Provides utility functions for native cell types. -""" - -__author__ = 'Martin Schulze' - - -def load_cell_types(simulator): - """ - This function provides a map containing PyNN wrappers around the simulator - specific MUSIC proxy implementations - - :param simulator : The PyNN simulator (e.g. pyNN.nest) - """ - cell_type_dict = {} - - if simulator.simulator.name == "NEST": - - music_event_out_proxy = simulator.native_cell_type('music_event_out_proxy') - music_event_in_proxy = simulator.native_cell_type('music_event_in_proxy') - parrot_neuron_type = simulator.native_cell_type('parrot_neuron') - - for model in [music_event_out_proxy, music_event_in_proxy, parrot_neuron_type]: - model.default_initial_values = {} - model.default_parameters = {} - - cell_type_dict['out'] = {'Event': music_event_out_proxy} - cell_type_dict['in'] = {'Event': music_event_in_proxy} - cell_type_dict['parrot'] = parrot_neuron_type - - else: - raise Exception("Implementation for {simulator_name} not found" - .format(simulator_name=simulator.name)) - - return cell_type_dict diff --git a/hbp_nrp_music_xml/hbp_nrp_music_xml/pynn/connector_factory.py b/hbp_nrp_music_xml/hbp_nrp_music_xml/pynn/connector_factory.py deleted file mode 100644 index cb4f47c..0000000 --- a/hbp_nrp_music_xml/hbp_nrp_music_xml/pynn/connector_factory.py +++ /dev/null @@ -1,114 +0,0 @@ -# ---LICENSE-BEGIN - DO NOT CHANGE OR MOVE THIS HEADER -# This file is part of the Neurorobotics Platform software -# Copyright (C) 2014,2015,2016,2017 Human Brain Project -# https://www.humanbrainproject.eu -# -# The Human Brain Project is a European Commission funded project -# in the frame of the Horizon2020 FET Flagship plan. -# http://ec.europa.eu/programmes/horizon2020/en/h2020-section/fet-flagships -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# ---LICENSE-END -""" -PyNN factory for PyNN projections between PyNN MUSIC proxy objects and other PyNN populations -""" -from hbp_nrp_music_xml.pynn.utils import desc_pynn_pop, pprint_pynn_pop, get_min_delay - -import logging - -__author__ = 'Martin Schulze' - -logger = logging.getLogger(__name__) - - -class PyNNConnectorFactory(object): - """ - PyNN factory for PyNN projections between PyNN MUSIC proxy objects and other PyNN populations - """ - - def __init__(self, simulator): - """ - :param simulator: The PyNN simulator (e.g. pyNN.nest) - """ - self.simulator = simulator - self.connector_types = {'one_to_one': simulator.OneToOneConnector, - 'all_to_all': simulator.AllToAllConnector} - - def create_and_connect_synapse(self, proxy, port_name, connection_rule, population_slice, - target_population, is_output_proxy, synapse=None): - """ - :param proxy: PyNN population that wraps the simulator specific MUSIC port implementation - :param port_name: The name of the port - :param connection_rule: {'one_to_one', 'all_to_all'} to connect proxy->target_population - or target_population->proxy - :param population_slice: Apply synapse only on subset (slice) of given target_population - :param target_population: The target/source population - :param is_output_proxy: A boolean which indicates whether the proxy to create represents an - ingoing or outgoing connection - :param synapse: Uses this PyNN synapse instead of 'StaticSynapse', default None - """ - - # only create default synapses to the cle ports since we will dynamically configure - # device connectivity <-> proxies from the cle (e.g. spike generators or recorders) - # but we need connectivity in the remote brain processes from parrot -> proxy so that - # spikes are propagated - if not port_name.endswith('to_cle'): - return - - try: - connector = self.connector_types[connection_rule] - except KeyError, e: - message = "Unsupported PyNN connector type: {connector_factory} does not support \ - '{synaptic_rule}' connectors."\ - .format(connector_factory=self.__class__, synaptic_rule=connection_rule) - logger.error(message, exc_info=True) - raise Exception(message) - - if population_slice: - logger.debug("Applying slice {population_slice} on population {population_name}" - .format(population_slice=population_slice, - population_name=desc_pynn_pop(target_population))) - target_population = self.simulator.PopulationView(target_population, population_slice) - - if is_output_proxy: - presynaptic_population = target_population - postsynaptic_population = proxy - else: - presynaptic_population = proxy - postsynaptic_population = target_population - - direction_arrow = '-->' if not is_output_proxy else '<--' - message = "Connecting port {port_name} {direction_arrow} PyNNPopulation \ - {postsynaptic_population}. Interface population for the proxy is\ - {presynaptic_population}"\ - .format(port_name=port_name, direction_arrow=direction_arrow, - postsynaptic_population=desc_pynn_pop(postsynaptic_population), - presynaptic_population=desc_pynn_pop(presynaptic_population)) - logger.debug(message) - - try: - if not synapse: - synaptic_delay = get_min_delay() - synapse = self.simulator.StaticSynapse(weight=1.0, delay=synaptic_delay) - self.simulator.Projection(presynaptic_population, postsynaptic_population, - connector(), synapse) - except Exception, e: - logging.error(e, exc_info=True) - logging.error('Debug information: \nStatus dictionaries of the \ - populations: \npresynaptic population: {presynaptic_population}\n\ - postsynaptic population: {postsynaptic_population}'. - format(presynaptic_population=pprint_pynn_pop(presynaptic_population), - postsynaptic_population=pprint_pynn_pop(postsynaptic_population))) - raise Exception("Failure creating PyNN/MUSIC synapse: {}" % message) diff --git a/hbp_nrp_music_xml/hbp_nrp_music_xml/pynn/factory.py b/hbp_nrp_music_xml/hbp_nrp_music_xml/pynn/factory.py deleted file mode 100644 index 080e941..0000000 --- a/hbp_nrp_music_xml/hbp_nrp_music_xml/pynn/factory.py +++ /dev/null @@ -1,188 +0,0 @@ -# ---LICENSE-BEGIN - DO NOT CHANGE OR MOVE THIS HEADER -# This file is part of the Neurorobotics Platform software -# Copyright (C) 2014,2015,2016,2017 Human Brain Project -# https://www.humanbrainproject.eu -# -# The Human Brain Project is a European Commission funded project -# in the frame of the Horizon2020 FET Flagship plan. -# http://ec.europa.eu/programmes/horizon2020/en/h2020-section/fet-flagships -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# ---LICENSE-END -""" -Factory class for PyNN MUSIC-proxy objects -""" - - -from hbp_nrp_music_xml.pynn.cell_types import load_cell_types -from hbp_nrp_music_xml.pynn.utils import get_min_delay - -import nest -import logging - -logger = logging.getLogger(__name__) - -__author__ = 'Martin Schulze' - - -def create_proxy_via_native_cell_types(simulator, proxy_width, cell_type, port_name, proxy_type, - acc_latency, is_output_proxy): - """ - Creates the PyNN proxy representation of a MUSIC port - - :param simulator: The PyNN simulator (e.g. pyNN.nest) - :param proxy_width: The number of proxies required to represent the port - :param cell_type: The PyNN celltype that encapsulates the proxy mechanism of - the underlying PyNN simulator - :param port_name: The port name - :param acc_latency: The maximum allowed latency in milisecond (ignored on output proxies) - :param is_output_proxy: A boolean which indicates whether the proxy to create represents - an ingoing or outgoing connection - """ - assert simulator.simulator.name == "NEST", "Currently only NEST is supported" - - if not acc_latency: - acc_latency = get_min_delay() - - proxy_label = "Port: {name}\nWidth: {width}".format(name=port_name, width=proxy_width) - proxy = simulator.Population(proxy_width, cell_type, {}, label=proxy_label) - - proxy_ids = map(int, proxy.all_cells) - nest.SetStatus(proxy_ids, 'port_name', port_name) - - # Acc. latency actually only matters for input ports - if not is_output_proxy: - nest.SetAcceptableLatency(port_name, acc_latency) - logger.debug("Created {proxy_width} {port_direction} {port_type} proxy neurons with " - "accLatency={port_acc_latency} for port {port_name} " - .format(proxy_width=proxy_width, port_direction="input", - port_type=proxy_type, port_acc_latency=acc_latency, - port_name=port_name)) - else: - logger.debug("Created {proxy_width} {port_direction} {port_type} proxy neurons for " - "port {port_name}" - .format(proxy_width=proxy_width, port_direction="output", - port_type=proxy_type, port_name=port_name)) - - if proxy_type == "Event" and not is_output_proxy: - for i, proxy_id in enumerate(proxy_ids): - nest.SetStatus([proxy_id], 'music_channel', i) - - return proxy - - -def export_proxy(simulator, presynaptic_population, proxy, synaptic_delay=get_min_delay()): - """ - This helper function connects a population to an output proxy. - - :param simulator: The PyNN simulator (e.g. pyNN.nest) - :param presynaptic_population: A PyNN population that is to be connected to the - desired output proxy - :param proxy: The PyNN proxy population - """ - assert simulator.simulator.name == "NEST", "Currently only NEST is supported" - - if not synaptic_delay: - synaptic_delay = get_min_delay() - - presynaptic_neuron_ids = map(int, presynaptic_population.all_cells) - proxy_ids = map(int, proxy.all_cells) - connection_params = {'delay': synaptic_delay, 'weight': 1.0} - - for i, pre_id in enumerate(presynaptic_neuron_ids): - connection_params.update(music_channel=i) - nest.Connect([pre_id], proxy_ids, 'one_to_one', connection_params) - - logger.debug("Connected presynaptic population {population_name} to output proxy " - "{proxy_name}" - .format(population_name=presynaptic_population, proxy_name=proxy)) - - -class PyNNProxyFactory(object): - """ - Factory class for PyNN MUSIC-proxy objects - """ - - def __init__(self, simulator, acc_latency=None, max_buffered=1): - """ - :param simulator: The PyNN simulator (e.g. pyNN.nest) - :param acc_latency: The maximum allowed latency in miliseconds - :param max_buffered: TODO - """ - assert simulator.simulator.name == "NEST", "Currently only NEST is supported" - - self.acc_latency = acc_latency - self.max_buffered = max_buffered - self.simulator = simulator - self.cell_types = load_cell_types(simulator) - - def create_proxy(self, port_name, proxy_type, port_width, is_output_proxy, synaptic_delay=None): - """ - Creates the actual PyNN MUSIC proxy object - - :param port_name: The name of the port - :param proxy_type: {'Event', 'Message', 'Continuous'} - :param port_width: The number of MUSIC connections for this port - :param is_output_proxy: A boolean which indicates whether the proxy to create represents an - ingoing or outgoing connection - :param synaptic_delay: Sets the synaptic delay of the connection, defaults to simulator - minimum synaptic delay - """ - - assert proxy_type == "Event", '{proxy_type} ports are currently not supported for PyNN.'\ - .format(proxy_type=proxy_type) - - if is_output_proxy: - cell_type = self.cell_types['out'][proxy_type] - # We only do need one output proxy (for spike_out_proxy and continuous_out_proxy) - proxy_width = 1 - else: - cell_type = self.cell_types['in'][proxy_type] - proxy_width = port_width - - # Currently NEST specific, will be replaced by a MUSIC branch of PyNN - proxy = create_proxy_via_native_cell_types(self.simulator, proxy_width, cell_type, - port_name, proxy_type, self.acc_latency, - is_output_proxy) - - if proxy_type == 'Event': - if is_output_proxy: - label = "Parrots for Port: {name}\nWidth: {width}"\ - .format(name=port_name, width=proxy_width) - parrots = self.simulator.Population(port_width, - self.cell_types['parrot'], - {}, - label=label) - - export_proxy(self.simulator, parrots, proxy, synaptic_delay) - logger.debug("Created {port_width} parrot neurons and connected it to outgoing " - "port {port_name}" - .format(port_width=port_width, port_name=port_name)) - - # [NRRPLT-4722] workaround for lack of dynamic MUSIC ports, since we have many - # more parrot neurons than required, freeze them. this means they will not be - # updated until we explicitly thaw them during connection. - if port_name.endswith('to_brain'): - nest.SetStatus(map(int, parrots.all_cells), 'frozen', True) - - return parrots - else: - # [NRRPLT-4722] workaround for lack of dynamic MUSIC ports, since we have many - # more parrot neurons than required, freeze them. this means they will not be - # updated until we explicitly thaw them during connection. - if port_name.endswith('to_brain'): - nest.SetStatus(map(int, proxy.all_cells), 'frozen', True) - - return proxy diff --git a/hbp_nrp_music_xml/hbp_nrp_music_xml/pynn/utils.py b/hbp_nrp_music_xml/hbp_nrp_music_xml/pynn/utils.py deleted file mode 100644 index 5999fcc..0000000 --- a/hbp_nrp_music_xml/hbp_nrp_music_xml/pynn/utils.py +++ /dev/null @@ -1,70 +0,0 @@ -# ---LICENSE-BEGIN - DO NOT CHANGE OR MOVE THIS HEADER -# This file is part of the Neurorobotics Platform software -# Copyright (C) 2014,2015,2016,2017 Human Brain Project -# https://www.humanbrainproject.eu -# -# The Human Brain Project is a European Commission funded project -# in the frame of the Horizon2020 FET Flagship plan. -# http://ec.europa.eu/programmes/horizon2020/en/h2020-section/fet-flagships -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# ---LICENSE-END -""" -Common Nest/PyNN helper utility functions. -""" - -import pprint -import nest - -__author__ = 'Martin Schulze' - -pp = pprint.PrettyPrinter(indent=4) - - -def desc_pynn_pop(pop): - """ - Return a string with a high-level description of a PyNN population. - - :param pop: The PyNN population to describe. - """ - return "<Type: {cell_type}, Width: {population_width}, Label: {population_label}>"\ - .format(cell_type=pop.celltype, population_width=pop.size, population_label=pop.label) - - -def desc_nest_pop(ids): - """ - Return a string with a high-level description of a Nest population. - - :param pop: The Nest population to describe. - """ - return "<Model: {nest_model_name}, Width: {population_width}>"\ - .format(nest_model_name=nest.GetStatus(list(ids), 'model'), - population_width=len(list(ids))) - - -def pprint_pynn_pop(pop): - """ - Return a string with a detailed population level description of a PyNN population. - - :param pop: The PyNN population to describe. - """ - return pp.pformat(nest.GetStatus(map(int, pop.all_cells))) - - -def get_min_delay(): - """ - Returns the minimum delay of the simulator. - """ - return nest.GetKernelStatus()['min_delay'] diff --git a/hbp_nrp_music_xml/hbp_nrp_music_xml/pynn/xml_factory.py b/hbp_nrp_music_xml/hbp_nrp_music_xml/pynn/xml_factory.py deleted file mode 100644 index 340c181..0000000 --- a/hbp_nrp_music_xml/hbp_nrp_music_xml/pynn/xml_factory.py +++ /dev/null @@ -1,249 +0,0 @@ -# ---LICENSE-BEGIN - DO NOT CHANGE OR MOVE THIS HEADER -# This file is part of the Neurorobotics Platform software -# Copyright (C) 2014,2015,2016,2017 Human Brain Project -# https://www.humanbrainproject.eu -# -# The Human Brain Project is a European Commission funded project -# in the frame of the Horizon2020 FET Flagship plan. -# http://ec.europa.eu/programmes/horizon2020/en/h2020-section/fet-flagships -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# ---LICENSE-END -""" -Provides a high level interface to convert a MUSIC XML file into MUSIC proxies. -""" - -from hbp_nrp_music_xml.schema.generated import music_xml - -from collections import OrderedDict -from prettytable import PrettyTable -from operator import attrgetter - -import logging - -logger = logging.getLogger(__name__) - -__author__ = 'Martin Schulze' - - -def extract_kwargs(xml_parameter_list): - """ - Extracts keyword arguments from a music_xml.ParameterList object - - :param xml_parameter_list: music_xml.ParameterList object - """ - - if not xml_parameter_list: - return {} - kwargs = {} - for elem in xml_parameter_list.orderedContent(): - kwargs[elem.value.name] = elem.value.content() - return kwargs - - -def is_application_receiver(application_name, xml_port): - """ - Returns 'True' if the application with 'application_name' is a receiver with - respect to the provided xml_port. Returns 'False' else - - :param application_name: The application name - :param xml_port: music_xml.Port object - """ - - xml_receivers = xml_port.receiver - receiver_names = map(attrgetter('name'), xml_receivers) - if application_name in receiver_names: - return True - return False - - -def build_port_overview_table(xml_ports, application_name): - """ - Constructs a tabular overview in string representation for logging purposes - - :param xml_ports: A list of music_xml.Port objects - :param application_name: The name of the current application (will be shown - in the table header) - """ - - overview_table = PrettyTable(['Portname', 'Type', 'Width', 'Direction']) - for xml_port in xml_ports: - xml_sender = xml_port.sender - direction = "Outgoing" if xml_sender.name == application_name else \ - "Ingoing" - port_name = xml_port.name - port_type = xml_port.type - width = int(xml_port.width) - if xml_sender.name == application_name or \ - is_application_receiver(application_name, xml_port): - overview_table.add_row([port_name, port_type, width, direction]) - return overview_table - - -def create_pop_slice(xml_selector): - """ - Returns either a python-slice or a list, depending on the type of xml_selector - - :param xml_selector: music_xml.SelectorType - """ - if isinstance(xml_selector, music_xml.SliceSelector): - return slice(xml_selector.start, xml_selector.stop, xml_selector.step) - elif isinstance(xml_selector, music_xml.ListSelector): - elements = [x for x in xml_selector.element] - return elements - else: - raise Exception("Can not convert object {xml_selector} to a " - "slice-like object. Unknown selector type" - .format(xml_selector=xml_selector)) - - -def create_selector(xml_synapse): - """ - Returns a python-slice or list if a synaptic selector has been specified - in the xml_synapse - - :param xml_synapse: music_xml.SynapticConnectionType - """ - - xml_selector = getattr(xml_synapse, 'selector', None) - if xml_selector: - selector = create_pop_slice(xml_selector) - else: - selector = None - return selector - - -class XmlFactory(object): - """ - This class helps unpacking the MUSIC-XML and makes subsequent calls to the - desired proxy/connector factories - """ - - def __init__(self, application_name, connector_factory, proxy_factory, - population_dict): - """ - :param application_name: The name of the application (process) where - this factory is instantiated on - :param connector_factory: A factory class to create PyNN ports - :param proxy_factory: A factory class to create PyNN wrappers around - simulator specific MUSIC proxy objects - :param population_dict: A dictionary containing population names as keys - and PyNN population objects as values. If the XML specifies synaptic - connections between proxies and populations then this dictionary - provides the respective mapping - """ - self.application_name = application_name - self.connector_factory = connector_factory - self.proxy_factory = proxy_factory - self.population_dict = population_dict - - def _get_synaptic_target_population(self, xml_synapse): - """ - Returns the target/source population as specified in the xml_synapse - object provided the population has been passed along with the - population dictionary in the constructor - - :param xml_synapse: music_xml.SynapticConnectionType object - """ - - population_name = getattr(xml_synapse, 'target') - try: - target_population = self.population_dict[population_name] - except KeyError: - raise Exception("Population with dictionary key {population_name} " - "has not been found in the provided " - "population-dictionary." - .format(population_name=population_name)) - return target_population - - def _create_and_connect_proxy(self, xml_port, xml_peer, is_output_proxy): - """ - Creates a PyNN proxy for a specific MUSIC port and connects it to a - source/target population as long this is specified in the - xml_port. - - :param xml_port: music_xml.Port object - :param xml_peer: music_xml.PeerType object, represents a sender or - receiver application - :param is_output_proxy: A boolean to indicate whether the proxy is - inbound or outbound - """ - port_name = xml_port.name - port_type = xml_port.type - width = int(xml_port.width) - - proxy = self.proxy_factory.create_proxy(port_name, port_type, width, - is_output_proxy) - xml_synapses = getattr(xml_peer, 'synapse', None) - if not xml_synapses: - raise Exception("No synapse connectivity specified for port {}, invalid " - "configuration!".format(port_name)) - - if self.population_dict and len(self.population_dict) > 0: - for xml_synapse in xml_synapses: - population_slice = create_selector(xml_synapse) - connection_rule = xml_synapse.type - synaptic_target = self._get_synaptic_target_population(xml_synapse) - if hasattr(synaptic_target, 'mask'): # prevent duplicate slicing - population_slice = None - self.connector_factory.create_and_connect_synapse(proxy, - port_name, - connection_rule, - population_slice, - synaptic_target, - is_output_proxy) - return proxy - - def _assemble_proxy(self, xml_port): - """ - Assembles and connects all PyNN proxy objects for a specific port. - Returns a population to interface the MUSIC ports - - :param xml_port: music_xml.Port - """ - - xml_sender = xml_port.sender - xml_receivers = xml_port.receiver - - if xml_sender.name == self.application_name: - for xml_receiver in list(xml_receivers): - return self._create_and_connect_proxy(xml_port, xml_receiver, True) - else: - for xml_receiver in list(xml_receivers): - if xml_receiver.name == self.application_name: - return self._create_and_connect_proxy(xml_port, - xml_receiver, False) - - def create_proxies(self, xml_string): - """ - This method parses, reads and creates and connects all MUSIC proxy - objects and returns a dictionary with keys: port_name and value: PyNN - populations that represent the MUSIC ports). - - :param xml_string: A raw string containing valid MUSIC-XML content - """ - - logger.debug("Parsing XML File") - xml_root = music_xml.CreateFromDocument(xml_string) - xml_ports = xml_root.port - proxy_dict = OrderedDict() - port_overview_table = build_port_overview_table(xml_ports, - self.application_name) - logger.debug("\nPort overview of application: " - "{}\n{}".format(self.application_name, port_overview_table)) - for xml_port in xml_ports: - proxy = self._assemble_proxy(xml_port) - proxy_dict[xml_port.name] = proxy - return proxy_dict diff --git a/hbp_nrp_music_xml/hbp_nrp_music_xml/schema/__init__.py b/hbp_nrp_music_xml/hbp_nrp_music_xml/schema/__init__.py deleted file mode 100644 index 1610e00..0000000 --- a/hbp_nrp_music_xml/hbp_nrp_music_xml/schema/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -""" -XML Schema and generated pyxb file for MUSIC configuration file. -""" - -__author__ = 'Martin Schulze' diff --git a/hbp_nrp_music_xml/hbp_nrp_music_xml/schema/generated/__init__.py b/hbp_nrp_music_xml/hbp_nrp_music_xml/schema/generated/__init__.py deleted file mode 100644 index e79ff7a..0000000 --- a/hbp_nrp_music_xml/hbp_nrp_music_xml/schema/generated/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -""" -Generated pyxb file for MUSIC schema. -""" - -__author__ = 'Martin Schulze' diff --git a/hbp_nrp_music_xml/hbp_nrp_music_xml/schema/generated/music_xml.py b/hbp_nrp_music_xml/hbp_nrp_music_xml/schema/generated/music_xml.py deleted file mode 100644 index b5c25e0..0000000 --- a/hbp_nrp_music_xml/hbp_nrp_music_xml/schema/generated/music_xml.py +++ /dev/null @@ -1,743 +0,0 @@ -# ---LICENSE-BEGIN - DO NOT CHANGE OR MOVE THIS HEADER -# This file is part of the Neurorobotics Platform software -# Copyright (C) 2014,2015,2016,2017 Human Brain Project -# https://www.humanbrainproject.eu -# -# The Human Brain Project is a European Commission funded project -# in the frame of the Horizon2020 FET Flagship plan. -# http://ec.europa.eu/programmes/horizon2020/en/h2020-section/fet-flagships -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# ---LICENSE-END -# ./music_xml.py -# -*- coding: utf-8 -*- -# PyXB bindings for NM:e92452c8d3e28a9e27abfc9994d2007779e7f4c9 -# Generated 2016-09-16 13:06:43.146805 by PyXB version 1.2.4 using Python 2.7.6.final.0 -# Namespace AbsentNamespace0 - -from __future__ import unicode_literals -import pyxb -import pyxb.binding -import pyxb.binding.saxer -import io -import pyxb.utils.utility -import pyxb.utils.domutils -import sys -import pyxb.utils.six as _six - -# Unique identifier for bindings created at the same time -_GenerationUID = pyxb.utils.utility.UniqueIdentifier('urn:uuid:398b796a-7d06-11e6-91a7-e4b318398898') - -# Version of PyXB used to generate the bindings -_PyXBVersion = '1.2.4' -# Generated bindings are not compatible across PyXB versions -if pyxb.__version__ != _PyXBVersion: - raise pyxb.PyXBVersionError(_PyXBVersion) - -# Import bindings for namespaces imported into schema -import pyxb.binding.datatypes - -# NOTE: All namespace declarations are reserved within the binding -Namespace = pyxb.namespace.CreateAbsentNamespace() -Namespace.configureCategories(['typeBinding', 'elementBinding']) - -def CreateFromDocument (xml_text, default_namespace=None, location_base=None): - """Parse the given XML and use the document element to create a - Python instance. - - @param xml_text An XML document. This should be data (Python 2 - str or Python 3 bytes), or a text (Python 2 unicode or Python 3 - str) in the L{pyxb._InputEncoding} encoding. - - @keyword default_namespace The L{pyxb.Namespace} instance to use as the - default namespace where there is no default namespace in scope. - If unspecified or C{None}, the namespace of the module containing - this function will be used. - - @keyword location_base: An object to be recorded as the base of all - L{pyxb.utils.utility.Location} instances associated with events and - objects handled by the parser. You might pass the URI from which - the document was obtained. - """ - - if pyxb.XMLStyle_saxer != pyxb._XMLStyle: - dom = pyxb.utils.domutils.StringToDOM(xml_text) - return CreateFromDOM(dom.documentElement, default_namespace=default_namespace) - if default_namespace is None: - default_namespace = Namespace.fallbackNamespace() - saxer = pyxb.binding.saxer.make_parser(fallback_namespace=default_namespace, location_base=location_base) - handler = saxer.getContentHandler() - xmld = xml_text - if isinstance(xmld, _six.text_type): - xmld = xmld.encode(pyxb._InputEncoding) - saxer.parse(io.BytesIO(xmld)) - instance = handler.rootObject() - return instance - -def CreateFromDOM (node, default_namespace=None): - """Create a Python instance from the given DOM node. - The node tag must correspond to an element declaration in this module. - - @deprecated: Forcing use of DOM interface is unnecessary; use L{CreateFromDocument}.""" - if default_namespace is None: - default_namespace = Namespace.fallbackNamespace() - return pyxb.binding.basis.element.AnyCreateFromDOM(node, default_namespace) - - -# Atomic simple type: PortType -class PortType (pyxb.binding.datatypes.string, pyxb.binding.basis.enumeration_mixin): - - """An atomic simple type.""" - - _ExpandedName = pyxb.namespace.ExpandedName(Namespace, 'PortType') - _XSDLocation = pyxb.utils.utility.Location('/home/kenny/Desktop/HBP/BrainSimulation/hbp_nrp_music_xml/hbp_nrp_music_xml/schema/music.xsd', 40, 2) - _Documentation = None -PortType._CF_enumeration = pyxb.binding.facets.CF_enumeration(value_datatype=PortType, enum_prefix=None) -PortType.Event = PortType._CF_enumeration.addEnumeration(unicode_value='Event', tag='Event') -PortType.Continuous = PortType._CF_enumeration.addEnumeration(unicode_value='Continuous', tag='Continuous') -PortType.Message = PortType._CF_enumeration.addEnumeration(unicode_value='Message', tag='Message') -PortType._InitializeFacetMap(PortType._CF_enumeration) -Namespace.addCategoryObject('typeBinding', 'PortType', PortType) - -# Atomic simple type: ConnectionType -class ConnectionType (pyxb.binding.datatypes.string, pyxb.binding.basis.enumeration_mixin): - - """An atomic simple type.""" - - _ExpandedName = pyxb.namespace.ExpandedName(Namespace, 'ConnectionType') - _XSDLocation = pyxb.utils.utility.Location('/home/kenny/Desktop/HBP/BrainSimulation/hbp_nrp_music_xml/hbp_nrp_music_xml/schema/music.xsd', 64, 2) - _Documentation = None -ConnectionType._CF_enumeration = pyxb.binding.facets.CF_enumeration(value_datatype=ConnectionType, enum_prefix=None) -ConnectionType.one_to_one = ConnectionType._CF_enumeration.addEnumeration(unicode_value='one_to_one', tag='one_to_one') -ConnectionType.all_to_all = ConnectionType._CF_enumeration.addEnumeration(unicode_value='all_to_all', tag='all_to_all') -ConnectionType._InitializeFacetMap(ConnectionType._CF_enumeration) -Namespace.addCategoryObject('typeBinding', 'ConnectionType', ConnectionType) - -# Complex type [anonymous] with content type ELEMENT_ONLY -class CTD_ANON (pyxb.binding.basis.complexTypeDefinition): - """Complex type [anonymous] with content type ELEMENT_ONLY""" - _TypeDefinition = None - _ContentTypeTag = pyxb.binding.basis.complexTypeDefinition._CT_ELEMENT_ONLY - _Abstract = False - _ExpandedName = None - _XSDLocation = pyxb.utils.utility.Location('/home/kenny/Desktop/HBP/BrainSimulation/hbp_nrp_music_xml/hbp_nrp_music_xml/schema/music.xsd', 10, 4) - _ElementMap = {} - _AttributeMap = {} - # Base type is pyxb.binding.datatypes.anyType - - # Element port uses Python identifier port - __port = pyxb.binding.content.ElementDeclaration(pyxb.namespace.ExpandedName(None, 'port'), 'port', '__AbsentNamespace0_CTD_ANON_port', True, pyxb.utils.utility.Location('/home/kenny/Desktop/HBP/BrainSimulation/hbp_nrp_music_xml/hbp_nrp_music_xml/schema/music.xsd', 12, 8), ) - - - port = property(__port.value, __port.set, None, None) - - _ElementMap.update({ - __port.name() : __port - }) - _AttributeMap.update({ - - }) - - - -# Complex type Port with content type ELEMENT_ONLY -class Port (pyxb.binding.basis.complexTypeDefinition): - """Complex type Port with content type ELEMENT_ONLY""" - _TypeDefinition = None - _ContentTypeTag = pyxb.binding.basis.complexTypeDefinition._CT_ELEMENT_ONLY - _Abstract = False - _ExpandedName = pyxb.namespace.ExpandedName(Namespace, 'Port') - _XSDLocation = pyxb.utils.utility.Location('/home/kenny/Desktop/HBP/BrainSimulation/hbp_nrp_music_xml/hbp_nrp_music_xml/schema/music.xsd', 25, 2) - _ElementMap = {} - _AttributeMap = {} - # Base type is pyxb.binding.datatypes.anyType - - # Element type uses Python identifier type - __type = pyxb.binding.content.ElementDeclaration(pyxb.namespace.ExpandedName(None, 'type'), 'type', '__AbsentNamespace0_Port_type', False, pyxb.utils.utility.Location('/home/kenny/Desktop/HBP/BrainSimulation/hbp_nrp_music_xml/hbp_nrp_music_xml/schema/music.xsd', 27, 6), ) - - - type = property(__type.value, __type.set, None, None) - - - # Element name uses Python identifier name - __name = pyxb.binding.content.ElementDeclaration(pyxb.namespace.ExpandedName(None, 'name'), 'name', '__AbsentNamespace0_Port_name', False, pyxb.utils.utility.Location('/home/kenny/Desktop/HBP/BrainSimulation/hbp_nrp_music_xml/hbp_nrp_music_xml/schema/music.xsd', 28, 6), ) - - - name = property(__name.value, __name.set, None, None) - - - # Element width uses Python identifier width - __width = pyxb.binding.content.ElementDeclaration(pyxb.namespace.ExpandedName(None, 'width'), 'width', '__AbsentNamespace0_Port_width', False, pyxb.utils.utility.Location('/home/kenny/Desktop/HBP/BrainSimulation/hbp_nrp_music_xml/hbp_nrp_music_xml/schema/music.xsd', 29, 6), ) - - - width = property(__width.value, __width.set, None, None) - - - # Element sender uses Python identifier sender - __sender = pyxb.binding.content.ElementDeclaration(pyxb.namespace.ExpandedName(None, 'sender'), 'sender', '__AbsentNamespace0_Port_sender', False, pyxb.utils.utility.Location('/home/kenny/Desktop/HBP/BrainSimulation/hbp_nrp_music_xml/hbp_nrp_music_xml/schema/music.xsd', 30, 6), ) - - - sender = property(__sender.value, __sender.set, None, None) - - - # Element receiver uses Python identifier receiver - __receiver = pyxb.binding.content.ElementDeclaration(pyxb.namespace.ExpandedName(None, 'receiver'), 'receiver', '__AbsentNamespace0_Port_receiver', True, pyxb.utils.utility.Location('/home/kenny/Desktop/HBP/BrainSimulation/hbp_nrp_music_xml/hbp_nrp_music_xml/schema/music.xsd', 31, 6), ) - - - receiver = property(__receiver.value, __receiver.set, None, None) - - _ElementMap.update({ - __type.name() : __type, - __name.name() : __name, - __width.name() : __width, - __sender.name() : __sender, - __receiver.name() : __receiver - }) - _AttributeMap.update({ - - }) -Namespace.addCategoryObject('typeBinding', 'Port', Port) - - -# Complex type Peer with content type ELEMENT_ONLY -class Peer (pyxb.binding.basis.complexTypeDefinition): - """Complex type Peer with content type ELEMENT_ONLY""" - _TypeDefinition = None - _ContentTypeTag = pyxb.binding.basis.complexTypeDefinition._CT_ELEMENT_ONLY - _Abstract = False - _ExpandedName = pyxb.namespace.ExpandedName(Namespace, 'Peer') - _XSDLocation = pyxb.utils.utility.Location('/home/kenny/Desktop/HBP/BrainSimulation/hbp_nrp_music_xml/hbp_nrp_music_xml/schema/music.xsd', 49, 2) - _ElementMap = {} - _AttributeMap = {} - # Base type is pyxb.binding.datatypes.anyType - - # Element name uses Python identifier name - __name = pyxb.binding.content.ElementDeclaration(pyxb.namespace.ExpandedName(None, 'name'), 'name', '__AbsentNamespace0_Peer_name', False, pyxb.utils.utility.Location('/home/kenny/Desktop/HBP/BrainSimulation/hbp_nrp_music_xml/hbp_nrp_music_xml/schema/music.xsd', 51, 6), ) - - - name = property(__name.value, __name.set, None, None) - - - # Element synapse uses Python identifier synapse - __synapse = pyxb.binding.content.ElementDeclaration(pyxb.namespace.ExpandedName(None, 'synapse'), 'synapse', '__AbsentNamespace0_Peer_synapse', True, pyxb.utils.utility.Location('/home/kenny/Desktop/HBP/BrainSimulation/hbp_nrp_music_xml/hbp_nrp_music_xml/schema/music.xsd', 52, 6), ) - - - synapse = property(__synapse.value, __synapse.set, None, None) - - - # Element parameters uses Python identifier parameters - __parameters = pyxb.binding.content.ElementDeclaration(pyxb.namespace.ExpandedName(None, 'parameters'), 'parameters', '__AbsentNamespace0_Peer_parameters', False, pyxb.utils.utility.Location('/home/kenny/Desktop/HBP/BrainSimulation/hbp_nrp_music_xml/hbp_nrp_music_xml/schema/music.xsd', 53, 6), ) - - - parameters = property(__parameters.value, __parameters.set, None, None) - - _ElementMap.update({ - __name.name() : __name, - __synapse.name() : __synapse, - __parameters.name() : __parameters - }) - _AttributeMap.update({ - - }) -Namespace.addCategoryObject('typeBinding', 'Peer', Peer) - - -# Complex type SynapticConnection with content type ELEMENT_ONLY -class SynapticConnection (pyxb.binding.basis.complexTypeDefinition): - """Complex type SynapticConnection with content type ELEMENT_ONLY""" - _TypeDefinition = None - _ContentTypeTag = pyxb.binding.basis.complexTypeDefinition._CT_ELEMENT_ONLY - _Abstract = False - _ExpandedName = pyxb.namespace.ExpandedName(Namespace, 'SynapticConnection') - _XSDLocation = pyxb.utils.utility.Location('/home/kenny/Desktop/HBP/BrainSimulation/hbp_nrp_music_xml/hbp_nrp_music_xml/schema/music.xsd', 56, 2) - _ElementMap = {} - _AttributeMap = {} - # Base type is pyxb.binding.datatypes.anyType - - # Element target uses Python identifier target - __target = pyxb.binding.content.ElementDeclaration(pyxb.namespace.ExpandedName(None, 'target'), 'target', '__AbsentNamespace0_SynapticConnection_target', False, pyxb.utils.utility.Location('/home/kenny/Desktop/HBP/BrainSimulation/hbp_nrp_music_xml/hbp_nrp_music_xml/schema/music.xsd', 58, 6), ) - - - target = property(__target.value, __target.set, None, None) - - - # Element type uses Python identifier type - __type = pyxb.binding.content.ElementDeclaration(pyxb.namespace.ExpandedName(None, 'type'), 'type', '__AbsentNamespace0_SynapticConnection_type', False, pyxb.utils.utility.Location('/home/kenny/Desktop/HBP/BrainSimulation/hbp_nrp_music_xml/hbp_nrp_music_xml/schema/music.xsd', 59, 6), ) - - - type = property(__type.value, __type.set, None, None) - - - # Element selector uses Python identifier selector - __selector = pyxb.binding.content.ElementDeclaration(pyxb.namespace.ExpandedName(None, 'selector'), 'selector', '__AbsentNamespace0_SynapticConnection_selector', False, pyxb.utils.utility.Location('/home/kenny/Desktop/HBP/BrainSimulation/hbp_nrp_music_xml/hbp_nrp_music_xml/schema/music.xsd', 60, 6), ) - - - selector = property(__selector.value, __selector.set, None, None) - - - # Element parameters uses Python identifier parameters - __parameters = pyxb.binding.content.ElementDeclaration(pyxb.namespace.ExpandedName(None, 'parameters'), 'parameters', '__AbsentNamespace0_SynapticConnection_parameters', False, pyxb.utils.utility.Location('/home/kenny/Desktop/HBP/BrainSimulation/hbp_nrp_music_xml/hbp_nrp_music_xml/schema/music.xsd', 61, 6), ) - - - parameters = property(__parameters.value, __parameters.set, None, None) - - _ElementMap.update({ - __target.name() : __target, - __type.name() : __type, - __selector.name() : __selector, - __parameters.name() : __parameters - }) - _AttributeMap.update({ - - }) -Namespace.addCategoryObject('typeBinding', 'SynapticConnection', SynapticConnection) - - -# Complex type SynapticSelector with content type EMPTY -class SynapticSelector (pyxb.binding.basis.complexTypeDefinition): - """Complex type SynapticSelector with content type EMPTY""" - _TypeDefinition = None - _ContentTypeTag = pyxb.binding.basis.complexTypeDefinition._CT_EMPTY - _Abstract = True - _ExpandedName = pyxb.namespace.ExpandedName(Namespace, 'SynapticSelector') - _XSDLocation = pyxb.utils.utility.Location('/home/kenny/Desktop/HBP/BrainSimulation/hbp_nrp_music_xml/hbp_nrp_music_xml/schema/music.xsd', 72, 2) - _ElementMap = {} - _AttributeMap = {} - # Base type is pyxb.binding.datatypes.anyType - _ElementMap.update({ - - }) - _AttributeMap.update({ - - }) -Namespace.addCategoryObject('typeBinding', 'SynapticSelector', SynapticSelector) - - -# Complex type ParameterList with content type ELEMENT_ONLY -class ParameterList (pyxb.binding.basis.complexTypeDefinition): - """Complex type ParameterList with content type ELEMENT_ONLY""" - _TypeDefinition = None - _ContentTypeTag = pyxb.binding.basis.complexTypeDefinition._CT_ELEMENT_ONLY - _Abstract = False - _ExpandedName = pyxb.namespace.ExpandedName(Namespace, 'ParameterList') - _XSDLocation = pyxb.utils.utility.Location('/home/kenny/Desktop/HBP/BrainSimulation/hbp_nrp_music_xml/hbp_nrp_music_xml/schema/music.xsd', 95, 2) - _ElementMap = {} - _AttributeMap = {} - # Base type is pyxb.binding.datatypes.anyType - - # Element parameter uses Python identifier parameter - __parameter = pyxb.binding.content.ElementDeclaration(pyxb.namespace.ExpandedName(None, 'parameter'), 'parameter', '__AbsentNamespace0_ParameterList_parameter', True, pyxb.utils.utility.Location('/home/kenny/Desktop/HBP/BrainSimulation/hbp_nrp_music_xml/hbp_nrp_music_xml/schema/music.xsd', 97, 6), ) - - - parameter = property(__parameter.value, __parameter.set, None, None) - - _ElementMap.update({ - __parameter.name() : __parameter - }) - _AttributeMap.update({ - - }) -Namespace.addCategoryObject('typeBinding', 'ParameterList', ParameterList) - - -# Complex type Parameter with content type SIMPLE -class Parameter (pyxb.binding.basis.complexTypeDefinition): - """Complex type Parameter with content type SIMPLE""" - _TypeDefinition = pyxb.binding.datatypes.string - _ContentTypeTag = pyxb.binding.basis.complexTypeDefinition._CT_SIMPLE - _Abstract = False - _ExpandedName = pyxb.namespace.ExpandedName(Namespace, 'Parameter') - _XSDLocation = pyxb.utils.utility.Location('/home/kenny/Desktop/HBP/BrainSimulation/hbp_nrp_music_xml/hbp_nrp_music_xml/schema/music.xsd', 100, 2) - _ElementMap = {} - _AttributeMap = {} - # Base type is pyxb.binding.datatypes.string - - # Attribute name uses Python identifier name - __name = pyxb.binding.content.AttributeUse(pyxb.namespace.ExpandedName(None, 'name'), 'name', '__AbsentNamespace0_Parameter_name', pyxb.binding.datatypes.string, required=True) - __name._DeclarationLocation = pyxb.utils.utility.Location('/home/kenny/Desktop/HBP/BrainSimulation/hbp_nrp_music_xml/hbp_nrp_music_xml/schema/music.xsd', 103, 8) - __name._UseLocation = pyxb.utils.utility.Location('/home/kenny/Desktop/HBP/BrainSimulation/hbp_nrp_music_xml/hbp_nrp_music_xml/schema/music.xsd', 103, 8) - - name = property(__name.value, __name.set, None, None) - - _ElementMap.update({ - - }) - _AttributeMap.update({ - __name.name() : __name - }) -Namespace.addCategoryObject('typeBinding', 'Parameter', Parameter) - - -# Complex type SliceSelector with content type ELEMENT_ONLY -class SliceSelector (SynapticSelector): - """Complex type SliceSelector with content type ELEMENT_ONLY""" - _TypeDefinition = None - _ContentTypeTag = pyxb.binding.basis.complexTypeDefinition._CT_ELEMENT_ONLY - _Abstract = False - _ExpandedName = pyxb.namespace.ExpandedName(Namespace, 'SliceSelector') - _XSDLocation = pyxb.utils.utility.Location('/home/kenny/Desktop/HBP/BrainSimulation/hbp_nrp_music_xml/hbp_nrp_music_xml/schema/music.xsd', 73, 2) - _ElementMap = SynapticSelector._ElementMap.copy() - _AttributeMap = SynapticSelector._AttributeMap.copy() - # Base type is SynapticSelector - - # Element start uses Python identifier start - __start = pyxb.binding.content.ElementDeclaration(pyxb.namespace.ExpandedName(None, 'start'), 'start', '__AbsentNamespace0_SliceSelector_start', False, pyxb.utils.utility.Location('/home/kenny/Desktop/HBP/BrainSimulation/hbp_nrp_music_xml/hbp_nrp_music_xml/schema/music.xsd', 77, 10), ) - - - start = property(__start.value, __start.set, None, None) - - - # Element stop uses Python identifier stop - __stop = pyxb.binding.content.ElementDeclaration(pyxb.namespace.ExpandedName(None, 'stop'), 'stop', '__AbsentNamespace0_SliceSelector_stop', False, pyxb.utils.utility.Location('/home/kenny/Desktop/HBP/BrainSimulation/hbp_nrp_music_xml/hbp_nrp_music_xml/schema/music.xsd', 78, 10), ) - - - stop = property(__stop.value, __stop.set, None, None) - - - # Element step uses Python identifier step - __step = pyxb.binding.content.ElementDeclaration(pyxb.namespace.ExpandedName(None, 'step'), 'step', '__AbsentNamespace0_SliceSelector_step', False, pyxb.utils.utility.Location('/home/kenny/Desktop/HBP/BrainSimulation/hbp_nrp_music_xml/hbp_nrp_music_xml/schema/music.xsd', 79, 10), ) - - - step = property(__step.value, __step.set, None, None) - - _ElementMap.update({ - __start.name() : __start, - __stop.name() : __stop, - __step.name() : __step - }) - _AttributeMap.update({ - - }) -Namespace.addCategoryObject('typeBinding', 'SliceSelector', SliceSelector) - - -# Complex type ListSelector with content type ELEMENT_ONLY -class ListSelector (SynapticSelector): - """Complex type ListSelector with content type ELEMENT_ONLY""" - _TypeDefinition = None - _ContentTypeTag = pyxb.binding.basis.complexTypeDefinition._CT_ELEMENT_ONLY - _Abstract = False - _ExpandedName = pyxb.namespace.ExpandedName(Namespace, 'ListSelector') - _XSDLocation = pyxb.utils.utility.Location('/home/kenny/Desktop/HBP/BrainSimulation/hbp_nrp_music_xml/hbp_nrp_music_xml/schema/music.xsd', 84, 2) - _ElementMap = SynapticSelector._ElementMap.copy() - _AttributeMap = SynapticSelector._AttributeMap.copy() - # Base type is SynapticSelector - - # Element element uses Python identifier element - __element = pyxb.binding.content.ElementDeclaration(pyxb.namespace.ExpandedName(None, 'element'), 'element', '__AbsentNamespace0_ListSelector_element', True, pyxb.utils.utility.Location('/home/kenny/Desktop/HBP/BrainSimulation/hbp_nrp_music_xml/hbp_nrp_music_xml/schema/music.xsd', 88, 10), ) - - - element = property(__element.value, __element.set, None, None) - - _ElementMap.update({ - __element.name() : __element - }) - _AttributeMap.update({ - - }) -Namespace.addCategoryObject('typeBinding', 'ListSelector', ListSelector) - - -root = pyxb.binding.basis.element(pyxb.namespace.ExpandedName(Namespace, 'root'), CTD_ANON, location=pyxb.utils.utility.Location('/home/kenny/Desktop/HBP/BrainSimulation/hbp_nrp_music_xml/hbp_nrp_music_xml/schema/music.xsd', 9, 2)) -Namespace.addCategoryObject('elementBinding', root.name().localName(), root) - - - -CTD_ANON._AddElement(pyxb.binding.basis.element(pyxb.namespace.ExpandedName(None, 'port'), Port, scope=CTD_ANON, location=pyxb.utils.utility.Location('/home/kenny/Desktop/HBP/BrainSimulation/hbp_nrp_music_xml/hbp_nrp_music_xml/schema/music.xsd', 12, 8))) - -def _BuildAutomaton (): - # Remove this helper function from the namespace after it is invoked - global _BuildAutomaton - del _BuildAutomaton - import pyxb.utils.fac as fac - - counters = set() - states = [] - final_update = set() - symbol = pyxb.binding.content.ElementUse(CTD_ANON._UseForTag(pyxb.namespace.ExpandedName(None, 'port')), pyxb.utils.utility.Location('/home/kenny/Desktop/HBP/BrainSimulation/hbp_nrp_music_xml/hbp_nrp_music_xml/schema/music.xsd', 12, 8)) - st_0 = fac.State(symbol, is_initial=True, final_update=final_update, is_unordered_catenation=False) - states.append(st_0) - transitions = [] - transitions.append(fac.Transition(st_0, [ - ])) - st_0._set_transitionSet(transitions) - return fac.Automaton(states, counters, False, containing_state=None) -CTD_ANON._Automaton = _BuildAutomaton() - - - - -Port._AddElement(pyxb.binding.basis.element(pyxb.namespace.ExpandedName(None, 'type'), PortType, scope=Port, location=pyxb.utils.utility.Location('/home/kenny/Desktop/HBP/BrainSimulation/hbp_nrp_music_xml/hbp_nrp_music_xml/schema/music.xsd', 27, 6))) - -Port._AddElement(pyxb.binding.basis.element(pyxb.namespace.ExpandedName(None, 'name'), pyxb.binding.datatypes.string, scope=Port, location=pyxb.utils.utility.Location('/home/kenny/Desktop/HBP/BrainSimulation/hbp_nrp_music_xml/hbp_nrp_music_xml/schema/music.xsd', 28, 6))) - -Port._AddElement(pyxb.binding.basis.element(pyxb.namespace.ExpandedName(None, 'width'), pyxb.binding.datatypes.positiveInteger, scope=Port, location=pyxb.utils.utility.Location('/home/kenny/Desktop/HBP/BrainSimulation/hbp_nrp_music_xml/hbp_nrp_music_xml/schema/music.xsd', 29, 6))) - -Port._AddElement(pyxb.binding.basis.element(pyxb.namespace.ExpandedName(None, 'sender'), Peer, scope=Port, location=pyxb.utils.utility.Location('/home/kenny/Desktop/HBP/BrainSimulation/hbp_nrp_music_xml/hbp_nrp_music_xml/schema/music.xsd', 30, 6))) - -Port._AddElement(pyxb.binding.basis.element(pyxb.namespace.ExpandedName(None, 'receiver'), Peer, scope=Port, location=pyxb.utils.utility.Location('/home/kenny/Desktop/HBP/BrainSimulation/hbp_nrp_music_xml/hbp_nrp_music_xml/schema/music.xsd', 31, 6))) - -def _BuildAutomaton_ (): - # Remove this helper function from the namespace after it is invoked - global _BuildAutomaton_ - del _BuildAutomaton_ - import pyxb.utils.fac as fac - - counters = set() - states = [] - final_update = None - symbol = pyxb.binding.content.ElementUse(Port._UseForTag(pyxb.namespace.ExpandedName(None, 'type')), pyxb.utils.utility.Location('/home/kenny/Desktop/HBP/BrainSimulation/hbp_nrp_music_xml/hbp_nrp_music_xml/schema/music.xsd', 27, 6)) - st_0 = fac.State(symbol, is_initial=True, final_update=final_update, is_unordered_catenation=False) - states.append(st_0) - final_update = None - symbol = pyxb.binding.content.ElementUse(Port._UseForTag(pyxb.namespace.ExpandedName(None, 'name')), pyxb.utils.utility.Location('/home/kenny/Desktop/HBP/BrainSimulation/hbp_nrp_music_xml/hbp_nrp_music_xml/schema/music.xsd', 28, 6)) - st_1 = fac.State(symbol, is_initial=False, final_update=final_update, is_unordered_catenation=False) - states.append(st_1) - final_update = None - symbol = pyxb.binding.content.ElementUse(Port._UseForTag(pyxb.namespace.ExpandedName(None, 'width')), pyxb.utils.utility.Location('/home/kenny/Desktop/HBP/BrainSimulation/hbp_nrp_music_xml/hbp_nrp_music_xml/schema/music.xsd', 29, 6)) - st_2 = fac.State(symbol, is_initial=False, final_update=final_update, is_unordered_catenation=False) - states.append(st_2) - final_update = None - symbol = pyxb.binding.content.ElementUse(Port._UseForTag(pyxb.namespace.ExpandedName(None, 'sender')), pyxb.utils.utility.Location('/home/kenny/Desktop/HBP/BrainSimulation/hbp_nrp_music_xml/hbp_nrp_music_xml/schema/music.xsd', 30, 6)) - st_3 = fac.State(symbol, is_initial=False, final_update=final_update, is_unordered_catenation=False) - states.append(st_3) - final_update = set() - symbol = pyxb.binding.content.ElementUse(Port._UseForTag(pyxb.namespace.ExpandedName(None, 'receiver')), pyxb.utils.utility.Location('/home/kenny/Desktop/HBP/BrainSimulation/hbp_nrp_music_xml/hbp_nrp_music_xml/schema/music.xsd', 31, 6)) - st_4 = fac.State(symbol, is_initial=False, final_update=final_update, is_unordered_catenation=False) - states.append(st_4) - transitions = [] - transitions.append(fac.Transition(st_1, [ - ])) - st_0._set_transitionSet(transitions) - transitions = [] - transitions.append(fac.Transition(st_2, [ - ])) - st_1._set_transitionSet(transitions) - transitions = [] - transitions.append(fac.Transition(st_3, [ - ])) - st_2._set_transitionSet(transitions) - transitions = [] - transitions.append(fac.Transition(st_4, [ - ])) - st_3._set_transitionSet(transitions) - transitions = [] - transitions.append(fac.Transition(st_4, [ - ])) - st_4._set_transitionSet(transitions) - return fac.Automaton(states, counters, False, containing_state=None) -Port._Automaton = _BuildAutomaton_() - - - - -Peer._AddElement(pyxb.binding.basis.element(pyxb.namespace.ExpandedName(None, 'name'), pyxb.binding.datatypes.string, scope=Peer, location=pyxb.utils.utility.Location('/home/kenny/Desktop/HBP/BrainSimulation/hbp_nrp_music_xml/hbp_nrp_music_xml/schema/music.xsd', 51, 6))) - -Peer._AddElement(pyxb.binding.basis.element(pyxb.namespace.ExpandedName(None, 'synapse'), SynapticConnection, scope=Peer, location=pyxb.utils.utility.Location('/home/kenny/Desktop/HBP/BrainSimulation/hbp_nrp_music_xml/hbp_nrp_music_xml/schema/music.xsd', 52, 6))) - -Peer._AddElement(pyxb.binding.basis.element(pyxb.namespace.ExpandedName(None, 'parameters'), ParameterList, scope=Peer, location=pyxb.utils.utility.Location('/home/kenny/Desktop/HBP/BrainSimulation/hbp_nrp_music_xml/hbp_nrp_music_xml/schema/music.xsd', 53, 6))) - -def _BuildAutomaton_2 (): - # Remove this helper function from the namespace after it is invoked - global _BuildAutomaton_2 - del _BuildAutomaton_2 - import pyxb.utils.fac as fac - - counters = set() - cc_0 = fac.CounterCondition(min=0, max=None, metadata=pyxb.utils.utility.Location('/home/kenny/Desktop/HBP/BrainSimulation/hbp_nrp_music_xml/hbp_nrp_music_xml/schema/music.xsd', 52, 6)) - counters.add(cc_0) - cc_1 = fac.CounterCondition(min=0, max=1, metadata=pyxb.utils.utility.Location('/home/kenny/Desktop/HBP/BrainSimulation/hbp_nrp_music_xml/hbp_nrp_music_xml/schema/music.xsd', 53, 6)) - counters.add(cc_1) - states = [] - final_update = set() - symbol = pyxb.binding.content.ElementUse(Peer._UseForTag(pyxb.namespace.ExpandedName(None, 'name')), pyxb.utils.utility.Location('/home/kenny/Desktop/HBP/BrainSimulation/hbp_nrp_music_xml/hbp_nrp_music_xml/schema/music.xsd', 51, 6)) - st_0 = fac.State(symbol, is_initial=True, final_update=final_update, is_unordered_catenation=False) - states.append(st_0) - final_update = set() - final_update.add(fac.UpdateInstruction(cc_0, False)) - symbol = pyxb.binding.content.ElementUse(Peer._UseForTag(pyxb.namespace.ExpandedName(None, 'synapse')), pyxb.utils.utility.Location('/home/kenny/Desktop/HBP/BrainSimulation/hbp_nrp_music_xml/hbp_nrp_music_xml/schema/music.xsd', 52, 6)) - st_1 = fac.State(symbol, is_initial=False, final_update=final_update, is_unordered_catenation=False) - states.append(st_1) - final_update = set() - final_update.add(fac.UpdateInstruction(cc_1, False)) - symbol = pyxb.binding.content.ElementUse(Peer._UseForTag(pyxb.namespace.ExpandedName(None, 'parameters')), pyxb.utils.utility.Location('/home/kenny/Desktop/HBP/BrainSimulation/hbp_nrp_music_xml/hbp_nrp_music_xml/schema/music.xsd', 53, 6)) - st_2 = fac.State(symbol, is_initial=False, final_update=final_update, is_unordered_catenation=False) - states.append(st_2) - transitions = [] - transitions.append(fac.Transition(st_1, [ - ])) - transitions.append(fac.Transition(st_2, [ - ])) - st_0._set_transitionSet(transitions) - transitions = [] - transitions.append(fac.Transition(st_1, [ - fac.UpdateInstruction(cc_0, True) ])) - transitions.append(fac.Transition(st_2, [ - fac.UpdateInstruction(cc_0, False) ])) - st_1._set_transitionSet(transitions) - transitions = [] - transitions.append(fac.Transition(st_2, [ - fac.UpdateInstruction(cc_1, True) ])) - st_2._set_transitionSet(transitions) - return fac.Automaton(states, counters, False, containing_state=None) -Peer._Automaton = _BuildAutomaton_2() - - - - -SynapticConnection._AddElement(pyxb.binding.basis.element(pyxb.namespace.ExpandedName(None, 'target'), pyxb.binding.datatypes.string, scope=SynapticConnection, location=pyxb.utils.utility.Location('/home/kenny/Desktop/HBP/BrainSimulation/hbp_nrp_music_xml/hbp_nrp_music_xml/schema/music.xsd', 58, 6))) - -SynapticConnection._AddElement(pyxb.binding.basis.element(pyxb.namespace.ExpandedName(None, 'type'), ConnectionType, scope=SynapticConnection, location=pyxb.utils.utility.Location('/home/kenny/Desktop/HBP/BrainSimulation/hbp_nrp_music_xml/hbp_nrp_music_xml/schema/music.xsd', 59, 6))) - -SynapticConnection._AddElement(pyxb.binding.basis.element(pyxb.namespace.ExpandedName(None, 'selector'), SynapticSelector, scope=SynapticConnection, location=pyxb.utils.utility.Location('/home/kenny/Desktop/HBP/BrainSimulation/hbp_nrp_music_xml/hbp_nrp_music_xml/schema/music.xsd', 60, 6))) - -SynapticConnection._AddElement(pyxb.binding.basis.element(pyxb.namespace.ExpandedName(None, 'parameters'), ParameterList, scope=SynapticConnection, location=pyxb.utils.utility.Location('/home/kenny/Desktop/HBP/BrainSimulation/hbp_nrp_music_xml/hbp_nrp_music_xml/schema/music.xsd', 61, 6))) - -def _BuildAutomaton_3 (): - # Remove this helper function from the namespace after it is invoked - global _BuildAutomaton_3 - del _BuildAutomaton_3 - import pyxb.utils.fac as fac - - counters = set() - cc_0 = fac.CounterCondition(min=0, max=1, metadata=pyxb.utils.utility.Location('/home/kenny/Desktop/HBP/BrainSimulation/hbp_nrp_music_xml/hbp_nrp_music_xml/schema/music.xsd', 61, 6)) - counters.add(cc_0) - states = [] - final_update = None - symbol = pyxb.binding.content.ElementUse(SynapticConnection._UseForTag(pyxb.namespace.ExpandedName(None, 'target')), pyxb.utils.utility.Location('/home/kenny/Desktop/HBP/BrainSimulation/hbp_nrp_music_xml/hbp_nrp_music_xml/schema/music.xsd', 58, 6)) - st_0 = fac.State(symbol, is_initial=True, final_update=final_update, is_unordered_catenation=False) - states.append(st_0) - final_update = None - symbol = pyxb.binding.content.ElementUse(SynapticConnection._UseForTag(pyxb.namespace.ExpandedName(None, 'type')), pyxb.utils.utility.Location('/home/kenny/Desktop/HBP/BrainSimulation/hbp_nrp_music_xml/hbp_nrp_music_xml/schema/music.xsd', 59, 6)) - st_1 = fac.State(symbol, is_initial=False, final_update=final_update, is_unordered_catenation=False) - states.append(st_1) - final_update = set() - symbol = pyxb.binding.content.ElementUse(SynapticConnection._UseForTag(pyxb.namespace.ExpandedName(None, 'selector')), pyxb.utils.utility.Location('/home/kenny/Desktop/HBP/BrainSimulation/hbp_nrp_music_xml/hbp_nrp_music_xml/schema/music.xsd', 60, 6)) - st_2 = fac.State(symbol, is_initial=False, final_update=final_update, is_unordered_catenation=False) - states.append(st_2) - final_update = set() - final_update.add(fac.UpdateInstruction(cc_0, False)) - symbol = pyxb.binding.content.ElementUse(SynapticConnection._UseForTag(pyxb.namespace.ExpandedName(None, 'parameters')), pyxb.utils.utility.Location('/home/kenny/Desktop/HBP/BrainSimulation/hbp_nrp_music_xml/hbp_nrp_music_xml/schema/music.xsd', 61, 6)) - st_3 = fac.State(symbol, is_initial=False, final_update=final_update, is_unordered_catenation=False) - states.append(st_3) - transitions = [] - transitions.append(fac.Transition(st_1, [ - ])) - st_0._set_transitionSet(transitions) - transitions = [] - transitions.append(fac.Transition(st_2, [ - ])) - st_1._set_transitionSet(transitions) - transitions = [] - transitions.append(fac.Transition(st_3, [ - ])) - st_2._set_transitionSet(transitions) - transitions = [] - transitions.append(fac.Transition(st_3, [ - fac.UpdateInstruction(cc_0, True) ])) - st_3._set_transitionSet(transitions) - return fac.Automaton(states, counters, False, containing_state=None) -SynapticConnection._Automaton = _BuildAutomaton_3() - - - - -ParameterList._AddElement(pyxb.binding.basis.element(pyxb.namespace.ExpandedName(None, 'parameter'), Parameter, scope=ParameterList, location=pyxb.utils.utility.Location('/home/kenny/Desktop/HBP/BrainSimulation/hbp_nrp_music_xml/hbp_nrp_music_xml/schema/music.xsd', 97, 6))) - -def _BuildAutomaton_4 (): - # Remove this helper function from the namespace after it is invoked - global _BuildAutomaton_4 - del _BuildAutomaton_4 - import pyxb.utils.fac as fac - - counters = set() - states = [] - final_update = set() - symbol = pyxb.binding.content.ElementUse(ParameterList._UseForTag(pyxb.namespace.ExpandedName(None, 'parameter')), pyxb.utils.utility.Location('/home/kenny/Desktop/HBP/BrainSimulation/hbp_nrp_music_xml/hbp_nrp_music_xml/schema/music.xsd', 97, 6)) - st_0 = fac.State(symbol, is_initial=True, final_update=final_update, is_unordered_catenation=False) - states.append(st_0) - transitions = [] - transitions.append(fac.Transition(st_0, [ - ])) - st_0._set_transitionSet(transitions) - return fac.Automaton(states, counters, False, containing_state=None) -ParameterList._Automaton = _BuildAutomaton_4() - - - - -SliceSelector._AddElement(pyxb.binding.basis.element(pyxb.namespace.ExpandedName(None, 'start'), pyxb.binding.datatypes.nonNegativeInteger, scope=SliceSelector, location=pyxb.utils.utility.Location('/home/kenny/Desktop/HBP/BrainSimulation/hbp_nrp_music_xml/hbp_nrp_music_xml/schema/music.xsd', 77, 10))) - -SliceSelector._AddElement(pyxb.binding.basis.element(pyxb.namespace.ExpandedName(None, 'stop'), pyxb.binding.datatypes.positiveInteger, scope=SliceSelector, location=pyxb.utils.utility.Location('/home/kenny/Desktop/HBP/BrainSimulation/hbp_nrp_music_xml/hbp_nrp_music_xml/schema/music.xsd', 78, 10))) - -SliceSelector._AddElement(pyxb.binding.basis.element(pyxb.namespace.ExpandedName(None, 'step'), pyxb.binding.datatypes.positiveInteger, scope=SliceSelector, location=pyxb.utils.utility.Location('/home/kenny/Desktop/HBP/BrainSimulation/hbp_nrp_music_xml/hbp_nrp_music_xml/schema/music.xsd', 79, 10))) - -def _BuildAutomaton_5 (): - # Remove this helper function from the namespace after it is invoked - global _BuildAutomaton_5 - del _BuildAutomaton_5 - import pyxb.utils.fac as fac - - counters = set() - states = [] - final_update = None - symbol = pyxb.binding.content.ElementUse(SliceSelector._UseForTag(pyxb.namespace.ExpandedName(None, 'start')), pyxb.utils.utility.Location('/home/kenny/Desktop/HBP/BrainSimulation/hbp_nrp_music_xml/hbp_nrp_music_xml/schema/music.xsd', 77, 10)) - st_0 = fac.State(symbol, is_initial=True, final_update=final_update, is_unordered_catenation=False) - states.append(st_0) - final_update = None - symbol = pyxb.binding.content.ElementUse(SliceSelector._UseForTag(pyxb.namespace.ExpandedName(None, 'stop')), pyxb.utils.utility.Location('/home/kenny/Desktop/HBP/BrainSimulation/hbp_nrp_music_xml/hbp_nrp_music_xml/schema/music.xsd', 78, 10)) - st_1 = fac.State(symbol, is_initial=False, final_update=final_update, is_unordered_catenation=False) - states.append(st_1) - final_update = set() - symbol = pyxb.binding.content.ElementUse(SliceSelector._UseForTag(pyxb.namespace.ExpandedName(None, 'step')), pyxb.utils.utility.Location('/home/kenny/Desktop/HBP/BrainSimulation/hbp_nrp_music_xml/hbp_nrp_music_xml/schema/music.xsd', 79, 10)) - st_2 = fac.State(symbol, is_initial=False, final_update=final_update, is_unordered_catenation=False) - states.append(st_2) - transitions = [] - transitions.append(fac.Transition(st_1, [ - ])) - st_0._set_transitionSet(transitions) - transitions = [] - transitions.append(fac.Transition(st_2, [ - ])) - st_1._set_transitionSet(transitions) - transitions = [] - st_2._set_transitionSet(transitions) - return fac.Automaton(states, counters, False, containing_state=None) -SliceSelector._Automaton = _BuildAutomaton_5() - - - - -ListSelector._AddElement(pyxb.binding.basis.element(pyxb.namespace.ExpandedName(None, 'element'), pyxb.binding.datatypes.nonNegativeInteger, scope=ListSelector, location=pyxb.utils.utility.Location('/home/kenny/Desktop/HBP/BrainSimulation/hbp_nrp_music_xml/hbp_nrp_music_xml/schema/music.xsd', 88, 10))) - -def _BuildAutomaton_6 (): - # Remove this helper function from the namespace after it is invoked - global _BuildAutomaton_6 - del _BuildAutomaton_6 - import pyxb.utils.fac as fac - - counters = set() - states = [] - final_update = set() - symbol = pyxb.binding.content.ElementUse(ListSelector._UseForTag(pyxb.namespace.ExpandedName(None, 'element')), pyxb.utils.utility.Location('/home/kenny/Desktop/HBP/BrainSimulation/hbp_nrp_music_xml/hbp_nrp_music_xml/schema/music.xsd', 88, 10)) - st_0 = fac.State(symbol, is_initial=True, final_update=final_update, is_unordered_catenation=False) - states.append(st_0) - transitions = [] - transitions.append(fac.Transition(st_0, [ - ])) - st_0._set_transitionSet(transitions) - return fac.Automaton(states, counters, False, containing_state=None) -ListSelector._Automaton = _BuildAutomaton_6() - diff --git a/hbp_nrp_music_xml/hbp_nrp_music_xml/schema/music.xsd b/hbp_nrp_music_xml/hbp_nrp_music_xml/schema/music.xsd deleted file mode 100644 index 5e28f13..0000000 --- a/hbp_nrp_music_xml/hbp_nrp_music_xml/schema/music.xsd +++ /dev/null @@ -1,108 +0,0 @@ -<?xml version="1.0"?> -<!-- MUSIC Documentation is available at: http://http://software.incf.org/software/music/home - - Descriptions provided inline below are from the reference documentation. ---> -<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> - - <!-- root element is a list of port connections between music applications --> - <xs:element name="root"> - <xs:complexType> - <xs:sequence> - <xs:element name="port" type="Port" minOccurs="1" maxOccurs="unbounded"/> - </xs:sequence> - </xs:complexType> - </xs:element> - - <!-- Communication between applications is handled by ports. Ports are named - sources (output ports) or sinks (input ports) of data flows. The data to be - communicated may be differently organized in process memory on the receiver - side compared to the sender side. - - The width of a port, that is the number of data elements transferred in parallel - from a cont port or the largest possible id of an event port + 1. - --> - <xs:complexType name="Port"> - <xs:sequence> - <xs:element name="type" type="PortType"/> - <xs:element name="name" type="xs:string"/> - <xs:element name="width" type="xs:positiveInteger"/> - <xs:element name="sender" type="Peer" minOccurs="1" maxOccurs="1"/> - <xs:element name="receiver" type="Peer" minOccurs="1" maxOccurs="unbounded"/> - </xs:sequence> - </xs:complexType> - - <!-- Each application declares its ability to produce and consume data by - publishing ports. Ports are named by the application and provided with - information about the datatype (continuous data, spike events, messages) - and mapping onto different processors. - --> - <xs:simpleType name="PortType"> - <xs:restriction base="xs:string"> - <xs:enumeration value="Event"/> - <xs:enumeration value="Continuous"/> - <xs:enumeration value="Message"/> - </xs:restriction> - </xs:simpleType> - - <!-- A named peer (sender/receiver) for a port, describes the neurons/synapses to connect to/from --> - <xs:complexType name="Peer"> - <xs:sequence> - <xs:element name="name" type="xs:string"/> - <xs:element name="synapse" type="SynapticConnection" minOccurs="0" maxOccurs="unbounded"/> - <xs:element name="parameters" type="ParameterList" minOccurs="0" maxOccurs="1"/> - </xs:sequence> - </xs:complexType> - <xs:complexType name="SynapticConnection"> - <xs:sequence> - <xs:element name="target" type="xs:string"/> - <xs:element name="type" type="ConnectionType"/> - <xs:element name="selector" type="SynapticSelector" minOccurs="1" maxOccurs="1"/> - <xs:element name="parameters" type="ParameterList" minOccurs="0" maxOccurs="1"/> - </xs:sequence> - </xs:complexType> - <xs:simpleType name="ConnectionType"> - <xs:restriction base="xs:string"> - <xs:enumeration value="one_to_one"/> - <xs:enumeration value="all_to_all"/> - </xs:restriction> - </xs:simpleType> - - <!-- neuron/synapse selection, similar to the abstract selectors in the BIBI --> - <xs:complexType name="SynapticSelector" abstract="true"/> - <xs:complexType name="SliceSelector"> - <xs:complexContent> - <xs:extension base="SynapticSelector"> - <xs:sequence> - <xs:element name="start" type="xs:nonNegativeInteger"/> - <xs:element name="stop" type="xs:positiveInteger"/> - <xs:element name="step" type="xs:positiveInteger"/> - </xs:sequence> - </xs:extension> - </xs:complexContent> - </xs:complexType> - <xs:complexType name="ListSelector"> - <xs:complexContent> - <xs:extension base="SynapticSelector"> - <xs:sequence> - <xs:element name="element" type="xs:nonNegativeInteger" maxOccurs="unbounded"/> - </xs:sequence> - </xs:extension> - </xs:complexContent> - </xs:complexType> - - <!-- A list of parameters with attribute name and key value (string) --> - <xs:complexType name="ParameterList"> - <xs:sequence> - <xs:element name="parameter" type="Parameter" minOccurs="1" maxOccurs="unbounded"/> - </xs:sequence> - </xs:complexType> - <xs:complexType name="Parameter"> - <xs:simpleContent> - <xs:extension base="xs:string"> - <xs:attribute name="name" type="xs:string" use="required"/> - </xs:extension> - </xs:simpleContent> - </xs:complexType> - -</xs:schema> diff --git a/hbp_nrp_music_xml/hbp_nrp_music_xml/tests/__init__.py b/hbp_nrp_music_xml/hbp_nrp_music_xml/tests/__init__.py deleted file mode 100644 index fcfe222..0000000 --- a/hbp_nrp_music_xml/hbp_nrp_music_xml/tests/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -""" -This package contains all tests for the MUSIC-XML package -""" - -__author__ = 'Martin Schulze' diff --git a/hbp_nrp_music_xml/hbp_nrp_music_xml/tests/config/__init__.py b/hbp_nrp_music_xml/hbp_nrp_music_xml/tests/config/__init__.py deleted file mode 100644 index 1e62c49..0000000 --- a/hbp_nrp_music_xml/hbp_nrp_music_xml/tests/config/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -""" -This package contains all tests for the MUSIC config package. -""" - -__author__ = 'Martin Schulze' diff --git a/hbp_nrp_music_xml/hbp_nrp_music_xml/tests/config/test_music_config.py b/hbp_nrp_music_xml/hbp_nrp_music_xml/tests/config/test_music_config.py deleted file mode 100644 index 67d9ca1..0000000 --- a/hbp_nrp_music_xml/hbp_nrp_music_xml/tests/config/test_music_config.py +++ /dev/null @@ -1,75 +0,0 @@ -# ---LICENSE-BEGIN - DO NOT CHANGE OR MOVE THIS HEADER -# This file is part of the Neurorobotics Platform software -# Copyright (C) 2014,2015,2016,2017 Human Brain Project -# https://www.humanbrainproject.eu -# -# The Human Brain Project is a European Commission funded project -# in the frame of the Horizon2020 FET Flagship plan. -# http://ec.europa.eu/programmes/horizon2020/en/h2020-section/fet-flagships -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# ---LICENSE-END -import unittest -import io - -from mock import patch -import hbp_nrp_music_xml.config.music_config as config - -__author__ = 'Martin Schulze, Kenny Sharma' - - -class MockedPortReceiver(object): - def __init__(self, name): - self.name = name - - -class MockedXMLPort(object): - sender = MockedPortReceiver('sender') - name = "port" - receiver = [MockedPortReceiver('receiver1')] - width = 42 - - def __str__(self): - return 'port' - - -class TestMusicConfig(unittest.TestCase): - - # application tests - def test_application(self): - ma = config.Application('foo', 'bar', ['arg1', 'arg2'], 3) - self.assertEqual(ma.name, 'foo') - self.assertEqual(ma.binary, 'bar') - self.assertEqual(ma.args, '"arg1 arg2"') - self.assertEqual(ma.num_processes, 3) - - # config writer tests - def test_config_writer(self): - header = {'foo': 'bar'} - apps = [config.Application('foo', 'bar', None, 3)] - projs = [MockedXMLPort()] - mcw = config.MusicConfigWriter(header, apps, projs) - self.assertEqual(str(mcw), - 'foo = "bar"\n\n[foo]\nnp = 3\nbinary = bar\n' +\ - 'args = \n\nport\n') - - # music config port mock test - @patch("hbp_nrp_music_xml.schema.generated.music_xml.Port", new=MockedXMLPort) - def test_config_port(self): - proj = config.MusicConfigPort(MockedXMLPort()) - self.assertEqual(str(proj), 'sender.port -> receiver1.port [42]') - -if __name__ == "__main__": - unittest.main() diff --git a/hbp_nrp_music_xml/hbp_nrp_music_xml/tests/pynn/__init__.py b/hbp_nrp_music_xml/hbp_nrp_music_xml/tests/pynn/__init__.py deleted file mode 100644 index 30b6db4..0000000 --- a/hbp_nrp_music_xml/hbp_nrp_music_xml/tests/pynn/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -""" -Unit tests for the MUSIC XML -> PyNN Proxy factories. -""" - -__author__ = 'Martin Schulze' diff --git a/hbp_nrp_music_xml/hbp_nrp_music_xml/tests/pynn/test_factory.py b/hbp_nrp_music_xml/hbp_nrp_music_xml/tests/pynn/test_factory.py deleted file mode 100644 index 76b3464..0000000 --- a/hbp_nrp_music_xml/hbp_nrp_music_xml/tests/pynn/test_factory.py +++ /dev/null @@ -1,169 +0,0 @@ -# ---LICENSE-BEGIN - DO NOT CHANGE OR MOVE THIS HEADER -# This file is part of the Neurorobotics Platform software -# Copyright (C) 2014,2015,2016,2017 Human Brain Project -# https://www.humanbrainproject.eu -# -# The Human Brain Project is a European Commission funded project -# in the frame of the Horizon2020 FET Flagship plan. -# http://ec.europa.eu/programmes/horizon2020/en/h2020-section/fet-flagships -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# ---LICENSE-END -import unittest -from mock import MagicMock - -from hbp_nrp_music_xml.pynn.factory import PyNNProxyFactory, export_proxy, create_proxy_via_native_cell_types -from hbp_nrp_music_xml.pynn.connector_factory import PyNNConnectorFactory -from hbp_nrp_music_xml.pynn.cell_types import load_cell_types -from hbp_nrp_music_xml.schema.generated import music_xml - -import pyNN.nest as sim -import nest - -__author__ = 'Martin Schulze' - -class TestPyNNConnectorFactory(unittest.TestCase): - - def test_create_and_connect_synapse(self): - nest.ResetKernel() - connector_factory = PyNNConnectorFactory(sim) - - def check_all_to_all(source_ids, target_ids): - for target_id in target_ids: - for source_id in source_ids: - connections = nest.GetConnections(source=[source_id], - target=[target_id]) - self.assertTrue(connections) - self.assertTrue(len(connections) == 1) - - def check_one_to_one(source_ids, target_ids): - for (source_id, target_id) in zip(source_ids, target_ids): - connections = nest.GetConnections(source=[source_id], - target=[target_id]) - self.assertTrue(connections) - self.assertTrue(len(connections) == 1) - - def test_case(connection_rule, population_slice, is_output_proxy): - sim.setup(min_delay=0.1, max_delay=20.0, threads=1) - connection_check_func = {'all_to_all': check_all_to_all, - 'one_to_one': check_one_to_one} - - if is_output_proxy: - proxy = sim.Population(1, sim.IF_cond_alpha()) - else: - proxy = sim.Population(10, sim.IF_cond_alpha()) - - target_population = sim.Population(10, sim.IF_cond_alpha()) - connector_factory.create_and_connect_synapse(proxy, "test_to_cle", - connection_rule, - population_slice, - target_population, - is_output_proxy) - connections = nest.GetConnections() - proxy_ids = map(int, proxy.all_cells) - target_ids = map(int, target_population.all_cells) - if population_slice: - target_ids = target_ids[population_slice] - if is_output_proxy: - connection_check_func[connection_rule](target_ids, proxy_ids) - else: - connection_check_func[connection_rule](proxy_ids, target_ids) - sim.end() - nest.ResetKernel() - - # Output proxy tests - test_case('all_to_all', None, True) - test_case('all_to_all', slice(0, 5, 1), True) - test_case('one_to_one', None, True) - test_case('one_to_one', slice(0, 5, 1), True) - - # Input proxy tests - test_case('all_to_all', None, False) - test_case('all_to_all', slice(0, 5, 1), False) - test_case('one_to_one', None, False) - test_case('one_to_one', slice(0, 5, 1), False) - test_case('all_to_all', slice(0,11,1), False) - - # Invalid test cases - with self.assertRaises(Exception): - test_case('foo', None, False) - -class TestPyNNProxyFactory(unittest.TestCase): - - def setUp(self): - sim.setup(min_delay=0.1, max_delay=20.0, threads=1) - self._music_cell_types = load_cell_types(sim) - self._music_event_in_cell = self._music_cell_types['in']['Event'] - self._music_event_out_cell = self._music_cell_types['out']['Event'] - self._parrot_cell = self._music_cell_types['parrot'] - - def tearDown(self): - sim.end() - nest.ResetKernel() - - def test_create_proxy_via_native_cell_types(self): - - def setup_nest_mocks(): - # nest.SetStatus = MagicMock(return_value=False) - nest.SetAcceptableLatency = MagicMock(return_value=False) - - def check_nest_calls(proxy, port_name, acc_latency, is_output_proxy): - proxy_ids = map(int, proxy.all_cells) - self.assertEqual(nest.GetStatus(proxy_ids, 'port_name'), (port_name,)*len(proxy_ids)) - if not is_output_proxy: - for i, proxy_id in enumerate(proxy_ids): - self.assertEqual(nest.GetStatus([proxy_id], 'music_channel')[0], i) - - set_acc_latency_call_args = nest.SetAcceptableLatency.call_args_list - self.assertEqual(set_acc_latency_call_args[0][0][:], (port_name, acc_latency)) - - # Input proxy test - setup_nest_mocks() - proxy = create_proxy_via_native_cell_types(sim, 2, self._music_event_in_cell, "test", "Event", 10.0, False) - check_nest_calls(proxy, "test", 10.0, False) - - setup_nest_mocks() - proxy = create_proxy_via_native_cell_types(sim, 2, self._music_event_in_cell, "test", "Event", None, False) - check_nest_calls(proxy, "test", sim.get_min_delay(), False) - - # Output proxy test - proxy = create_proxy_via_native_cell_types(sim, 2, self._music_event_out_cell, "test", "Event", 10.0, True) - check_nest_calls(proxy, "test", 10.0, True) - - def test_export_proxy(self): - presyn_pop = sim.Population(2, sim.IF_cond_alpha()) - proxy = create_proxy_via_native_cell_types(sim, 1, self._music_event_out_cell, "test", "Event", None, True) - proxy_ids = map(int, proxy.all_cells) - export_proxy(sim, presyn_pop, proxy, 3.0) - connections = nest.GetConnections(target=proxy_ids) - delays = nest.GetStatus(connections, 'delay') - music_channels = nest.GetStatus(connections, 'receptor') - self.assertTrue(delays, (3.0,)*len(proxy_ids)) - self.assertTrue(music_channels, range(len(proxy_ids))) - - def test_create_proxy(self): - proxy_factory = PyNNProxyFactory(sim) - proxy_parrots = proxy_factory.create_proxy("test", "Event", 2, True) - self.assertEqual(proxy_parrots.celltype.nest_name['on_grid'], 'parrot_neuron') - self.assertEqual(proxy_parrots.size, 2) - - -if __name__ == "__main__": - unittest.main() - - - - - diff --git a/hbp_nrp_music_xml/hbp_nrp_music_xml/tests/pynn/test_xml_factory.py b/hbp_nrp_music_xml/hbp_nrp_music_xml/tests/pynn/test_xml_factory.py deleted file mode 100644 index 8c70740..0000000 --- a/hbp_nrp_music_xml/hbp_nrp_music_xml/tests/pynn/test_xml_factory.py +++ /dev/null @@ -1,178 +0,0 @@ -# ---LICENSE-BEGIN - DO NOT CHANGE OR MOVE THIS HEADER -# This file is part of the Neurorobotics Platform software -# Copyright (C) 2014,2015,2016,2017 Human Brain Project -# https://www.humanbrainproject.eu -# -# The Human Brain Project is a European Commission funded project -# in the frame of the Horizon2020 FET Flagship plan. -# http://ec.europa.eu/programmes/horizon2020/en/h2020-section/fet-flagships -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# ---LICENSE-END -import unittest -from mock import Mock, patch, MagicMock - -from hbp_nrp_music_xml.pynn.xml_factory import XmlFactory, create_selector, create_pop_slice -from hbp_nrp_music_xml.pynn.xml_factory import extract_kwargs, is_application_receiver, build_port_overview_table -from hbp_nrp_music_xml.schema.generated import music_xml - -import pyNN.nest as sim -import nest - -__author__ = 'Martin Schulze' - - -class TestPyNNXMLFactory(unittest.TestCase): - - def setUp(self): - self._population_dict = {'Pop1': sim.Population(10, sim.IF_cond_alpha()), - 'Pop2': sim.Population(5, sim.IF_cond_alpha())} - self._connector_factory = None - def create_proxy(port_name, proxy_type, port_width, - is_output_proxy, synaptic_delay=None): - if is_output_proxy: - return sim.Population(1, sim.IF_cond_alpha()) - else: - return sim.Population(10, sim.IF_cond_alpha()) - - self._proxy_factory = MagicMock() - self._proxy_factory.create_proxy = MagicMock(side_effect=create_proxy) - self._xml_factory = XmlFactory("TestApp", self._connector_factory, - self._proxy_factory, self._population_dict) - - def test_create_pop_slice(self): - slice_selector = music_xml.SliceSelector() - slice_selector.start = 0 - slice_selector.stop = 9 - slice_selector.step = 2 - population_slice = create_pop_slice(slice_selector) - self.assertEqual(slice(0, 9, 2), population_slice) - - list_selector = music_xml.ListSelector() - list_selector.element = [0,2,4,6] - population_slice_list = create_pop_slice(list_selector) - self.assertEqual([0,2,4,6], population_slice_list) - with self.assertRaises(Exception): - create_pop_slice(None) - - def test_create_selector(self): - xml_synapse = music_xml.SynapticConnection() - selector = create_selector(xml_synapse) - self.assertIsNone(selector) - - xml_synapse.selector = music_xml.SliceSelector() - xml_synapse.selector.start = 0 - xml_synapse.selector.stop = 9 - xml_synapse.selector.step = 1 - selector = create_selector(xml_synapse) - self.assertEqual(selector, slice(0, 9, 1)) - - def test_get_synaptic_target_population(self): - xml_synapse = music_xml.SynapticConnection() - with self.assertRaises(Exception): - self._xml_factory._get_synaptic_target_population(xml_synapse) - - with self.assertRaises(Exception): - xml_synapse.target = 'Pop3' - self._xml_factory._get_synaptic_target_population(xml_synapse) - - xml_synapse.target = 'Pop1' - self.assertEqual(self._xml_factory. - _get_synaptic_target_population(xml_synapse), - self._population_dict['Pop1']) - - def test_create_and_connect_proxy(self): - - def check_create_proxy_args(xml_projection, is_output_proxy): - call_args_list = self._proxy_factory.create_proxy.call_args_list - self.assertEqual(call_args_list[0][0][0], xml_projection.name) - self.assertEqual(call_args_list[0][0][1], xml_projection.type) - self.assertEqual(call_args_list[0][0][2], xml_projection.width) - self.assertEqual(call_args_list[0][0][3], True) - - xml_projection = music_xml.Port() - xml_projection.type = "Event" - xml_projection.name = "Port1" - xml_projection.connector = "Static" - xml_projection.width = 10 - xml_peer = music_xml.Peer() - xml_peer.synapse = None - xml_peer.name = "App1" - - # Without synapses - with self.assertRaises(Exception): - proxy = self._xml_factory._create_and_connect_proxy(xml_projection, - xml_peer, True) - - with self.assertRaises(Exception): - proxy = self._xml_factory._create_and_connect_proxy(xml_projection, - xml_peer, False) - - # With synapses - xml_synapse = music_xml.SynapticConnection() - xml_synapse.target = "Pop1" - xml_synapse.type = "one_to_one" - xml_projection.synapse = xml_synapse - xml_peer.synapse = 'foo' - self.assertRaises(Exception, self._xml_factory._create_and_connect_proxy, - xml_projection, xml_peer, True) - - def test_assemble_proxy(self): - xml_projection = music_xml.Port() - xml_sender = music_xml.Peer() - xml_sender.name = "Sender1" - xml_receiver = music_xml.Peer() - xml_receiver.name = "Receiver1" - xml_projection.sender = xml_sender - xml_projection.receiver = [xml_receiver, xml_sender] - xml_factory_sender = XmlFactory("Sender1", - self._connector_factory, - self._proxy_factory, - self._population_dict) - xml_factory_receiver = XmlFactory("Receiver1", - self._connector_factory, - self._proxy_factory, - self._population_dict) - xml_factory_sender._create_and_connect_proxy = MagicMock() - xml_factory_receiver._create_and_connect_proxy = MagicMock() - - self._xml_factory._assemble_proxy(xml_projection) - - def test_extract_kwargs(self): - self.assertEquals(extract_kwargs(None), {}) - - def test_application_receiver(self): - xml_port = music_xml.Port() - xml_port.receiver = [] - self.assertEqual(is_application_receiver('foo', xml_port), False) - - xml_receiver = music_xml.Peer() - xml_receiver.name = 'foo' - xml_port.receiver = [xml_receiver] - self.assertEqual(is_application_receiver('foo', xml_port), True) - - def test_build_port_overview_table(self): - xml_port = music_xml.Port(name='bar.port', type='Event', width=42, sender=music_xml.Peer(name='foo')) - self.assertEqual(str(build_port_overview_table([xml_port], 'foo')), - '+----------+-------+-------+-----------+\n' + - '| Portname | Type | Width | Direction |\n' + - '+----------+-------+-------+-----------+\n' + - '| bar.port | Event | 42 | Outgoing |\n' + - '+----------+-------+-------+-----------+') - -if __name__ == "__main__": - unittest.main() - - diff --git a/hbp_nrp_music_xml/hbp_nrp_music_xml/version.py b/hbp_nrp_music_xml/hbp_nrp_music_xml/version.py deleted file mode 100644 index cff3ffe..0000000 --- a/hbp_nrp_music_xml/hbp_nrp_music_xml/version.py +++ /dev/null @@ -1,2 +0,0 @@ -'''version string - generated by setVersion.sh''' -VERSION = '2.3.0' diff --git a/hbp_nrp_music_xml/requirements.txt b/hbp_nrp_music_xml/requirements.txt deleted file mode 100644 index bb56fe3..0000000 --- a/hbp_nrp_music_xml/requirements.txt +++ /dev/null @@ -1,4 +0,0 @@ -# third party requirements -configparser==3.5.0 -prettytable==0.7.2 -pyxb==1.2.4 diff --git a/hbp_nrp_music_xml/requirements_extension_tests.txt b/hbp_nrp_music_xml/requirements_extension_tests.txt deleted file mode 100644 index 031ce95..0000000 --- a/hbp_nrp_music_xml/requirements_extension_tests.txt +++ /dev/null @@ -1,3 +0,0 @@ -#the following is required for the unit testing -mock==1.0.1 -testfixtures==3.0.2 diff --git a/hbp_nrp_music_xml/setup.py b/hbp_nrp_music_xml/setup.py deleted file mode 100644 index ba19ca8..0000000 --- a/hbp_nrp_music_xml/setup.py +++ /dev/null @@ -1,71 +0,0 @@ -'''setup.py''' - -from setuptools import setup - -import hbp_nrp_music_xml -import pip - -from optparse import Option -options = Option('--workaround') -options.skip_requirements_regex = None -reqs_file = './requirements.txt' -options = Option("--workaround") -options.skip_requirements_regex = None -# Hack for old pip versions -if pip.__version__.startswith('10.'): - # Versions greater or equal to 10.x don't rely on pip.req.parse_requirements - install_reqs = list(val.strip() for val in open(reqs_file)) - reqs = install_reqs -elif pip.__version__.startswith('1.'): - # Versions 1.x rely on pip.req.parse_requirements - # but don't require a "session" parameter - from pip.req import parse_requirements # pylint:disable=no-name-in-module, import-error - install_reqs = parse_requirements(reqs_file, options=options) - reqs = [str(ir.req) for ir in install_reqs] -else: - # Versions greater than 1.x but smaller than 10.x rely on pip.req.parse_requirements - # and requires a "session" parameter - from pip.req import parse_requirements # pylint:disable=no-name-in-module, import-error - from pip.download import PipSession # pylint:disable=no-name-in-module, import-error - options.isolated_mode = False - install_reqs = parse_requirements( # pylint:disable=unexpected-keyword-arg - reqs_file, - session=PipSession, - options=options - ) - reqs = [str(ir.req) for ir in install_reqs] - -# ensure we install numpy before the main list of requirements, ignore -# failures if numpy/cython are not requirements and just proceed (future proof) -try: - cython_req = next(r for r in reqs if r.startswith('cython')) - numpy_req = next(r for r in reqs if r.startswith('numpy')) - if pip.__version__.startswith('10.'): - import subprocess - subprocess.check_call( - ["python", '-m', 'pip', 'install', "--no-clean", "--user", cython_req, numpy_req] - ) - else: - pip.main(['install', '--no-clean', cython_req, numpy_req]) # pylint:disable=no-member -# pylint: disable=bare-except -except: - pass - -config = { - 'description': 'MUSIC XML support for pyNN/NEST for HBP SP10', - 'author': 'HBP Neurorobotics', - 'url': 'http://neurorobotics.net', - 'author_email': 'neurorobotics@humanbrainproject.eu', - 'version': hbp_nrp_music_xml.__version__, - 'install_requires': reqs, - 'packages': ['hbp_nrp_music_xml', - 'hbp_nrp_music_xml.config', - 'hbp_nrp_music_xml.pynn', - 'hbp_nrp_music_xml.schema', - 'hbp_nrp_music_xml.schema.generated'], - 'scripts': [], - 'name': 'hbp-nrp-music-xml', - 'include_package_data': True, -} - -setup(**config) diff --git a/hbp_nrp_music_xml_deprec.tar.gz b/hbp_nrp_music_xml_deprec.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..4c2fd8943ca9c6237dae4d4833a0147ab9ea7e17 GIT binary patch literal 19985 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 literal 0 HcmV?d00001 diff --git a/verify.sh b/verify.sh index 662e0df..ec2889c 100755 --- a/verify.sh +++ b/verify.sh @@ -2,10 +2,10 @@ # This script is designed for local usage. # Ignore generated files -# export IGNORE_LINT="platform_venv|hbp_nrp_music_xml/hbp_nrp_music_xml/schema/generated" +# export IGNORE_LINT="platform_venv|" -# Ignore generated and music related files -export IGNORE_LINT='platform_venv|hbp_nrp_music_xml|hbp_nrp_music_interface|hbp_nrp_music_xml/hbp_nrp_music_xml/schema/generated|migrations|nest' +# Ignore generated related files +export IGNORE_LINT='platform_venv|migrations|nest' export VIRTUAL_ENV=$NRP_VIRTUAL_ENV # This script only runs static code analysis, the tests can be run separately using run_tests.sh -- GitLab