From 28a915ddcab7ccc363d1bc690a2ea54efd50d7f7 Mon Sep 17 00:00:00 2001 From: Jonathan Heathcote Date: Tue, 16 Jun 2015 16:16:50 +0100 Subject: [PATCH 1/2] Add nengo_spinnaker_setup command. This command automatically creates nengo_spinnaker config files (in the right places) based on easily-answered questions. Add Rig v0.5.3 dependency. This dependency ensures the features required by nengo_spinnaker_setup are present. --- README.rst | 83 +++++------------- nengo_spinnaker.conf.example | 47 ++++++++++ nengo_spinnaker/scripts/__init__.py | 0 .../scripts/nengo_spinnaker_setup.py | 83 ++++++++++++++++++ setup.py | 9 +- tests/scripts/test_nengo_spinnaker_setup.py | 87 +++++++++++++++++++ 6 files changed, 247 insertions(+), 62 deletions(-) create mode 100644 nengo_spinnaker.conf.example create mode 100644 nengo_spinnaker/scripts/__init__.py create mode 100644 nengo_spinnaker/scripts/nengo_spinnaker_setup.py create mode 100644 tests/scripts/test_nengo_spinnaker_setup.py diff --git a/README.rst b/README.rst index 775e4d4..22f8196 100644 --- a/README.rst +++ b/README.rst @@ -17,69 +17,11 @@ Quick Start Install using ``pip``:: - pip install nengo_spinnaker + $ pip install nengo_spinnaker -Settings File -------------- - -To use SpiNNaker with Nengo you must create a ``nengo_spinnaker.conf`` file in -either the directory you will be running your code from or, more usefully, a -centralised location. The centralised location varies based on your operating -system: - -- Windows: ``%userprofile%\.nengo\nengo_spinnaker.conf`` -- Other: ``~/.config/nengo/nengo_spinnaker.conf`` +Configure ``nengo_spinnaker`` to use your local SpiNNaker system:: -This file exists to inform ``nengo_spinnaker`` of the nature of the SpiNNaker -machine you wish to simulate with and how to communicate with it. This file may -look like:: - - ### SpiNNaker system configuration - # - # Settings for the SpiNNaker machine which will be used to simulate Nengo - # models. - - [spinnaker_machine] - hostname: - width: - height: - - # Required parameters are: - # - hostname: (string) either the hostname or the IP address of the board - # containing chip (0, 0). - # - width: (int) width of the machine (0 <= width < 256) - # - height: (int) height of the machine (0 <= height < 256) - # - # Optional parameters are: - # - "hardware_version: (int) Version number of the SpiNNaker boards - # used in the system (e.g. SpiNN-5 boards would be 5). At the - # time of writing this value is ignored and can be safely set to - # the default value of 0. - # - "led_config": (int) Defines LED pin numbers for the SpiNNaker boards - # used in the system. The four least significant bits (3:0) give - # the number of LEDs. The next four bits give the pin number of the - # first LED, the next four the pin number of the second LED, and so - # forth. At the time of writing, all SpiNNaker board versions have - # their first LED attached to pin 0 and thus the default value of - # 0x00000001 is safe. - # - # For a Spin3 board connected to 192.168.240.253 this section would look - # like: - # - # hostname: 192.168.240.253 - # width: 2 - # height: 2 - # hardware_version: 3 - # led_config: 0x00000502 - # - # For a Spin5 board connected to 192.168.1.1 this section would look - # like: - # - # hostname: 192.168.1.1 - # width: 8 - # height: 8 - # hardware_version: 5 - # led_config: 0x00000001 + $ nengo_spinnaker_setup Using ``nengo_spinnaker`` @@ -123,6 +65,25 @@ For example:: nengo_spinnaker.add_spinnaker_params(model.config) model.config[signal].function_of_time = True +Settings File +------------- + +In order to know which SpiNNaker system to use, ``nengo_spinnaker`` uses a +config file called ``nengo_spinnaker.conf`` file in either the directory you +will be running your code from or, more usefully, a centralised location. The +centralised location varies based on your operating system: + +- Windows: ``%userprofile%\.nengo\nengo_spinnaker.conf`` +- Other: ``~/.config/nengo/nengo_spinnaker.conf`` + +A utility called ``nengo_spinnaker_setup`` installed with ``nengo_spinnaker`` +can be used to create this file. By default, the config file is created +centrally but adding the ``--project`` option will create a config file in the +current directory which applies only . + +An annotated `example config file <./nengo_spinnaker.conf.example>`_ is provided +for users who wish to create their config file by hand. + Developers ========== diff --git a/nengo_spinnaker.conf.example b/nengo_spinnaker.conf.example new file mode 100644 index 0000000..5a3817d --- /dev/null +++ b/nengo_spinnaker.conf.example @@ -0,0 +1,47 @@ +### SpiNNaker system configuration +# +# Settings for the SpiNNaker machine which will be used to simulate Nengo +# models. + +[spinnaker_machine] +hostname: +width: +height: + +# Required parameters are: +# - hostname: (string) either the hostname or the IP address of the board +# containing chip (0, 0). +# - width: (int) width of the machine (0 <= width < 256) +# - height: (int) height of the machine (0 <= height < 256) +# +# Optional parameters are: +# - "hardware_version: (int) Version number of the SpiNNaker boards +# used in the system (e.g. SpiNN-5 boards would be 5). At the +# time of writing this value is ignored and can be safely set to +# the default value of 0. +# - "led_config": (int) Defines LED pin numbers for the SpiNNaker boards +# used in the system. The four least significant bits (3:0) give +# the number of LEDs. The next four bits give the pin number of the +# first LED, the next four the pin number of the second LED, and so +# forth. At the time of writing, all SpiNNaker board versions have +# their first LED attached to pin 0 and thus the default value of +# 0x00000001 is safe. +# +# For a Spin3 board connected to 192.168.240.253 this section would look +# like: +# +# hostname: 192.168.240.253 +# width: 2 +# height: 2 +# hardware_version: 3 +# led_config: 0x00000502 +# +# For a Spin5 board connected to 192.168.1.1 this section would look +# like: +# +# hostname: 192.168.1.1 +# width: 8 +# height: 8 +# hardware_version: 5 +# led_config: 0x00000001 + diff --git a/nengo_spinnaker/scripts/__init__.py b/nengo_spinnaker/scripts/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/nengo_spinnaker/scripts/nengo_spinnaker_setup.py b/nengo_spinnaker/scripts/nengo_spinnaker_setup.py new file mode 100644 index 0000000..a131664 --- /dev/null +++ b/nengo_spinnaker/scripts/nengo_spinnaker_setup.py @@ -0,0 +1,83 @@ +"""A script which helps users create their nengo_spinnaker config file.""" + +import argparse + +from nengo_spinnaker.utils.paths import nengo_spinnaker_rc + +from rig import wizard + +import os + + +CONFIG_TEMPLATE = """\ +### SpiNNaker system configuration. +# File automatically generated by nengo_spinnaker_setup. +[spinnaker_machine] +hostname: {hostname} +width: {width} +height: {height} +""" + + +def generate_config_file(filename, dimensions, ip_address): + """Generate a new config file with the specified filename and + parameters. + """ + try: + os.makedirs(os.path.dirname(filename)) + except (IOError, OSError): + # Directory already created, good job! + pass + + with open(filename, "w") as f: + f.write(CONFIG_TEMPLATE.format( + hostname=ip_address, + width=dimensions[0], + height=dimensions[1], + )) + + +def main(args=None): + parser = argparse.ArgumentParser( + description="Interactive tool for creating a nengo_spinnaker " + "config file.") + + file_group = parser.add_mutually_exclusive_group() + file_group.add_argument("--system", "-s", dest="file", + const="system", action="store_const", + help="Create a system-wide config file.") + file_group.add_argument("--user", "-u", dest="file", + const="user", action="store_const", + help="Create a user-specific config file " + "(default).") + file_group.add_argument("--project", "-p", dest="file", + const="project", action="store_const", + help="Create a project-specific config file in " + "the current directory.") + file_group.set_defaults(file="user") + + parser.add_argument("--force", "-f", action="store_true", + help="Overwrite an existing config file.") + + args = parser.parse_args(args) + + filename = nengo_spinnaker_rc[args.file] + + if not args.force and os.path.isfile(filename): + print("Config file {} already exists. Use --force to " + "overwrite.".format(filename)) + return 1 + + resp = wizard.cli_wrapper(wizard.cat(wizard.dimensions_wizard(), + wizard.ip_address_wizard())) + + if resp is None: + return 1 + else: + generate_config_file(filename, **resp) + print("Successfully created config file in {}".format(filename)) + return 0 + +if __name__ == "__main__": # pragma: no cover + import sys + sys.exit(main()) diff --git a/setup.py b/setup.py index 2655471..9ecf1ae 100644 --- a/setup.py +++ b/setup.py @@ -90,7 +90,14 @@ def get_new_url(url): keywords="spinnaker nengo neural cognitive simulation", # Requirements - install_requires=["nengo>=2.0.0, <3.0.0", "rig>=0.4.0, <1.0.0", + install_requires=["nengo>=2.0.0, <3.0.0", "rig>=0.5.3, <1.0.0", "bitarray>=0.8.1, <1.0.0"], zip_safe=False, # Partly for performance reasons + + # Scripts + entry_points={ + "console_scripts": [ + "nengo_spinnaker_setup = nengo_spinnaker.scripts.nengo_spinnaker_setup:main", + ], + } ) diff --git a/tests/scripts/test_nengo_spinnaker_setup.py b/tests/scripts/test_nengo_spinnaker_setup.py new file mode 100644 index 0000000..4a20c0a --- /dev/null +++ b/tests/scripts/test_nengo_spinnaker_setup.py @@ -0,0 +1,87 @@ +import pytest + +from mock import Mock + +import tempfile + +import os + +import shutil + +from nengo_spinnaker.scripts.nengo_spinnaker_setup \ + import main, generate_config_file + +from nengo_spinnaker.utils.paths import nengo_spinnaker_rc + +from rig import wizard + + +def test_bad_args(): + # Should fail if more than one config file is specified + with pytest.raises(SystemExit): + main("--system --user".split()) + + +def test_generate_config_file(): + # Config file generation should work straight-forwardly + fileno, filename = tempfile.mkstemp() + print(filename) + + generate_config_file(filename, dimensions=(4, 8), + ip_address="127.0.0.1") + + with open(filename, "r") as f: + config = f.read() + os.remove(filename) + + assert "[spinnaker_machine]\n" in config + assert "hostname: 127.0.0.1\n" in config + assert "width: 4\n" in config + assert "height: 8\n" in config + + +def test_bad_main(monkeypatch): + # Check that when questions aren't answered right, the program exits with a + # failing status. + mock_cli_wrapper = Mock(return_value=None) + monkeypatch.setattr(wizard, "cli_wrapper", mock_cli_wrapper) + + assert main("-pf".split()) != 0 + + +def test_main(monkeypatch): + # Check that questions are asked and a config file generated + mock_cli_wrapper = Mock(return_value={"dimensions": (2, 2), + "ip_address": "127.0.0.1"}) + monkeypatch.setattr(wizard, "cli_wrapper", mock_cli_wrapper) + + # Temporarily any existing project config file out of the way + config = nengo_spinnaker_rc["project"] + if os.path.isfile(config): # pragma: no cover + _, temp = tempfile.mkstemp() + print(config, temp) + shutil.move(config, temp) + else: + temp = None + + # Create a project config file in the test runner's directory (which + # shouldn't exist yet). + assert main("-p".split()) == 0 + assert mock_cli_wrapper.called + mock_cli_wrapper.reset_mock() + assert os.path.isfile(config) + + # Should fail to create a config file when one already exists + assert main("-p".split()) != 0 + assert not mock_cli_wrapper.called + + # ...unless forced + assert main("-p --force".split()) == 0 + assert mock_cli_wrapper.called + mock_cli_wrapper.reset_mock() + + # Restore the old config file + if temp is not None: # pragma: no cover + shutil.move(temp, config) + else: + os.remove(config) From 7c09c0b496c79185cc92b7b8de5729b9c9d0b5d4 Mon Sep 17 00:00:00 2001 From: Jonathan Heathcote Date: Tue, 16 Jun 2015 19:44:56 +0100 Subject: [PATCH 2/2] Remove --system option from nengo_spinnaker_setup Removed until a future time where an *editable* system wide config file exists as suggested by @mundya. --- nengo_spinnaker/scripts/nengo_spinnaker_setup.py | 3 --- tests/scripts/test_nengo_spinnaker_setup.py | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/nengo_spinnaker/scripts/nengo_spinnaker_setup.py b/nengo_spinnaker/scripts/nengo_spinnaker_setup.py index a131664..cf2e28b 100644 --- a/nengo_spinnaker/scripts/nengo_spinnaker_setup.py +++ b/nengo_spinnaker/scripts/nengo_spinnaker_setup.py @@ -43,9 +43,6 @@ def main(args=None): "config file.") file_group = parser.add_mutually_exclusive_group() - file_group.add_argument("--system", "-s", dest="file", - const="system", action="store_const", - help="Create a system-wide config file.") file_group.add_argument("--user", "-u", dest="file", const="user", action="store_const", help="Create a user-specific config file " diff --git a/tests/scripts/test_nengo_spinnaker_setup.py b/tests/scripts/test_nengo_spinnaker_setup.py index 4a20c0a..933a23d 100644 --- a/tests/scripts/test_nengo_spinnaker_setup.py +++ b/tests/scripts/test_nengo_spinnaker_setup.py @@ -19,7 +19,7 @@ def test_bad_args(): # Should fail if more than one config file is specified with pytest.raises(SystemExit): - main("--system --user".split()) + main("--project --user".split()) def test_generate_config_file():