diff --git a/.gitignore b/.gitignore
index 89559c0b..9079e45b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -55,10 +55,27 @@ target/
# Mac OS X stuff
.DS_Store
-# nengo_viz specific
-*.py.cfg
+# nengo_gui specific
+# *.py.cfg
.ipynb_checkpoints
# Vagrant
.vagrant
Vagrantfile
+
+# Node JS
+node_modules/
+
+# jsDoc
+out/
+
+# TypeScript
+typings/
+nengo_gui/static/**/*.js
+nengo_gui/static/**/*.js.map
+
+# JS files associated with tests
+**/tests/*.js
+
+# IDE related files
+.vscode/
diff --git a/.jscs.json b/.jscs.json
index fc6f0f0f..fe1b7ee4 100644
--- a/.jscs.json
+++ b/.jscs.json
@@ -1,4 +1,6 @@
{
+ "excludeFiles": ["nengo_gui/static/dist/**"],
+
"requireCurlyBraces": [
"if",
"else",
@@ -11,10 +13,20 @@
"requireOperatorBeforeLineBreak": true,
"maximumLineLength": {
"value": 80,
- "allExcept": ["comments", "regex"]
+ "allExcept": ["regex"]
},
"validateIndentation": 4,
+ "requireCapitalizedComments": true,
+ "requireSpaceAfterLineComment": true,
+ "disallowMultipleSpaces": true,
+ "requireKeywordsOnNewLine": [
+ "do", "for", "while",
+ "switch", "case",
+ "try", "catch", "finally",
+ "return"
+ ],
+
"disallowMultipleLineStrings": true,
"disallowMixedSpacesAndTabs": true,
"disallowTrailingWhitespace": true,
@@ -33,15 +45,25 @@
"try",
"catch"
],
+ "requireSpaceAfterComma": {
+ "allExcept": ["trailing"]
+ },
"requireSpaceBeforeBinaryOperators": [
"=", "+=", "-=", "*=", "/=", "%=", "<<=", ">>=", ">>>=",
"&=", "|=", "^=", "+=",
- "+", "-", "*", "/", "%", "<<", ">>", ">>>", "&",
+ "+", "-", "%", "<<", ">>", ">>>", "&",
+ "|", "^", "&&", "||", "===", "==", ">=",
+ "<=", "<", ">", "!=", "!=="
+ ],
+ "requireSpaceAfterBinaryOperators": [
+ "=", "+=", "-=", "*=", "/=", "%=", "<<=", ">>=", ">>>=",
+ "&=", "|=", "^=", "+=",
+
+ "+", "-", "%", "<<", ">>", ">>>", "&",
"|", "^", "&&", "||", "===", "==", ">=",
"<=", "<", ">", "!=", "!=="
],
- "requireSpaceAfterBinaryOperators": true,
"requireSpacesInConditionalExpression": true,
"requireSpaceBeforeBlockStatements": true,
"requireSpacesInForStatement": true,
diff --git a/CHANGES.rst b/CHANGES.rst
index 6d575099..cb5b9090 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -13,11 +13,11 @@ Release History
.. Changes should be organized in one of several sections:
- - API changes
- - Improvements
- - Behavioural changes
- - Bugfixes
- - Documentation
+ - Added
+ - Changed
+ - Deprecated
+ - Removed
+ - Fixed
0.2.1 (unreleased)
==================
diff --git a/browserslist b/browserslist
new file mode 100644
index 00000000..6d38c21a
--- /dev/null
+++ b/browserslist
@@ -0,0 +1,5 @@
+# Browsers that Nengo GUI supports
+Chrome >= 50
+Firefox >= 45
+Safari >= 9
+# Should test with Microsoft Edge, IE, Opera
diff --git a/docs/_static/custom.css b/docs/_static/custom.css
new file mode 100644
index 00000000..c0ceb93c
--- /dev/null
+++ b/docs/_static/custom.css
@@ -0,0 +1,30 @@
+.MathJax .mi, .MathJax .mo {
+ color: inherit;
+}
+
+.container.docutils {
+ margin: inherit;
+ padding: inherit;
+ width: inherit;
+}
+
+.container.docutils.toggle {
+ margin: 0;
+ padding: 0;
+}
+
+.toggle .header {
+ clear: both;
+ cursor: pointer;
+ display: block;
+}
+
+.toggle .header::after {
+ content: " ▼";
+ display: inline;
+}
+
+.toggle .header.open::after {
+ content: " ▲";
+ display: inline;
+}
diff --git a/docs/_templates/layout.html b/docs/_templates/layout.html
new file mode 100644
index 00000000..c7e67b49
--- /dev/null
+++ b/docs/_templates/layout.html
@@ -0,0 +1,22 @@
+{# Import the theme's layout. #}
+{% extends "!layout.html" %}
+
+{%- block extrahead %}
+
+{# Call the parent block #}
+{{ super() }}
+{%- endblock %}
+
+{%- block footer %}
+
+{{ super() }}
+{%- endblock %}
diff --git a/docs/api.rst b/docs/api.rst
new file mode 100644
index 00000000..7177e86d
--- /dev/null
+++ b/docs/api.rst
@@ -0,0 +1,131 @@
+*************
+Nengo GUI API
+*************
+
+.. automodule:: nengo_gui.config
+ :members:
+ :undoc-members:
+
+.. automodule:: nengo_gui.exec_env
+ :members:
+ :undoc-members:
+
+.. automodule:: nengo_gui.gui
+ :members:
+ :undoc-members:
+
+.. automodule:: nengo_gui.guibackend
+ :members:
+ :undoc-members:
+
+.. automodule:: nengo_gui.ipython
+ :members:
+ :undoc-members:
+
+.. automodule:: nengo_gui.layout
+ :members:
+ :undoc-members:
+
+.. automodule:: nengo_gui.main
+ :members:
+ :undoc-members:
+
+.. automodule:: nengo_gui.modal_js
+ :members:
+ :undoc-members:
+
+.. automodule:: nengo_gui.namefinder
+ :members:
+ :undoc-members:
+
+.. automodule:: nengo_gui.page
+ :members:
+ :undoc-members:
+
+.. automodule:: nengo_gui.password
+ :members:
+ :undoc-members:
+
+
+.. automodule:: nengo_gui.server
+ :members:
+ :undoc-members:
+
+.. automodule:: nengo_gui.static_plots
+ :members:
+ :undoc-members:
+
+.. automodule:: nengo_gui.user_action
+ :members:
+ :undoc-members:
+
+.. automodule:: nengo_gui.version
+ :members:
+ :undoc-members:
+
+.. automodule:: nengo_gui.viz
+ :members:
+ :undoc-members:
+
+Components
+==========
+
+.. automodule:: nengo_gui.components.component
+ :members:
+ :undoc-members:
+
+.. automodule:: nengo_gui.components.ace_editor
+ :members:
+ :undoc-members:
+
+.. automodule:: nengo_gui.components.editor
+ :members:
+ :undoc-members:
+
+.. automodule:: nengo_gui.components.htmlview
+ :members:
+ :undoc-members:
+
+.. automodule:: nengo_gui.components.netgraph
+ :members:
+ :undoc-members:
+
+.. automodule:: nengo_gui.components.pointer
+ :members:
+ :undoc-members:
+
+.. automodule:: nengo_gui.components.raster
+ :members:
+ :undoc-members:
+
+.. automodule:: nengo_gui.components.sim_control
+ :members:
+ :undoc-members:
+
+.. automodule:: nengo_gui.components.slider
+ :members:
+ :undoc-members:
+
+.. automodule:: nengo_gui.components.spa_plot
+ :members:
+ :undoc-members:
+
+.. automodule:: nengo_gui.components.spa_similarity
+ :members:
+ :undoc-members:
+
+.. automodule:: nengo_gui.components.spike_grid
+ :members:
+ :undoc-members:
+
+.. automodule:: nengo_gui.components.value
+ :members:
+ :undoc-members:
+
+.. automodule:: nengo_gui.components.voltage
+ :members:
+ :undoc-members:
+
+.. automodule:: nengo_gui.components.xyvalue
+ :members:
+ :undoc-members:
diff --git a/docs/codeflow.rst b/docs/codeflow.rst
deleted file mode 100644
index 38f08363..00000000
--- a/docs/codeflow.rst
+++ /dev/null
@@ -1,6 +0,0 @@
-**************************
-How Nengo GUI's Code Works
-**************************
-
-For details on how all the code of Nengo GUI is organised and interacts with
-itself, please check out this presentation: `https://docs.google.com/presentation/d/1XtAv3GDW2f7pRjKDJcRmqgwWxSQRXcsDZxUiK4hRrjs/edit?usp=sharing` by Sean Aubin.
diff --git a/docs/conf.py b/docs/conf.py
new file mode 100644
index 00000000..257a19ee
--- /dev/null
+++ b/docs/conf.py
@@ -0,0 +1,105 @@
+# -*- coding: utf-8 -*-
+
+import sys
+
+try:
+ import nengo
+ import nengo_gui
+ import guzzle_sphinx_theme
+except ImportError:
+ print("To build the documentation, nengo_gui and guzzle_sphinx_theme must "
+ "be installed in the current environment. Please install these and "
+ "their requirements first. A virtualenv is recommended!")
+ sys.exit(1)
+
+extensions = [
+ 'sphinx.ext.autodoc',
+ 'sphinx.ext.githubpages',
+ 'sphinx.ext.intersphinx',
+ 'sphinx.ext.mathjax',
+ 'sphinx.ext.todo',
+ 'sphinx.ext.viewcode',
+ 'guzzle_sphinx_theme',
+ 'numpydoc',
+]
+
+# -- sphinx.ext.autodoc
+autoclass_content = 'both' # class and __init__ docstrings are concatenated
+autodoc_default_flags = ['members']
+autodoc_member_order = 'bysource' # default is alphabetical
+
+# -- sphinx.ext.intersphinx
+intersphinx_mapping = {
+ "nengo": ("https://www.nengo.ai/nengo/", None)
+}
+
+# -- sphinx.ext.todo
+todo_include_todos = True
+
+# -- numpydoc config
+numpydoc_show_class_members = False
+
+# -- sphinx
+exclude_patterns = ['_build']
+source_suffix = '.rst'
+source_encoding = 'utf-8'
+master_doc = 'index'
+
+# Need to include https Mathjax path for sphinx < v1.3
+mathjax_path = ("https://cdn.mathjax.org/mathjax/latest/MathJax.js"
+ "?config=TeX-AMS-MML_HTMLorMML")
+
+project = u'Nengo GUI'
+authors = u'Applied Brain Research'
+copyright = nengo.__copyright__
+version = '.'.join(nengo_gui.__version__.split('.')[:2]) # Short X.Y version
+release = nengo_gui.__version__ # Full version, with tags
+pygments_style = 'default'
+
+# -- Options for HTML output --------------------------------------------------
+
+pygments_style = "sphinx"
+templates_path = ["_templates"]
+html_static_path = ["_static"]
+
+html_theme_path = guzzle_sphinx_theme.html_theme_path()
+html_theme = "guzzle_sphinx_theme"
+
+html_theme_options = {
+ "project_nav_name": "Nengo GUI %s" % (version,),
+ "base_url": "https://www.nengo.ai/nengo_gui",
+}
+
+html_title = "Nengo GUI {0} docs".format(release)
+htmlhelp_basename = 'Nengo GUI'
+html_last_updated_fmt = '' # Suppress 'Last updated on:' timestamp
+html_show_sphinx = False
+
+# -- Options for LaTeX output -------------------------------------------------
+
+latex_elements = {
+ 'papersize': 'letterpaper',
+ 'pointsize': '11pt',
+ # 'preamble': '',
+}
+
+latex_documents = [
+ # (source start file, target, title, author, documentclass [howto/manual])
+ ('index', 'nengo_gui.tex', html_title, authors, 'manual'),
+]
+
+# -- Options for manual page output -------------------------------------------
+
+man_pages = [
+ # (source start file, name, description, authors, manual section).
+ ('index', 'nengo_gui', html_title, [authors], 1)
+]
+
+# -- Options for Texinfo output -----------------------------------------------
+
+texinfo_documents = [
+ # (source start file, target, title, author, dir menu entry,
+ # description, category)
+ ('index', 'nengo_gui', html_title, authors, 'Nengo GUI',
+ 'Large-scale neural simulation in Python', 'Miscellaneous'),
+]
diff --git a/docs/dev_guide.rst b/docs/dev_guide.rst
index 6083585b..3a826946 100644
--- a/docs/dev_guide.rst
+++ b/docs/dev_guide.rst
@@ -1,20 +1,25 @@
***************
-Developer Guide
+Developer guide
***************
-If you would like access the the development version of nengo_gui, you can
-download it from guthub:
+If you would like access
+the development version of Nengo GUI,
+you can download it from Github:
-.. code:: shell
+.. code:: bash
git clone https://github.com/nengo/nengo_gui
cd nengo_gui
- python setup.py develop --user
+ pip install -e .
-The following sections will help you understand how to contribute to Nengo GUI
-development.
+For information on the architecture of Nengo GUI,
+see `this presentation
+`_.
+
+For information on becoming a Nengo GUI developer, see
+`the general Nengo developer guide `_.
.. toctree::
- :maxdepth: 2
- codeflow
- workflow
+ :maxdepth: 1
+
+ api
diff --git a/docs/getting_started.rst b/docs/getting_started.rst
index f9d648a3..efb04e5e 100644
--- a/docs/getting_started.rst
+++ b/docs/getting_started.rst
@@ -1,36 +1,34 @@
***************
-Getting Started
+Getting started
***************
Installation
============
-The simplest way to install is with the standard Python installation system:
+The simplest way to install is with ``pip``.
-.. code:: shell
+.. code:: bash
pip install nengo_gui
-Running nengo_gui
+Running Nengo GUI
=================
-There are two ways to run nengo_gui. First, you can use it from the command
-line by running the installed script:
+There are two ways to run Nengo GUI.
+First, you can use it from the command line by running:
.. code:: shell
- nengo_gui
+ nengo
-If you specify a file to load, nengo_gui will do so:
+If you specify a file, the GUI will open that file:
.. code:: shell
- nengo_gui myfile.py
-
-Alternatively, you can start nengo_gui manually from within your code. To
-do so, add this to the bottom of your file that defines your Nengo model.
+ nengo myfile.py
-.. code:: python
+Alternatively, you can start the GUI manually from within your code.
+To do so, add to the bottom your Nengo script::
import nengo_gui
nengo_gui.GUI(__file__).start()
@@ -38,32 +36,42 @@ do so, add this to the bottom of your file that defines your Nengo model.
Basic usage
===========
-The graph of the Nengo network should appear. Rectangles are nengo.Nodes,
-ellipses are nengo.Ensembles, and rounded rectangles are nengo.Networks.
-
-Items can be dragged to move them and resized by dragging their edge or via
-the scroll wheel.
-
-To start (or continue) the simulation, click the play button in the lower
-right. A spinning gear icon indicates the model is in the process of being
-built (or re-built after new graphs are added).
-
-Clicking on an item will show a menu of options, depending on what you
-have clicked on. Here are some of the standard options for network items:
-
- - value: show a graph of the decoded output value over time
- - xy-value: show a state-space plot of two decoded values against each other
- - spikes: show the spiking activity of the nengo.Ensemble
- - slider: show sliders that let you adjust the value in a nengo.Node
- - expand/collapse: reveal or hide the insides of a nengo.Network
-
-Once you have graphs, you can also click on them to adjust their options. For
-example:
-
- - set range: adjust the limits of the graph
- - show label/hide label: whether to show the title at the top of the graph
- - remove: get rid of the graph
-
-The graphs record their data from previous time steps. You can show this
-previous data by dragging the transparent area in the time axis at the
-bottom (beside the play button).
\ No newline at end of file
+The graph of the Nengo network should appear
+when you start the GUI.
+A rectangle is a `nengo.Node`,
+a group of ellipses is a `nengo.Ensemble`,
+and a rounded rectangle is a `nengo.Network`.
+
+Items can be dragged to move them
+and resized by dragging their edges or corners.
+
+To start (or continue) the simulation,
+click the play button in the lower right.
+A spinning gear icon indicates that the model
+is in the process of being built
+(or re-built after new plots are added).
+
+Right-clicking on an object will show a menu of options,
+depending on what you have clicked on.
+Here are some of the most useful options for objects:
+
+- **Value**: Show a plot of the decoded output value over time.
+- **XY-value**:
+ Show a state-space plot of two decoded values against each other.
+- **Spikes**: Show the spiking activity of a `nengo.Ensemble`.
+- **Slider**: Show sliders to adjust the value in a `nengo.Node`.
+- **Expand / collapse network**:
+ Reveal or hide the insides of a `nengo.Network`.
+
+Once you have plots,
+you can also right-click on them to adjust their options.
+For example:
+
+- **Set range**: Adjust the limits of the plot.
+- **Show / hide label**: Whether to show the title at the top of the plot.
+- **Remove**: Get rid of the plot.
+
+Plots record their data from previous time steps.
+You can show this previous data by dragging
+the transparent area in the time axis at the bottom
+(beside the play button).
diff --git a/docs/index.rst b/docs/index.rst
index 182f24e4..a19547d3 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -12,8 +12,6 @@ the model is running.
:maxdepth: 2
getting_started
- examples
- user_guide
dev_guide
Indices and tables
@@ -21,4 +19,4 @@ Indices and tables
* :ref:`genindex`
* :ref:`modindex`
-* :ref:`search`
\ No newline at end of file
+* :ref:`search`
diff --git a/docs/workflow.rst b/docs/workflow.rst
deleted file mode 100644
index 7e40dcf3..00000000
--- a/docs/workflow.rst
+++ /dev/null
@@ -1,67 +0,0 @@
-********************
-Development workflow
-********************
-
-Development happens on `Github `_.
-Feel free to fork any of our repositories and send a pull request!
-However, note that we ask contributors to sign
-`a copyright assignment agreement `_.
-
-Code style: Python
-==================
-
-We adhere to
-`PEP8 `_,
-and use ``flake8`` to automatically check for adherence on all commits.
-
-We use ``numpydoc`` and
-`NumPy guidelines `_
-for docstrings, as they are a bit nicer to read in plain text,
-and produce decent output with Sphinx.
-
-Code style: JavaScript
-======================
-
-We adhere to a modified version of the Google style guide using
-`JSCS `_. Our custom rules for JSCS are saved in the
-``jscs.json`` file in the root of this repository.
-
-Unit testing
-============
-
-We use `PyTest `_
-to run our Python unit tests and `Mocha `_
-for JavaScript. Eventually these tests will run
-on `Travis-CI `_. Please contribute unit tests
-where possible.
-
-Git
-===
-
-We use a pretty strict ``git`` workflow
-to ensure that the history of the ``master`` branch
-is clean and readable.
-Every commit in the ``master`` branch should pass
-unit testing, including PEP8.
-
-Developers should never edit code on the ``master`` branch.
-When changing code, create a new topic branch
-that implements your new feature or fixes a bug.
-When your branch is ready to be reviewed,
-push it to Github and create a pull request.
-One or more people will review your pull request,
-and over one or many cycles of review,
-your PR will be accepted or rejected.
-We almost never reject PRs,
-though we do let them languish in the limbo
-of the PR queue if we're not sure
-if they're quite ready yet.
-
-Terry Stewart primarily repsonsible for reviewing your work,
-and merging it into the ``master`` branch when it's been accepted.
-He is the only person allowed to push to the ``master`` branch.
-
-If you have any questions about our workflow,
-or how you can best climb the learning curve
-that Nengo GUI and ``git`` present, please contact
-the development lead, `Sean `_.
\ No newline at end of file
diff --git a/examples/net.py.cfg b/examples/net.py.cfg
new file mode 100644
index 00000000..f905feb1
--- /dev/null
+++ b/examples/net.py.cfg
@@ -0,0 +1,29 @@
+_viz_ace_editor = nengo_gui.components.AceEditor()
+_viz_net_graph = nengo_gui.components.NetGraph()
+_viz_sim_control = nengo_gui.components.SimControl()
+_viz_config[_viz_sim_control].kept_time = 4.0
+_viz_config[_viz_sim_control].shown_time = 0.5
+_viz_config[a].pos=(0.5769230769230769, 0.21000000000000002)
+_viz_config[a].size=(0.07692307692307693, 0.1)
+_viz_config[b].pos=(0.8846153846153847, 0.5)
+_viz_config[b].size=(0.07692307692307693, 0.1)
+_viz_config[ens].pos=(0.3072289156626506, 0.5)
+_viz_config[ens].size=(0.060240963855421686, 0.1)
+_viz_config[model].pos=(0.11662077368779833, 0.1211053797056143)
+_viz_config[model].size=(0.8442598538023351, 0.8442598538023351)
+_viz_config[model].expanded=True
+_viz_config[model].has_layout=True
+_viz_config[result].pos=(0.5481927710843373, 0.5)
+_viz_config[result].size=(0.060240963855421686, 0.1)
+_viz_config[stimulus_A].pos=(0.0783132530120482, 0.22000000000000003)
+_viz_config[stimulus_A].size=(0.048192771084337345, 0.08)
+_viz_config[stimulus_B].pos=(0.0783132530120482, 0.78)
+_viz_config[stimulus_B].size=(0.048192771084337345, 0.08)
+_viz_config[subnet].pos=(0.8493975903614457, 0.5)
+_viz_config[subnet].size=(0.12048192771084337, 0.4)
+_viz_config[subnet].expanded=True
+_viz_config[subnet].has_layout=True
+_viz_config[subsubnet].pos=(0.19230769230769232, 0.5)
+_viz_config[subsubnet].size=(0.15384615384615385, 0.4)
+_viz_config[subsubnet].expanded=False
+_viz_config[subsubnet].has_layout=False
\ No newline at end of file
diff --git a/examples/spa.py.cfg b/examples/spa.py.cfg
new file mode 100644
index 00000000..3c1bb7a1
--- /dev/null
+++ b/examples/spa.py.cfg
@@ -0,0 +1,52 @@
+_viz_0 = nengo_gui.components.SpaSimilarity(model.a,target=u'default')
+_viz_config[_viz_0].max_value = 1.5
+_viz_config[_viz_0].min_value = -1.5
+_viz_config[_viz_0].height = 0.11723329425556858
+_viz_config[_viz_0].label_visible = True
+_viz_config[_viz_0].width = 0.06548788474132286
+_viz_config[_viz_0].y = 0.5806451612903225
+_viz_config[_viz_0].x = 0.6176470588235294
+_viz_config[_viz_0].show_pairs = False
+_viz_1 = nengo_gui.components.Pointer(model.a,target=u'default')
+_viz_config[_viz_1].label_visible = True
+_viz_config[_viz_1].width = 0.06548788474132286
+_viz_config[_viz_1].y = 0.3218034116610221
+_viz_config[_viz_1].x = 0.2779011436736956
+_viz_config[_viz_1].show_pairs = False
+_viz_config[_viz_1].max_size = 1000.0
+_viz_config[_viz_1].height = 0.11723329425556858
+_viz_ace_editor = nengo_gui.components.AceEditor()
+_viz_net_graph = nengo_gui.components.NetGraph()
+_viz_sim_control = nengo_gui.components.SimControl()
+_viz_config[_viz_sim_control].kept_time = 4.0
+_viz_config[_viz_sim_control].shown_time = 0.5
+_viz_config[model].pos=(0, 0)
+_viz_config[model].size=(1.0, 1.0)
+_viz_config[model].expanded=True
+_viz_config[model].has_layout=True
+_viz_config[model.a].pos=(0.46594629993451253, 0.4305109102598038)
+_viz_config[model.a].size=(0.11764705882352941, 0.12903225806451613)
+_viz_config[model.a].expanded=False
+_viz_config[model.a].has_layout=False
+_viz_config[model.a.state].expanded=False
+_viz_config[model.a.state].has_layout=False
+_viz_config[model.b].pos=(0.5, 0.8387096774193548)
+_viz_config[model.b].size=(0.11764705882352941, 0.12903225806451613)
+_viz_config[model.b].expanded=False
+_viz_config[model.b].has_layout=False
+_viz_config[model.b.state].expanded=False
+_viz_config[model.b.state].has_layout=False
+_viz_config[model.c].pos=(0.8529411764705882, 0.6451612903225805)
+_viz_config[model.c].size=(0.11764705882352941, 0.12903225806451613)
+_viz_config[model.c].expanded=False
+_viz_config[model.c].has_layout=False
+_viz_config[model.c.state].expanded=False
+_viz_config[model.c.state].has_layout=False
+_viz_config[model.cortical].pos=(0.5, 0.16129032258064516)
+_viz_config[model.cortical].size=(0.4, 0.12903225806451613)
+_viz_config[model.cortical].expanded=False
+_viz_config[model.cortical].has_layout=False
+_viz_config[model.input].pos=(0.14705882352941177, 0.6451612903225805)
+_viz_config[model.input].size=(0.11764705882352941, 0.12903225806451613)
+_viz_config[model.input].expanded=False
+_viz_config[model.input].has_layout=False
\ No newline at end of file
diff --git a/nengo_gui/__init__.py b/nengo_gui/__init__.py
index a75e8070..e7e1c1d2 100644
--- a/nengo_gui/__init__.py
+++ b/nengo_gui/__init__.py
@@ -1,5 +1,7 @@
+from . import exec_env, server
+
from .gui import GUI, InteractiveGUI
from .viz import Viz # deprecated
from .version import version as __version__
-from .namefinder import NameFinder
+from .netgraph import NameFinder
from .main import main, old_main
diff --git a/nengo_gui/client.py b/nengo_gui/client.py
new file mode 100644
index 00000000..2bc742a3
--- /dev/null
+++ b/nengo_gui/client.py
@@ -0,0 +1,156 @@
+from collections import defaultdict
+from functools import partial
+import inspect
+import json
+import warnings
+import weakref
+
+import numpy as np
+from nengo.utils.compat import is_array, with_metaclass
+
+
+class NengoGUIConfig(json.JSONEncoder):
+ def default(self, obj):
+ if hasattr(obj, "to_json"):
+ return obj.to_json()
+ return super(NengoGUIConfig, self).default(obj)
+
+
+def bind(name):
+ def _bind(method):
+ if isinstance(method, property):
+ raise RuntimeError("Bind to the method, not the property")
+
+ if isinstance(method, staticmethod):
+ method.__func__.__route__ = name
+ return method.__func__
+ elif isinstance(method, classmethod):
+ method.__func__.__route__ = name
+ return method
+ else:
+ method.__route__ = name
+ return method
+ return _bind
+
+
+class ClientConnection(object):
+ def __init__(self, ws):
+ self.ws = ws
+ self.callbacks = defaultdict(set)
+
+ @staticmethod
+ def _element(callback):
+ if inspect.ismethod(callback):
+ return weakref.ref(callback.__self__), callback.__func__.__name__
+ elif isinstance(callback, partial):
+ return weakref.ref(callback.args[0]), callback
+ else:
+ return weakref.ref(callback), None
+
+ def _prune(self, name):
+ if len(self.callbacks[name]) == 0:
+ del self.callbacks[name]
+
+ def bind(self, name, callback):
+ """Define a function name that the client can call."""
+ self.callbacks[name].add(self._element(callback))
+
+ def is_bound(self, name):
+ return name in self.callbacks
+
+ def dispatch(self, name, **kwargs):
+ """Call a function bound to this page."""
+ # Iterate over a copy so we can remove stale elements
+ retval = None
+ for ref, meth in self.callbacks[name].copy():
+ if meth is None:
+ cb = ref()
+ elif isinstance(meth, partial):
+ cb = meth if ref() is not None else None
+ else:
+ cb = getattr(ref(), meth, None)
+
+ if cb is None:
+ self.callbacks[name].remove((ref, meth))
+ else:
+ retval = cb(**kwargs)
+
+ # Do this check after iterating in case size changes during iteration
+ self._prune(name)
+ if not self.is_bound(name):
+ warnings.warn("Nothing bound for %r" % (name,))
+
+ # We return the value of the last call
+ return retval
+
+ def send(self, name, **kwargs):
+ """Send a message to the client."""
+ assert self.ws is not None
+ self.ws.write_text(json.dumps([name, kwargs], cls=NengoGUIConfig))
+
+ def unbind(self, name, callback=None):
+ if callback is None:
+ del self.callbacks[name]
+ else:
+ el = self._element(callback)
+ if el in self.callbacks[name]:
+ self.callbacks[name].remove(el)
+ self._prune(name)
+
+
+class Bindable(type):
+ """A metaclass used to bind methods exposed to the client."""
+
+ def __call__(cls, *args, **kwargs):
+ """Override default __call__ behavior so that methods get bound."""
+ inst = type.__call__(cls, *args, **kwargs)
+ assert isinstance(inst.client, ClientConnection), inst.client
+
+ # Bind methods and static functions
+ def bindable(f):
+ if inspect.ismethod(f) or inspect.isfunction(f):
+ return (hasattr(f, "__route__") or
+ (hasattr(f, "__func__")
+ and hasattr(f.__func__, "__route__")))
+
+ for _, method in inspect.getmembers(inst, predicate=bindable):
+ inst.client.bind(method.__route__.format(self=inst), method)
+
+ # Bind properties
+ is_prop = lambda f: isinstance(f, property)
+ for _, prop in inspect.getmembers(type(inst), predicate=is_prop):
+
+ # Check if the get, set, or del are bound
+ for attr in ("fget", "fset", "fdel"):
+ f = getattr(prop, attr)
+ if hasattr(f, "__route__"):
+ # If so, we manually bind the instance to the function
+ # with partial, which makes it act like an instance method
+ inst.client.bind(f.__route__.format(self=inst),
+ partial(f, inst))
+
+ return inst
+
+
+class ExposedToClient(with_metaclass(Bindable)):
+ def __init__(self, client):
+ self.client = client
+
+
+class FastClientConnection(object):
+ def __init__(self, ws):
+ self.ws = ws
+ self.callback = None
+ self.dtype = None
+
+ def bind(self, callback, dtype=np.float64):
+ self.callback = callback
+ self.dtype = None
+
+ def receive(self, data):
+ if self.callback is not None:
+ self.callback(np.frombuffer(data, dtype=self.dtype))
+
+ def send(self, data):
+ assert is_array(data)
+ self.ws.write_binary(data.tobytes())
diff --git a/nengo_gui/compat.py b/nengo_gui/compat.py
new file mode 100644
index 00000000..95796455
--- /dev/null
+++ b/nengo_gui/compat.py
@@ -0,0 +1,41 @@
+from __future__ import absolute_import
+
+import sys
+
+# Only test for Python 2 so that we have less changes for Python 4
+PY2 = sys.version_info[0] == 2
+
+if PY2:
+ import BaseHTTPServer as server
+ from Cookie import SimpleCookie
+ import SocketServer as socketserver
+ from urllib2 import urlopen
+ from urlparse import parse_qs, unquote, urlparse
+ execfile = execfile
+
+else:
+ from http import server
+ from http.cookies import SimpleCookie
+ import socketserver
+ from urllib.parse import parse_qs, unquote, urlparse
+ from urllib.request import urlopen
+
+ def execfile(path, globals, locals):
+ if globals is None:
+ globals = sys._getframe(1).f_globals
+ if locals is None:
+ locals = sys._getframe(1).f_locals
+ with open(path, "r") as fp:
+ code = fp.read()
+ compiled = compile(code, path, mode="exec")
+ exec(compiled, globals, locals)
+
+
+assert execfile
+assert parse_qs
+assert server
+assert SimpleCookie
+assert socketserver
+assert unquote
+assert urlopen
+assert urlparse
diff --git a/nengo_gui/components/__init__.py b/nengo_gui/components/__init__.py
index c3d5f146..101d575e 100644
--- a/nengo_gui/components/__init__.py
+++ b/nengo_gui/components/__init__.py
@@ -1,28 +1,14 @@
-# flake8: noqa
-from .component import Component
+from .base import Component, Position
from .slider import Slider
from .value import Value
from .xyvalue import XYValue
-from .sim_control import SimControl
from .raster import Raster
from .voltage import Voltage
-from .pointer import Pointer
-from .netgraph import NetGraph
-from .ace_editor import AceEditor
-from .editor import NoEditor
-from .spa_similarity import SpaSimilarity
+from .spa import SpaPointer, SpaSimilarity
from .htmlview import HTMLView
from .spike_grid import SpikeGrid
-# Old versions of the .cfg files used Templates which had slightly different
-# names than the Components currently use. This code allows us to
-# successfully parse those old .cfg files
-SliderTemplate = Slider
-ValueTemplate = Value
-XYValueTemplate = XYValue
-SimControlTemplate = SimControl
-RasterTemplate = Raster
-VoltageTemplate = Voltage
-PointerTemplate = Pointer
-NetGraphTemplate = NetGraph
-AceEditorTemplate = AceEditor
+from .connection import Connection
+from .ensemble import Ensemble
+from .network import Network
+from .node import Node
diff --git a/nengo_gui/components/ace_editor.py b/nengo_gui/components/ace_editor.py
deleted file mode 100644
index 53616d03..00000000
--- a/nengo_gui/components/ace_editor.py
+++ /dev/null
@@ -1,94 +0,0 @@
-import json
-import os
-
-from nengo_gui.components.editor import Editor
-import nengo_gui.exec_env
-
-
-class AceEditor(Editor):
-
- config_defaults = {}
-
- def __init__(self):
- super(AceEditor, self).__init__()
- self.pending_messages = []
-
- def attach(self, page, config, uid):
- super(AceEditor, self).attach(page, config, uid)
- self.current_code = page.code
- self.serve_code = True
- self.last_error = None
- self.last_stdout = None
-
- def update_code(self, code):
- self.current_code = code
- self.serve_code = True
-
- def update_client(self, client):
- while self.pending_messages:
- client.write_text(self.pending_messages.pop())
- if self.serve_code:
- i = json.dumps({'code': self.current_code})
- client.write_text(i)
- self.serve_code = False
- if nengo_gui.exec_env.is_executing():
- return
- error = self.page.error
- stdout = self.page.stdout
- if error != self.last_error or stdout != self.last_stdout:
- if error is None:
- short_msg = None
- else:
- if '\n' in error['trace']:
- short_msg = error['trace'].rsplit('\n', 2)[-2]
- else:
- short_msg = error['trace']
- client.write_text(json.dumps({'error': error,
- 'short_msg': short_msg,
- 'stdout': stdout}))
- self.last_error = error
- self.last_stdout = stdout
-
- def javascript(self):
- return 'Nengo.ace = new Nengo.Ace("%s", {})' % (id(self),)
-
- def message(self, msg):
- data = json.loads(msg)
- self.current_code = data['code']
-
- save_as = data.get('save_as', None)
- if save_as is not None:
- if os.path.exists(save_as):
- msg = ("Could not rename to %s; "
- "File already exists" % save_as)
- self.pending_messages.append(json.dumps(
- {'filename': save_as,
- 'valid': False,
- 'error': msg}))
- else:
- try:
- self.page.filename_cfg = save_as + '.cfg'
- self.page.save_config(force=True)
- self.page.filename = save_as
- with open(self.page.filename, 'w') as f:
- f.write(self.current_code)
- self.pending_messages.append(json.dumps(
- {'filename': save_as,
- 'valid': True}))
- self.page.net_graph.update_code(self.current_code)
- except IOError:
- msg = "Could not save %s; permission denied" % save_as
- self.pending_messages.append(json.dumps(
- {'filename': save_as,
- 'valid': False,
- 'error': msg}))
- elif data['save']:
- try:
- with open(self.page.filename, 'w') as f:
- f.write(self.current_code)
- except IOError:
- print("Could not save %s; permission denied" %
- self.page.filename)
- self.page.net_graph.update_code(self.current_code)
- else:
- self.page.net_graph.update_code(self.current_code)
diff --git a/nengo_gui/components/base.py b/nengo_gui/components/base.py
new file mode 100644
index 00000000..ddd0a79b
--- /dev/null
+++ b/nengo_gui/components/base.py
@@ -0,0 +1,174 @@
+from nengo_gui.client import ExposedToClient, FastClientConnection
+from nengo_gui.exceptions import NotAttachedError
+
+
+class Position(object):
+ __slots__ = ("left", "top", "width", "height")
+
+ def __init__(self, left=0, top=0, width=100, height=100):
+ self.left = left
+ self.top = top
+ self.width = width
+ self.height = height
+
+ def to_json(self):
+ return {"left": self.left,
+ "top": self.top,
+ "width": self.width,
+ "height": self.height}
+
+ def __repr__(self):
+ return "Position(left={!r}, top={!r}, width={!r}, height={!r})".format(
+ self.left, self.top, self.width, self.height)
+
+
+class Component(ExposedToClient):
+ """Abstract handler for a particular Component of the user interface.
+
+ Each part of the user interface has part of the code on the server-side
+ (in Python) and a part on the client-side (in Javascript). These two sides
+ communicate via WebSockets, and the server-side is always a subclass of
+ Component.
+
+ Each Component can be configured via the nengo.Config system. Components
+ can add required nengo objects into the model to allow them to gather
+ required data or input overriding data (in the case of Pointer and Slider)
+ to/from the running model. Communication from server to
+ client is done via ``Component.update_client()``, which is called regularly
+ by the ``Server.ws_viz_component`` handler. Communication from client to
+ server is via ``Component.message()``.
+ """
+
+ def __init__(self, client, obj, uid, pos=None, label_visible=True):
+ super(Component, self).__init__(client)
+ self.obj = obj
+ self._uid = uid
+ self.pos = pos
+ self.label_visible = label_visible
+
+ @property
+ def label(self):
+ """Return a readable label for an object.
+
+ An important difference between a label and a name is that a label
+ does not have to be unique in a namespace.
+
+ If the object has a .label set, this will be used. Otherwise, it
+ uses names, which thanks to the NameFinder will be legal
+ Python code for referring to the object given the current locals()
+ dictionary ("model.ensembles[1]" or "ens" or "model.buffer.state").
+ If it has to use names, it will only use the last part of the
+ label (after the last "."). This avoids redundancy in nested displays.
+ """
+ label = self.obj.label
+ if label is None:
+ label = self.uid
+ if '.' in label:
+ label = label.rsplit('.', 1)[1]
+ return label
+
+ @property
+ def uid(self):
+ return self._uid
+
+ # TODO: rename
+ def add_nengo_objects(self, network):
+ """Add or modify the nengo model before build.
+
+ Components may need to modify the underlying nengo.Network by adding
+ Nodes and Connections or modifying the structure in other ways.
+ This method will be called for all Components just before the build
+ phase.
+ """
+ pass
+
+ def create(self):
+ """Instruct the client to create this object."""
+ raise NotImplementedError("Components must implement `create`")
+
+ def delete(self):
+ """Instruct the client to delete this object."""
+ raise NotImplementedError("Components must implement `delete`")
+
+ def dumps(self, names):
+ """Important to do correctly, as it's used in the config file."""
+ raise NotImplementedError("Components must implement `dumps`")
+
+ # TODO: rename
+ def remove_nengo_objects(self, network):
+ """Undo the effects of add_nengo_objects.
+
+ After the build is complete, remove the changes to the nengo.Network
+ so that it is all set to be built again in the future.
+ """
+ pass
+
+ def similar(self, other):
+ """Determine whether this component is similar to another component.
+
+ Similar, in this case, means that the `.diff` method can be used to
+ mutate the other component to be the same as this component.
+ """
+ return self.uid == other.uid and type(self) == type(other)
+
+ def to_json(self):
+ d = self.__dict__.copy()
+ d["cls"] = type(self).__name__
+ return d
+
+ def update(self, other):
+ """Update the client based on another version of this component."""
+ if self.label != other.label:
+ self.client.send("%s.label" % self.uid, label=self.label)
+
+ # def javascript_config(self, cfg):
+ # """Convert the nengo.Config information into javascript.
+
+ # This is needed so we can send that config information to the client.
+ # """
+ # for attr in self.config._clsparams.params:
+ # if attr in cfg:
+ # raise AttributeError("Value for %s is already set in the "
+ # "config of this component. Do not try to "
+ # "modify it via this function. Instead "
+ # "modify the config directly." % (attr))
+ # else:
+ # cfg[attr] = getattr(self.config, attr)
+ # return json.dumps(cfg)
+
+ # def code_python(self, uids):
+ # """Generate Python code for this Component.
+
+ # This is used in the .cfg file to generate a valid Python expression
+ # that re-creates this Component.
+
+ # The input uids is a dictionary from Python objects to strings that
+ # refer to those Python objects (the reverse of the locals() dictionary)
+ # """
+ # args = self.code_python_args(uids)
+ # name = self.__class__.__name__
+ # return 'nengo_gui.components.%s(%s)' % (name, ','.join(args))
+
+ # def code_python_args(self, uids):
+ # """Return a list of strings giving the constructor arguments.
+
+ # This is used by code_python to re-create the Python string that
+ # generated this Component, so it can be saved in the .cfg file.
+
+ # The input uids is a dictionary from Python objects to strings that
+ # refer to those Python objects (the reverse of the locals() dictionary)
+ # """
+ # return []
+
+
+class Widget(Component):
+
+ def __getattr__(self, name):
+ # NB: This method will only be called is `name` is not an attribute
+ if name == "fast_client":
+ raise NotAttachedError("This Widget is not yet attached.")
+ raise AttributeError("%r object has no attribute %r"
+ % (type(self).__name__, name))
+
+ def attach(self, fast_client):
+ self.fast_client = fast_client
diff --git a/nengo_gui/components/component.py b/nengo_gui/components/component.py
deleted file mode 100644
index fb1d6ff3..00000000
--- a/nengo_gui/components/component.py
+++ /dev/null
@@ -1,124 +0,0 @@
-import json
-
-
-class Component(object):
- """Abstract handler for a particular Component of the user interface.
-
- Each part of the user interface has part of the code on the server-side
- (in Python) and a part on the client-side (in Javascript). These two sides
- communicate via WebSockets, and the server-side is always a subclass of
- Component.
-
- Each Component can be configured via the nengo.Config system. Components
- can add required nengo objects into the model to allow them to gather
- required data or input overriding data (in the case of Pointer and Slider)
- to/from the running model. Communication from server to
- client is done via Component.update_client(), which is called regularly
- by the Server.ws_viz_component handler. Communication from client to
- server is via Component.message().
- """
-
- # The parameters that will be stored in the .cfg file for this Component
- # Subclasses should override this as needed.
- config_defaults = dict(x=0, y=0, width=100, height=100, label_visible=True)
-
- def __init__(self, component_order=0):
- # when generating Javascript for all the Components in a Page, they
- # will be sorted by component_order. This way some Components can
- # be defined before others.
- self.component_order = component_order
-
- # If we have reloaded the model (while typing in the editor), we need
- # to swap out the old Component with the new one.
- self.replace_with = None
-
- # If we have been swapped out, keep track of the id of the original
- # component, since that's the identifier needed to refer to it on
- # the client side
- self.original_id = id(self)
-
- def attach(self, page, config, uid):
- """Connect the Component to a Page."""
- self.config = config # the nengo.Config[component] for this component
- self.page = page # the Page this component is in
- self.uid = uid # The Python string referencing this component
-
- def update_client(self, client):
- """Send any required information to the client.
-
- This method is called regularly by Server.ws_viz_component(). You
- send text data to the client-side via a WebSocket as follows:
- client.write_text(data)
- You send binary data as:
- client.write_binary(data)
- """
- pass
-
- def message(self, msg):
- """Receive data from the client.
-
- Any data sent by the client ove the WebSocket will be passed into
- this method.
- """
- print('unhandled message', msg)
-
- def finish(self):
- """Close this Component"""
- pass
-
- def add_nengo_objects(self, page):
- """Add or modify the nengo model before build.
-
- Components may need to modify the underlying nengo.Network by adding
- Nodes and Connections or modifying the structure in other ways.
- This method will be called for all Components just before the build
- phase.
- """
- pass
-
- def remove_nengo_objects(self, page):
- """Undo the effects of add_nengo_objects.
-
- After the build is complete, remove the changes to the nengo.Network
- so that it is all set to be built again in the future.
- """
- pass
-
- def javascript_config(self, cfg):
- """Convert the nengo.Config information into javascript.
-
- This is needed so we can send that config information to the client.
- """
- for attr in self.config._clsparams.params:
- if attr in cfg:
- raise AttributeError("Value for %s is already set in the "
- "config of this component. Do not try to "
- "modify it via this function. Instead "
- "modify the config directly." % (attr))
- else:
- cfg[attr] = getattr(self.config, attr)
- return json.dumps(cfg)
-
- def code_python(self, uids):
- """Generate Python code for this Component.
-
- This is used in the .cfg file to generate a valid Python expression
- that re-creates this Component.
-
- The input uids is a dictionary from Python objects to strings that
- refer to those Python objects (the reverse of the locals() dictionary)
- """
- args = self.code_python_args(uids)
- name = self.__class__.__name__
- return 'nengo_gui.components.%s(%s)' % (name, ','.join(args))
-
- def code_python_args(self, uids):
- """Return a list of strings giving the constructor arguments.
-
- This is used by code_python to re-create the Python string that
- generated this Component, so it can be saved in the .cfg file.
-
- The input uids is a dictionary from Python objects to strings that
- refer to those Python objects (the reverse of the locals() dictionary)
- """
- return []
diff --git a/nengo_gui/components/connection.py b/nengo_gui/components/connection.py
new file mode 100644
index 00000000..faa8e369
--- /dev/null
+++ b/nengo_gui/components/connection.py
@@ -0,0 +1,56 @@
+import nengo
+
+from .base import Component
+
+
+class Connection(Component):
+
+ # TODO: would be nice to not have to get namefinder here
+ def __init__(self, client, obj, uid, namefinder,
+ pos=None, label_visible=True):
+ super(Connection, self).__init__(
+ client, obj, uid, pos=pos, label_visible=label_visible)
+ self.pre = self._get_pre(self.obj)
+ self.post = self._get_post(self.obj)
+ self.pre_uid = namefinder[self.pre]
+ self.post_uid = namefinder[self.post]
+
+ def create(self):
+ # TODO: figure out args to pass to this
+ self.client.send("netgraph.create_connection",
+ pre=self.pre_uid,
+ post=self.post_uid)
+
+ def update(self, other):
+ super(Connection, self).update(other)
+ if self.pre_uid != other.pre_uid or self.post_uid != other.post_uid:
+ self.client.send("%s.reconnect" % self.uid,
+ pre=self.pre_uid, post=self.post_uid)
+
+ # if the connection has changed, tell javascript
+ # pres = self.get_parents(
+ # other.pre,
+ # default_labels=new_name_finder.known_name)[:-1]
+ # posts = self.get_parents(
+ # other.post,
+ # default_labels=new_name_finder.known_name)[:-1]
+ # self.to_be_sent.append(dict(
+ # type='reconnect', uid=uid,
+ # pres=pres, posts=posts))
+ # return True
+
+ @staticmethod
+ def _get_pre(conn):
+ pre = conn.pre_obj
+ if isinstance(pre, nengo.ensemble.Neurons):
+ pre = pre.ensemble
+ return pre
+
+ @staticmethod
+ def _get_post(conn):
+ post = conn.post_obj
+ if isinstance(post, nengo.connection.LearningRule):
+ post = post.connection.post_obj
+ if isinstance(post, nengo.ensemble.Neurons):
+ post = post.ensemble
+ return post
diff --git a/nengo_gui/components/editor.py b/nengo_gui/components/editor.py
deleted file mode 100644
index c90e92fb..00000000
--- a/nengo_gui/components/editor.py
+++ /dev/null
@@ -1,24 +0,0 @@
-from nengo_gui.components.component import Component
-
-
-class Editor(Component):
- config_defaults = {}
-
- def __init__(self):
- # the IPython integration requires this component to be early
- # in the list
- super(Editor, self).__init__(component_order=-8)
-
- def update_code(self, msg):
- pass
-
- def javascript(self):
- return 'Nengo.disable_editor();'
-
-
-class NoEditor(Editor):
- def __init__(self):
- super(NoEditor, self).__init__()
-
- def message(self, msg):
- pass
diff --git a/nengo_gui/components/ensemble.py b/nengo_gui/components/ensemble.py
new file mode 100644
index 00000000..d774f99c
--- /dev/null
+++ b/nengo_gui/components/ensemble.py
@@ -0,0 +1,25 @@
+from .base import Component
+
+
+class Ensemble(Component):
+
+ @property
+ def dimensions(self):
+ return self.obj.dimensions
+
+ @property
+ def n_neurons(self):
+ return self.obj.n_neurons
+
+ def create(self):
+ self.client.send("netgraph.create_ensemble",
+ uid=self.uid,
+ label=self.label,
+ pos=self.pos,
+ dimensions=self.dimensions,
+ labelVisible=self.label_visible)
+
+ def similar(self, other):
+ return (super(Ensemble, self).similar(other)
+ and self.dimensions == other.dimensions
+ and self.n_neurons == other.n_neurons)
diff --git a/nengo_gui/components/htmlview.py b/nengo_gui/components/htmlview.py
index b7486c79..1741afad 100644
--- a/nengo_gui/components/htmlview.py
+++ b/nengo_gui/components/htmlview.py
@@ -1,45 +1,41 @@
-import collections
-
-from nengo_gui.components.component import Component
+from .base import Component
class HTMLView(Component):
- """Arbitrary HTML display taking input from a Node
-
- See nengo_gui/examples/basics/html.py for example usage"""
-
- def __init__(self, obj):
- super(HTMLView, self).__init__()
- self.obj = obj
- self.obj_output = obj.output
- self.data = collections.deque()
-
- def attach(self, page, config, uid):
- super(HTMLView, self).attach(page, config, uid)
- self.label = page.get_label(self.obj)
-
- def add_nengo_objects(self, page):
- with page.model:
- self.obj.output = self.gather_data
-
- def remove_nengo_objects(self, page):
- self.obj.output = self.obj_output
-
- def gather_data(self, t, *x):
- value = self.obj_output(t, *x)
- data = '%g %s' % (t, self.obj_output._nengo_html_)
- self.data.append(data)
- return value
-
- def update_client(self, client):
- while len(self.data) > 0:
- item = self.data.popleft()
- client.write_text(item)
-
- def javascript(self):
- info = dict(uid=id(self), label=self.label)
- json = self.javascript_config(info)
- return 'new Nengo.HTMLView(main, sim, %s);' % json
-
- def code_python_args(self, uids):
- return [uids[self.obj]]
+ """Arbitrary HTML display taking input from a Node.
+
+ See nengo_gui/examples/basics/html.py for example usage.
+
+ Note that, because the HTML to send across the websocket is text, this
+ component is not a widget as it does not benefit from a fast binary
+ websocket connection.
+ """
+
+ def __init__(self, client, obj, uid, pos=None, label_visible=True):
+ super(HTMLView, self).__init__(
+ client, obj, uid, pos=pos, label_visible=label_visible)
+ self._old_output = None
+
+ def add_nengo_objects(self, model):
+
+ def send_to_client(t, *x):
+ value = self._old_output(t, *x)
+ self.client.send("%s.html" % (self.uid,),
+ t=t, html=self._old_output._nengo_html_)
+ return value
+
+ self._old_output = self.obj.output
+ self.obj.output = send_to_client
+
+ def create(self):
+ self.client.send("netgraph.create_htmlview",
+ uid=self.uid,
+ pos=self.pos,
+ label=self.label,
+ labelVisible=self.label_visible,
+ dimensions=1, # TODO
+ syanpse=0.005) # TODO
+
+ def remove_nengo_objects(self, network):
+ self.obj.output = self._old_output
+ self._old_output = None
diff --git a/nengo_gui/components/netgraph.py b/nengo_gui/components/netgraph.py
deleted file mode 100644
index efebcbf2..00000000
--- a/nengo_gui/components/netgraph.py
+++ /dev/null
@@ -1,580 +0,0 @@
-import time
-import os
-import traceback
-import collections
-import threading
-
-import nengo
-from nengo import spa
-import json
-
-from nengo_gui.components.component import Component
-from nengo_gui.components.value import Value
-from nengo_gui.components.slider import OverriddenOutput
-from nengo_gui.modal_js import infomodal
-import nengo_gui.user_action
-import nengo_gui.layout
-
-class NetGraph(Component):
- """Handles computations and communications for NetGraph on the JS side.
-
- Communicates to all NetGraph components for creation, deletion and
- manipulation.
- """
-
- config_defaults = {}
- configs = {}
-
- def __init__(self):
- # this component must be ordered before all the normal graphs (so that
- # other graphs are on top of the NetGraph), so its
- # order is between that of SimControl and the default (0)
- super(NetGraph, self).__init__(component_order=-5)
-
- # this lock ensures safety between check_for_reload() and update_code()
- self.code_lock = threading.Lock()
- self.new_code = None
-
- self.uids = {}
- self.parents = {}
- self.initialized_pan_and_zoom = False
-
- def attach(self, page, config, uid):
- super(NetGraph, self).attach(page, config, uid)
- self.layout = nengo_gui.layout.Layout(self.page.model)
- self.to_be_expanded = collections.deque([self.page.model])
- self.to_be_sent = collections.deque()
-
- self.networks_to_search = [self.page.model]
-
- try:
- self.last_modify_time = os.path.getmtime(self.page.filename)
- except OSError:
- self.last_modify_time = None
- except TypeError: # happens if self.filename is None
- self.last_modify_time = None
- self.last_reload_check = time.time()
-
- def check_for_reload(self):
- if self.page.filename is not None:
- try:
- t = os.path.getmtime(self.page.filename)
- if self.last_modify_time is None or self.last_modify_time < t:
- self.reload()
- self.last_modify_time = t
- except OSError:
- pass
-
- with self.code_lock:
- new_code = self.new_code
- # the lock is in case update_code() is called between these lines
- self.new_code = None
-
- if new_code is not None:
- self.reload(code=new_code)
-
- def update_code(self, code):
- """Set new version of code to display."""
- with self.code_lock:
- self.new_code = code
-
- def reload(self, code=None):
- """Called when new code has been detected
- checks that the page is not currently being used
- and thus can be updated"""
- with self.page.lock:
- self._reload(code=code)
-
- def _reload(self, code=None):
- """Loads and executes the code, removing old items,
- updating changed items
- and adding new ones"""
-
- old_locals = self.page.last_good_locals
- old_default_labels = self.page.default_labels
-
- if code is None:
- with open(self.page.filename) as f:
- code = f.read()
- if self.page.code == code:
- # don't re-execute the identical code
- return
- else:
- # send the new code to the client
- self.page.editor.update_code(code)
-
- self.page.execute(code)
-
- if self.page.error is not None:
- return
-
- name_finder = nengo_gui.NameFinder(self.page.locals, self.page.model)
-
- self.networks_to_search = [self.page.model]
- self.parents = {}
-
- removed_uids = {}
- rebuilt_objects = []
-
- # for each item in the old model, find the matching new item
- # for Nodes, Ensembles, and Networks, this means to find the item
- # with the same uid. For Connections, we don't really have a uid,
- # so we use the uids of the pre and post objects.
- for uid, old_item in nengo.utils.compat.iteritems(dict(self.uids)):
- try:
- new_item = eval(uid, self.page.locals)
- except:
- new_item = None
-
- # check to make sure the new item's uid is the same as the
- # old item. This is to catch situations where an old uid
- # happens to still refer to something in the new model, but that's
- # not the normal uid for that item. For example, the uid
- # "ensembles[0]" might still refer to something even after that
- # ensemble is removed.
- new_uid = self.page.get_uid(new_item,
- default_labels=name_finder.known_name)
- if new_uid != uid:
- new_item = None
-
- same_class = False
- for cls in [nengo.Ensemble, nengo.Node, nengo.Network, nengo.Connection]:
- if isinstance(new_item, cls) and isinstance(old_item, cls):
- same_class = True
- break
-
- # find reasons to delete the object. Any deleted object will
- # be recreated, so try to keep this to a minimum
- keep_object = True
- if new_item is None:
- keep_object = False
- elif not same_class:
- # don't allow changing classes
- keep_object = False
- elif self.get_extra_info(new_item) != self.get_extra_info(old_item):
- keep_object = False
-
- if not keep_object:
- self.to_be_sent.append(dict(
- type='remove', uid=uid))
- del self.uids[uid]
- removed_uids[old_item] = uid
- rebuilt_objects.append(uid)
- else:
- # fix aspects of the item that may have changed
- if self._reload_update_item(uid, old_item, new_item,
- name_finder):
- # something has changed about this object, so rebuild
- # the components that use it
- rebuilt_objects.append(uid)
-
- self.uids[uid] = new_item
-
- self.to_be_expanded.append(self.page.model)
-
- self.page.name_finder = name_finder
- self.page.default_labels = name_finder.known_name
- self.page.config = self.page.load_config()
- self.page.uid_prefix_counter = {}
- self.layout = nengo_gui.layout.Layout(self.page.model)
- self.page.code = code
-
- orphan_components = []
- rebuild_components = []
-
- # items that are shown in components, but not currently displayed
- # in the NetGraph (i.e. things that are inside collapsed
- # Networks, but whose values are being shown in a graph)
- collapsed_items = []
-
- # remove graphs no longer associated to NetgraphItems
- removed_items = list(removed_uids.values())
- for c in self.page.components[:]:
- for item in c.code_python_args(old_default_labels):
- if item not in self.uids.keys() and item not in collapsed_items:
-
- # item is a python string that is an argument to the
- # constructor for the Component. So it could be 'a',
- # 'model.ensembles[3]', 'True', or even 'target=a'.
- # We need to evaluate this string in the context of the
- # locals dictionary and see what object it refers to
- # so we can determine whether to rebuild this component.
- #
- # The following lambda should do this, handling both
- # the normal argument case and the keyword argument case.
- safe_eval = ('(lambda *a, **b: '
- 'list(a) + list(b.values()))(%s)[0]')
-
- # this Component depends on an item inside a collapsed
- # Network, so we need to check if that component has
- # changed or been removed
- old_obj = eval(safe_eval % item, old_locals)
-
- try:
- new_obj = eval(safe_eval % item, self.page.locals)
- except:
- # the object this Component depends on no longer exists
- new_obj = None
-
- if new_obj is None:
- removed_items.append(item)
- elif not isinstance(new_obj, old_obj.__class__):
- rebuilt_objects.append(item)
- elif (self.get_extra_info(new_obj) !=
- self.get_extra_info(old_obj)):
- rebuilt_objects.append(item)
-
- # add this to the list of collapsed items, so we
- # don't recheck it if there's another Component that
- # also depends on this
- collapsed_items.append(item)
-
- if item in rebuilt_objects:
- self.to_be_sent.append(dict(type='delete_graph',
- uid=c.original_id,
- notify_server=False))
- rebuild_components.append(c.uid)
- self.page.components.remove(c)
- break
- else:
- for item in c.code_python_args(old_default_labels):
- if item in removed_items:
- self.to_be_sent.append(dict(type='delete_graph',
- uid=c.original_id,
- notify_server=False))
- orphan_components.append(c)
- break
-
- components = []
- # the old names for the old components
- component_uids = [c.uid for c in self.page.components]
-
- for name, obj in list(self.page.locals.items()):
- if isinstance(obj, Component):
- # the object has been removed, so the Component should
- # be removed as well
- if obj in orphan_components:
- continue
-
- # this is a Component that was previously removed,
- # but is still in the config file, or it has to be
- # rebuilt, so let's recover it
- if name not in component_uids:
- self.page.add_component(obj)
- self.to_be_sent.append(dict(type='js',
- code=obj.javascript()))
- components.append(obj)
- continue
-
- # otherwise, find the corresponding old Component
- index = component_uids.index(name)
- old_component = self.page.components[index]
- if isinstance(obj, (nengo_gui.components.SimControlTemplate,
- nengo_gui.components.AceEditorTemplate,
- nengo_gui.components.NetGraphTemplate)):
- # just keep these ones
- components.append(old_component)
- else:
- # replace these components with the newly generated ones
- try:
- self.page.add_component(obj)
- old_component.replace_with = obj
- obj.original_id = old_component.original_id
- except:
- traceback.print_exc()
- print('failed to recreate plot for %s' % obj)
- components.append(obj)
-
- components.sort(key=lambda x: x.component_order)
-
- self.page.components = components
-
- # notifies SimControl to pause the simulation
- self.page.changed = True
-
- def _reload_update_item(self, uid, old_item, new_item, new_name_finder):
- """Tell the client about changes to the item due to reload."""
- changed = False
- if isinstance(old_item, (nengo.Node,
- nengo.Ensemble,
- nengo.Network)):
- old_label = self.page.get_label(old_item)
- new_label = self.page.get_label(
- new_item, default_labels=new_name_finder.known_name)
-
- if old_label != new_label:
- self.to_be_sent.append(dict(
- type='rename', uid=uid, name=new_label))
- changed = True
- if isinstance(old_item, nengo.Network):
- if self.page.config[old_item].expanded:
- self.to_be_expanded.append(new_item)
- changed = True
-
- elif isinstance(old_item, nengo.Connection):
- old_pre = old_item.pre_obj
- old_post = old_item.post_obj
- new_pre = new_item.pre_obj
- new_post = new_item.post_obj
- if isinstance(old_pre, nengo.ensemble.Neurons):
- old_pre = old_pre.ensemble
- if isinstance(old_post, nengo.connection.LearningRule):
- old_post = old_post.connection.post_obj
- if isinstance(old_post, nengo.ensemble.Neurons):
- old_post = old_post.ensemble
- if isinstance(new_pre, nengo.ensemble.Neurons):
- new_pre = new_pre.ensemble
- if isinstance(new_post, nengo.connection.LearningRule):
- new_post = new_post.connection.post_obj
- if isinstance(new_post, nengo.ensemble.Neurons):
- new_post = new_post.ensemble
-
- old_pre = self.page.get_uid(old_pre)
- old_post = self.page.get_uid(old_post)
- new_pre = self.page.get_uid(
- new_pre, default_labels=new_name_finder.known_name)
- new_post = self.page.get_uid(
- new_post, default_labels=new_name_finder.known_name)
-
- if new_pre != old_pre or new_post != old_post:
- # if the connection has changed, tell javascript
- pres = self.get_parents(
- new_pre,
- default_labels=new_name_finder.known_name)[:-1]
- posts = self.get_parents(
- new_post,
- default_labels=new_name_finder.known_name)[:-1]
- self.to_be_sent.append(dict(
- type='reconnect', uid=uid,
- pres=pres, posts=posts))
- changed = True
- return changed
-
- def get_parents(self, uid, default_labels=None):
- while uid not in self.parents:
- net = self.networks_to_search.pop(0)
- net_uid = self.page.get_uid(net, default_labels=default_labels)
- for n in net.nodes:
- n_uid = self.page.get_uid(n, default_labels=default_labels)
- self.parents[n_uid] = net_uid
- for e in net.ensembles:
- e_uid = self.page.get_uid(e, default_labels=default_labels)
- self.parents[e_uid] = net_uid
- for n in net.networks:
- n_uid = self.page.get_uid(n, default_labels=default_labels)
- self.parents[n_uid] = net_uid
- self.networks_to_search.append(n)
- parents = [uid]
- while parents[-1] in self.parents:
- parents.append(self.parents[parents[-1]])
- return parents
-
- def modified_config(self):
- self.page.modified_config()
-
- def update_client(self, client):
- now = time.time()
- if now > self.last_reload_check + 0.5:
- self.check_for_reload()
- self.last_reload_check = now
-
- if not self.initialized_pan_and_zoom:
- self.send_pan_and_zoom(client)
- self.initialized_pan_and_zoom = True
-
- while len(self.to_be_sent) > 0:
- info = self.to_be_sent.popleft()
- client.write_text(json.dumps(info))
-
- if len(self.to_be_expanded) > 0:
- with self.page.lock:
- network = self.to_be_expanded.popleft()
- self.expand_network(network, client)
-
- def javascript(self):
- return 'new Nengo.NetGraph(main, {uid:"%s"});' % id(self)
-
- def message(self, msg):
- try:
- info = json.loads(msg)
- except ValueError:
- print('invalid message', repr(msg))
- return
- action = info.get('act', None)
- undo = info.get('undo', None)
- if action is not None:
- del info['act']
- if action in ('auto_expand', 'auto_collapse'):
- getattr(self, 'act_' + action[5:])(**info)
- elif action in ('pan', 'zoom', 'create_modal'):
- # These should not use the undo stack
- getattr(self, 'act_' + action)(**info)
- else:
- act = nengo_gui.user_action.create_action(action, self, **info)
- self.page.undo_stack.append([act])
- del self.page.redo_stack[:]
- elif undo is not None:
- if undo == '1':
- self.undo()
- else:
- self.redo()
- else:
- print('received message', msg)
-
- def undo(self):
- if self.page.undo_stack:
- action = self.page.undo_stack.pop()
- re = []
- for act in action:
- act.undo()
- re.insert(0, act)
- self.page.redo_stack.append(re)
-
- def redo(self):
- if self.page.redo_stack:
- action = self.page.redo_stack.pop()
- un = []
- for act in action:
- act.apply()
- un.insert(0, act)
- self.page.undo_stack.append(un)
-
- def act_expand(self, uid):
- net = self.uids[uid]
- self.to_be_expanded.append(net)
- self.page.config[net].expanded = True
- self.modified_config()
-
- def act_collapse(self, uid):
- net = self.uids[uid]
- self.page.config[net].expanded = False
- self.remove_uids(net)
- self.modified_config()
-
- def remove_uids(self, net):
- for items in [net.ensembles, net.networks, net.nodes, net.connections]:
- for item in items:
- uid = self.page.get_uid(item)
- if uid in self.uids:
- del self.uids[uid]
- for n in net.networks:
- self.remove_uids(n)
-
- def act_pan(self, x, y):
- self.page.config[self.page.model].pos = x, y
- self.modified_config()
-
- def act_zoom(self, scale, x, y):
- self.page.config[self.page.model].size = scale, scale
- self.page.config[self.page.model].pos = x, y
- self.modified_config()
-
- def act_create_modal(self, uid, **info):
- js = infomodal(self, uid, **info)
- self.to_be_sent.append(dict(type='js', code=js))
-
- def expand_network(self, network, client):
- if not self.page.config[network].has_layout:
- pos = self.layout.make_layout(network)
- for obj, layout in pos.items():
- self.page.config[obj].pos = layout['y'], layout['x']
- self.page.config[obj].size = layout['h'] / 2, layout['w'] / 2
- self.page.config[network].has_layout = True
-
- if network is self.page.model:
- parent = None
- else:
- parent = self.page.get_uid(network)
- for ens in network.ensembles:
- self.create_object(client, ens, type='ens', parent=parent)
- for node in network.nodes:
- self.create_object(client, node, type='node', parent=parent)
- for net in network.networks:
- self.create_object(client, net, type='net', parent=parent)
- for conn in network.connections:
- self.create_connection(client, conn, parent=parent)
- self.page.config[network].expanded = True
-
- def create_object(self, client, obj, type, parent):
- uid = self.page.get_uid(obj)
- if uid in self.uids:
- return
-
- pos = self.page.config[obj].pos
- if pos is None:
- import random
- pos = random.uniform(0, 1), random.uniform(0, 1)
- self.page.config[obj].pos = pos
- size = self.page.config[obj].size
- if size is None:
- size = (0.1, 0.1)
- self.page.config[obj].size = size
- label = self.page.get_label(obj)
- self.uids[uid] = obj
- info = dict(uid=uid, label=label, pos=pos, type=type, size=size,
- parent=parent)
- if type == 'net':
- info['expanded'] = self.page.config[obj].expanded
- info.update(self.get_extra_info(obj))
-
- client.write_text(json.dumps(info))
-
- def get_extra_info(self, obj):
- '''Determine helper information for each nengo object.
-
- This is used by the client side to configure the display. It is also
- used by the reload() code to determine if a NetGraph object should
- be recreated.
- '''
- info = {}
- if isinstance(obj, nengo.Node):
- if obj.output is None or (
- isinstance(obj.output, OverriddenOutput)
- and obj.output.base_output is None):
- info['passthrough'] = True
- if callable(obj.output) and hasattr(obj.output, '_nengo_html_'):
- info['html'] = True
- info['dimensions'] = int(obj.size_out)
- elif isinstance(obj, nengo.Ensemble):
- info['dimensions'] = int(obj.size_out)
- info['n_neurons'] = int(obj.n_neurons)
- elif Value.default_output(obj) is not None:
- info['default_output'] = True
-
- info['sp_targets'] = (
- nengo_gui.components.spa_plot.SpaPlot.applicable_targets(obj))
- return info
-
- def send_pan_and_zoom(self, client):
- pan = self.page.config[self.page.model].pos
- if pan is None:
- pan = 0, 0
- zoom = self.page.config[self.page.model].size
- if zoom is None:
- zoom = 1.0
- else:
- zoom = zoom[0]
- client.write_text(json.dumps(dict(type='pan', pan=pan)))
- client.write_text(json.dumps(dict(type='zoom', zoom=zoom)))
-
- def create_connection(self, client, conn, parent):
- uid = self.page.get_uid(conn)
- if uid in self.uids:
- return
- pre = conn.pre_obj
- if isinstance(pre, nengo.ensemble.Neurons):
- pre = pre.ensemble
- post = conn.post_obj
- if isinstance(post, nengo.connection.LearningRule):
- post = post.connection.post
- if isinstance(post, nengo.base.ObjView):
- post = post.obj
- if isinstance(post, nengo.ensemble.Neurons):
- post = post.ensemble
- pre = self.page.get_uid(pre)
- post = self.page.get_uid(post)
- self.uids[uid] = conn
- pres = self.get_parents(pre)[:-1]
- posts = self.get_parents(post)[:-1]
- info = dict(uid=uid, pre=pres, post=posts, type='conn', parent=parent)
- client.write_text(json.dumps(info))
diff --git a/nengo_gui/components/network.py b/nengo_gui/components/network.py
new file mode 100644
index 00000000..d9199de8
--- /dev/null
+++ b/nengo_gui/components/network.py
@@ -0,0 +1,37 @@
+from nengo.spa.module import Module
+
+from .base import Component
+
+
+# TODO: has_layout?
+class Network(Component):
+
+ def __init__(self, client, obj, uid,
+ pos=None, label_visible=True,
+ expanded=False, has_layout=False):
+ super(Network, self).__init__(client, obj, uid, pos, label_visible)
+ self.expanded = expanded
+ self.has_layout = has_layout
+
+ @property
+ def output(self):
+ """Used in value plots"""
+ if isinstance(self.obj, Module) and "default" in self.obj.outputs:
+ return self.obj.outputs["default"][0]
+ elif hasattr(self.obj, "output"):
+ return self.obj.output
+ return None
+
+ def create(self):
+ # TODO: figure out args to pass to this
+ self.client.send("netgraph.create_network",
+ uid=self.uid,
+ pos=self.pos,
+ label=self.label,
+ labelVisible=self.label_visible,
+ dimensions=1, # TODO
+ expanded=self.expanded)
+
+ def similar(self, other):
+ return (super(Network, self).similar(other)
+ and self.output == other.output)
diff --git a/nengo_gui/components/node.py b/nengo_gui/components/node.py
new file mode 100644
index 00000000..71bde0bd
--- /dev/null
+++ b/nengo_gui/components/node.py
@@ -0,0 +1,35 @@
+from .base import Component
+from .slider import OverriddenOutput
+
+
+class Node(Component):
+
+ @property
+ def dimensions(self):
+ return self.obj.size_out
+
+ @property
+ def html(self):
+ return (callable(self.obj.output)
+ and hasattr(self.obj.output, '_nengo_html_'))
+
+ @property
+ def passthrough(self):
+ return self.obj.output is None or (
+ isinstance(self.obj.output, OverriddenOutput)
+ and self.obj.output.base_output is None)
+
+ def create(self):
+ # TODO: differentiate passthrough from normal?
+ self.client.send("netgraph.create_node",
+ uid=self.uid,
+ pos=self.pos,
+ label=self.label,
+ labelVisible=self.label_visible,
+ dimensions=self.dimensions)
+
+ def similar(self, other):
+ return (super(Node, self).similar(other)
+ and self.dimensions == other.dimensions
+ and self.passthrough == other.passthrough
+ and self.html == other.html)
diff --git a/nengo_gui/components/pointer.py b/nengo_gui/components/pointer.py
deleted file mode 100644
index 6457c236..00000000
--- a/nengo_gui/components/pointer.py
+++ /dev/null
@@ -1,112 +0,0 @@
-import copy
-import itertools
-
-import nengo
-import nengo.spa as spa
-import numpy as np
-
-from nengo_gui.components.component import Component
-from nengo_gui.components.spa_plot import SpaPlot
-
-
-class Pointer(SpaPlot):
- """Server side component for the Semantic Pointer Cloud"""
-
- config_defaults = dict(show_pairs=False, **Component.config_defaults)
-
- def __init__(self, obj, **kwargs):
- super(Pointer, self).__init__(obj, **kwargs)
-
- # the semantic pointer value as set by the user in the GUI
- # a value of 'None' means do not override
- self.override_target = None
-
- # The white list indicates the networks whose user-defined
- # over-ride value can be inserted on the input
- # thus the value loops in from the output to the input.
- # All other networks have their value inserted on the output.
- # Looping-in has the advantage of actually changing the
- # neural activity of the population, rather than just changing
- # the output.
- self.loop_in_whitelist = [spa.Buffer, spa.Memory, spa.State]
-
- self.node = None
- self.conn1 = None
- self.conn2 = None
-
- def add_nengo_objects(self, page):
- with page.model:
- output = self.obj.outputs[self.target][0]
- self.node = nengo.Node(self.gather_data,
- size_in=self.vocab_out.dimensions,
- size_out=self.vocab_out.dimensions)
- self.conn1 = nengo.Connection(output, self.node, synapse=0.01)
- loop_in = type(self.obj) in self.loop_in_whitelist
- if loop_in and self.target == 'default':
- input = self.obj.inputs[self.target][0]
- self.conn2 = nengo.Connection(self.node, input, synapse=0.01)
- else:
- self.conn2 = nengo.Connection(self.node, output, synapse=0.01)
-
- def remove_nengo_objects(self, page):
- page.model.connections.remove(self.conn1)
- page.model.connections.remove(self.conn2)
- page.model.nodes.remove(self.node)
-
- def gather_data(self, t, x):
- vocab = self.vocab_out
- key_similarities = np.dot(vocab.vectors, x)
- over_threshold = key_similarities > 0.01
- matches = zip(key_similarities[over_threshold],
- np.array(vocab.keys)[over_threshold])
- if self.config.show_pairs:
- self.vocab_out.include_pairs = True
- pair_similarities = np.dot(vocab.vector_pairs, x)
- over_threshold = pair_similarities > 0.01
- pair_matches = zip(pair_similarities[over_threshold],
- np.array(vocab.key_pairs)[over_threshold])
- matches = itertools.chain(matches, pair_matches)
-
- text = ';'.join(['%0.2f%s' % ( min(sim, 9.99), key) for sim, key in matches])
-
- # msg sent as a string due to variable size of pointer names
- msg = '%g %s' % (t, text)
- self.data.append(msg)
- if self.override_target is None:
- return np.zeros(self.vocab_out.dimensions)
- else:
- v = (self.override_target.v - x) * 3
- return v
-
- def update_client(self, client):
- while len(self.data) > 0:
- data = self.data.popleft()
- client.write_text(data)
-
- def javascript(self):
- info = dict(uid=id(self), label=self.label)
- json = self.javascript_config(info)
- return 'new Nengo.Pointer(main, sim, %s);' % json
-
- def code_python_args(self, uids):
- return [uids[self.obj], 'target=%r' % self.target]
-
- def message(self, msg):
- if msg == ':empty:':
- self.override_target = None
- elif msg[0:12] == ':check only:':
- if len(msg) == 12:
- self.data.append("good_pointer")
- else:
- vocab = copy.deepcopy(self.vocab_out)
- try:
- vocab.parse(msg[12:])
- self.data.append("good_pointer")
- except:
- self.data.append("bad_pointer")
- else:
- # The message value is the new value for the output of the pointer
- try:
- self.override_target = self.vocab_out.parse(msg)
- except:
- self.override_target = None
diff --git a/nengo_gui/components/raster.py b/nengo_gui/components/raster.py
index 2d46623f..c749d170 100644
--- a/nengo_gui/components/raster.py
+++ b/nengo_gui/components/raster.py
@@ -1,76 +1,73 @@
-import struct
-import collections
-
import nengo
import numpy as np
-from nengo_gui.components.component import Component
+from ..client import bind
+from .base import Widget
-class Raster(Component):
+class Raster(Widget):
"""Plot showing spike events over time."""
- config_defaults = dict(n_neurons=10,
- **Component.config_defaults)
+ def __init__(self, client, obj, uid,
+ n_neurons=10, pos=None, label_visible=True):
+ super(Raster, self).__init__(
+ client, obj, uid, pos=pos, label_visible=label_visible)
+
+ self.chosen = None # Filled in when n_neurons set
- def __init__(self, obj):
- super(Raster, self).__init__()
- self.neuron_type = obj.neuron_type
- self.obj = obj.neurons
- self.data = collections.deque()
- self.max_neurons = obj.n_neurons
+ self.n_neurons = n_neurons
self.conn = None
self.node = None
- self.chosen = None
+ @property
+ def max_neurons(self):
+ return self.obj.n_neurons
+
+ @property
+ def n_neurons(self):
+ return self._n_neurons
+
+ @n_neurons.setter
+ @bind("{self.uid}.n_neurons")
+ def n_neurons(self, n_neurons):
+ self._n_neurons = min(n_neurons, self.max_neurons)
+ self.chosen = np.linspace(
+ 0, self.max_neurons-1, self._n_neurons).astype(int)
+ @property
+ def neurons(self):
+ return self.obj.neurons
- def attach(self, page, config, uid):
- super(Raster, self).attach(page, config, uid)
- self.label = page.get_label(self.obj.ensemble)
+ @property
+ def neuron_type(self):
+ return self.obj.neuron_type
- def add_nengo_objects(self, page):
- with page.model:
- self.node = nengo.Node(self.gather_data, size_in=self.max_neurons)
+ def add_nengo_objects(self, model):
+
+ def fast_send_to_client(t, x):
+ indices = np.nonzero(x[self.chosen])[0]
+ self.fast_client.send(np.hstack((t, indices)))
+
+ with model:
+ self.node = nengo.Node(fast_send_to_client,
+ size_in=self.max_neurons,
+ size_out=0)
if 'spikes' in self.neuron_type.probeable:
- self.conn = nengo.Connection(self.obj, self.node, synapse=None)
-
- def remove_nengo_objects(self, page):
- page.model.nodes.remove(self.node)
- if 'spikes' in self.neuron_type.probeable:
- page.model.connections.remove(self.conn)
-
- def gather_data(self, t, x):
- if self.chosen is None:
- self.compute_chosen_neurons()
- indices = np.nonzero(x[self.chosen])[0]
- data = struct.pack(' 0:
- data = self.data.popleft()
- client.write_binary(data)
-
- def javascript(self):
- info = dict(uid=id(self), label=self.label,
- max_neurons=self.max_neurons)
- json = self.javascript_config(info)
- return 'new Nengo.Raster(main, sim, %s);' % json
-
- def code_python_args(self, uids):
- return [uids[self.obj.ensemble]]
-
- def message(self, msg):
- if msg.startswith('n_neurons:'):
- n_neurons = min(int(msg[10:]), self.max_neurons)
- self.page.config[self].n_neurons = n_neurons
- self.compute_chosen_neurons()
- self.page.modified_config()
+ self.conn = nengo.Connection(
+ self.neurons, self.node, synapse=None)
+
+ def create(self):
+ self.client.send("netgraph.create_raster",
+ uid=self.uid,
+ pos=self.pos,
+ label=self.label,
+ labelVisible=self.label_visible,
+ nNeurons=self.n_neurons)
+
+ def remove_nengo_objects(self, model):
+ model.nodes.remove(self.node)
+ self.node = None
+ if self.conn is not None:
+ model.connections.remove(self.conn)
+ self.conn = None
diff --git a/nengo_gui/components/sim_control.py b/nengo_gui/components/sim_control.py
deleted file mode 100644
index b05a80c1..00000000
--- a/nengo_gui/components/sim_control.py
+++ /dev/null
@@ -1,221 +0,0 @@
-import time
-import timeit
-import struct
-
-import numpy as np
-import nengo
-import json
-
-from nengo_gui.components.component import Component
-import nengo_gui.exec_env
-from nengo_gui.server import WebSocketFrame
-
-
-class SimControl(Component):
- """Controls simulation via control node embedded in the neural model.
-
- Also instantiates and communicates with the SimControl and the Toolbar
- on the JavaScript side, which includes the task of back-end selection."""
-
- config_defaults = dict(shown_time=0.5, kept_time=4.0)
-
- def __init__(self, dt=0.001):
- # this component must be the very first one defined, so
- # its component_order is the smallest overall
- super(SimControl, self).__init__(component_order=-10)
- self.paused = True
- self.last_tick = None
- self.rate = 0.0
- self.model_dt = dt
- self.rate_tau = 0.5
- self.last_send_rate = None
- self.sim_ticks = 0
- self.skipped = 1
- self.time = 0.0
- self.last_status = None
- self.next_ping_time = None
- self.send_config_options = False
- self.reset_inform = False
- self.node = None
- self.target_rate = 1.0 # desired speed of simulation
- self.target_scale = None # desired proportion of full speed
- self.delay_time = 0.0 # amount of delay per time step
- self.rate_proportion = 1.0 # current proportion of full speed
- self.smart_sleep_offset = 0.0 # difference from actual sleep time
-
- def attach(self, page, config, uid):
- super(SimControl, self).attach(page, config, uid)
- self.shown_time = config.shown_time
- self.kept_time = config.kept_time
-
- def add_nengo_objects(self, page):
- with page.model:
- self.node = nengo.Node(self.control, size_out=0)
-
- def remove_nengo_objects(self, page):
- page.model.nodes.remove(self.node)
-
- def finish(self):
- self.page.finish()
-
- def control(self, t):
- """Node embedded in the model to control simulation progression.
-
- Sleeps while the simulation is paused.
- """
-
- self.actual_model_dt = t - self.time
- self.time = t
- self.sim_ticks += 1
-
- now = timeit.default_timer()
- if self.last_tick is not None:
- dt = now - self.last_tick
- if dt == 0:
- self.skipped += 1
- else:
- rate = self.actual_model_dt * self.skipped / dt
- decay = np.exp(-dt / self.rate_tau)
- self.rate *= decay
- self.rate += (1 - decay) * rate
- self.skipped = 1
-
- if self.actual_model_dt > 0:
- # compute current proportion of full speed
- self.rate_proportion = 1.0 - ((self.rate * self.delay_time) /
- self.actual_model_dt)
-
- # if we have a desired proportion, use it to control delay_time
- # Note that we need last_tick to not be None so that we have a
- # valid dt value.
- if self.target_scale is not None and self.last_tick is not None:
- s = self.target_scale
- if s <=0:
- self.delay_time = 0.5
- else:
- self.delay_time = (1.0/s - s) * (dt - self.delay_time)
-
- # if we have a desired rate, do a simple P-controller to get there
- if self.target_rate is not None:
- rate_error = self.rate - self.target_rate
- delta = rate_error * 0.0000002
- self.delay_time += delta
-
- self.delay_time = np.clip(self.delay_time, 0, 0.5)
-
- if self.delay_time > 0:
- self.smart_sleep(self.delay_time)
-
- self.last_tick = now
-
- # Sleeps to prevent the simulation from advancing
- # while the simulation is paused
- while self.paused and self.page.sim is not None:
- time.sleep(0.01)
- self.last_tick = None
-
- def busy_sleep(self, delay_time):
- now = timeit.default_timer()
- start = now
- while now < start + delay_time:
- now = timeit.default_timer()
-
- def smart_sleep(self, delay_time):
- """Attempt to sleep for an amount of time without a busy loop.
-
- This keeps track of the difference between the requested time.sleep()
- time and the actual amount of time slept, and then subtracts that
- difference from future smart_sleep calls. This should give an
- overall consistent sleep() time even if the actual sleep() time
- is inaccurate.
- """
- t = delay_time + self.smart_sleep_offset
- if t >= 0:
- start = timeit.default_timer()
- time.sleep(t)
- end = timeit.default_timer()
- self.smart_sleep_offset += delay_time - (end - start)
- else:
- self.smart_sleep_offset += delay_time
-
- def config_settings(self, data):
- for i in data:
- print(i)
-
- def update_client(self, client):
- now = time.time()
- # send off a ping now and then so we'll notice when connection closes
- if self.next_ping_time is None or now > self.next_ping_time:
- client.write_frame(WebSocketFrame(
- 1, 0, WebSocketFrame.OP_PING, 0, b''))
- self.next_ping_time = now + 2.0
-
- if self.page.changed:
- self.paused = True
- self.page.sim = None
- self.page.changed = False
- if not self.paused or self.reset_inform:
- client.write_binary(struct.pack(
- ' 0:
- to_client = self.to_client.popleft()
- client.write_binary(to_client)
-
- def message(self, msg):
- index, value = msg.split(',')
- index = int(index)
- if value == 'reset':
- self.from_client[index] = np.nan
- else:
- self.from_client[index] = float(value)
-
- def code_python_args(self, uids):
- return [uids[self.node]]
+ start_value[...] = self.base_output
+ self.client.send("netgraph.create_slider",
+ uid=self.uid,
+ pos=self.pos,
+ label=self.label,
+ labelVisible=self.label_visible,
+ dimensions=self.obj.size_out,
+ synapse=0.005, # TODO
+ startValue=[float(x) for x in start_value])
+
+ def remove_nengo_objects(self, model):
+ # If we're setting the output back to None, clear size_out
+ # to avoid a warning when size_out is automatically set
+ if self.base_output is None:
+ self.obj.size_out = None
+ self.obj.output = self.base_output
+
+ @bind("{self.uid}.forget")
+ def forget(self):
+ # Make sure we're currently running
+ if self.obj.output is not self.base_output:
+ # A bit of a hack, but to forget user-specified values we set all
+ # of the values to nan as nan values are not overridden.
+ nans = np.zeros(self.obj.size_out) * np.nan
+ # Send directly to the fast client
+ self.fast_client.receive(nans.tobytes())
diff --git a/nengo_gui/components/spa.py b/nengo_gui/components/spa.py
new file mode 100644
index 00000000..821cdba8
--- /dev/null
+++ b/nengo_gui/components/spa.py
@@ -0,0 +1,227 @@
+import copy
+
+import numpy as np
+import nengo
+from nengo.spa import Buffer, Memory, State
+
+from ..client import bind
+from .base import Widget
+
+
+class SpaWidget(Widget):
+ def __init__(self, client, obj, uid,
+ target="default", show_pairs=False,
+ pos=None, label_visible=True):
+ super(SpaWidget, self).__init__(client, obj, uid, pos, label_visible)
+ self.target = target
+ self.show_pairs = show_pairs
+
+ @property
+ def keys(self):
+ if self.show_pairs:
+ # TODO: is this needed?
+ # while self.vocab.key_pairs is None:
+ # time.sleep(0.001)
+ return self.vocab.keys + self.vocab.key_pairs
+ else:
+ return self.vocab.keys
+
+ @property
+ def n_lines(self):
+ return len(self.keys)
+
+ @property
+ def show_pairs(self):
+ return self.vocab.include_pairs
+
+ @show_pairs.setter
+ @bind("{self.uid}.show_pairs")
+ def show_pairs(self, val):
+ if val != self.vocab.include_pairs:
+ self.vocab.include_pairs = val
+ keys = self.keys
+ self.client.send("%s.set_keys" % self.uid, keys=keys)
+
+ @property
+ def vectors(self):
+ if self.show_pairs:
+ # TODO: is this needed?
+ # while self.vocab.key_pairs is None:
+ # time.sleep(0.001)
+ return self.vocab.vectors + self.vocab.vectors_pairs
+ else:
+ return self.vocab.vectors
+
+ @property
+ def vocab(self):
+ return self.obj.outputs[self.target][1]
+
+
+class SpaPointer(SpaWidget):
+ """Server side component for the Semantic Pointer Cloud"""
+
+ # This white list indicates the networks whose user-defined
+ # override value can be inserted on the input
+ # thus the value loops in from the output to the input.
+ # All other networks have their value inserted on the output.
+ # Looping-in has the advantage of actually changing the
+ # neural activity of the population, rather than just changing
+ # the output.
+ CAN_LOOP_IN = [Buffer, Memory, State]
+
+ def __init__(self, client, obj, uid,
+ target="default", show_pairs=False, override=None,
+ pos=None, label=None):
+ super(SpaPointer, self).__init__(
+ client, obj, uid, target, show_pairs, pos, label)
+
+ # the semantic pointer value as set by the user in the GUI
+ # a value of 'None' means do not override
+ self.override = override
+
+ self.node = None
+ self.conn1 = None
+ self.conn2 = None
+
+ @bind("{self.uid}.check_target")
+ def check_target(self, target):
+ vocab = copy.deepcopy(self.vocab)
+ try:
+ vocab.parse(target)
+ self.client.send("%s.check_target", ok=True)
+ except:
+ self.client.send("%s.check_target", ok=False)
+
+ @property
+ def override(self):
+ return self._override
+
+ @override.setter
+ @bind("{self.uid}.set_override") # TODO: was set_target
+ def override(self, override):
+ if override is not None:
+ # Add the pointer to the vocab if not yet present
+ override = self.vocab.parse(override)
+ self._override = override
+
+ def add_nengo_objects(self, network):
+
+ def send_to_client(t, x):
+ similarities = np.dot(self.vectors, x)
+ over_threshold = similarities > 0.01
+ matches = zip(similarities[over_threshold],
+ np.array(self.keys)[over_threshold])
+
+ self.client.send("%s.matches" % (self.uid,), data=';'.join(
+ ['%0.2f%s' % (min(sim, 9.99), key) for sim, key in matches]))
+
+ if self.override is None:
+ return np.zeros(self.vocab.dimensions)
+ else:
+ # TODO: Why - x and * 3??
+ return (self.override.v - x) * 3
+
+ with network:
+ output = self.obj.outputs[self.target][0]
+ self.node = nengo.Node(send_to_client,
+ size_in=self.vocab.dimensions,
+ size_out=self.vocab.dimensions)
+ self.conn1 = nengo.Connection(output, self.node, synapse=0.01)
+ loop_in = type(self.obj) in self.CAN_LOOP_IN
+ if loop_in and self.target == 'default':
+ input = self.obj.inputs[self.target][0]
+ self.conn2 = nengo.Connection(self.node, input, synapse=0.01)
+ else:
+ self.conn2 = nengo.Connection(self.node, output, synapse=0.01)
+
+ def create(self):
+ self.client.send("netgraph.create_spa_pointer",
+ uid=self.uid,
+ pos=self.pos,
+ label=self.label,
+ labelVisible=self.label_visible,
+ dimensions=1, # TODO
+ synapse=0.005, # TODO
+ showPairs=self.show_pairs)
+
+ def remove_nengo_objects(self, model):
+ model.connections.remove(self.conn1)
+ model.connections.remove(self.conn2)
+ model.nodes.remove(self.node)
+ self.conn1, self.conn2, self.node = None, None, None
+
+
+class SpaSimilarity(SpaWidget):
+ """Line graph showing semantic pointer decoded values over time"""
+
+ def __init__(self, client, obj, uid,
+ ylim=(-1.5, 1.5), target="default", show_pairs=False,
+ pos=None, label=None):
+ super(SpaSimilarity, self).__init__(
+ client, obj, uid, target, show_pairs, pos, label)
+
+ # Nengo objects for data collection
+ self.node = None
+ self.conn = None
+
+ def add_nengo_objects(self, model):
+
+ last_n_lines = np.array(self.n_lines)
+ data = np.zeros(self.n_lines + 1)
+
+ def fast_send_to_client(t, x):
+ if self.n_lines != last_n_lines:
+ data.resize(self.n_lines + 1)
+ self.update()
+ last_n_lines[...] = self.n_lines
+ n_keys = len(self.vocab.keys)
+
+ data[0] = t
+ data[1:1+n_keys] = np.dot(self.vocab.vectors, x)
+ if self.show_pairs:
+ data[1+n_keys:] = np.dot(self.vocab.vector_pairs, x)
+ self.fast_client.send(data)
+
+ with model:
+ output = self.obj.outputs[self.target][0]
+ self.node = nengo.Node(
+ fast_send_to_client, size_in=self.vocab.dimensions)
+ self.conn = nengo.Connection(output, self.node, synapse=0.01)
+
+ def create(self):
+ # TODO: get n_lines from this.labels.length
+ self.client.send("netgraph.create_spa_similarity",
+ uid=self.uid,
+ pos=self.pos,
+ dimensions=1, # TODO
+ synapse=0.005, # TODO
+ xlim=[-0.5, 0], # TODO
+ ylim=[-1, 1]) # TODO
+
+ def remove_nengo_objects(self, model):
+ """Undo the changes made by add_nengo_objects."""
+ model.connections.remove(self.conn)
+ model.nodes.remove(self.node)
+ self.conn, self.node = None, None
+
+ def update(self):
+ self.client.send("%s.reset_legend_and_data" % self.uid,
+ keys=self.keys)
+
+ # TODO: figure out what update_legend was doing
+
+ # # pass all the missing keys
+ # legend_update = []
+ # legend_update += (vocab.keys[self.old_vocab_length:])
+ # self.old_vocab_length = len(vocab.keys)
+ # # and all the missing pairs if we're showing pairs
+ # if self.config.show_pairs:
+ # # briefly there can be no pairs, so catch the error
+ # try:
+ # legend_update += vocab.key_pairs[self.old_pairs_length:]
+ # self.old_pairs_length = len(vocab.key_pairs)
+ # except TypeError:
+ # pass
+
+ # self.data.append(
+ # '["update_legend", "%s"]' % ('","'.join(legend_update)))
diff --git a/nengo_gui/components/spa_plot.py b/nengo_gui/components/spa_plot.py
deleted file mode 100644
index b970b4b5..00000000
--- a/nengo_gui/components/spa_plot.py
+++ /dev/null
@@ -1,38 +0,0 @@
-import collections
-
-from nengo.spa.module import Module
-
-from nengo_gui.components.component import Component
-
-
-class SpaPlot(Component):
- """Parent class for pointer.Pointer and spa_similarity.SpaSimilarity"""
-
- def __init__(self, obj, **kwargs):
- super(SpaPlot, self).__init__()
- self.obj = obj
- self.data = collections.deque()
- self.target = kwargs.get('args', 'default')
- self.vocab_out = obj.outputs[self.target][1]
-
- def attach(self, page, config, uid):
- super(SpaPlot, self).attach(page, config, uid)
- self.label = page.get_label(self.obj)
- self.vocab_out.include_pairs = config.show_pairs
-
- def update_client(self, client):
- while len(self.data) > 0:
- data = self.data.popleft()
- client.write_text(data)
-
- def code_python_args(self, uids):
- return [uids[self.obj], 'target=%r' % self.target]
-
- @staticmethod
- def applicable_targets(obj):
- targets = []
- if isinstance(obj, Module):
- for target_name, (obj, vocab) in obj.outputs.items():
- if vocab is not None:
- targets.append(target_name)
- return targets
diff --git a/nengo_gui/components/spa_similarity.py b/nengo_gui/components/spa_similarity.py
deleted file mode 100644
index a588f640..00000000
--- a/nengo_gui/components/spa_similarity.py
+++ /dev/null
@@ -1,100 +0,0 @@
-import numpy as np
-import nengo
-
-from nengo_gui.components.component import Component
-from nengo_gui.components.spa_plot import SpaPlot
-
-
-class SpaSimilarity(SpaPlot):
- """Line graph showing semantic pointer decoded values over time"""
-
- config_defaults = dict(max_value=1.5, min_value=-1.5,
- show_pairs=False,
- **Component.config_defaults)
-
- def __init__(self, obj, **kwargs):
- super(SpaSimilarity, self).__init__(obj, **kwargs)
-
- self.old_vocab_length = len(self.vocab_out.keys)
- self.old_pairs_length = 0
- self.labels = self.vocab_out.keys
- self.previous_pairs = False
-
- # Nengo objects for data collection
- self.node = None
- self.conn = None
-
- def add_nengo_objects(self, page):
- with page.model:
- output = self.obj.outputs[self.target][0]
- self.node = nengo.Node(self.gather_data,
- size_in=self.vocab_out.dimensions)
- self.conn = nengo.Connection(output, self.node, synapse=0.01)
-
- def remove_nengo_objects(self, page):
- """Undo the changes made by add_nengo_objects."""
- page.model.connections.remove(self.conn)
- page.model.nodes.remove(self.node)
-
- def gather_data(self, t, x):
- vocab = self.vocab_out
-
- if self.old_vocab_length != len(vocab.keys):
- self.update_legend(vocab)
-
- # get the similarity and send it
- key_similarity = np.dot(vocab.vectors, x)
- simi_list = ['{:.2f}'.format(simi) for simi in key_similarity]
-
- if self.config.show_pairs:
-
- # briefly there can be no pairs, so catch the error
- try:
- pair_similarity = np.dot(vocab.vector_pairs, x)
- simi_list += ['{:.2f}'.format(simi) for simi in pair_similarity]
- except TypeError:
- pass
-
- if(simi_list != []):
- self.data.append( '["data_msg", %g, %s]'
- %( t, ",".join(simi_list) ) )
-
- def update_legend(self, vocab):
- # pass all the missing keys
- legend_update = []
- legend_update += (vocab.keys[self.old_vocab_length:])
- self.old_vocab_length = len(vocab.keys)
- # and all the missing pairs if we're showing pairs
- if self.config.show_pairs:
- # briefly there can be no pairs, so catch the error
- try:
- legend_update += vocab.key_pairs[self.old_pairs_length:]
- self.old_pairs_length = len(vocab.key_pairs)
- except TypeError:
- pass
-
- self.data.append('["update_legend", "%s"]'
- %('","'.join(legend_update)))
-
- def javascript(self):
- """Generate the javascript that will create the client-side object"""
- info = dict(uid=id(self), label=self.label, n_lines=len(self.labels),
- synapse=0, pointer_labels=self.labels)
- json = self.javascript_config(info)
- return 'new Nengo.SpaSimilarity(main, sim, %s);' % json
-
- def message(self, msg):
- """Message receive function for show_pairs toggling and reset"""
- vocab = self.vocab_out
- # Send the new labels
- if self.config.show_pairs:
- vocab.include_pairs = True
- self.data.append(
- '["reset_legend_and_data", "%s"]' % (
- '","'.join(vocab.keys + vocab.key_pairs)))
- # if we're starting to show pairs, track pair length
- self.old_pairs_length = len(vocab.key_pairs)
- else:
- vocab.include_pairs = False
- self.data.append('["reset_legend_and_data", "%s"]'
- % ('","'.join(vocab.keys)))
diff --git a/nengo_gui/components/spike_grid.py b/nengo_gui/components/spike_grid.py
index 86204f1d..8c4d9bc5 100644
--- a/nengo_gui/components/spike_grid.py
+++ b/nengo_gui/components/spike_grid.py
@@ -1,77 +1,85 @@
import nengo
import numpy as np
-import struct
-from nengo_gui.components.component import Component
+from .base import Widget
-class SpikeGrid(Component):
+class SpikeGrid(Widget):
"""Represents an ensemble of neurons as squares in a grid.
-
+
The color of the squares corresponds to the neuron spiking.
"""
- def __init__(self, obj, n_neurons=None):
- super(SpikeGrid, self).__init__()
- self.obj = obj
- self.data = []
- self.max_neurons = self.obj.neurons.size_out
- if n_neurons is None:
- n_neurons = self.max_neurons
- self.n_neurons = n_neurons
- self.pixels_x = np.ceil(np.sqrt(self.n_neurons))
- self.pixels_y = np.ceil(float(self.n_neurons) / self.pixels_x)
- self.n_pixels = self.pixels_x * self.pixels_y
- self.struct = struct.Struct(' self.n_neurons:
- x = x[:self.n_neurons]
- y = np.zeros(int(self.n_pixels), dtype=np.uint8)
- if self.max_value > 0:
- y[:x.size] = x * 255 / self.max_value
- data = self.struct.pack(t, *y)
- self.data.append(data)
-
- def update_client(self, client):
- length = len(self.data)
- if length > 0:
- item = bytes().join(self.data[:length])
- del self.data[:length]
- try:
- client.write_binary(item)
- except:
- # if there is a communication problem, just drop the frames
- # (this usually happens when there is too much data to send)
- pass
-
- def javascript(self):
- info = dict(uid=id(self), label=self.label,
- pixels_x=self.pixels_x, pixels_y=self.pixels_y)
- json = self.javascript_config(info)
- return 'new Nengo.Image(main, sim, %s);' % json
-
- def code_python_args(self, uids):
- args = [uids[self.obj]]
- if self.n_neurons != self.max_neurons:
- args.append('n_neurons=%d' % self.n_neurons)
- return args
+ @property
+ def max_neurons(self):
+ return self.obj.neurons.size_out
+
+ @property
+ def n_pixels(self):
+ return self.pixels_x * self.pixels_y
+
+ @property
+ def pixels_x(self):
+ return int(np.ceil(np.sqrt(self.n_neurons)))
+
+ @property
+ def pixels_y(self):
+ return int(np.ceil(float(self.n_neurons) / self.pixels_x))
+
+ def add_nengo_objects(self, model):
+
+ def fast_send_to_client(t, x):
+ self.max_value = max(self.max_value, np.max(x))
+
+ # TODO: Does this every happen? Can it???
+ # if x.size > self.n_neurons:
+ # x = x[:self.n_neurons]
+ y = (x * 255 / self.max_value).astype(np.uint8)
+ self.fast_client.send(y)
+
+ # try:
+ # client.write_binary(item)
+ # except:
+ # # if there is a communication problem, just drop the frames
+ # # (this usually happens when there is too much data to send)
+ # pass
+
+ # y = np.zeros(int(self.n_pixels), dtype=np.uint8)
+ # if self.max_value > 0:
+ # y[:x.size] = x * 255 / self.max_value
+ # data = self.struct.pack(t, *y)
+ # self.data.append(data)
+
+ with model:
+ self.node = nengo.Node(
+ fast_send_to_client,
+ size_in=self.obj.neurons.size_out,
+ size_out=0)
+ self.conn = nengo.Connection(
+ self.obj.neurons, self.node, synapse=0.01)
+
+ def create(self):
+ self.client.send("netgraph.create_spike_grid",
+ uid=self.uid,
+ pos=self.pos,
+ label=self.label,
+ labelVisible=self.label_visible,
+ dimensions=1, # TODO
+ synapse=0.005, # TODO
+ xlim=[-0.5, 0], # TODO
+ ylim=[-1, 1]) # TODO
+
+ def remove_nengo_objects(self, model):
+ model.connections.remove(self.conn)
+ model.nodes.remove(self.node)
+ self.conn, self.node = None, None
diff --git a/nengo_gui/components/tests/__init__.py b/nengo_gui/components/tests/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/nengo_gui/components/tests/conftest.py b/nengo_gui/components/tests/conftest.py
deleted file mode 100644
index 4af5625d..00000000
--- a/nengo_gui/components/tests/conftest.py
+++ /dev/null
@@ -1 +0,0 @@
-from nengo_gui import conftest #imports conftest functions from nengo_gui
\ No newline at end of file
diff --git a/nengo_gui/components/tests/test_base.py b/nengo_gui/components/tests/test_base.py
new file mode 100644
index 00000000..7b437b72
--- /dev/null
+++ b/nengo_gui/components/tests/test_base.py
@@ -0,0 +1,70 @@
+import pytest
+
+from nengo_gui.components.base import Component, Position, Widget
+from nengo_gui.exceptions import NotAttachedError
+
+
+class TestPosition(object):
+
+ def test_defaults(self):
+ pos = Position()
+ assert pos.x == 0
+ assert pos.y == 0
+ assert pos.width == 100
+ assert pos.height == 100
+
+ def test_repr(self):
+ pos = Position()
+ assert repr(pos) == "Position(x=0, y=0, width=100, height=100)"
+
+
+class TestComponent(object):
+
+ def test_uid_readonly(self, client):
+ comp = Component(client, None, "comp")
+ assert comp.uid == "comp"
+ with pytest.raises(AttributeError):
+ comp.uid = "comp"
+
+ def test_similar(self, client):
+ c1 = Component(client, None, "c1")
+ c2 = Component(client, None, "c2")
+ assert not c1.similar(c2) and not c2.similar(c1)
+ c2._uid = "c1"
+ assert c1.similar(c2) and c2.similar(c1)
+
+ def test_update(self, client):
+ c1 = Component(client, None, "comp")
+ c2 = Component(client, None, "comp")
+ c1.update(c2)
+ assert client.ws.text is None # No update
+ c1.label = "comp"
+ c1.update(c2)
+ assert client.ws.text == '["comp.label", {"label": "comp"}]'
+ c2.update(c1)
+ assert client.ws.text == '["comp.label", {"label": null}]'
+
+ def test_must_implement(self, client):
+ comp = Component(client, None, "comp")
+
+ with pytest.raises(NotImplementedError):
+ comp.create()
+ with pytest.raises(NotImplementedError):
+ comp.delete()
+
+
+class TestWidget(object):
+
+ def test_fast_client(self, client):
+ widget = Widget(client, None, "widget")
+
+ # For other nonexistent attributes
+ with pytest.raises(AttributeError):
+ widget.test
+
+ # For fast_client
+ with pytest.raises(NotAttachedError):
+ widget.fast_client
+
+ widget.attach(None)
+ assert widget.fast_client is None
diff --git a/nengo_gui/components/tests/test_connection.py b/nengo_gui/components/tests/test_connection.py
new file mode 100644
index 00000000..8de4b3f0
--- /dev/null
+++ b/nengo_gui/components/tests/test_connection.py
@@ -0,0 +1,51 @@
+import json
+
+import nengo
+
+from nengo_gui.components import Connection
+from nengo_gui.netgraph import NameFinder
+
+
+def test_create(client):
+ with nengo.Network():
+ a = nengo.Ensemble(10, 1)
+ b = nengo.Ensemble(10, 1)
+ c = nengo.Connection(a, b)
+
+ names = NameFinder()
+ names.update(locals())
+
+ comp = Connection(client, c, names[c], names)
+ comp.create()
+
+ assert client.ws.text == '["netgraph.create_connection", {}]'
+
+
+def test_similar_update(client):
+ with nengo.Network():
+ a = nengo.Ensemble(10, 1)
+ b = nengo.Ensemble(10, 1)
+ cab = nengo.Connection(a, b)
+ cba = nengo.Connection(b, a)
+
+ names = NameFinder()
+ names.update(locals())
+
+ # Note: uids must be the same
+ c1 = Connection(client, cab, "conn", names)
+ c2 = Connection(client, cba, "conn", names)
+
+ assert c1.similar(c2) and c2.similar(c1)
+
+ c1.update(c2)
+ assert json.loads(client.ws.text) == ["conn.reconnect", {
+ "pre": "a", "post": "b",
+ }]
+
+ c2.update(c1)
+ assert json.loads(client.ws.text) == ["conn.reconnect", {
+ "pre": "b", "post": "a",
+ }]
+
+ c1._uid = "notconn"
+ assert not c1.similar(c2) and not c2.similar(c1)
diff --git a/nengo_gui/components/tests/test_ensemble.py b/nengo_gui/components/tests/test_ensemble.py
new file mode 100644
index 00000000..151106d3
--- /dev/null
+++ b/nengo_gui/components/tests/test_ensemble.py
@@ -0,0 +1,31 @@
+import nengo
+
+from nengo_gui.components import Ensemble
+
+
+def test_create(client):
+ with nengo.Network():
+ a = nengo.Ensemble(10, 1)
+
+ comp = Ensemble(client, a, "a")
+ comp.create()
+ assert client.ws.text == '["netgraph.create_ensemble", {}]'
+
+
+def test_similar(client):
+ with nengo.Network():
+ a = nengo.Ensemble(10, 1)
+ b = nengo.Ensemble(10, 1)
+
+ # Note: uids must be the same
+ e1 = Ensemble(client, a, "ens")
+ e2 = Ensemble(client, b, "ens")
+
+ assert e1.similar(e2) and e2.similar(e1)
+
+ a.n_neurons = 20
+ assert not e1.similar(e2) and not e2.similar(e1)
+
+ a.n_neurons = 10
+ b.dimensions = 2
+ assert not e1.similar(e2) and not e2.similar(e1)
diff --git a/nengo_gui/components/tests/test_htmlview.py b/nengo_gui/components/tests/test_htmlview.py
new file mode 100644
index 00000000..e359fb1e
--- /dev/null
+++ b/nengo_gui/components/tests/test_htmlview.py
@@ -0,0 +1,39 @@
+import json
+
+import nengo
+
+from nengo_gui.components import HTMLView
+
+
+def test_create(client):
+ with nengo.Network():
+ n = nengo.Node(None)
+
+ html = HTMLView(client, n, "htmlview")
+ html.create()
+ assert json.loads(client.ws.text) == ["netgraph.create_htmlview", {
+ "label": None, "uid": "htmlview",
+ }]
+
+
+def test_add_remove(client):
+
+ f = lambda t: 0.0
+ f._nengo_html_ = "test"
+ with nengo.Network() as net:
+ n = nengo.Node(output=f)
+
+ html = HTMLView(client, n, "htmlview")
+ html.add_nengo_objects(net)
+ assert n.output is not f
+ assert html._old_output is f
+ assert client.ws.text is None
+
+ n.output(0.0)
+ assert json.loads(client.ws.text) == ["htmlview.html", {
+ "t": 0.0, "html": "test",
+ }]
+
+ html.remove_nengo_objects(net)
+ assert n.output is f
+ assert html._old_output is None
diff --git a/nengo_gui/components/tests/test_network.py b/nengo_gui/components/tests/test_network.py
new file mode 100644
index 00000000..53fb6964
--- /dev/null
+++ b/nengo_gui/components/tests/test_network.py
@@ -0,0 +1,26 @@
+import nengo
+
+from nengo_gui.components import Network
+
+
+def test_create(client):
+ n = nengo.Network()
+
+ comp = Network(client, n, "n")
+ comp.create()
+ assert client.ws.text == '["netgraph.create_network", {}]'
+
+
+def test_similar(client):
+ with nengo.Network() as net1:
+ net2 = nengo.Network()
+
+ # Note: uids must be the same
+ n1 = Network(client, net1, "node")
+ n2 = Network(client, net2, "node")
+
+ assert n1.similar(n2) and n2.similar(n1)
+
+ with net2:
+ net2.output = nengo.Ensemble(10, 1)
+ assert not n1.similar(n2) and not n2.similar(n1)
diff --git a/nengo_gui/components/tests/test_node.py b/nengo_gui/components/tests/test_node.py
new file mode 100644
index 00000000..9d9a5546
--- /dev/null
+++ b/nengo_gui/components/tests/test_node.py
@@ -0,0 +1,37 @@
+import nengo
+
+from nengo_gui.components import Node
+
+
+def test_create(client):
+ with nengo.Network():
+ n = nengo.Node([0])
+
+ comp = Node(client, n, "n")
+ comp.create()
+ assert client.ws.text == '["netgraph.create_node", {}]'
+
+
+def test_similar(client):
+ with nengo.Network():
+ node1 = nengo.Node(None, size_in=1)
+ node2 = nengo.Node(None, size_in=1)
+
+ # Note: uids must be the same
+ n1 = Node(client, node1, "node")
+ n2 = Node(client, node2, "node")
+
+ assert n1.similar(n2) and n2.similar(n1)
+
+ node1.size_out = 2
+ assert not n1.similar(n2) and not n2.similar(n1)
+
+ node1.size_out = 1
+ assert n1.similar(n2) and n2.similar(n1)
+ node2.output = lambda t: t
+ assert not n1.similar(n2) and not n2.similar(n1)
+
+ node1.output = lambda t: t
+ assert n1.similar(n2) and n2.similar(n1)
+ node1.output._nengo_html_ = None
+ assert not n1.similar(n2) and not n2.similar(n1)
diff --git a/nengo_gui/components/tests/test_raster.py b/nengo_gui/components/tests/test_raster.py
new file mode 100644
index 00000000..9fb7f9e6
--- /dev/null
+++ b/nengo_gui/components/tests/test_raster.py
@@ -0,0 +1,40 @@
+import json
+
+import nengo
+import numpy as np
+
+from nengo_gui.components import Raster
+
+
+def test_create(client):
+ with nengo.Network():
+ e = nengo.Ensemble(10, 1)
+
+ raster = Raster(client, e, "raster")
+ raster.create()
+ assert json.loads(client.ws.text) == ["netgraph.create_raster", {
+ "max_neurons": 10, "label": None,
+ }]
+
+
+def test_add_remove(client, fast_client):
+ with nengo.Network() as net:
+ e = nengo.Ensemble(10, 1)
+
+ raster = Raster(client, e, "raster", n_neurons=10)
+ raster.attach(fast_client)
+ raster.add_nengo_objects(net)
+ assert raster.node is not None and raster.conn is not None
+ assert fast_client.ws.binary is None
+
+ # Spike in indices 1 and 3
+ raster.node.output(0.0, np.array([0, 1, 0, 1, 0, 0, 0, 0, 0, 0]))
+ assert fast_client.ws.binary == np.array([0.0, 1.0, 3.0]).tobytes()
+
+ # Spike in indices 2 and 4
+ raster.node.output(0.01, np.array([0, 0, 1, 0, 1, 0, 0, 0, 0, 0]))
+ assert fast_client.ws.binary == np.array([0.01, 2.0, 4.0]).tobytes()
+
+ raster.remove_nengo_objects(net)
+ assert len(net.nodes) == 0
+ assert len(net.connections) == 0
diff --git a/nengo_gui/components/tests/test_slider.py b/nengo_gui/components/tests/test_slider.py
new file mode 100644
index 00000000..635c8588
--- /dev/null
+++ b/nengo_gui/components/tests/test_slider.py
@@ -0,0 +1,202 @@
+import json
+
+import nengo
+import numpy as np
+
+from nengo_gui.components import Slider
+
+
+def test_create(client):
+ with nengo.Network():
+ n = nengo.Node(None, size_in=2)
+
+ slider = Slider(client, n, "slider")
+ slider.create()
+ assert json.loads(client.ws.text) == ["netgraph.create_slider", {
+ "label": None,
+ "n_sliders": 2,
+ "start_value": [0.0, 0.0],
+ "uid": "slider",
+ }]
+
+
+def test_passthrough(client, fast_client):
+ with nengo.Network() as net:
+ n = nengo.Node(None, size_in=2)
+
+ dummy_backend = lambda: "nengo"
+ dummy_dt = lambda: 0.001
+ client.bind("simcontrol.get_backend", dummy_backend)
+ client.bind("simcontrol.get_dt", dummy_dt)
+
+ slider = Slider(client, n, "slider")
+ slider.attach(fast_client)
+ slider.add_nengo_objects(net)
+
+ step = n.output if callable(n.output) else n.output.make_step(
+ shape_in=None, shape_out=n.size_out, dt=None, rng=None)
+
+ assert fast_client.ws.binary is None
+ assert n.output is not slider.base_output
+
+ # Without overriding, output should be same as input
+ assert (step(0.01, np.array([0.5, 0.5])) == np.array([0.5, 0.5])).all()
+ assert (step(0.02, np.array([-0.5, -0.5])) == np.array([-0.5, -0.5])).all()
+ # Value sent over fast client
+ assert fast_client.ws.binary == np.array([-0.5, -0.5]).tobytes()
+
+ # Override the second dimension
+ fast_client.receive(np.array([np.nan, 0.0]).tobytes())
+ assert (step(0.03, np.array([0.5, 0.5])) == np.array([0.5, 0.0])).all()
+ assert (step(0.04, np.array([-0.5, -0.5])) == np.array([-0.5, 0.0])).all()
+ # Whole value still sent over fast client
+ assert fast_client.ws.binary == np.array([-0.5, -0.5]).tobytes()
+
+ # Override both dimensions
+ fast_client.receive(np.array([0.0, 0.0]).tobytes())
+ assert (step(0.05, np.array([0.5, 0.5])) == np.array([0.0, 0.0])).all()
+ assert (step(0.06, np.array([-0.5, -0.5])) == np.array([0.0, 0.0])).all()
+ # Whole value still sent over fast client
+ assert fast_client.ws.binary == np.array([-0.5, -0.5]).tobytes()
+
+ # Reset, no longer override
+ slider.reset()
+ assert (step(0.07, np.array([0.5, 0.5])) == np.array([0.5, 0.5])).all()
+ assert (step(0.08, np.array([-0.5, -0.5])) == np.array([-0.5, -0.5])).all()
+ assert fast_client.ws.binary == np.array([-0.5, -0.5]).tobytes()
+
+ slider.remove_nengo_objects(net)
+ assert n.output is slider.base_output
+
+
+def test_value(client, fast_client):
+ with nengo.Network() as net:
+ n = nengo.Node([1.0])
+
+ dummy_backend = lambda: "nengo"
+ dummy_dt = lambda: 0.001
+ client.bind("simcontrol.get_backend", dummy_backend)
+ client.bind("simcontrol.get_dt", dummy_dt)
+
+ slider = Slider(client, n, "slider")
+ slider.attach(fast_client)
+ slider.add_nengo_objects(net)
+
+ step = n.output if callable(n.output) else n.output.make_step(
+ shape_in=None, shape_out=n.size_out, dt=None, rng=None)
+
+ assert n.output is not slider.base_output
+
+ # Without overriding, should always output 1.0
+ assert step(0.01) == 1.0
+ assert step(0.02) == 1.0
+
+ # Override the output
+ fast_client.receive(np.array([-1.0]).tobytes())
+ assert step(0.03) == -1.0
+ assert step(0.04) == -1.0
+
+ # Reset, no longer override
+ slider.reset()
+ assert step(0.05) == 1.0
+ assert step(0.06) == 1.0
+
+ # Value not sent over when using static output
+ assert fast_client.ws.binary is None
+
+ slider.remove_nengo_objects(net)
+ assert n.output is slider.base_output
+
+
+def test_callable(client, fast_client):
+ with nengo.Network() as net:
+ n = nengo.Node(lambda t: t)
+
+ dummy_backend = lambda: "nengo"
+ dummy_dt = lambda: 0.001
+ client.bind("simcontrol.get_backend", dummy_backend)
+ client.bind("simcontrol.get_dt", dummy_dt)
+
+ slider = Slider(client, n, "slider")
+ slider.attach(fast_client)
+ slider.add_nengo_objects(net)
+
+ step = n.output if callable(n.output) else n.output.make_step(
+ shape_in=None, shape_out=n.size_out, dt=None, rng=None)
+
+ assert fast_client.ws.binary is None
+ assert n.output is not slider.base_output
+
+ # Without overriding output should be t
+ assert step(0.01) == 0.01
+ assert step(0.02) == 0.02
+ # Value sent over fast client
+ assert fast_client.ws.binary == np.array([0.02]).tobytes()
+
+ # Override output
+ fast_client.receive(np.array([-1.0]).tobytes())
+ assert step(0.03) == -1.0
+ assert step(0.04) == -1.0
+ # Original value still sent over fast client
+ assert fast_client.ws.binary == np.array([0.04]).tobytes()
+
+ # Reset, no longer override
+ slider.reset()
+ assert step(0.05) == 0.05
+ assert step(0.06) == 0.06
+ assert fast_client.ws.binary == np.array([0.06]).tobytes()
+
+ slider.remove_nengo_objects(net)
+ assert n.output is slider.base_output
+
+
+def test_process(client, fast_client):
+ with nengo.Network() as net:
+ n = nengo.Node(nengo.processes.PresentInput(
+ inputs=[[0.1, 0.1], [0.2, 0.2]], presentation_time=0.01,
+ ), size_out=2)
+
+ dummy_backend = lambda: "nengo"
+ dummy_dt = lambda: 0.001
+ client.bind("simcontrol.get_backend", dummy_backend)
+ client.bind("simcontrol.get_dt", dummy_dt)
+
+ slider = Slider(client, n, "slider")
+ slider.attach(fast_client)
+ slider.add_nengo_objects(net)
+
+ step = n.output if callable(n.output) else n.output.make_step(
+ shape_in=(0,), shape_out=(n.size_out,), dt=0.01, rng=None)
+
+ assert fast_client.ws.binary is None
+ assert n.output is not slider.base_output
+
+ # Without overriding, output should be from process
+ assert (step(0.01) == np.array([0.1, 0.1])).all()
+ assert (step(0.02) == np.array([0.2, 0.2])).all()
+ # Value sent over fast client
+ print(np.frombuffer(fast_client.ws.binary))
+ assert fast_client.ws.binary == np.array([0.2, 0.2]).tobytes()
+
+ # Override the second dimension
+ fast_client.receive(np.array([np.nan, 0.0]).tobytes())
+ assert (step(0.03) == np.array([0.1, 0.0])).all()
+ assert (step(0.04) == np.array([0.2, 0.0])).all()
+ # Whole value still sent over fast client
+ assert fast_client.ws.binary == np.array([0.2, 0.2]).tobytes()
+
+ # Override both dimensions
+ fast_client.receive(np.array([0.0, 0.0]).tobytes())
+ assert (step(0.05) == np.array([0.0, 0.0])).all()
+ assert (step(0.06) == np.array([0.0, 0.0])).all()
+ # Whole value still sent over fast client
+ assert fast_client.ws.binary == np.array([0.2, 0.2]).tobytes()
+
+ # Reset, no longer override
+ slider.reset()
+ assert (step(0.07) == np.array([0.1, 0.1])).all()
+ assert (step(0.08) == np.array([0.2, 0.2])).all()
+ assert fast_client.ws.binary == np.array([0.2, 0.2]).tobytes()
+
+ slider.remove_nengo_objects(net)
+ assert n.output is slider.base_output
diff --git a/nengo_gui/components/tests/test_spa.py b/nengo_gui/components/tests/test_spa.py
new file mode 100644
index 00000000..3197d6fe
--- /dev/null
+++ b/nengo_gui/components/tests/test_spa.py
@@ -0,0 +1,121 @@
+import json
+
+import nengo.spa
+import numpy as np
+
+from nengo_gui.components.spa import SpaPointer, SpaSimilarity, SpaWidget
+
+
+class TestSpaWidget(object):
+
+ def test_show_pairs(self, client):
+ with nengo.spa.SPA() as model:
+ model.state = nengo.spa.State(16)
+
+ widget = SpaWidget(client, model.state, "widget")
+ widget.vocab.parse("A+B")
+ assert not widget.show_pairs
+ assert widget.keys == ["A", "B"]
+ widget.show_pairs = True
+ assert widget.show_pairs
+ assert widget.keys == ["A", "B", "A*B"]
+ assert client.ws.text == (
+ '["widget.set_keys", {"keys": ["A", "B", "A*B"]}]')
+ client.dispatch("widget.show_pairs", val=False)
+ assert client.ws.text == '["widget.set_keys", {"keys": ["A", "B"]}]'
+ assert not widget.show_pairs
+ assert widget.keys == ["A", "B"]
+
+
+class TestSpaPointer(object):
+
+ def test_create(self, client):
+ with nengo.spa.SPA() as model:
+ model.state = nengo.spa.State(16)
+
+ pointer = SpaPointer(client, model.state, "pointer")
+ assert pointer.override is None
+ pointer.create()
+ assert json.loads(client.ws.text) == [
+ "netgraph.create_spa_pointer", {"label": None, "uid": "pointer"}
+ ]
+
+ def test_add_remove(self, client):
+ with nengo.spa.SPA() as model:
+ model.state = nengo.spa.State(16)
+
+ pointer = SpaPointer(client, model.state, "pointer")
+ assert pointer.override is None
+ pointer.vocab.add("A", np.ones(16))
+ pointer.add_nengo_objects(model)
+
+ # No override, input v: return 0, max similarity
+ out = pointer.node.output(0.001, np.ones(16))
+ assert np.all(out == 0)
+ assert client.ws.text == '["pointer.matches", {"data": "9.99A"}]'
+
+ # Override, input 0: return v*3, no similarity
+ pointer.override = "A"
+ out = pointer.node.output(0.002, np.zeros(16))
+ assert np.allclose(out, 3 * np.ones(16))
+ assert client.ws.text == '["pointer.matches", {"data": ""}]'
+
+ # Override, input v: return 0, max similarity
+ pointer.override = "A"
+ out = pointer.node.output(0.003, np.ones(16))
+ assert np.allclose(out, 0)
+ assert client.ws.text == '["pointer.matches", {"data": "9.99A"}]'
+
+ pointer.remove_nengo_objects(model)
+ assert pointer.node is None
+ assert pointer.conn1 is None
+ assert pointer.conn2 is None
+
+
+class TestSpaSimilarity(object):
+
+ def test_create(self, client):
+ with nengo.spa.SPA() as model:
+ model.state = nengo.spa.State(16)
+
+ similarity = SpaSimilarity(client, model.state, "similarity")
+ similarity.vocab.parse("A+B+C")
+ similarity.create()
+ assert json.loads(client.ws.text) == [
+ "netgraph.create_spa_similarity", {
+ "keys": ["A", "B", "C"],
+ "label": None,
+ "uid": "similarity",
+ }
+ ]
+
+ def test_add_remove(self, client, fast_client):
+ with nengo.spa.SPA() as model:
+ model.state = nengo.spa.State(2)
+
+ similarity = SpaSimilarity(client, model.state, "similarity")
+ similarity.attach(fast_client)
+ similarity.vocab.add("A", np.ones(2))
+ similarity.add_nengo_objects(model)
+
+ assert similarity.node.output(0.001, np.ones(2)) is None
+ assert client.ws.text is None
+ assert fast_client.ws.binary == np.array([0.001, 2.0]).tobytes()
+
+ similarity.vocab.add("B", np.zeros(2))
+ assert similarity.node.output(0.002, np.ones(2)) is None
+ assert client.ws.text == (
+ '["similarity.reset_legend_and_data", {"keys": ["A", "B"]}]')
+ assert fast_client.ws.binary == np.array([0.002, 2.0, 0.0]).tobytes()
+
+ similarity.show_pairs = True
+ assert similarity.node.output(0.003, np.ones(2)) is None
+ assert client.ws.text == (
+ '["similarity.reset_legend_and_data", {"keys": ["A", "B", "A*B"]}]'
+ )
+ assert (fast_client.ws.binary ==
+ np.array([0.003, 2.0, 0.0, 0.0]).tobytes())
+
+ similarity.remove_nengo_objects(model)
+ assert similarity.node is None
+ assert similarity.conn is None
diff --git a/nengo_gui/components/tests/test_spike_grid.py b/nengo_gui/components/tests/test_spike_grid.py
new file mode 100644
index 00000000..79057822
--- /dev/null
+++ b/nengo_gui/components/tests/test_spike_grid.py
@@ -0,0 +1,42 @@
+import json
+
+import nengo
+import numpy as np
+
+from nengo_gui.components import SpikeGrid
+
+
+def test_create(client):
+ with nengo.Network():
+ e = nengo.Ensemble(10, 1)
+
+ grid = SpikeGrid(client, e, "grid")
+ grid.create()
+ assert json.loads(client.ws.text) == ["netgraph.create_spike_grid", {
+ "label": None, "pixels_x": 4, "pixels_y": 3,
+ }]
+
+
+def test_add_remove(client, fast_client):
+ with nengo.Network() as net:
+ e = nengo.Ensemble(5, 1)
+
+ grid = SpikeGrid(client, e, "grid", n_neurons=10)
+ grid.attach(fast_client)
+ grid.add_nengo_objects(net)
+ assert grid.node is not None and grid.conn is not None
+ assert fast_client.ws.binary is None
+
+ grid.node.output(0.0, np.array([0, 1, 0, 1, 0]))
+ assert (fast_client.ws.binary ==
+ np.array([0, 255, 0, 255, 0], dtype=np.uint8).tobytes())
+
+ grid.node.output(0.01, np.array([0, 0, 0.5, 0, 0.5]))
+ assert (fast_client.ws.binary ==
+ np.array([0, 0, 127, 0, 127], dtype=np.uint8).tobytes())
+
+ grid.remove_nengo_objects(net)
+ assert len(net.nodes) == 0
+ assert len(net.connections) == 0
+ assert grid.conn is None
+ assert grid.node is None
diff --git a/nengo_gui/components/tests/test_value.py b/nengo_gui/components/tests/test_value.py
new file mode 100644
index 00000000..c59d1c12
--- /dev/null
+++ b/nengo_gui/components/tests/test_value.py
@@ -0,0 +1,40 @@
+import json
+
+import nengo
+import numpy as np
+
+from nengo_gui.components import Value
+
+
+def test_create(client):
+ with nengo.Network():
+ e = nengo.Ensemble(10, 1)
+
+ value = Value(client, e, "value")
+
+ value.create()
+ assert json.loads(client.ws.text) == ["netgraph.create_value", {
+ "label": None, "n_lines": 1, "uid": "value",
+ }]
+
+
+def test_add_remove(client, fast_client):
+ with nengo.Network() as net:
+ e = nengo.Ensemble(5, 1)
+
+ value = Value(client, e, "value")
+ value.attach(fast_client)
+ value.add_nengo_objects(net)
+ assert value.node is not None and value.conn is not None
+ assert fast_client.ws.binary is None
+
+ value.node.output(0.0, np.array([0.1]))
+ assert fast_client.ws.binary == np.array([0.0, 0.1]).tobytes()
+ value.node.output(0.01, np.array([-0.1]))
+ assert fast_client.ws.binary == np.array([0.01, -0.1]).tobytes()
+
+ value.remove_nengo_objects(net)
+ assert len(net.nodes) == 0
+ assert len(net.connections) == 0
+ assert value.conn is None
+ assert value.node is None
diff --git a/nengo_gui/components/tests/test_voltage.py b/nengo_gui/components/tests/test_voltage.py
new file mode 100644
index 00000000..8a5817fc
--- /dev/null
+++ b/nengo_gui/components/tests/test_voltage.py
@@ -0,0 +1,33 @@
+import json
+
+import nengo
+import numpy as np
+
+from nengo_gui.components import Voltage
+
+
+# TODO: test the horrible hack
+
+def test_create(client):
+ with nengo.Network():
+ e = nengo.Ensemble(10, 1)
+
+ voltage = Voltage(client, e, "voltage")
+ voltage.create()
+ # Note: n_neurons defaults to 5
+ assert json.loads(client.ws.text) == ["netgraph.create_voltage", {
+ "n_neurons": 5, "label": None, "synapse": 0, "uid": "voltage",
+ }]
+
+
+def test_add_remove(client, fast_client):
+ with nengo.Network() as net:
+ e = nengo.Ensemble(10, 1)
+
+ voltage = Voltage(client, e, "voltage")
+ voltage.add_nengo_objects(net)
+ assert voltage.probe is not None
+
+ voltage.remove_nengo_objects(net)
+ assert len(net.probes) == 0
+ assert voltage.probe is None
diff --git a/nengo_gui/components/tests/test_xyvalue.py b/nengo_gui/components/tests/test_xyvalue.py
new file mode 100644
index 00000000..131bab1c
--- /dev/null
+++ b/nengo_gui/components/tests/test_xyvalue.py
@@ -0,0 +1,40 @@
+import json
+
+import nengo
+import numpy as np
+
+from nengo_gui.components import XYValue
+
+
+def test_create(client):
+ with nengo.Network():
+ e = nengo.Ensemble(10, 2)
+
+ xyvalue = XYValue(client, e, "xyvalue")
+
+ xyvalue.create()
+ assert json.loads(client.ws.text) == ["netgraph.create_xyvalue", {
+ "label": None, "n_lines": 2, "uid": "xyvalue",
+ }]
+
+
+def test_add_remove(client, fast_client):
+ with nengo.Network() as net:
+ e = nengo.Ensemble(5, 2)
+
+ xyvalue = XYValue(client, e, "xyvalue")
+ xyvalue.attach(fast_client)
+ xyvalue.add_nengo_objects(net)
+ assert xyvalue.node is not None and xyvalue.conn is not None
+ assert fast_client.ws.binary is None
+
+ xyvalue.node.output(0.0, np.array([0.1, 0.2]))
+ assert fast_client.ws.binary == np.array([0.0, 0.1, 0.2]).tobytes()
+ xyvalue.node.output(0.01, np.array([-0.1, -0.2]))
+ assert fast_client.ws.binary == np.array([0.01, -0.1, -0.2]).tobytes()
+
+ xyvalue.remove_nengo_objects(net)
+ assert len(net.nodes) == 0
+ assert len(net.connections) == 0
+ assert xyvalue.conn is None
+ assert xyvalue.node is None
diff --git a/nengo_gui/components/value.py b/nengo_gui/components/value.py
index 90c1311b..b7463689 100644
--- a/nengo_gui/components/value.py
+++ b/nengo_gui/components/value.py
@@ -1,111 +1,100 @@
-import struct
-
+import numpy as np
import nengo
-from nengo import spa
+import nengo.spa
-from nengo_gui.components.component import Component
+from ..client import bind
+from .base import Widget
-class Value(Component):
+class Value(Widget):
"""The server-side system for a Value plot."""
- # the parameters to be stored in the .cfg file
- config_defaults = dict(max_value=1, min_value=-1,
- show_legend=False, legend_labels=[],
- synapse=0.01,
- **Component.config_defaults)
-
- def __init__(self, obj):
- super(Value, self).__init__()
- # the object whose decoded value should be displayed
- self.obj = obj
-
- # the pending data to be sent to the client
- self.data = []
-
- # grab the output of the object
- self.output = obj
- default_out = Value.default_output(self.obj)
- if default_out is not None:
- self.output = default_out
+ def __init__(self, client, obj, uid,
+ ylim=(-1, 1), legend_labels=None, synapse=0.01, legend=False,
+ pos=None, label_visible=True):
+ super(Value, self).__init__(
+ client, obj, uid, pos=pos, label_visible=label_visible)
- # the number of data values to send
- self.n_lines = int(self.output.size_out)
-
- # the binary data format to sent in. In this case, it is a list of
- # floats, with the first float being the time stamp and the rest
- # being the vector values, one per dimension.
- self.struct = struct.Struct('<%df' % (1 + self.n_lines))
+ self.ylim = ylim
+ self.legend_labels = [] if legend_labels is None else legend_labels
+ self.legend = legend
+ self.synapse = synapse
# Nengo objects for data collection
self.node = None
self.conn = None
- def attach(self, page, config, uid):
- super(Value, self).attach(page, config, uid)
- # use the label of the object being plotted as our label
- self.label = page.get_label(self.obj)
-
- def add_nengo_objects(self, page):
+ @property
+ def n_lines(self):
+ return self.output.size_out
+
+ @property
+ def output(self):
+ if isinstance(self.obj, nengo.Network) and hasattr(self.obj, "output"):
+ return self.obj.output
+ else:
+ return self.obj
+
+ @property
+ def synapse(self):
+ return self._synapse
+
+ @synapse.setter
+ @bind("{self.uid}.synapse")
+ def synapse(self, synapse):
+ self._synapse = synapse
+
+ # TODO: when GUI sets synapse, should also rebuild sim (don't do it here)
+ # if msg.startswith('synapse:'):
+ # synapse = float(msg[8:])
+ # self.page.config[self].synapse = synapse
+ # self.page.modified_config()
+ # self.page.sim = None
+
+ def add_nengo_objects(self, model):
# create a Node and a Connection so the Node will be given the
# data we want to show while the model is running.
- with page.model:
- self.node = nengo.Node(self.gather_data,
- size_in=self.n_lines)
- synapse = self.page.config[self].synapse
- self.conn = nengo.Connection(self.output, self.node,
- synapse=synapse)
-
- def remove_nengo_objects(self, page):
+
+ data = np.zeros(1 + self.n_lines, dtype=np.float64)
+
+ def fast_send_to_client(t, x):
+ data[0] = t
+ data[1:] = x
+ self.fast_client.send(data)
+
+ with model:
+ self.node = nengo.Node(
+ fast_send_to_client, size_in=self.n_lines, size_out=0)
+ self.conn = nengo.Connection(
+ self.output, self.node, synapse=self.synapse)
+
+ def create(self):
+ self.client.send("netgraph.create_value",
+ uid=self.uid,
+ pos=self.pos,
+ label=self.label,
+ labelVisible=self.label_visible,
+ dimensions=self.n_lines,
+ synapse=0.005, # TODO
+ xlim=[-0.5, 0], # TODO
+ ylim=[-1, 1]) # TODO
+
+ def dumps(self, names):
+ """Important to do correctly, as it's used in the config file."""
+ return ("Value(client, {names[self.obj]}, {self.uid}, "
+ "ylim={self.ylim}, legend_labels={self.legend_labels}, "
+ "synapse={self.synapse}, legend={self.legend}, "
+ "pos={self.pos}, label={self.label}".format(
+ names=names, self=self))
+
+ def remove_nengo_objects(self, model):
# undo the changes made by add_nengo_objects
- page.model.connections.remove(self.conn)
- page.model.nodes.remove(self.node)
-
- def gather_data(self, t, x):
- """This is the Node function for the Node created in add_nengo_objects
- It will be called by the running model, and will store the data
- that should be sent to the client"""
- self.data.append(self.struct.pack(t, *x))
-
- def update_client(self, client):
- length = len(self.data)
- if length > 0:
- # we do this slicing because self.gather_data is concurrently
- # appending things to self.data. This means that self.data may
- # increase in length during this call, so we do the slicing
- # and deletion to maintain thread safety
- item = bytes().join(self.data[:length])
- del self.data[:length]
- client.write_binary(item)
-
- def javascript(self):
- # generate the javascript that will create the client-side object
- info = dict(uid=id(self), label=self.label,
- n_lines=self.n_lines)
-
- json = self.javascript_config(info)
- return 'new Nengo.Value(main, sim, %s);' % json
-
- def code_python_args(self, uids):
- # generate the list of strings for the .cfg file to save this Component
- # (this is the text that would be passed in to the constructor)
- return [uids[self.obj]]
-
- def message(self, msg):
- if msg.startswith('synapse:'):
- synapse = float(msg[8:])
- self.page.config[self].synapse = synapse
- self.page.modified_config()
- self.page.sim = None
-
- @staticmethod
- def default_output(obj):
- """Find default output object for the input object if it exists"""
- output = None
- if isinstance(obj, spa.module.Module):
- if 'default' in obj.outputs.keys():
- output = obj.outputs['default'][0]
- elif isinstance(obj, nengo.network.Network):
- if hasattr(obj, 'output'):
- output = obj.output
- return output
+ model.connections.remove(self.conn)
+ model.nodes.remove(self.node)
+ self.conn, self.node = None, None
+
+ # TODO: make sure code_python_args never needed
+ # def code_python_args(self, uids):
+ # # generate the list of strings for the .cfg file to save this Component
+ # # (this is the text that would be passed in to the constructor)
+ # return [uids[self.obj]]
diff --git a/nengo_gui/components/voltage.py b/nengo_gui/components/voltage.py
index e9b646af..055a0640 100644
--- a/nengo_gui/components/voltage.py
+++ b/nengo_gui/components/voltage.py
@@ -1,62 +1,38 @@
from __future__ import division
import nengo
-import numpy as np
-import struct
-from nengo_gui.components.component import Component
+from .base import Widget
-class Voltage(Component):
+class Voltage(Widget):
"""Represents neuron voltage over time."""
- config_defaults = dict(
- max_value=5.0, min_value=0.0, **Component.config_defaults)
-
- def __init__(self, obj, n_neurons=5):
- super(Voltage, self).__init__()
- self.obj = obj.neurons
- self.data = []
- self.max_neurons = int(self.obj.size_out)
+ def __init__(self, client, obj, uid,
+ ylim=(0, 5), n_neurons=5, pos=None, label_visible=True):
+ super(Voltage, self).__init__(
+ client, obj, uid, pos=pos, label_visible=label_visible)
self.n_neurons = min(n_neurons, self.max_neurons)
- self.struct = struct.Struct('<%df' % (1 + self.n_neurons))
-
- def attach(self, page, config, uid):
- super(Voltage, self).attach(page, config, uid)
- self.label = page.get_label(self.obj.ensemble)
-
- def add_nengo_objects(self, page):
- with page.model:
- self.probe = nengo.Probe(self.obj[:self.n_neurons], 'voltage')
-
- def remove_nengo_objects(self, page):
- page.model.probes.remove(self.probe)
-
- def format_data(self, t, x):
- data = self.struct.pack(t, *x[:self.n_neurons])
- self.data.append(data)
-
- def update_client(self, client):
- sim = self.page.sim
- if sim is None:
- return
-
- # TODO: this is hack to delete probe data in Nengo 2.0.1, since we
- # can't limit the size of probes. Fix this up with Nengo 2.1.
- data = sim.data.raw[self.probe][:]
- del sim.data.raw[self.probe][:] # clear the data
- trange = sim.trange()[-len(data):]
-
- for t, datum in zip(trange, data):
- datum = (datum + np.arange(self.n_neurons))
- packet = self.struct.pack(t, *datum)
- client.write_binary(packet)
-
- def javascript(self):
- info = dict(uid=id(self), label=self.label,
- n_lines=self.n_neurons, synapse=0)
- json = self.javascript_config(info)
- return 'new Nengo.Value(main, sim, %s);' % json
-
- def code_python_args(self, uids):
- return [uids[self.obj.ensemble]]
+
+ @property
+ def max_neurons(self):
+ return self.obj.neurons.size_out
+
+ def add_nengo_objects(self, model):
+ with model:
+ # Note: this probe is read in simcontrol.control, which is a
+ # huge terrible hack.
+ self.probe = nengo.Probe(
+ self.obj.neurons[:self.n_neurons], 'voltage')
+
+ def create(self):
+ self.client.send("netgraph.create_voltage",
+ uid=self.uid,
+ label=self.label,
+ labelVisible=self.label_visible,
+ n_neurons=self.n_neurons,
+ synapse=0)
+
+ def remove_nengo_objects(self, model):
+ model.probes.remove(self.probe)
+ self.probe = None
diff --git a/nengo_gui/components/xyvalue.py b/nengo_gui/components/xyvalue.py
index 6119fcde..4da2827a 100644
--- a/nengo_gui/components/xyvalue.py
+++ b/nengo_gui/components/xyvalue.py
@@ -1,53 +1,49 @@
-import struct
-import collections
-
import nengo
+import numpy as np
-from nengo_gui.components.component import Component
-
+from .base import Widget
-class XYValue(Component):
- """Represents (at least) two dimensional values as co-ordinates on an
- x-y plot."""
- config_defaults = dict(max_value=1, min_value=-1, index_x=0, index_y=1,
- **Component.config_defaults)
+# TODO: does this need a separate widget from value?
+class XYValue(Widget):
+ """Represents two values as co-ordinates on an x-y plot."""
- def __init__(self, obj):
- super(XYValue, self).__init__()
- self.obj = obj
- self.data = collections.deque()
- self.n_lines = int(obj.size_out)
- self.struct = struct.Struct('<%df' % (1 + self.n_lines))
+ def __init__(self, client, obj, uid,
+ xlim=(-1, 1), ylim=(-1, 1), index_x=0, index_y=1,
+ pos=None, label_visible=True):
+ super(XYValue, self).__init__(client, obj, uid,
+ pos=pos, label_visible=label_visible)
self.node = None
self.conn = None
- def attach(self, page, config, uid):
- super(XYValue, self).attach(page, config, uid)
- self.label = page.get_label(self.obj)
+ @property
+ def n_lines(self):
+ return int(self.obj.size_out)
- def add_nengo_objects(self, page):
- with page.model:
- self.node = nengo.Node(self.gather_data,
- size_in=self.obj.size_out)
- self.conn = nengo.Connection(self.obj, self.node, synapse=0.01)
+ def add_nengo_objects(self, model):
- def remove_nengo_objects(self, page):
- page.model.connections.remove(self.conn)
- page.model.nodes.remove(self.node)
+ data = np.zeros(self.n_lines + 1)
- def gather_data(self, t, x):
- self.data.append(self.struct.pack(t, *x))
+ def fast_send_to_client(t, x):
+ data[0] = t
+ data[1:] = x
+ self.fast_client.send(data)
- def update_client(self, client):
- while len(self.data) > 0:
- data = self.data.popleft()
- client.write_binary(data)
-
- def javascript(self):
- info = dict(uid=id(self), n_lines=self.n_lines, label=self.label)
- json = self.javascript_config(info)
- return 'new Nengo.XYValue(main, sim, %s);' % json
+ with model:
+ self.node = nengo.Node(fast_send_to_client,
+ size_in=self.obj.size_out,
+ size_out=0)
+ # TODO: make synapse modifiable?
+ self.conn = nengo.Connection(self.obj, self.node, synapse=0.01)
- def code_python_args(self, uids):
- return [uids[self.obj]]
+ def create(self):
+ self.client.send("netgraph.create_xyvalue",
+ uid=self.uid,
+ n_lines=self.n_lines,
+ label=self.label,
+ labelVisible=self.label_visible)
+
+ def remove_nengo_objects(self, model):
+ model.connections.remove(self.conn)
+ model.nodes.remove(self.node)
+ self.conn, self.node = None, None
diff --git a/nengo_gui/config.py b/nengo_gui/config.py
index 91ad8118..112659c4 100644
--- a/nengo_gui/config.py
+++ b/nengo_gui/config.py
@@ -1,83 +1,113 @@
-import inspect
+import json
+import re
+import warnings
import nengo
+from nengo.utils.compat import iteritems
-import nengo_gui.components
-
-def make_param(name, default):
- try:
- # the most recent way of making Parameter objects
- p = nengo.params.Parameter(name=name, default=default)
- except TypeError:
- # this was for older releases of nengo (v2.0.3 and earlier)
- p = nengo.params.Parameter(default=default)
- return p
-
-class Config(nengo.Config):
- def __init__(self):
- super(Config, self).__init__()
- for cls in [nengo.Ensemble, nengo.Node]:
- self.configures(cls)
- self[cls].set_param('pos', make_param(name='pos', default=None))
- self[cls].set_param('size', make_param(name='size', default=None))
- self.configures(nengo.Network)
- self[nengo.Network].set_param('pos',
- make_param(name='pos', default=None))
- self[nengo.Network].set_param('size',
- make_param(name='size', default=None))
- self[nengo.Network].set_param('expanded',
- make_param(name='expanded',
- default=False))
- self[nengo.Network].set_param('has_layout',
- make_param(name='has_layout',
- default=False))
-
- for clsname, cls in inspect.getmembers(nengo_gui.components):
- if inspect.isclass(cls):
- if issubclass(cls, nengo_gui.components.component.Component):
- if cls != nengo_gui.components.component.Component:
- self.configures(cls)
- for k, v in cls.config_defaults.items():
- p = make_param(name=k, default=v)
- self[cls].set_param(k, p)
-
- def dumps(self, uids):
- lines = []
- for obj, uid in sorted(uids.items(), key=lambda x: x[1]):
-
- if isinstance(obj, (nengo.Ensemble, nengo.Node, nengo.Network)):
- if self[obj].pos is not None:
- lines.append('_viz_config[%s].pos=%s' % (uid,
- self[obj].pos))
- if self[obj].size is not None:
- lines.append('_viz_config[%s].size=%s' % (uid,
- self[obj].size))
- if isinstance(obj, nengo.Network):
- lines.append('_viz_config[%s].expanded=%s'
- % (uid, self[obj].expanded))
- lines.append('_viz_config[%s].has_layout=%s'
- % (uid, self[obj].has_layout))
-
- elif isinstance(obj, nengo_gui.components.component.Component):
- lines.append('%s = %s' % (uid, obj.code_python(uids)))
- for k in obj.config_defaults.keys():
- v = getattr(self[obj], k)
- val = repr(v)
-
- try:
- recovered_v = eval(val, {})
- except:
- raise ValueError("Cannot save %s to config. Only "
- "values that can be successfully "
- "evaluated are allowed." % (val))
-
- if recovered_v != v:
- raise ValueError("Cannot save %s to config, recovery "
- "failed. Only "
- "values that can be recovered after "
- "being entered into the config file "
- "can be saved." % (val))
-
- lines.append('_viz_config[%s].%s = %s' % (uid, k, val))
-
- return '\n'.join(lines)
+from nengo_gui.client import NengoGUIConfig
+from nengo_gui.components import Position
+
+
+def upgrade(old_text, locals):
+ """Upgrades a .cfg file from the old format (GUI 0.2)."""
+
+ new_config = {}
+
+ # All lines in the old files were assignments, so we won't
+ # use a full-fledged Python parser for this
+ cfgline = re.compile(r"(?P\S+?)\[(?P\S+)\]\.(?P\S+)")
+ compline = re.compile(
+ r"nengo_gui\.components\.(?P\S+)\((?P\S+)\)")
+
+ for line in old_text.splitlines():
+ left, right = line.split("=", 1)
+ left, right = left.strip(), right.strip()
+
+ # AceEditor, NetGraph and SimControl are no longer in cfg file
+ removed = ["AceEditor", "NetGraph", "SimControl"]
+ if any(r in right for r in removed) or "sim_control" in left:
+ continue
+
+ # Setting a value on a config item
+ elif "]." in left:
+ # Use a regex to parse left
+ match = cfgline.match(left)
+ if match is None:
+ raise ValueError("Could not parse %r" % left)
+
+ # TODO: some sanity checking on cfg group
+ obj = match.group("obj")
+ kw = match.group("kw")
+ if obj not in new_config:
+ # Could be a Nengo object with position / size.
+ # Try to figure out what type of object and add it.
+ try:
+ o = eval(obj, locals)
+ if isinstance(o, nengo.Network):
+ cls = "Network"
+ else:
+ cls = type(o).__name__
+ new_config[obj] = {"cls": cls}
+ except Exception as e:
+ warnings.warn("Skipping %r: %s" % (obj, e))
+ if obj in new_config:
+ new_config[obj][kw] = eval(right)
+
+ # Making a new component
+ else:
+ assert "nengo_gui.components" in right
+ obj = left
+ match = compline.match(right)
+ if match is None:
+ raise ValueError("Could not parse %r" % right)
+ args = match.group("arg").split(",")
+ new_config[obj] = {
+ "cls": match.group("cls").replace("Template", ""),
+ "obj": args[0],
+ }
+ for arg in args[1:]:
+ key, val = arg.split("=")
+ new_config[obj][key] = eval(val)
+
+ # Some components have been renamed
+ if new_config[obj]["cls"] == "Pointer":
+ new_config[obj]["cls"] = "SpaPointer"
+
+ # Additional changes
+ for obj, kwargs in iteritems(new_config):
+ # pos and size now one object
+ if "size" in kwargs:
+ pos = kwargs.pop("pos")
+ size = kwargs.pop("size")
+ kwargs["pos"] = Position(
+ left=pos[0], top=pos[1], width=size[0], height=size[1])
+
+ # x, y, width, height now one object
+ if "width" in kwargs:
+ kwargs["pos"] = Position(left=kwargs.pop("x"),
+ top=kwargs.pop("y"),
+ width=kwargs.pop("width"),
+ height=kwargs.pop("height"))
+
+ # show_legend now legend
+ if "show_legend" in kwargs:
+ kwargs["legend"] = kwargs.pop("show_legend")
+
+ # max_value, min_value now ylim
+ if "max_value" in kwargs:
+ maxval = kwargs.pop("max_value")
+ minval = kwargs.pop("min_value")
+ kwargs["ylim"] = (minval, maxval)
+
+ # Make sure label_visible is in there
+ kwargs.setdefault("label_visible", True)
+
+ # The scale of things is quite different now.
+ # Scale to a reasonable size.
+ kwargs["pos"].height *= 600
+ kwargs["pos"].width *= 600
+ kwargs["pos"].left *= 600
+ kwargs["pos"].top *= 600
+
+ return json.dumps(new_config, cls=NengoGUIConfig, indent=2, sort_keys=True)
diff --git a/nengo_gui/conftest.py b/nengo_gui/conftest.py
index a1e1f4d5..27e0130a 100644
--- a/nengo_gui/conftest.py
+++ b/nengo_gui/conftest.py
@@ -1,27 +1,51 @@
-from __future__ import print_function
-import time
+import os
+from pkg_resources import resource_filename
+
import pytest
-from selenium import webdriver
+
+from nengo_gui.client import ClientConnection, FastClientConnection
+
+
+class MockWebSocket(object):
+ def __init__(self):
+ self.binary = None
+ self.binary_history = []
+ self.text = None
+ self.text_history = []
+
+ def write_binary(self, binary):
+ self.binary_history.append(binary)
+ self.binary = binary
+
+ def write_text(self, text):
+ self.text_history.append(text)
+ self.text = text
+
+
+@pytest.fixture
+def client():
+ return ClientConnection(MockWebSocket())
-@pytest.fixture(scope="module")
-def driver(request):
+@pytest.fixture
+def fast_client():
+ return FastClientConnection(MockWebSocket())
- driver = webdriver.Firefox()
- driver.get('localhost:8080/')
- driver.maximize_window()
- time.sleep(4)
- def fin():
- driver.close()
+@pytest.fixture
+def example():
+ return lambda ex: resource_filename("nengo_gui", "examples/%s" % (ex,))
- request.addfinalizer(fin)
- try:
- assert driver.title != "Problem loading page"
- except AssertionError:
- print("ERROR: The 'nengo' server is not currently running. "
- "Start the server before running tests.")
- raise
+def pytest_generate_tests(metafunc):
+ examples = []
+ example_dir = resource_filename("nengo_gui", "examples")
+ for subdir, _, files in os.walk(example_dir):
+ if (os.path.sep + '.') in subdir:
+ continue
+ examples.extend([
+ os.path.join(subdir, f) for f in files if f.endswith(".py")
+ ])
- return driver
+ if "all_examples" in metafunc.funcargnames:
+ metafunc.parametrize("all_examples", examples)
diff --git a/nengo_gui/editor.py b/nengo_gui/editor.py
new file mode 100644
index 00000000..69115e33
--- /dev/null
+++ b/nengo_gui/editor.py
@@ -0,0 +1,100 @@
+from nengo_gui.client import bind, ExposedToClient
+
+
+class Stream(ExposedToClient):
+ """A stream that only sends when output has changed."""
+
+ def __init__(self, name, client):
+ super(Stream, self).__init__(client)
+ self.name = name
+ self._output = None
+ self._line = None
+
+ def clear(self):
+ if self._output is not None or self._line is not None:
+ self._output = None
+ self._line = None
+ self.send()
+
+ def send(self):
+ raise NotImplementedError()
+
+ @bind("editor.{self.name}")
+ def set(self, output, line=None):
+ if output != self._output or line != self._line:
+ self._output = output
+ self._line = line
+ self.send()
+
+
+class TerminalStream(Stream):
+ def send(self):
+ if self._line is not None:
+ print("L%d: %s" % (self._line, self._output))
+ elif self._output is not None:
+ print(self._output)
+
+
+class NetworkStream(Stream):
+ def send(self):
+ self.client.send("editor.%s" % (self.name,),
+ output=self._output, line=self._line)
+
+
+class Editor(ExposedToClient):
+ def __init__(self, client):
+ super(Editor, self).__init__(client)
+ self.stdout = None
+ self.stderr = None
+
+ @property
+ def code(self):
+ return ""
+
+ @bind("editor.ready")
+ def ready(self):
+ pass
+
+ def send_filename(self):
+ pass
+
+ def update(self, code):
+ pass
+
+
+class NoEditor(Editor):
+ def __init__(self, client):
+ super(NoEditor, self).__init__(client)
+ self.stdout = TerminalStream("stdout", self.client)
+ self.stderr = TerminalStream("stderr", self.client)
+
+
+class AceEditor(Editor):
+
+ def __init__(self, client):
+ super(AceEditor, self).__init__(client)
+ self.stdout = NetworkStream("stdout", self.client)
+ self.stderr = NetworkStream("stderr", self.client)
+
+ self._code = None
+
+ @property
+ @bind("editor.get_code")
+ def code(self):
+ return self._code
+
+ @code.setter
+ @bind("editor.set_code")
+ def code(self, code):
+ if code != self._code:
+ self._code = code
+
+ @bind("editor.sync")
+ def sync(self):
+ self.client.send("editor.code", code=self.code)
+ self.stdout.send()
+ self.stderr.send()
+
+ def send_filename(self, filename, error=None):
+ self.client.send("editor.filename",
+ filename=filename, error=error)
diff --git a/nengo_gui/examples/basics/2d_representation.py b/nengo_gui/examples/basics/2d_representation.py
index 65c3002d..5784f9eb 100644
--- a/nengo_gui/examples/basics/2d_representation.py
+++ b/nengo_gui/examples/basics/2d_representation.py
@@ -6,7 +6,6 @@
# integrate-and-fire neurons.
import nengo
-import numpy as np
model = nengo.Network()
with model:
diff --git a/nengo_gui/examples/basics/combining.py b/nengo_gui/examples/basics/combining.py
index b74e9f15..c9993059 100644
--- a/nengo_gui/examples/basics/combining.py
+++ b/nengo_gui/examples/basics/combining.py
@@ -26,4 +26,4 @@
# The square brackets define which dimension the input will project to
nengo.Connection(a, output[1])
- nengo.Connection(b, output[0])
\ No newline at end of file
+ nengo.Connection(b, output[0])
diff --git a/nengo_gui/examples/basics/ensemble_array.py b/nengo_gui/examples/basics/ensemble_array.py
index 393e8d87..12cfad04 100644
--- a/nengo_gui/examples/basics/ensemble_array.py
+++ b/nengo_gui/examples/basics/ensemble_array.py
@@ -24,4 +24,4 @@
# Connect the model elements, just feedforward
nengo.Connection(sin, a.input)
nengo.Connection(a.output, b)
- nengo.Connection(b, c.input)
\ No newline at end of file
+ nengo.Connection(b, c.input)
diff --git a/nengo_gui/examples/basics/ensemble_array.py.cfg b/nengo_gui/examples/basics/ensemble_array.py.cfg
new file mode 100644
index 00000000..5b43ba77
--- /dev/null
+++ b/nengo_gui/examples/basics/ensemble_array.py.cfg
@@ -0,0 +1,67 @@
+_viz_0 = nengo_gui.components.Value(a)
+_viz_config[_viz_0].synapse = 0.01
+_viz_config[_viz_0].max_value = 1
+_viz_config[_viz_0].min_value = -1
+_viz_config[_viz_0].legend_labels = [u'label_0', u'label_1']
+_viz_config[_viz_0].height = 0.1644745445278466
+_viz_config[_viz_0].label_visible = True
+_viz_config[_viz_0].width = 0.11829408640999423
+_viz_config[_viz_0].show_legend = False
+_viz_config[_viz_0].y = 1.1055931806598083
+_viz_config[_viz_0].x = 0.3254703706064484
+_viz_1 = nengo_gui.components.Value(c)
+_viz_config[_viz_1].synapse = 0.01
+_viz_config[_viz_1].max_value = 1
+_viz_config[_viz_1].min_value = -1
+_viz_config[_viz_1].legend_labels = [u'label_0', u'label_1']
+_viz_config[_viz_1].height = 0.1644745445278466
+_viz_config[_viz_1].label_visible = True
+_viz_config[_viz_1].width = 0.11829408640999423
+_viz_config[_viz_1].show_legend = False
+_viz_config[_viz_1].y = 1.103948435214527
+_viz_config[_viz_1].x = 0.8654706609367949
+_viz_2 = nengo_gui.components.Slider(sin)
+_viz_config[_viz_2].label_visible = True
+_viz_config[_viz_2].width = 0.05914704320499711
+_viz_config[_viz_2].x = 0.053756182601454636
+_viz_config[_viz_2].y = 1.1013843061532724
+_viz_config[_viz_2].max_value = 1
+_viz_config[_viz_2].min_value = -1
+_viz_config[_viz_2].height = 0.1644745445278466
+_viz_ace_editor = nengo_gui.components.AceEditor()
+_viz_net_graph = nengo_gui.components.NetGraph()
+_viz_sim_control = nengo_gui.components.SimControl()
+_viz_config[_viz_sim_control].kept_time = 4.0
+_viz_config[_viz_sim_control].shown_time = 0.5
+_viz_config[a].pos=(0.32795698924731187, 0.5)
+_viz_config[a].size=(0.10752688172043011, 0.4)
+_viz_config[a].expanded=True
+_viz_config[a].has_layout=True
+_viz_config[a.ea_ensembles[0]].pos=(0.49999999999999994, 0.2)
+_viz_config[a.ea_ensembles[0]].size=(0.09803921568627451, 0.1)
+_viz_config[a.ea_ensembles[1]].pos=(0.49999999999999994, 0.7999999999999999)
+_viz_config[a.ea_ensembles[1]].size=(0.09803921568627451, 0.1)
+_viz_config[a.input].pos=(0.12745098039215685, 0.5)
+_viz_config[a.input].size=(0.07843137254901959, 0.08)
+_viz_config[a.output].pos=(0.872549019607843, 0.5)
+_viz_config[a.output].size=(0.07843137254901959, 0.08)
+_viz_config[b].pos=(0.5967741935483871, 0.5)
+_viz_config[b].size=(0.053763440860215055, 0.1)
+_viz_config[c].pos=(0.8655913978494624, 0.5)
+_viz_config[c].size=(0.10752688172043011, 0.4)
+_viz_config[c].expanded=True
+_viz_config[c].has_layout=True
+_viz_config[c.ea_ensembles[0]].pos=(0.49999999999999994, 0.2)
+_viz_config[c.ea_ensembles[0]].size=(0.09803921568627451, 0.1)
+_viz_config[c.ea_ensembles[1]].pos=(0.49999999999999994, 0.7999999999999999)
+_viz_config[c.ea_ensembles[1]].size=(0.09803921568627451, 0.1)
+_viz_config[c.input].pos=(0.12745098039215685, 0.5)
+_viz_config[c.input].size=(0.07843137254901959, 0.08)
+_viz_config[c.output].pos=(0.872549019607843, 0.5)
+_viz_config[c.output].size=(0.07843137254901959, 0.08)
+_viz_config[model].pos=(0.12861743759071817, -0.044436633405907096)
+_viz_config[model].size=(0.7127747007423403, 0.7127747007423403)
+_viz_config[model].expanded=True
+_viz_config[model].has_layout=True
+_viz_config[sin].pos=(0.06989247311827958, 0.5)
+_viz_config[sin].size=(0.043010752688172046, 0.08)
\ No newline at end of file
diff --git a/nengo_gui/examples/basics/html.py b/nengo_gui/examples/basics/html.py
index 29054fc3..db8271aa 100644
--- a/nengo_gui/examples/basics/html.py
+++ b/nengo_gui/examples/basics/html.py
@@ -16,7 +16,7 @@
#
# The second example shows that the custom HTML can also be based on the values
# represented by neurons. Here, we make a Node that reads in a value that is
-# treated as an amount. If the value is above 0.5, the HTML says "large",
+# treated as an amount. If the value is above 0.5, the HTML says "large",
# if the value is below -0.5, it says "small", and otherwise it says "medium".
#
# Finally, the third example uses SVG and trigonometry to create a simple
@@ -30,7 +30,7 @@
model = nengo.Network()
with model:
-
+
# Example 1: a timer
def timer_function(t):
if t < 1.0:
@@ -43,9 +43,8 @@ def timer_function(t):
timer_function._nengo_html_ = '
Go!
'
return 1
timer = nengo.Node(timer_function)
-
-
- # Example 2: displaying a value
+
+ # Example 2: displaying a value
def amount_function(t, x):
if x < -0.5:
amount_function._nengo_html_ = '
small
'
@@ -58,9 +57,8 @@ def amount_function(t, x):
display_amount = nengo.Node(amount_function, size_in=1)
nengo.Connection(stim_amount, amount)
nengo.Connection(amount, display_amount)
-
-
- # Example 3: a two-joint arm
+
+ # Example 3: a two-joint arm
def arm_function(t, angles):
len0 = 50
len1 = 30
@@ -71,14 +69,13 @@ def arm_function(t, angles):
x3 = x2 + len1 * np.sin(angles[0] + angles[1])
y3 = y2 - len1 * np.cos(angles[0] + angles[1])
arm_function._nengo_html_ = '''
-
+
'''.format(**locals())
stim_angles = nengo.Node([0.3, 0.3])
angles = nengo.Ensemble(n_neurons=200, dimensions=2)
arm = nengo.Node(arm_function, size_in=2)
nengo.Connection(stim_angles, angles)
nengo.Connection(angles, arm)
-
\ No newline at end of file
diff --git a/nengo_gui/examples/basics/inhibitory_gating.py b/nengo_gui/examples/basics/inhibitory_gating.py
index c89730a5..a966bceb 100644
--- a/nengo_gui/examples/basics/inhibitory_gating.py
+++ b/nengo_gui/examples/basics/inhibitory_gating.py
@@ -1,5 +1,5 @@
# Nengo Example: Inhibitory Gating of Ensembles
-
+#
# ## Step 1: Create the network
#
# Our model consists of two ensembles (called A and B) that receive inputs from
diff --git a/nengo_gui/examples/basics/multiplication.py b/nengo_gui/examples/basics/multiplication.py
index bd944538..fb339a43 100644
--- a/nengo_gui/examples/basics/multiplication.py
+++ b/nengo_gui/examples/basics/multiplication.py
@@ -1,5 +1,5 @@
# Nengo Example: Multiplication
-
+#
# This example will show you how to multiply two values. The model
# architecture can be thought of as a combination of the combining demo and
# the squaring demo. Essentially, we project both inputs independently into a
@@ -7,6 +7,7 @@
# product of the first and second vector elements).
import nengo
+
model = nengo.Network()
with model:
# Create 4 ensembles of leaky integrate-and-fire neurons
@@ -18,12 +19,12 @@
prod = nengo.Ensemble(n_neurons=100, dimensions=1, radius=1)
-
# These next two lines make all of the encoders in the Combined population
# point at the corners of the cube. This improves the quality of the
# computation.
- combined.encoders = nengo.dists.Choice([[1,1],[-1,1],[1,-1],[-1,-1]])
-
+ combined.encoders = nengo.dists.Choice(
+ [[1, 1], [-1, 1], [1, -1], [-1, -1]])
+
stim_a = nengo.Node([0])
stim_b = nengo.Node([0])
@@ -40,4 +41,4 @@ def product(x):
return x[0] * x[1]
# Connect the combined ensemble to the output ensemble
- nengo.Connection(combined, prod, function=product)
\ No newline at end of file
+ nengo.Connection(combined, prod, function=product)
diff --git a/nengo_gui/examples/basics/single_neuron.py b/nengo_gui/examples/basics/single_neuron.py
index f9350f9b..78937912 100644
--- a/nengo_gui/examples/basics/single_neuron.py
+++ b/nengo_gui/examples/basics/single_neuron.py
@@ -6,17 +6,18 @@
# there is only one neuron.
import nengo
+
model = nengo.Network()
with model:
neuron = nengo.Ensemble(n_neurons=1, dimensions=1)
# Set intercept to 0.5
- neuron.intercepts=nengo.dists.Uniform(-.5, -.5)
+ neuron.intercepts = nengo.dists.Uniform(-.5, -.5)
# Set the maximum firing rate of the neuron to 100hz
- neuron.max_rates=nengo.dists.Uniform(100, 100)
+ neuron.max_rates = nengo.dists.Uniform(100, 100)
# Sets the neurons firing rate to increase for positive input
- neuron.encoders=[[1]]
+ neuron.encoders = [[1]]
stim = nengo.Node(0)
# Connect the input signal to the neuron
- nengo.Connection(stim, neuron)
\ No newline at end of file
+ nengo.Connection(stim, neuron)
diff --git a/nengo_gui/examples/basics/squaring.py b/nengo_gui/examples/basics/squaring.py
index cab0769a..a84025d5 100644
--- a/nengo_gui/examples/basics/squaring.py
+++ b/nengo_gui/examples/basics/squaring.py
@@ -1,10 +1,11 @@
# Nengo Example: Squaring the Input
-
+#
# This demo shows you how to construct a network that squares the value
# encoded in a first population in the output of a second population.
# Create the model object
import nengo
+
model = nengo.Network()
with model:
# Create two ensembles of 100 leaky-integrate-and-fire neurons
@@ -22,4 +23,4 @@ def square(x):
return x[0] * x[0]
# Connection ensemble a to ensemble b
- nengo.Connection(a, b, function=square)
\ No newline at end of file
+ nengo.Connection(a, b, function=square)
diff --git a/nengo_gui/examples/basics/two_neurons.py b/nengo_gui/examples/basics/two_neurons.py
index c5761c63..70ca8da3 100644
--- a/nengo_gui/examples/basics/two_neurons.py
+++ b/nengo_gui/examples/basics/two_neurons.py
@@ -11,19 +11,17 @@
# reasonable representation of a scalar value.
import nengo
-import numpy as np
model = nengo.Network()
with model:
neurons = nengo.Ensemble(n_neurons=2, dimensions=1)
# Set the intercepts at .5
- neurons.intercepts=nengo.dists.Uniform(-.5, -.5)
+ neurons.intercepts = nengo.dists.Uniform(-.5, -.5)
# Set the max firing rate at 100hz
- neurons.max_rates=nengo.dists.Uniform(100, 100)
+ neurons.max_rates = nengo.dists.Uniform(100, 100)
# One 'on' and one 'off' neuron
- neurons.encoders=[[1],[-1]]
-
- # make a
+ neurons.encoders = [[1], [-1]]
+
stim = nengo.Node([0])
nengo.Connection(stim, neurons, synapse=0.01)
diff --git a/nengo_gui/examples/default.json b/nengo_gui/examples/default.json
new file mode 100644
index 00000000..a640d697
--- /dev/null
+++ b/nengo_gui/examples/default.json
@@ -0,0 +1,69 @@
+{
+ "_viz_0": {
+ "cls": "Value",
+ "label_visible": true,
+ "legend": false,
+ "legend_labels": [
+ "label_0"
+ ],
+ "obj": "a",
+ "pos": {
+ "height": 70.33997655334115,
+ "left": 462.75848551210237,
+ "top": 537.2548882310801,
+ "width": 50.16722408026756
+ },
+ "synapse": 0.01,
+ "ylim": [
+ -1,
+ 1
+ ]
+ },
+ "_viz_1": {
+ "cls": "Slider",
+ "label_visible": true,
+ "obj": "stim",
+ "pos": {
+ "height": 90.67509346098738,
+ "left": 113.95125886942171,
+ "top": 300.3088766314966,
+ "width": 32.33522354607953
+ },
+ "ylim": [
+ -1,
+ 1
+ ]
+ },
+ "a": {
+ "cls": "Ensemble",
+ "label_visible": true,
+ "pos": {
+ "height": 150.0,
+ "left": 464.9297725782068,
+ "top": 297.2797471961704,
+ "width": 90.9090909090909
+ }
+ },
+ "model": {
+ "cls": "Network",
+ "expanded": true,
+ "has_layout": true,
+ "label_visible": true,
+ "pos": {
+ "height": 427.66482044540237,
+ "left": 231.03431258051188,
+ "top": 100.53641766478532,
+ "width": 427.66482044540237
+ }
+ },
+ "stim": {
+ "cls": "Node",
+ "label_visible": true,
+ "pos": {
+ "height": 120.0,
+ "left": 118.18181818181817,
+ "top": 300.0,
+ "width": 72.72727272727272
+ }
+ }
+}
\ No newline at end of file
diff --git a/nengo_gui/examples/function_space/03-attend.py.cfg b/nengo_gui/examples/function_space/03-attend.py.cfg
new file mode 100644
index 00000000..eea5f3fa
--- /dev/null
+++ b/nengo_gui/examples/function_space/03-attend.py.cfg
@@ -0,0 +1,38 @@
+_viz_0 = nengo_gui.components.Value(choice)
+_viz_config[_viz_0].synapse = 0.01
+_viz_config[_viz_0].x = 0.7258064516129032
+_viz_config[_viz_0].height = 0.11723329425556858
+_viz_config[_viz_0].width = 0.09900990099009901
+_viz_config[_viz_0].max_value = 1
+_viz_config[_viz_0].y = 0.44411764705882356
+_viz_config[_viz_0].show_legend = False
+_viz_config[_viz_0].label_visible = True
+_viz_config[_viz_0].legend_labels = []
+_viz_config[_viz_0].min_value = -1
+_viz_ace_editor = nengo_gui.components.AceEditor()
+_viz_net_graph = nengo_gui.components.NetGraph()
+_viz_sim_control = nengo_gui.components.SimControl()
+_viz_config[_viz_sim_control].shown_time = 0.5
+_viz_config[_viz_sim_control].kept_time = 4.0
+_viz_config[choice].pos=(0.6720430107526882, 0.4147058823529412)
+_viz_config[choice].size=(0.053763440860215055, 0.029411764705882353)
+_viz_config[ens].pos=(0.4032258064516129, 0.5)
+_viz_config[ens].size=(0.053763440860215055, 0.029411764705882353)
+_viz_config[model].pos=(0, 0)
+_viz_config[model].size=(1.0, 1.0)
+_viz_config[model].expanded=True
+_viz_config[model].has_layout=True
+_viz_config[model.networks[0]].pos=(0.13440860215053763, 0.14705882352941177)
+_viz_config[model.networks[0]].size=(0.10752688172043011, 0.11764705882352941)
+_viz_config[model.networks[0]].expanded=False
+_viz_config[model.networks[0]].has_layout=False
+_viz_config[model.networks[1]].pos=(0.13440860215053763, 0.5)
+_viz_config[model.networks[1]].size=(0.10752688172043011, 0.11764705882352941)
+_viz_config[model.networks[1]].expanded=False
+_viz_config[model.networks[1]].has_layout=False
+_viz_config[plot].pos=(0.9301075268817204, 0.5)
+_viz_config[plot].size=(0.043010752688172046, 0.023529411764705882)
+_viz_config[stim].pos=(0.13440860215053763, 0.8529411764705882)
+_viz_config[stim].size=(0.10752688172043011, 0.11764705882352941)
+_viz_config[stim].expanded=False
+_viz_config[stim].has_layout=False
\ No newline at end of file
diff --git a/nengo_gui/examples/recurrent/controlled_integrator.py b/nengo_gui/examples/recurrent/controlled_integrator.py
index 4dc03a71..19596ecc 100644
--- a/nengo_gui/examples/recurrent/controlled_integrator.py
+++ b/nengo_gui/examples/recurrent/controlled_integrator.py
@@ -6,7 +6,7 @@
# 2. Control - the control signal to the integrator
#
# A controlled integrator accumulates input, but its state can be directly
-# manipulated by the control signal.
+# manipulated by the control signal.
#
# We can use standard network-creation commands to begin creating our
# controlled integrator. We create a Network, and then we create a population
@@ -42,4 +42,4 @@
# a transform
nengo.Connection(a, a[0],
function=lambda x: x[0] * x[1],
- synapse=tau)
\ No newline at end of file
+ synapse=tau)
diff --git a/nengo_gui/examples/recurrent/controlled_oscillator.py b/nengo_gui/examples/recurrent/controlled_oscillator.py
index ff767809..e64f73a8 100644
--- a/nengo_gui/examples/recurrent/controlled_oscillator.py
+++ b/nengo_gui/examples/recurrent/controlled_oscillator.py
@@ -5,7 +5,7 @@
import nengo
-tau = 0.1 # Post-synaptic time constant for feedback
+tau = 0.1 # Post-synaptic time constant for feedback
w_max = 10 # Maximum frequency is w_max/(2*pi)
model = nengo.Network()
diff --git a/nengo_gui/examples/recurrent/integrator.py b/nengo_gui/examples/recurrent/integrator.py
index 80160db9..8f990535 100644
--- a/nengo_gui/examples/recurrent/integrator.py
+++ b/nengo_gui/examples/recurrent/integrator.py
@@ -3,13 +3,13 @@
# This demo implements a one-dimensional neural integrator.
#
# This is the first example of a recurrent network in the demos. It shows how
-# neurons can be used to implement stable dynamics. Such dynamics are
-# important for memory, noise cleanup, statistical inference, and many
+# neurons can be used to implement stable dynamics. Such dynamics are
+# important for memory, noise cleanup, statistical inference, and many
# other dynamic transformations.
#
-# Note that since the integrator constantly sums its input, it will
-# saturate quickly if you leave the input non-zero. This makes it clear
-# that neurons have a finite range of representation. Such saturation
+# Note that since the integrator constantly sums its input, it will
+# saturate quickly if you leave the input non-zero. This makes it clear
+# that neurons have a finite range of representation. Such saturation
# effects can be exploited to perform useful computations
# (e.g. soft normalization).
@@ -23,12 +23,12 @@
# Create a piecewise step function for input
stim = nengo.Node([0])
-
- # Connect the population to itself using a long time constant (tau)
+
+ # Connect the population to itself using a long time constant (tau)
# for stability
tau = 0.1
nengo.Connection(a, a, synapse=tau)
# Connect the input using the same time constant as on the recurrent
# connection to make it more ideal
- nengo.Connection(stim, a, transform=tau, synapse=tau)
\ No newline at end of file
+ nengo.Connection(stim, a, transform=tau, synapse=tau)
diff --git a/nengo_gui/examples/recurrent/lorenz_attractor.py b/nengo_gui/examples/recurrent/lorenz_attractor.py
index d460e9ee..d4cd69dc 100644
--- a/nengo_gui/examples/recurrent/lorenz_attractor.py
+++ b/nengo_gui/examples/recurrent/lorenz_attractor.py
@@ -8,24 +8,25 @@
# dx2/dt = x0 * x1 - beta * x2
#
# Since x2 is centered around approximately rho, and since NEF ensembles
-# are usually optimized to represent values within a certain radius of the
+# are usually optimized to represent values within a certain radius of the
# origin, we substitute x2' = x2 - rho, giving these equations:
#
# dx0/dt = sigma * (x1 - x0)
# dx1/dt = - x0 * x2' - x1
# dx2/dt = x0 * x1 - beta * (x2 + rho) - rho
#
-# For more information, see
+# For more information, see
# http://compneuro.uwaterloo.ca/publications/eliasmith2005b.html
-# "Chris Eliasmith. A unified approach to building and controlling
+# "Chris Eliasmith. A unified approach to building and controlling
# spiking attractor networks. Neural computation, 7(6):1276-1314, 2005."
+import nengo
+
tau = 0.1
sigma = 10
beta = 8.0/3
rho = 28
-import nengo
def feedback(x):
dx0 = -sigma * x[0] + sigma * x[1]
@@ -36,6 +37,7 @@ def feedback(x):
dx1 * tau + x[1],
dx2 * tau + x[2]]
+
model = nengo.Network(seed=1)
with model:
state = nengo.Ensemble(2000, 3, radius=30)
diff --git a/nengo_gui/examples/recurrent/lorenz_attractor.py.cfg b/nengo_gui/examples/recurrent/lorenz_attractor.py.cfg
new file mode 100644
index 00000000..559283fa
--- /dev/null
+++ b/nengo_gui/examples/recurrent/lorenz_attractor.py.cfg
@@ -0,0 +1,11 @@
+_viz_ace_editor = nengo_gui.components.AceEditor()
+_viz_net_graph = nengo_gui.components.NetGraph()
+_viz_sim_control = nengo_gui.components.SimControl()
+_viz_config[_viz_sim_control].kept_time = 4.0
+_viz_config[_viz_sim_control].shown_time = 0.5
+_viz_config[model].pos=(0.19144080494505522, 0.1932027135545571)
+_viz_config[model].size=(0.7757364659745805, 0.7757364659745805)
+_viz_config[model].expanded=True
+_viz_config[model].has_layout=True
+_viz_config[state].pos=(0.5, 0.5)
+_viz_config[state].size=(0.33333333333333337, 0.25)
\ No newline at end of file
diff --git a/nengo_gui/examples/recurrent/oscillator.py b/nengo_gui/examples/recurrent/oscillator.py
index c34c3726..620bfecb 100644
--- a/nengo_gui/examples/recurrent/oscillator.py
+++ b/nengo_gui/examples/recurrent/oscillator.py
@@ -1,10 +1,10 @@
# Nengo Example: A Simple Harmonic Oscillator
#
-# This demo implements a simple harmonic oscillator in a 2D neural
-# population. The oscillator is more visually interesting on its own than
-# the integrator, but the principle at work is the same. Here, instead of
-# having the recurrent input just integrate (i.e. feeding the full input
-# value back to the population), we have two dimensions which interact.
+# This demo implements a simple harmonic oscillator in a 2D neural
+# population. The oscillator is more visually interesting on its own than
+# the integrator, but the principle at work is the same. Here, instead of
+# having the recurrent input just integrate (i.e. feeding the full input
+# value back to the population), we have two dimensions which interact.
import nengo
@@ -20,4 +20,6 @@
nengo.Connection(stim, neurons[0])
# Create the feedback connection
- nengo.Connection(neurons, neurons, transform=[[1, 1], [-1, 1]], synapse=0.1)
\ No newline at end of file
+ nengo.Connection(neurons, neurons,
+ transform=[[1, 1], [-1, 1]],
+ synapse=0.1)
diff --git a/nengo_gui/examples/tutorial/00-intro.py b/nengo_gui/examples/tutorial/00-intro.py
index e8407579..4bdaae6f 100644
--- a/nengo_gui/examples/tutorial/00-intro.py
+++ b/nengo_gui/examples/tutorial/00-intro.py
@@ -1,11 +1,11 @@
# Tutorial 0: Welcome to Nengo
-
+#
# Nengo is a tool for creating large-scale biologically
# realistic neural models. It was developed by the Centre for Theoretical
# Neuroscience at the University of Waterloo and the affiliated spin-off
# company Applied Brain Research. It has been used to create Spaun, the
# first simulated brain that is capable of performing tasks.
-
+#
# This sequence of tutorials takes you through the various features of Nengo.
#
# You can go to the next tutorial by clicking on the "Open file" icon in the
diff --git a/nengo_gui/examples/tutorial/00-intro.py.cfg b/nengo_gui/examples/tutorial/00-intro.py.cfg
index 23ef82d4..058ec54a 100644
--- a/nengo_gui/examples/tutorial/00-intro.py.cfg
+++ b/nengo_gui/examples/tutorial/00-intro.py.cfg
@@ -31,7 +31,5 @@ _viz_config[model].pos=(0.348830468735809, 0.49201138799364114)
_viz_config[model].size=(0.6017670646427314, 0.6017670646427314)
_viz_config[model].expanded=True
_viz_config[model].has_layout=True
-_viz_config[slowdown].pos=(-0.20466619557631088, 0.9573856355604904)
-_viz_config[slowdown].size=(0.046883981863295635, 0.03943425722388151)
_viz_config[stim].pos=(0.19696969696969696, 0.5)
-_viz_config[stim].size=(0.1212121212121212, 0.2)
\ No newline at end of file
+_viz_config[stim].size=(0.1212121212121212, 0.2)
diff --git a/nengo_gui/examples/tutorial/01-one-neuron.py b/nengo_gui/examples/tutorial/01-one-neuron.py
index b62f8570..98c856a7 100644
--- a/nengo_gui/examples/tutorial/01-one-neuron.py
+++ b/nengo_gui/examples/tutorial/01-one-neuron.py
@@ -1,12 +1,12 @@
# Tutorial 1: A Single Neuron
-
+#
# Here we show one single neuron. The slider on the left adjusts the input
# to that neuron. The top graph shows the voltage in the neuron. When that
# voltage is high enough, the neuron "spikes", producing an output (middle
# graph). That output releases neurotransmitter which is gradually
# reabsorbed. Given only that neurotransmitter output, it is difficult
# to reconstruct the original input (bottom graph).
-
+#
# User Interface Tip: You can adjust the amount of time shown on the graphs
# by dragging the left side of the gray bar inside the timeline at the bottom
# of the screen. Try reducing it down to a smaller size so you can see
diff --git a/nengo_gui/examples/tutorial/01-one-neuron.py.cfg b/nengo_gui/examples/tutorial/01-one-neuron.py.cfg
index 4d80c68b..7774bd7c 100644
--- a/nengo_gui/examples/tutorial/01-one-neuron.py.cfg
+++ b/nengo_gui/examples/tutorial/01-one-neuron.py.cfg
@@ -39,7 +39,5 @@ _viz_config[model].pos=(0.29008605754397315, 0.3199774777364316)
_viz_config[model].size=(0.6017670646427314, 0.6017670646427314)
_viz_config[model].expanded=True
_viz_config[model].has_layout=True
-_viz_config[slowdown].pos=(0.030859781980228436, 0.9023282461839404)
-_viz_config[slowdown].size=(0.03391052259673938, 0.04352059432201608)
_viz_config[stim].pos=(0.14178396618598132, 0.3030303030303031)
-_viz_config[stim].size=(0.06602639042840559, 0.12121212121212124)
\ No newline at end of file
+_viz_config[stim].size=(0.06602639042840559, 0.12121212121212124)
diff --git a/nengo_gui/examples/tutorial/02-two-neurons.py b/nengo_gui/examples/tutorial/02-two-neurons.py
index 5705023d..99eaffdf 100644
--- a/nengo_gui/examples/tutorial/02-two-neurons.py
+++ b/nengo_gui/examples/tutorial/02-two-neurons.py
@@ -1,11 +1,11 @@
# Tutorial 2: Two neurons
-
+#
# Now we show what happens with two neurons. Notice that the neurons
# respond very differently to the two inputs. One neuron responds more
# strongly to positive values, while the other responds more to negative
# values. This is typical of real neurons (sometimes called "on" and "off"
# neurons).
-
+#
# Given these two neurons, more information is available about what the input
# is. The bottom graph shows the "decoded" value found by taking the output
# of both neurons and combining them. Notice that these two neurons do a
diff --git a/nengo_gui/examples/tutorial/02-two-neurons.py.cfg b/nengo_gui/examples/tutorial/02-two-neurons.py.cfg
index ca41dba9..1cf2d7d6 100644
--- a/nengo_gui/examples/tutorial/02-two-neurons.py.cfg
+++ b/nengo_gui/examples/tutorial/02-two-neurons.py.cfg
@@ -39,7 +39,5 @@ _viz_config[model].pos=(0.17221039638865257, 0.2877231522537397)
_viz_config[model].size=(0.7127747007423387, 0.7127747007423387)
_viz_config[model].expanded=True
_viz_config[model].has_layout=True
-_viz_config[slowdown].pos=(0.030859781980228436, 0.9023282461839404)
-_viz_config[slowdown].size=(0.03391052259673938, 0.04352059432201608)
_viz_config[stim].pos=(0.14178396618598132, 0.3030303030303031)
-_viz_config[stim].size=(0.06602639042840559, 0.12121212121212124)
\ No newline at end of file
+_viz_config[stim].size=(0.06602639042840559, 0.12121212121212124)
diff --git a/nengo_gui/examples/tutorial/03-many-neurons.py b/nengo_gui/examples/tutorial/03-many-neurons.py
index fdbb4a76..46f57e05 100644
--- a/nengo_gui/examples/tutorial/03-many-neurons.py
+++ b/nengo_gui/examples/tutorial/03-many-neurons.py
@@ -1,16 +1,16 @@
# Tutorial 3: Many neurons
-
+#
# Brains have many neurons. What happens if we use 20 neurons? 50? 100?
-
+#
# The code below shows 20 neurons in a group (we call groups of neurons
# an "Ensemble" of neurons). The representation is much more accurate.
# To see this, press Play. Now, as you move the slider, the bottom graph
-# should follow your movements well.
-
+# should follow your movements well.
+#
# Try changing the number of neurons by editing the code below where it says
# "n_neurons=20". Try 50. Try 100. The representation should get more and
# more accurate.
-
+#
# Nengo Tip: Don't use too big a number! Depending on your computer, using
# more than 1000 neurons in a single Ensemble can take a long time to compute.
# This is because Nengo needs to figure out how to weight the outputs of all
diff --git a/nengo_gui/examples/tutorial/03-many-neurons.py.cfg b/nengo_gui/examples/tutorial/03-many-neurons.py.cfg
index bb1ea175..9c3d847d 100644
--- a/nengo_gui/examples/tutorial/03-many-neurons.py.cfg
+++ b/nengo_gui/examples/tutorial/03-many-neurons.py.cfg
@@ -31,7 +31,5 @@ _viz_config[model].pos=(0.17221039638865257, 0.2877231522537397)
_viz_config[model].size=(0.7127747007423387, 0.7127747007423387)
_viz_config[model].expanded=True
_viz_config[model].has_layout=True
-_viz_config[slowdown].pos=(0.030859781980228436, 0.9023282461839404)
-_viz_config[slowdown].size=(0.03391052259673938, 0.04352059432201608)
_viz_config[stim].pos=(0.14178396618598132, 0.3030303030303031)
-_viz_config[stim].size=(0.06602639042840559, 0.12121212121212124)
\ No newline at end of file
+_viz_config[stim].size=(0.06602639042840559, 0.12121212121212124)
diff --git a/nengo_gui/examples/tutorial/04-connecting-neurons.py b/nengo_gui/examples/tutorial/04-connecting-neurons.py
index c3b98bc8..839f5825 100644
--- a/nengo_gui/examples/tutorial/04-connecting-neurons.py
+++ b/nengo_gui/examples/tutorial/04-connecting-neurons.py
@@ -1,17 +1,17 @@
# Tutorial 4: Connecting neurons
-
+#
# So far, we have just fed input to a group of neurons and read their output.
# In order to do more interesting things, we need to be able to connect one
# group of neurons to another. In this example, we have two groups of neurons
# connected together.
-
+#
# If you just make a Connection between two groups of neurons, Nengo will
# find the connection weights between each of the neurons such that whatever
# value is being represented by the first group of neurons will be passed on
# to the second group of neurons. Notice that now if you move the slider,
# this affects the value in the first group of neurons, and these in turn
# affect the value in the second group of neurons.
-
+#
# Whenever you make a connection, you can specify a "synapse" value. This
# indicates the properties of the neurotransmitters and synapses (the actual
# connections between neurons). The most important paramter is the
@@ -29,7 +29,7 @@
stim = nengo.Node(0)
a = nengo.Ensemble(n_neurons=50, dimensions=1)
nengo.Connection(stim, a)
-
+
b = nengo.Ensemble(n_neurons=50, dimensions=1)
-
+
nengo.Connection(a, b, synapse=0.01)
diff --git a/nengo_gui/examples/tutorial/04-connecting-neurons.py.cfg b/nengo_gui/examples/tutorial/04-connecting-neurons.py.cfg
index 7024dc03..4ff9ab40 100644
--- a/nengo_gui/examples/tutorial/04-connecting-neurons.py.cfg
+++ b/nengo_gui/examples/tutorial/04-connecting-neurons.py.cfg
@@ -47,7 +47,5 @@ _viz_config[model].pos=(-0.002863544871064495, 0.17841592967742728)
_viz_config[model].size=(1.0, 1.0)
_viz_config[model].expanded=True
_viz_config[model].has_layout=True
-_viz_config[slowdown].pos=(0.1156716417910446, 0.10255746531373007)
-_viz_config[slowdown].size=(0.02485648679678531, 0.023831711189342263)
_viz_config[stim].pos=(0.12264150943396226, 0.3030303030303031)
-_viz_config[stim].size=(0.07547169811320754, 0.12121212121212124)
\ No newline at end of file
+_viz_config[stim].size=(0.07547169811320754, 0.12121212121212124)
diff --git a/nengo_gui/examples/tutorial/05-computing.py b/nengo_gui/examples/tutorial/05-computing.py
index a097285f..b73496b4 100644
--- a/nengo_gui/examples/tutorial/05-computing.py
+++ b/nengo_gui/examples/tutorial/05-computing.py
@@ -1,15 +1,15 @@
# Tutorial 5: Computing a function
-
+#
# Whenever we make a Connection between groups of neurons, we don't have to
# just pass the information from one group of neurons to the next. Instead,
# we can also modify that information. We do this by specifying a function,
# and Nengo will connect the individual neurons to best approximate that
# function.
-
+#
# In the example here, we are computing the square of the value. So for an
# input of -1 it should output 1, for 0 it should output 0, and for 1 it should
# output 1.
-
+#
# You can change the function by adjusting the computations done in the
# part of the code labelled "compute_this". This can be any arbitrary Python
# function. For example, try computing the negative of x ("return -x"). Try
@@ -23,10 +23,10 @@
stim = nengo.Node(0)
a = nengo.Ensemble(n_neurons=50, dimensions=1)
nengo.Connection(stim, a)
-
+
b = nengo.Ensemble(n_neurons=50, dimensions=1)
-
+
def compute_this(x):
return x * x
-
+
nengo.Connection(a, b, synapse=0.01, function=compute_this)
diff --git a/nengo_gui/examples/tutorial/05-computing.py.cfg b/nengo_gui/examples/tutorial/05-computing.py.cfg
index 9901f2d7..f9e9222e 100644
--- a/nengo_gui/examples/tutorial/05-computing.py.cfg
+++ b/nengo_gui/examples/tutorial/05-computing.py.cfg
@@ -47,7 +47,5 @@ _viz_config[model].pos=(-0.019402985074626844, 0.18564920273348517)
_viz_config[model].size=(0.9999999999999997, 0.9999999999999997)
_viz_config[model].expanded=True
_viz_config[model].has_layout=True
-_viz_config[slowdown].pos=(0.1156716417910446, 0.10255746531373007)
-_viz_config[slowdown].size=(0.02485648679678531, 0.023831711189342263)
_viz_config[stim].pos=(0.12264150943396226, 0.3030303030303031)
-_viz_config[stim].size=(0.07547169811320754, 0.12121212121212124)
\ No newline at end of file
+_viz_config[stim].size=(0.07547169811320754, 0.12121212121212124)
diff --git a/nengo_gui/examples/tutorial/06-adding.py b/nengo_gui/examples/tutorial/06-adding.py
index e176bee6..86720f65 100644
--- a/nengo_gui/examples/tutorial/06-adding.py
+++ b/nengo_gui/examples/tutorial/06-adding.py
@@ -1,9 +1,9 @@
# Tutorial 6: Adding
-
+#
# If we make two Connections into the same Ensemble, the Ensemble will get
# input from both sources, and it will end up representing the sum of all of
# its inputs. Here, we use this to add two values together.
-
+#
# Notice that the value being represented by Ensemble c is, most of the time,
# the sum of the two inputs (a and b). However, if that value gets too large
# (or too small), it does not work very well. This is because every Ensemble
@@ -17,7 +17,6 @@
# select "Set range" and set it to "-2,2". Now it should add correctly over
# the full range of inputs.
-
import nengo
model = nengo.Network()
@@ -25,12 +24,11 @@
stim_a = nengo.Node(0)
a = nengo.Ensemble(n_neurons=50, dimensions=1)
nengo.Connection(stim_a, a)
-
+
stim_b = nengo.Node(0)
b = nengo.Ensemble(n_neurons=50, dimensions=1)
nengo.Connection(stim_b, b)
-
+
c = nengo.Ensemble(n_neurons=50, dimensions=1, radius=1)
nengo.Connection(a, c)
nengo.Connection(b, c)
-
diff --git a/nengo_gui/examples/tutorial/07-multiple-dimensions.py b/nengo_gui/examples/tutorial/07-multiple-dimensions.py
index a25c3e91..f32981a8 100644
--- a/nengo_gui/examples/tutorial/07-multiple-dimensions.py
+++ b/nengo_gui/examples/tutorial/07-multiple-dimensions.py
@@ -1,16 +1,17 @@
# Tutorial 7: Multiple Dimensions
-
+#
# Ensembles of neurons do not just have to represent one thing. Instead,
# a group of neurons can represent multiple values at the same time. We call
# the number of values represented at once the "dimensions" of the Ensemble.
# So, if a group of neurons is supposed to represent the spatial location of
-# something in three dimensions (x, y, z), then we say that it has dimensions=3.
-
+# something in three dimensions (x, y, z), then we say that it has
+# dimensions=3.
+#
# In this case, three different values are being decoded from each of the
# groups of neurons. Nengo decodes these values by finding different ways
# of weighting together the actual spiking output (top graphs) in order to
-# produce the bottom graphs.
-
+# produce the bottom graphs.
+#
# Nengo Tip: If you change the number of dimensions, the graphs and sliders
# may not update to reflect those changes. You can force them to do so by
# removing them and re-creating them. To remove them, right-click on the graph
@@ -27,6 +28,6 @@
stim = nengo.Node([0, 0, 0])
a = nengo.Ensemble(n_neurons=200, dimensions=3)
nengo.Connection(stim, a)
-
+
b = nengo.Ensemble(n_neurons=200, dimensions=3)
- nengo.Connection(a, b)
\ No newline at end of file
+ nengo.Connection(a, b)
diff --git a/nengo_gui/examples/tutorial/08-combining.py b/nengo_gui/examples/tutorial/08-combining.py
index c230674c..f57a984e 100644
--- a/nengo_gui/examples/tutorial/08-combining.py
+++ b/nengo_gui/examples/tutorial/08-combining.py
@@ -1,5 +1,5 @@
# Tutorial 8: Combining Information
-
+#
# Now that we can represent multiple things using the same group of neurons,
# we can also combine information together. Here we introduce a new syntax
# when making Connections: just can specify which of the multiple dimensions
@@ -8,12 +8,12 @@
# counting at zero, so the first dimension is x[0], then x[1], then x[2], and
# so on). You can also do this when connecting out of a group, so you could do
# something like "Connection(c[1], d[3])".
-
+#
# In the example below, we combine the information from two different Ensembles
# (a and b) into a third one (c) that represents both values. Notice that this
# is different than the Addition tutorial in that we are keeping both values
# separate, rather than adding them together.
-
+#
# Advanced Nengo Tip: Connections support full Python slice notation, so you
# can also do things like [-1], or [1:5] or [::-1] and so on.
@@ -24,12 +24,11 @@
stim_a = nengo.Node(0)
a = nengo.Ensemble(n_neurons=50, dimensions=1)
nengo.Connection(stim_a, a)
-
+
stim_b = nengo.Node(0)
b = nengo.Ensemble(n_neurons=50, dimensions=1)
nengo.Connection(stim_b, b)
-
+
c = nengo.Ensemble(n_neurons=200, dimensions=2)
nengo.Connection(a, c[0])
nengo.Connection(b, c[1])
-
diff --git a/nengo_gui/examples/tutorial/09-multiplication.json b/nengo_gui/examples/tutorial/09-multiplication.json
new file mode 100644
index 00000000..cf49b90e
--- /dev/null
+++ b/nengo_gui/examples/tutorial/09-multiplication.json
@@ -0,0 +1,157 @@
+{
+ "_viz_0": {
+ "cls": "Slider",
+ "label": "_viz_0",
+ "obj": "stim_b",
+ "pos": {
+ "height": 0.173906,
+ "width": 0.0569738,
+ "left": -0.181332,
+ "top": 0.989538
+ },
+ "ylim": [
+ -1,
+ 1
+ ]
+ },
+ "_viz_1": {
+ "cls": "Slider",
+ "label": "_viz_1",
+ "obj": "stim_a",
+ "pos": {
+ "height": 0.173906,
+ "width": 0.0569738,
+ "left": -0.183611,
+ "top": 0.0835091
+ },
+ "ylim": [
+ -1,
+ 1
+ ]
+ },
+ "_viz_2": {
+ "cls": "Value",
+ "label": "_viz_2",
+ "obj": "c",
+ "pos": {
+ "height": 0.173906,
+ "width": 0.113948,
+ "left": 0.83125,
+ "top": 0.321147
+ },
+ "ylim": [
+ -1,
+ 1
+ ]
+ },
+ "_viz_3": {
+ "cls": "Value",
+ "label": "_viz_3",
+ "obj": "a",
+ "pos": {
+ "height": 0.173906,
+ "width": 0.113948,
+ "left": 0.541287,
+ "top": 0.000904821
+ },
+ "ylim": [
+ -1,
+ 1
+ ]
+ },
+ "_viz_4": {
+ "cls": "Value",
+ "label": "_viz_4",
+ "obj": "b",
+ "pos": {
+ "height": 0.173906,
+ "width": 0.113948,
+ "left": 0.503087,
+ "top": 1.08427
+ },
+ "ylim": [
+ -1,
+ 1
+ ]
+ },
+ "_viz_5": {
+ "cls": "Value",
+ "label": "_viz_5",
+ "obj": "d",
+ "pos": {
+ "height": 0.173906,
+ "width": 0.113948,
+ "left": 1.1082,
+ "top": 0.918409
+ },
+ "ylim": [
+ -1,
+ 1
+ ]
+ },
+ "a": {
+ "cls": "Ensemble",
+ "pos": {
+ "height": 0.15268966928818553,
+ "width": 0.1045819652658805,
+ "left": 0.3071136331212679,
+ "top": 0.09339768677100643
+ }
+ },
+ "b": {
+ "cls": "Ensemble",
+ "pos": {
+ "height": 0.15268966928818553,
+ "width": 0.1045819652658805,
+ "left": 0.2717899036590758,
+ "top": 0.9858844553210839
+ }
+ },
+ "c": {
+ "cls": "Ensemble",
+ "pos": {
+ "height": 0.15268966928818553,
+ "width": 0.1045819652658805,
+ "left": 0.6787230132831809,
+ "top": 0.5518145070940784
+ }
+ },
+ "d": {
+ "cls": "Ensemble",
+ "pos": {
+ "height": 0.15268966928818553,
+ "width": 0.1045819652658805,
+ "left": 1.0970508743467027,
+ "top": 0.5518145070940784
+ }
+ },
+ "model": {
+ "cls": "Network",
+ "expanded": true,
+ "has_layout": true,
+ "pos": {
+ "height": 0.6549231553528394,
+ "width": 0.6549231553528394,
+ "left": 0.2729728706363317,
+ "top": 0.2116338393468492
+ }
+ },
+ "stim_a": {
+ "cls": "Node",
+ "pos": {
+ "height": 0.12215173543054843,
+ "width": 0.08366557221270439,
+ "left": -0.02192932625257712,
+ "top": 0.10209299823388712
+ }
+ },
+ "stim_b": {
+ "cls": "Node",
+ "pos": {
+ "height": 0.12215173543054843,
+ "width": 0.08366557221270439,
+ "left": -0.03332407769199389,
+ "top": 0.9737110192730507
+ }
+ }
+}
diff --git a/nengo_gui/examples/tutorial/09-multiplication.py b/nengo_gui/examples/tutorial/09-multiplication.py
index 77b886b6..e71eed8c 100644
--- a/nengo_gui/examples/tutorial/09-multiplication.py
+++ b/nengo_gui/examples/tutorial/09-multiplication.py
@@ -1,11 +1,11 @@
# Tutorial 9: Multiplication
-
+#
# Now that we can combine information, we can use this to compute more
# complex functions. For example, to multiply two numbers together, we
# first make a combined Ensemble as in the previous tutorial, and then we
# compute the pruduct of the two numbers by multiplying them together when
# we make a Connection out of that combined Ensemble.
-
+#
# Notice that we had to increase the radius of the combined Ensemble to 1.5.
# That is because it is representing two values, each of which can be in the
# range -1 to 1. In order to make sure the Ensemble is good at representing
@@ -19,17 +19,18 @@
stim_a = nengo.Node(0)
a = nengo.Ensemble(n_neurons=50, dimensions=1)
nengo.Connection(stim_a, a)
-
+
stim_b = nengo.Node(0)
b = nengo.Ensemble(n_neurons=50, dimensions=1)
nengo.Connection(stim_b, b)
-
+
c = nengo.Ensemble(n_neurons=200, dimensions=2, radius=1.5)
nengo.Connection(a, c[0])
nengo.Connection(b, c[1])
-
+
d = nengo.Ensemble(n_neurons=50, dimensions=1)
-
+
def multiply(x):
return x[0] * x[1]
+
nengo.Connection(c, d, function=multiply)
diff --git a/nengo_gui/examples/tutorial/09-multiplication.py.cfg b/nengo_gui/examples/tutorial/09-multiplication.py.cfg
deleted file mode 100644
index 9b17f4e8..00000000
--- a/nengo_gui/examples/tutorial/09-multiplication.py.cfg
+++ /dev/null
@@ -1,69 +0,0 @@
-_viz_0 = nengo_gui.components.SliderTemplate(stim_b)
-_viz_config[_viz_0].label_visible = 1
-_viz_config[_viz_0].width = 0.0569738
-_viz_config[_viz_0].x = -0.181332
-_viz_config[_viz_0].y = 0.989538
-_viz_config[_viz_0].max_value = 1
-_viz_config[_viz_0].min_value = -1
-_viz_config[_viz_0].height = 0.173906
-_viz_1 = nengo_gui.components.SliderTemplate(stim_a)
-_viz_config[_viz_1].label_visible = 1
-_viz_config[_viz_1].width = 0.0569738
-_viz_config[_viz_1].x = -0.183611
-_viz_config[_viz_1].y = 0.0835091
-_viz_config[_viz_1].max_value = 1
-_viz_config[_viz_1].min_value = -1
-_viz_config[_viz_1].height = 0.173906
-_viz_2 = nengo_gui.components.ValueTemplate(c)
-_viz_config[_viz_2].label_visible = 1
-_viz_config[_viz_2].width = 0.113948
-_viz_config[_viz_2].x = 0.83125
-_viz_config[_viz_2].y = 0.321147
-_viz_config[_viz_2].max_value = 1
-_viz_config[_viz_2].min_value = -1
-_viz_config[_viz_2].height = 0.173906
-_viz_3 = nengo_gui.components.ValueTemplate(a)
-_viz_config[_viz_3].label_visible = 1
-_viz_config[_viz_3].width = 0.113948
-_viz_config[_viz_3].x = 0.541287
-_viz_config[_viz_3].y = 0.000904821
-_viz_config[_viz_3].max_value = 1
-_viz_config[_viz_3].min_value = -1
-_viz_config[_viz_3].height = 0.173906
-_viz_4 = nengo_gui.components.ValueTemplate(b)
-_viz_config[_viz_4].label_visible = 1
-_viz_config[_viz_4].width = 0.113948
-_viz_config[_viz_4].x = 0.503087
-_viz_config[_viz_4].y = 1.08427
-_viz_config[_viz_4].max_value = 1
-_viz_config[_viz_4].min_value = -1
-_viz_config[_viz_4].height = 0.173906
-_viz_5 = nengo_gui.components.ValueTemplate(d)
-_viz_config[_viz_5].label_visible = 1
-_viz_config[_viz_5].width = 0.113948
-_viz_config[_viz_5].x = 1.1082
-_viz_config[_viz_5].y = 0.918409
-_viz_config[_viz_5].max_value = 1
-_viz_config[_viz_5].min_value = -1
-_viz_config[_viz_5].height = 0.173906
-_viz_ace_editor = nengo_gui.components.AceEditorTemplate()
-_viz_net_graph = nengo_gui.components.NetGraphTemplate()
-_viz_sim_control = nengo_gui.components.SimControlTemplate()
-_viz_config[_viz_sim_control].kept_time = 4
-_viz_config[_viz_sim_control].shown_time = 0.5
-_viz_config[a].pos=(0.3071136331212679, 0.09339768677100643)
-_viz_config[a].size=(0.1045819652658805, 0.15268966928818553)
-_viz_config[b].pos=(0.2717899036590758, 0.9858844553210839)
-_viz_config[b].size=(0.1045819652658805, 0.15268966928818553)
-_viz_config[c].pos=(0.6787230132831809, 0.5518145070940784)
-_viz_config[c].size=(0.1045819652658805, 0.15268966928818553)
-_viz_config[d].pos=(1.0970508743467027, 0.5518145070940784)
-_viz_config[d].size=(0.1045819652658805, 0.15268966928818553)
-_viz_config[model].pos=(0.2729728706363317, 0.2116338393468492)
-_viz_config[model].size=(0.6549231553528394, 0.6549231553528394)
-_viz_config[model].expanded=True
-_viz_config[model].has_layout=True
-_viz_config[stim_a].pos=(-0.02192932625257712, 0.10209299823388712)
-_viz_config[stim_a].size=(0.08366557221270439, 0.12215173543054843)
-_viz_config[stim_b].pos=(-0.03332407769199389, 0.9737110192730507)
-_viz_config[stim_b].size=(0.08366557221270439, 0.12215173543054843)
\ No newline at end of file
diff --git a/nengo_gui/examples/tutorial/10-transforms.py b/nengo_gui/examples/tutorial/10-transforms.py
index 2ce4c194..dfc827ee 100644
--- a/nengo_gui/examples/tutorial/10-transforms.py
+++ b/nengo_gui/examples/tutorial/10-transforms.py
@@ -1,10 +1,10 @@
# Tutorial 10: Transforms and scaling
-
+#
# When making Connections, we may want to scale the values being represented.
# For example, we might want to just multiply a value by a fixed number like
# 0.1 or 10 or something like that. Since this value doesn't change, we do
# not need a full multiplication system like in the previous tutorial.
-
+#
# We could implement this sort of connection by writing a function. However,
# this is such a common thing to do that Nengo has a shortcut for this by
# having a "transform" parameter. The examples below show the equivalence of
@@ -12,7 +12,7 @@
# and multiply it by -0.5. For a multidimensional example, d1 and d2 both take
# values from c and compute 2*c[0]-c[1]-c[2], but do so in different ways. In
# both cases the resulting models are identical.
-
+#
# You can use this trick to quickly define any linear transformation on the
# values represented by the Ensembles.
@@ -23,29 +23,29 @@
stim_a = nengo.Node(0)
a = nengo.Ensemble(n_neurons=50, dimensions=1)
nengo.Connection(stim_a, a)
-
+
b1 = nengo.Ensemble(n_neurons=50, dimensions=1)
b2 = nengo.Ensemble(n_neurons=50, dimensions=1)
-
+
# the long way to do it
def simple_function(a):
return -0.5 * a
+
nengo.Connection(a, b1, function=simple_function)
# the shortcut way to do it
nengo.Connection(a, b2, transform=-0.5)
-
-
+
stim_c = nengo.Node([0, 0, 0])
c = nengo.Ensemble(n_neurons=200, dimensions=3)
nengo.Connection(stim_c, c)
-
-
+
d1 = nengo.Ensemble(n_neurons=50, dimensions=1)
d2 = nengo.Ensemble(n_neurons=50, dimensions=1)
-
+
# the long way to do it
def harder_function(c):
return 2 * c[0] - c[1] - c[2]
+
nengo.Connection(c, d1, function=harder_function)
# the shortcut way to do it
nengo.Connection(c, d2, transform=[[2, -1, -1]])
diff --git a/nengo_gui/examples/tutorial/11-memory.py b/nengo_gui/examples/tutorial/11-memory.py
index 08a70bc4..9916eb25 100644
--- a/nengo_gui/examples/tutorial/11-memory.py
+++ b/nengo_gui/examples/tutorial/11-memory.py
@@ -1,24 +1,24 @@
# Tutorial 11: Memory
-
+#
# Nengo models can also store information over time. To do this, we simply
# make a Connection from an Ensemble back to itself. That is, we form a
# Connection that will feed a value into an Ensemble that is the same value
# that the Ensemble is currently representing. This means we can store data
# over time.
-
+#
# To use such a system, connect into it with another Ensemble. If that input
# is zero, then the stored value should stay the same as it currently is.
# If the input is positive, the stored value should increase. If the input is
# negative, it should decrease.
-
+#
# Notice that the input Connection has "transform=0.1". That is to control
# how strongly the input affects the stored value. If you make the transform
# larger, it will change more quickly.
-
+#
# Also notice that the recurrent Connection from b back to itself has
# synapse=0.1. This longer time constant makes the memory more stable, and
# is also commonly found in the real brain for recurrent connections.
-
+#
# Mathematical Note: In the case where the input transform is exactly equal to
# the recurrent synapse (as it is here), it turns out that the resulting system
# should compute the mathematical integral of the input.
@@ -30,8 +30,7 @@
stim_a = nengo.Node(0)
a = nengo.Ensemble(n_neurons=50, dimensions=1)
nengo.Connection(stim_a, a)
-
+
b = nengo.Ensemble(n_neurons=50, dimensions=1)
nengo.Connection(b, b, synapse=0.1)
nengo.Connection(a, b, transform=0.1)
-
diff --git a/nengo_gui/examples/tutorial/12-differential-eqns.py b/nengo_gui/examples/tutorial/12-differential-eqns.py
index 4bfdf378..a671d7b5 100644
--- a/nengo_gui/examples/tutorial/12-differential-eqns.py
+++ b/nengo_gui/examples/tutorial/12-differential-eqns.py
@@ -1,20 +1,20 @@
# Tutorial 12: Differential Equations
-
+#
# Recurrent connections can be used to implement not just memory, as in the
# previous tutorial, but also any differential equation.
-
+#
# For example, the differential equation for a low-pass filter (a system where
-# the output y is a smoothed version of the input x) is
+# the output y is a smoothed version of the input x) is
# dy/dt = x/tau - y/tau
# where tau is the time constant for the smoothing (larger means smoother)
-
+#
# To implement this in nengo, we need an input Connection that computes the
# x/tau part, and a recurrent Connection that computes the -y/tau part.
-
+#
# Because these functions are implemented by neurons, the time constant of
# the synapse itself turns out to be very important here. We have to take into
# account what synapse is being used when making this connection.
-
+#
# While the proof is outside the scope of this tutorial, the resulting rule
# is that both your Connections need to get scaled by the synapse value, and
# your recurrent Connection must also add the stored value back in. That is,
@@ -24,12 +24,12 @@
# (-y/tau) * synapse + y
# If we tell Nengo to implement those two functions, we will get the desired
# differential equation.
-
+#
# Nengo Tip: In this particular case, those two functions are both linear
# functions, and so we could implement them much more easily using the
# "transform=" approach (see tutorial 10). This is left as an exercise to the
# user.
-
+#
# Try running the model and seeing that y is a slowed-down, smoother version
# of x. What happens if you change the input up and down quickly? What
# happens with tau=0.1? What about tau=0.01?
@@ -41,17 +41,16 @@
stim_x = nengo.Node(0)
x = nengo.Ensemble(n_neurons=50, dimensions=1)
nengo.Connection(stim_x, x)
-
+
y = nengo.Ensemble(n_neurons=50, dimensions=1)
tau = 0.5
synapse = 0.1
-
+
def input_function(x):
return x / tau * synapse
+
def recurrent_function(y):
return (-y / tau) * synapse + y
-
-
+
nengo.Connection(x, y, synapse=synapse, function=input_function)
nengo.Connection(y, y, synapse=synapse, function=recurrent_function)
-
diff --git a/nengo_gui/examples/tutorial/13-oscillators.py b/nengo_gui/examples/tutorial/13-oscillators.py
index 475ff903..0dda47d9 100644
--- a/nengo_gui/examples/tutorial/13-oscillators.py
+++ b/nengo_gui/examples/tutorial/13-oscillators.py
@@ -1,22 +1,23 @@
# Tutorial 13: Oscillators
-
+#
# If we do differential equations in multiple dimensions, we can get
# oscillators. This gives an Ensemble of neurons that can produce patterns
# of behaviour all on its own without any external input. For example, here
# is a standard cycle in two dimensions:
# dx0/dt = -x1 * s + x0 * (r - x0**2 - x1**2)
# dx1/dt = x0 * s + x1 * (r - x0**2 - x1**2)
-# where r is the radius of the circle and s is the speed (in radians per second).
-
+# where r is the radius of the circle and s is the speed
+# (in radians per second).
+#
# As discussed in the previous tutorial, we can convert this into a Nengo
# model. In this case there is no input connection, so all we have to do
# is multiply by the synapse and add the original value.
-
+#
# Here we introduce a new kind of plot. The XY-value plot shows the same
# information as the normal Value plot, but plots the two dimensions together
# rather than using time to be the x-axis. This can be convenient for
# representing multidimensional data.
-
+#
# Try adjusting the r value to 0.5. Try 1.5. What about 0?
# Try adjusting the speed s. What happens when it is very slow (0.5)? 0.1?
@@ -26,14 +27,12 @@
with model:
x = nengo.Ensemble(n_neurons=200, dimensions=2)
-
synapse = 0.1
+
def oscillator(x):
r = 1
s = 6
return [synapse * (-x[1] * s + x[0] * (r - x[0]**2 - x[1]**2)) + x[0],
- synapse * ( x[0] * s + x[1] * (r - x[0]**2 - x[1]**2)) + x[1]]
+ synapse * (x[0] * s + x[1] * (r - x[0]**2 - x[1]**2)) + x[1]]
nengo.Connection(x, x, synapse=synapse, function=oscillator)
-
-
diff --git a/nengo_gui/examples/tutorial/14-controlled-oscillator.py b/nengo_gui/examples/tutorial/14-controlled-oscillator.py
index 4e0ec8c1..e1466067 100644
--- a/nengo_gui/examples/tutorial/14-controlled-oscillator.py
+++ b/nengo_gui/examples/tutorial/14-controlled-oscillator.py
@@ -1,20 +1,19 @@
# Tutorial 14: Controlled Oscillator
-
+#
# Here we do the exact same oscillator as in the previous example, but we
# introduce a new dimension that lets us control the speed of the oscillation
-
+#
# We use the same differential equation as before:
# dx0/dt = -x1 * s + x0 * (r - x0**2 - y0**2)
# dx1/dt = x0 * s + x1 * (r - x0**2 - y0**2)
# where r is the radius of the circle and s is the speed (in radians per
# second).
-
+#
# But, in this case, we make the Ensemble be 3-dimensional and use the third
# dimension (x[2]) to represent s. You can control it with a separate input.
# This shows how neurons can affect the pattern of activity of another
# group of neurons.
-
import nengo
model = nengo.Network()
@@ -28,7 +27,7 @@ def oscillator(x):
r = 1
s = 10 * x[2]
return [synapse * -x[1] * s + x[0] * (r - x[0]**2 - x[1]**2) + x[0],
- synapse * x[0] * s + x[1] * (r - x[0]**2 - x[1]**2) + x[1]]
+ synapse * x[0] * s + x[1] * (r - x[0]**2 - x[1]**2) + x[1]]
nengo.Connection(x, x[:2], synapse=synapse, function=oscillator)
diff --git a/nengo_gui/examples/tutorial/15-lorenz.py b/nengo_gui/examples/tutorial/15-lorenz.py
index 7bb8398e..6dd51b97 100644
--- a/nengo_gui/examples/tutorial/15-lorenz.py
+++ b/nengo_gui/examples/tutorial/15-lorenz.py
@@ -1,5 +1,5 @@
# Tutorial 15: The Lorenz Chaotic Attractor
-
+#
# Differential equations can also give chaotic behaviour. The classic example
# of this is the Lorenz "butterfly" attractor. The equations for it are
#
@@ -10,9 +10,9 @@
# Note: this is a slight transformation from the standard formulation so
# as to centre the value around the origin. For further information, see
# http://compneuro.uwaterloo.ca/publications/eliasmith2005b.html
-# "Chris Eliasmith. A unified approach to building and controlling
-# spiking attractor networks. Neural computation, 7(6):1276-1314, 2005."
-
+# "Chris Eliasmith. A unified approach to building and controlling
+# spiking attractor networks. Neural computation, 7(6):1276-1314, 2005."
+#
# Since there are three dimensions, we can show three different XY plots
# combining the different values in different ways.
@@ -20,22 +20,22 @@
model = nengo.Network(seed=3)
with model:
-
+
x = nengo.Ensemble(n_neurons=600, dimensions=3, radius=30)
synapse = 0.1
+
def lorenz(x):
sigma = 10
beta = 8.0/3
rho = 28
-
+
dx0 = -sigma * x[0] + sigma * x[1]
dx1 = -x[0] * x[2] - x[1]
dx2 = x[0] * x[1] - beta * (x[2] + rho) - rho
-
+
return [dx0 * synapse + x[0],
dx1 * synapse + x[1],
dx2 * synapse + x[2]]
nengo.Connection(x, x, synapse=synapse, function=lorenz)
-
diff --git a/nengo_gui/examples/tutorial/16-ensemble-properties.py b/nengo_gui/examples/tutorial/16-ensemble-properties.py
index 75d767b7..232f91f0 100644
--- a/nengo_gui/examples/tutorial/16-ensemble-properties.py
+++ b/nengo_gui/examples/tutorial/16-ensemble-properties.py
@@ -1,21 +1,21 @@
# Tutorial 16: Ensemble Properties
-
+#
# In addition to the number of neurons, the number of dimensions, and the
# radius, there are other parameters that can be specified when creating an
# Ensemble. Here are a few that may be useful.
-
+#
# max_rates
# Each neuron has a different maximum firing rate, and this parameter
# specifies the random distribution controlling this. The default is
# a uniform distribution between 200Hz and 400Hz.
-
+#
# encoders
# Each neuron has a different preferred stimulus. For a 1-dimensional
# Ensemble, this means that half of the neurons prefer -1 and the other
# half prefer +1. This is why some neurons fire more for large values and
# some for small values. In the example below, we set all the encoders to
# be +1.
-
+#
# intercepts
# Each neuron only starts firing when the similarity between the value and
# its preferred value reaches a particular limit. This is normally a
diff --git a/nengo_gui/examples/tutorial/17-neuron-models.py b/nengo_gui/examples/tutorial/17-neuron-models.py
index 5d1961a0..109d3a96 100644
--- a/nengo_gui/examples/tutorial/17-neuron-models.py
+++ b/nengo_gui/examples/tutorial/17-neuron-models.py
@@ -1,5 +1,5 @@
# Tutorial 17: Neuron Models
-
+#
# Nengo supports multiple different types of neurons. The default is the
# "Leaky Integrate-and-Fire" or LIF neuron. Other supported ones are shown
# here. The LIFRate neuron acts like the LIF neuron, but does not have spikes.
@@ -14,15 +14,15 @@
model = nengo.Network()
with model:
-
+
stim = nengo.Node(0)
-
+
a = nengo.Ensemble(n_neurons=50, dimensions=1,
neuron_type=nengo.LIF(tau_rc=0.02, tau_ref=0.002))
-
+
b = nengo.Ensemble(n_neurons=50, dimensions=1,
neuron_type=nengo.LIFRate(tau_rc=0.02, tau_ref=0.002))
-
+
c = nengo.Ensemble(n_neurons=50, dimensions=1,
neuron_type=nengo.Sigmoid(tau_ref=0.002))
@@ -40,4 +40,4 @@
nengo.Connection(stim, b)
nengo.Connection(stim, c)
nengo.Connection(stim, d)
- nengo.Connection(stim, e)
\ No newline at end of file
+ nengo.Connection(stim, e)
diff --git a/nengo_gui/examples/tutorial/18-networks.py b/nengo_gui/examples/tutorial/18-networks.py
index d6b127ec..82b7d1ec 100644
--- a/nengo_gui/examples/tutorial/18-networks.py
+++ b/nengo_gui/examples/tutorial/18-networks.py
@@ -1,7 +1,7 @@
# Tutorial 18: Networks
-
+#
# To help organize larger models, you can make Networks inside of the main
-# model Network.
+# model Network.
#
# In the graphic interface, the items inside these Networks are not shown
# by default. If you double-click on a Network you can show (or hide) its
@@ -14,14 +14,13 @@
# components that let you easily connect to or from all of the Ensembles at
# once.
-
import nengo
model = nengo.Network()
with model:
stim_a = nengo.Node([0, 0, 0])
stim_b = nengo.Node([0, 0])
-
+
part1 = nengo.Network()
with part1:
a = nengo.Ensemble(n_neurons=100, dimensions=3)
@@ -31,9 +30,7 @@
nengo.Connection(b, c[3:])
nengo.Connection(stim_a, a)
nengo.Connection(stim_b, b)
-
-
+
part2 = nengo.networks.EnsembleArray(n_neurons=50, n_ensembles=5)
-
+
nengo.Connection(c, part2.input)
-
\ No newline at end of file
diff --git a/nengo_gui/examples/tutorial/19-spa.py b/nengo_gui/examples/tutorial/19-spa.py
index 9f3663d1..5b0815a5 100644
--- a/nengo_gui/examples/tutorial/19-spa.py
+++ b/nengo_gui/examples/tutorial/19-spa.py
@@ -1,5 +1,5 @@
# Tutorial 19: Semantic Pointers
-
+#
# If we want to represent conceptual information, we need a way to represent
# concepts and symbol-like manipulations using Nengo. We do this by treating
# concepts like vectors: high-dimensional numerical data. That is, each
@@ -10,7 +10,7 @@
# We call these "semantic" because, in general, we would choose these
# numerical values such that concepts with similar semantics (like DOG and
# CAT) might have similar numerical values.
-
+#
# To help work with these vectors, we introduce a new collection of pre-built
# Networks, and a new type of graph. The new pre-built Networks can be
# accessed via nengo.spa ("spa" stands for "Semantic Pointer Architecture",
@@ -23,7 +23,7 @@
# individual values, it shows how close the currently represented vector is
# to the ideal original vectors. Furthermore, you can use it as an input
# system as well, and define new concepts.
-
+#
# Press play to start the simulation running. Now right-click on the "vision"
# graph (the blank space above the "vision" box in the diagram). Select "Set
# value..." and put in CAT as a value. Nengo will randomly generate a new
diff --git a/nengo_gui/examples/tutorial/19-spa.py.cfg b/nengo_gui/examples/tutorial/19-spa.py.cfg
index ba9b1733..77f8e941 100644
--- a/nengo_gui/examples/tutorial/19-spa.py.cfg
+++ b/nengo_gui/examples/tutorial/19-spa.py.cfg
@@ -25,11 +25,11 @@ _viz_config[model.memory].pos=(0.7727272727272727, 0.7397494305239181)
_viz_config[model.memory].size=(0.18181818181818182, 0.16025056947608207)
_viz_config[model.memory].expanded=False
_viz_config[model.memory].has_layout=False
-_viz_config[model.memory.state].expanded=False
-_viz_config[model.memory.state].has_layout=False
+_viz_config[model.memory.state_ensembles].expanded=False
+_viz_config[model.memory.state_ensembles].has_layout=False
_viz_config[model.vision].pos=(0.2272727215791493, 0.7471526195899774)
_viz_config[model.vision].size=(0.18181817612460383, 0.15284738041002283)
_viz_config[model.vision].expanded=False
_viz_config[model.vision].has_layout=False
-_viz_config[model.vision.state].expanded=False
-_viz_config[model.vision.state].has_layout=False
\ No newline at end of file
+_viz_config[model.vision.state_ensembles].expanded=False
+_viz_config[model.vision.state_ensembles].has_layout=False
diff --git a/nengo_gui/examples/tutorial/20-spa-actions.py b/nengo_gui/examples/tutorial/20-spa-actions.py
index bd8be44d..2cb0eb57 100644
--- a/nengo_gui/examples/tutorial/20-spa-actions.py
+++ b/nengo_gui/examples/tutorial/20-spa-actions.py
@@ -1,5 +1,5 @@
# Tutorial 20: Semantic Pointer Actions
-
+#
# A complex cognitive system needs a method to select and perform actions.
# In particular, it is useful to have a model that can do one thing at
# a time, sequentially. There is significant psychological data indicating
@@ -33,8 +33,8 @@
# such as CAT or COW. You can also try a combination, such as
# RAT+0.5*COW. The system will (almost always) select a single action
# to perform, and that will be the action with the highest utility at that
-# moment. If there is no input, or the input is not sufficiently similar
-# to any of the four animals it knows, then the speech State is set to
+# moment. If there is no input, or the input is not sufficiently similar
+# to any of the four animals it knows, then the speech State is set to
# zero ("speech=0").
#
# The input graph above the Basal Ganglia shows the utilities of the
@@ -43,7 +43,6 @@
# information between brain areas, so here it is used to implement the
# effects of the actions defined in the Basal Ganglia.
-import nengo
import nengo.spa as spa
D = 32 # the dimensionality of the vectors
@@ -60,7 +59,7 @@
'dot(vision, RAT) --> speech=SQUEAK',
'dot(vision, COW) --> speech=MOO',
'0.5 --> speech=0'
- )
+ )
model.bg = spa.BasalGanglia(actions)
model.thalamus = spa.Thalamus(model.bg)
diff --git a/nengo_gui/examples/tutorial/20-spa-actions.py.cfg b/nengo_gui/examples/tutorial/20-spa-actions.py.cfg
index b63f3731..6b8652bd 100644
--- a/nengo_gui/examples/tutorial/20-spa-actions.py.cfg
+++ b/nengo_gui/examples/tutorial/20-spa-actions.py.cfg
@@ -63,8 +63,8 @@ _viz_config[model.speech].pos=(0.8938488770881735, 0.6401446654611214)
_viz_config[model.speech].size=(0.08695652173913043, 0.2182640144665459)
_viz_config[model.speech].expanded=False
_viz_config[model.speech].has_layout=False
-_viz_config[model.speech.state].expanded=False
-_viz_config[model.speech.state].has_layout=False
+_viz_config[model.speech.state_ensembles].expanded=False
+_viz_config[model.speech.state_ensembles].has_layout=False
_viz_config[model.thalamus].pos=(0.6278902533466091, 0.6555153707052435)
_viz_config[model.thalamus].size=(0.08695652173913043, 0.23725135623869806)
_viz_config[model.thalamus].expanded=False
@@ -99,7 +99,7 @@ _viz_config[model.vision].pos=(0.10869565217391304, 0.6528028933092226)
_viz_config[model.vision].size=(0.08695652173913043, 0.23815551537070517)
_viz_config[model.vision].expanded=False
_viz_config[model.vision].has_layout=True
-_viz_config[model.vision.state].pos=(0.5, 0.5)
-_viz_config[model.vision.state].size=(0.4, 0.4)
-_viz_config[model.vision.state].expanded=False
-_viz_config[model.vision.state].has_layout=False
+_viz_config[model.vision.state_ensembles].pos=(0.5, 0.5)
+_viz_config[model.vision.state_ensembles].size=(0.4, 0.4)
+_viz_config[model.vision.state_ensembles].expanded=False
+_viz_config[model.vision.state_ensembles].has_layout=False
diff --git a/nengo_gui/examples/tutorial/21-spa-sequence.py b/nengo_gui/examples/tutorial/21-spa-sequence.py
index b3e5fe9b..5d30ec83 100644
--- a/nengo_gui/examples/tutorial/21-spa-sequence.py
+++ b/nengo_gui/examples/tutorial/21-spa-sequence.py
@@ -1,16 +1,15 @@
# Tutorial 21: Sequential Semantic Pointer Actions
-
+#
# In this example, we define a set of actions that follow through a
# repeating sequence (A, B, C, D, E). This shows that you can define
# actions which affect the performance of later actions.
-
+#
# In this example we have changed the default value of the optional argument
# feedback_synapse, which is the time constant controlling the exponential
# decay of the postsynaptic potential. Try using longer time constants
# (e.g. 0.1 or 0.5) and observe what changes. How is the stability of
# a memory representation related to the synaptic time constant?
-import nengo
import nengo.spa as spa
D = 32 # the dimensionality of the vectors
@@ -25,7 +24,7 @@
'dot(memory, C) --> memory=D',
'dot(memory, D) --> memory=E',
'dot(memory, E) --> memory=A',
- )
+ )
model.bg = spa.BasalGanglia(actions)
model.thalamus = spa.Thalamus(model.bg)
diff --git a/nengo_gui/examples/tutorial/22-spa-sequence-controlled.py b/nengo_gui/examples/tutorial/22-spa-sequence-controlled.py
index db3c9a08..eecf3f69 100644
--- a/nengo_gui/examples/tutorial/22-spa-sequence-controlled.py
+++ b/nengo_gui/examples/tutorial/22-spa-sequence-controlled.py
@@ -1,5 +1,5 @@
# Tutorial 22: Controlled Sequence of Semantic Pointer Actions
-
+#
# Here, we expand on the previous sequence example and define a model
# that instead of starting from A each time, starts from whatever value
# is currently seen. This shows that you can selectively route information
@@ -15,7 +15,6 @@
# sequence. This is due to the time needed for the neurons to respond,
# and is consistent with psychological reaction time data.
-import nengo
import nengo.spa as spa
D = 32 # the dimensionality of the vectors
@@ -32,7 +31,7 @@
'dot(memory, D) --> memory=E',
'dot(memory, E) --> memory=vision',
'0.5 --> memory=vision'
- )
+ )
model.bg = spa.BasalGanglia(actions)
model.thalamus = spa.Thalamus(model.bg)
diff --git a/nengo_gui/examples/tutorial/22-spa-sequence-controlled.py.cfg b/nengo_gui/examples/tutorial/22-spa-sequence-controlled.py.cfg
index 580dd449..d630866e 100644
--- a/nengo_gui/examples/tutorial/22-spa-sequence-controlled.py.cfg
+++ b/nengo_gui/examples/tutorial/22-spa-sequence-controlled.py.cfg
@@ -93,22 +93,6 @@ _viz_config[model.thalamus.bg].expanded=False
_viz_config[model.thalamus.bg].has_layout=True
_viz_config[model.thalamus.bias].pos=(0.15262792014175175, 0.768512911036981)
_viz_config[model.thalamus.bias].size=(0.043010752688172046, 0.05555555555555555)
-_viz_config[model.thalamus.ensembles[0]].pos=(0.5967741935483871, 0.6527777777777777)
-_viz_config[model.thalamus.ensembles[0]].size=(0.053763440860215055, 0.06944444444444445)
-_viz_config[model.thalamus.networks[1]].pos=(0.8655913978494624, 0.6527777777777777)
-_viz_config[model.thalamus.networks[1]].size=(0.10752688172043011, 0.2777777777777778)
-_viz_config[model.thalamus.networks[1]].expanded=True
-_viz_config[model.thalamus.networks[1]].has_layout=True
-_viz_config[model.thalamus.networks[1].ea_ensembles[0]].pos=(0.49999999999999994, 0.2)
-_viz_config[model.thalamus.networks[1].ea_ensembles[0]].size=(0.09803921568627451, 0.1)
-_viz_config[model.thalamus.networks[1].ea_ensembles[1]].pos=(0.49999999999999994, 0.7999999999999999)
-_viz_config[model.thalamus.networks[1].ea_ensembles[1]].size=(0.09803921568627451, 0.1)
-_viz_config[model.thalamus.networks[1].input].pos=(0.12745098039215685, 0.5)
-_viz_config[model.thalamus.networks[1].input].size=(0.07843137254901959, 0.08)
-_viz_config[model.thalamus.networks[1].output].pos=(0.872549019607843, 0.5)
-_viz_config[model.thalamus.networks[1].output].size=(0.07843137254901959, 0.08)
-_viz_config[model.thalamus.networks[2]].expanded=False
-_viz_config[model.thalamus.networks[2]].has_layout=False
_viz_config[model.thalamus.spa].pos=(0.24456685324716096, 0.1507632799672523)
_viz_config[model.thalamus.spa].size=(0.4756851457806843, 0.4756851457806843)
_viz_config[model.thalamus.spa].expanded=True
@@ -118,4 +102,4 @@ _viz_config[model.vision].size=(0.08493913168561505, 0.13276488513830279)
_viz_config[model.vision].expanded=False
_viz_config[model.vision].has_layout=False
_viz_config[model.vision.state_ensembles].expanded=False
-_viz_config[model.vision.state_ensembles].has_layout=False
\ No newline at end of file
+_viz_config[model.vision.state_ensembles].has_layout=False
diff --git a/nengo_gui/examples/tutorial/23-spa-binding.py b/nengo_gui/examples/tutorial/23-spa-binding.py
index 2668f736..a50b38db 100644
--- a/nengo_gui/examples/tutorial/23-spa-binding.py
+++ b/nengo_gui/examples/tutorial/23-spa-binding.py
@@ -1,5 +1,5 @@
# Tutorial 23: Binding Concepts
-
+#
# We now show that you can combine semantic pointers together to store
# structured information. One of the standard problems in cognitive science
# is the Binding Problem: how do different concepts get represented and
@@ -42,7 +42,6 @@
# longer to run). We have argued that around 800 dimensions are sufficient for
# human working memory capacity.
-import nengo
import nengo.spa as spa
D = 32 # the dimensionality of the vectors
@@ -55,6 +54,6 @@
actions = spa.Actions(
'memory = color * shape',
- )
+ )
model.cortical = spa.Cortical(actions)
diff --git a/nengo_gui/examples/tutorial/23-spa-binding.py.cfg b/nengo_gui/examples/tutorial/23-spa-binding.py.cfg
index c4c2b93e..2a8b7b26 100644
--- a/nengo_gui/examples/tutorial/23-spa-binding.py.cfg
+++ b/nengo_gui/examples/tutorial/23-spa-binding.py.cfg
@@ -34,14 +34,6 @@ _viz_config[model.cortical].pos=(0.5, 0.5)
_viz_config[model.cortical].size=(0.11764705882352941, 0.1818181818181818)
_viz_config[model.cortical].expanded=False
_viz_config[model.cortical].has_layout=False
-_viz_config[model.cortical.networks[0]].expanded=False
-_viz_config[model.cortical.networks[0]].has_layout=False
-_viz_config[model.cortical.networks[0].product].expanded=False
-_viz_config[model.cortical.networks[0].product].has_layout=False
-_viz_config[model.cortical.networks[0].product.sq1].expanded=False
-_viz_config[model.cortical.networks[0].product.sq1].has_layout=False
-_viz_config[model.cortical.networks[0].product.sq2].expanded=False
-_viz_config[model.cortical.networks[0].product.sq2].has_layout=False
_viz_config[model.cortical.spa].pos=(0.2229521406535118, 0.4008238542292871)
_viz_config[model.cortical.spa].size=(0.5225020088280712, 0.5225020088280712)
_viz_config[model.cortical.spa].expanded=True
diff --git a/nengo_gui/examples/tutorial/24-spa-unbinding.py b/nengo_gui/examples/tutorial/24-spa-unbinding.py
index e405e108..857c6793 100644
--- a/nengo_gui/examples/tutorial/24-spa-unbinding.py
+++ b/nengo_gui/examples/tutorial/24-spa-unbinding.py
@@ -1,5 +1,5 @@
# Tutorial 24: Unbinding Concepts
-
+#
# Now that we can combine information together into a single structure (see
# the previous tutorial), we also need to be able to extract information
# back out. We do this by exploiting a pseudo-inverse property of the
@@ -21,7 +21,6 @@
# get TRIANGLE. And for TRIANGLE you should get BLUE. Notice that the memory
# will gradually decay and fade the longer you try to run the system for.
-import nengo
import nengo.spa as spa
D = 32 # the dimensionality of the vectors
@@ -37,6 +36,6 @@
actions = spa.Actions(
'memory = color * shape',
'answer = memory * ~query',
- )
-
+ )
+
model.cortical = spa.Cortical(actions)
diff --git a/nengo_gui/examples/tutorial/24-spa-unbinding.py.cfg b/nengo_gui/examples/tutorial/24-spa-unbinding.py.cfg
index b2f9933b..76259a89 100644
--- a/nengo_gui/examples/tutorial/24-spa-unbinding.py.cfg
+++ b/nengo_gui/examples/tutorial/24-spa-unbinding.py.cfg
@@ -54,22 +54,6 @@ _viz_config[model.cortical].pos=(0.5, 0.32352941176470584)
_viz_config[model.cortical].size=(0.11764705882352941, 0.11764705882352941)
_viz_config[model.cortical].expanded=False
_viz_config[model.cortical].has_layout=False
-_viz_config[model.cortical.networks[0]].expanded=False
-_viz_config[model.cortical.networks[0]].has_layout=False
-_viz_config[model.cortical.networks[0].product].expanded=False
-_viz_config[model.cortical.networks[0].product].has_layout=False
-_viz_config[model.cortical.networks[0].product.sq1].expanded=False
-_viz_config[model.cortical.networks[0].product.sq1].has_layout=False
-_viz_config[model.cortical.networks[0].product.sq2].expanded=False
-_viz_config[model.cortical.networks[0].product.sq2].has_layout=False
-_viz_config[model.cortical.networks[1]].expanded=False
-_viz_config[model.cortical.networks[1]].has_layout=False
-_viz_config[model.cortical.networks[1].product].expanded=False
-_viz_config[model.cortical.networks[1].product].has_layout=False
-_viz_config[model.cortical.networks[1].product.sq1].expanded=False
-_viz_config[model.cortical.networks[1].product.sq1].has_layout=False
-_viz_config[model.cortical.networks[1].product.sq2].expanded=False
-_viz_config[model.cortical.networks[1].product.sq2].has_layout=False
_viz_config[model.cortical.spa].pos=(0.445107900344506, 0.28900116461649517)
_viz_config[model.cortical.spa].size=(0.629737609329446, 0.629737609329446)
_viz_config[model.cortical.spa].expanded=True
@@ -91,4 +75,4 @@ _viz_config[model.shape].size=(0.11764705882352941, 0.11764705882352941)
_viz_config[model.shape].expanded=False
_viz_config[model.shape].has_layout=False
_viz_config[model.shape.state_ensembles].expanded=False
-_viz_config[model.shape.state_ensembles].has_layout=False
\ No newline at end of file
+_viz_config[model.shape.state_ensembles].has_layout=False
diff --git a/nengo_gui/examples/tutorial/25-spa-parse.py b/nengo_gui/examples/tutorial/25-spa-parse.py
index 8d07c353..ca8381ac 100644
--- a/nengo_gui/examples/tutorial/25-spa-parse.py
+++ b/nengo_gui/examples/tutorial/25-spa-parse.py
@@ -1,5 +1,5 @@
# Tutorial 25: Parsing Simple Commands
-
+#
# In this tutorial, we use both the ability to combine structured information
# and the ability to make complex actions to implement a simple two-word
# parsing system. This model has a single visual input (vision) and you can
@@ -7,7 +7,7 @@
# by HI. It will remember the two terms, store them, and then respond
# appropriately by sending HI to its hand. It can also SAY BYE, where the
# vector for BYE will be set to the speech system.
-
+#
# The parsing process is implemented with the first two actions. The first
# action looks for verbs (WRITE or SAY) and sends them into the memory for
# verbs. The second action looks for nouns (YES or NO or HI or BYE or OK) and
@@ -18,21 +18,20 @@
# multiple different things at the same time by using the + operation.
# Overall, this shows how we can use a single input modality (vision) and treat
# the information in different ways as appropriate.
-
+#
# The last two actions deal with recognizing and executing actions. The
# first one looks for the VERB WRITE, and if it sees this it will take
# whatever is in phrase, extract out the NOUN, and send that to the hand.
# Furthermore, it will only do this if it doesn't currently see anything in
# vision (via the subtraction). This is to make sure it waits until it is
# getting no more visual input before responding.
-
+#
# To test the model, try presenting WRITE to the vision State. The phrase
# should fill with WRITE*VERB. Now change the vision input to HI. The phrase
# should now fill with NOUN*HI. If you get rid of the visual input (by setting
# the value to nothing), it shuold sent HI to the hand. If you try the same
# thing with SAY it should send the result to speech.
-import nengo
import nengo.spa as spa
D = 32 # the dimensionality of the vectors
@@ -55,10 +54,10 @@
'dot(vision, WRITE+SAY) --> verb=vision',
'dot(vision, YES+NO+HI+BYE+OK) --> noun=vision',
'dot(phrase, VERB*WRITE) - 2*dot(vision, WRITE+SAY+YES+NO+HI+BYE+OK)'
- '--> hand=phrase*~NOUN',
+ '--> hand=phrase*~NOUN',
'dot(phrase, VERB*SAY) - 2*dot(vision, WRITE+SAY+YES+NO+HI+BYE+OK)'
- '--> speech=phrase*~NOUN',
- )
+ '--> speech=phrase*~NOUN',
+ )
model.bg = spa.BasalGanglia(actions)
model.thalamus = spa.Thalamus(model.bg)
diff --git a/nengo_gui/examples/tutorial/25-spa-parse.py.cfg b/nengo_gui/examples/tutorial/25-spa-parse.py.cfg
index 6225f559..df35204a 100644
--- a/nengo_gui/examples/tutorial/25-spa-parse.py.cfg
+++ b/nengo_gui/examples/tutorial/25-spa-parse.py.cfg
@@ -145,38 +145,6 @@ _viz_config[model.thalamus.bg].expanded=False
_viz_config[model.thalamus.bg].has_layout=True
_viz_config[model.thalamus.bias].pos=(0.06989247311827958, 0.5000000000000001)
_viz_config[model.thalamus.bias].size=(0.043010752688172046, 0.01652892561983471)
-_viz_config[model.thalamus.ensembles[0]].pos=(0.5967741935483871, 0.10330578512396695)
-_viz_config[model.thalamus.ensembles[0]].size=(0.053763440860215055, 0.02066115702479339)
-_viz_config[model.thalamus.ensembles[1]].pos=(0.5967741935483871, 0.37603305785123964)
-_viz_config[model.thalamus.ensembles[1]].size=(0.053763440860215055, 0.02066115702479339)
-_viz_config[model.thalamus.ensembles[2]].pos=(0.5967741935483871, 0.6239669421487604)
-_viz_config[model.thalamus.ensembles[2]].size=(0.053763440860215055, 0.02066115702479339)
-_viz_config[model.thalamus.ensembles[3]].pos=(0.5967741935483871, 0.8966942148760331)
-_viz_config[model.thalamus.ensembles[3]].size=(0.053763440860215055, 0.02066115702479339)
-_viz_config[model.thalamus.networks[1]].pos=(0.8655913978494624, 0.10330578512396695)
-_viz_config[model.thalamus.networks[1]].size=(0.10752688172043011, 0.08264462809917356)
-_viz_config[model.thalamus.networks[1]].expanded=True
-_viz_config[model.thalamus.networks[1]].has_layout=True
-_viz_config[model.thalamus.networks[1].ea_ensembles[0]].pos=(0.49999999999999994, 0.9090909090909091)
-_viz_config[model.thalamus.networks[1].ea_ensembles[0]].size=(0.09803921568627451, 0.04545454545454545)
-_viz_config[model.thalamus.networks[1].ea_ensembles[1]].pos=(0.49999999999999994, 0.36363636363636365)
-_viz_config[model.thalamus.networks[1].ea_ensembles[1]].size=(0.09803921568627451, 0.04545454545454545)
-_viz_config[model.thalamus.networks[1].input].pos=(0.12745098039215685, 0.5)
-_viz_config[model.thalamus.networks[1].input].size=(0.07843137254901959, 0.03636363636363636)
-_viz_config[model.thalamus.networks[1].output].pos=(0.872549019607843, 0.5)
-_viz_config[model.thalamus.networks[1].output].size=(0.07843137254901959, 0.03636363636363636)
-_viz_config[model.thalamus.networks[2]].pos=(0.8655913978494624, 0.37603305785123964)
-_viz_config[model.thalamus.networks[2]].size=(0.10752688172043011, 0.08264462809917356)
-_viz_config[model.thalamus.networks[2]].expanded=False
-_viz_config[model.thalamus.networks[2]].has_layout=False
-_viz_config[model.thalamus.networks[3]].pos=(0.8655913978494624, 0.6239669421487604)
-_viz_config[model.thalamus.networks[3]].size=(0.10752688172043011, 0.08264462809917356)
-_viz_config[model.thalamus.networks[3]].expanded=False
-_viz_config[model.thalamus.networks[3]].has_layout=False
-_viz_config[model.thalamus.networks[4]].pos=(0.8655913978494624, 0.8966942148760331)
-_viz_config[model.thalamus.networks[4]].size=(0.10752688172043011, 0.08264462809917356)
-_viz_config[model.thalamus.networks[4]].expanded=False
-_viz_config[model.thalamus.networks[4]].has_layout=False
_viz_config[model.verb].pos=(1.1374084683143597, 0.9564957721561658)
_viz_config[model.verb].size=(0.15456115795951292, 0.13460281023501125)
_viz_config[model.verb].expanded=False
@@ -188,4 +156,4 @@ _viz_config[model.vision].size=(0.15456115795951292, 0.13460281023501125)
_viz_config[model.vision].expanded=False
_viz_config[model.vision].has_layout=False
_viz_config[model.vision.state_ensembles].expanded=False
-_viz_config[model.vision.state_ensembles].has_layout=False
\ No newline at end of file
+_viz_config[model.vision.state_ensembles].has_layout=False
diff --git a/nengo_gui/exceptions.py b/nengo_gui/exceptions.py
new file mode 100644
index 00000000..3539c8b7
--- /dev/null
+++ b/nengo_gui/exceptions.py
@@ -0,0 +1,14 @@
+class NengoGuiError(Exception):
+ pass
+
+
+class NotAttachedError(NengoGuiError):
+ pass
+
+
+class StartedSimulatorException(NengoGuiError):
+ pass
+
+
+class StartedGUIException(NengoGuiError):
+ pass
diff --git a/nengo_gui/exec_env.py b/nengo_gui/exec_env.py
index eb3a34ae..98e582f0 100644
--- a/nengo_gui/exec_env.py
+++ b/nengo_gui/exec_env.py
@@ -1,11 +1,12 @@
-import contextlib
import importlib
import os
+import sys
import threading
import traceback
-import sys
+
from nengo.utils.compat import StringIO
+from nengo_gui.exceptions import StartedSimulatorException
# list of Simulators to check for
known_modules = ['nengo', 'nengo_ocl', 'nengo_distilled',
@@ -26,14 +27,6 @@ def discover_backends():
return found_modules
-class StartedSimulatorException(Exception):
- pass
-
-
-class StartedGUIException(Exception):
- pass
-
-
# create a wrapper class that will throw an exception if we are
# currently executing a script
def make_dummy(cls):
@@ -42,39 +35,42 @@ def __init__(self, *args, **kwargs):
if is_executing():
raise StartedSimulatorException()
super(DummySimulator, self).__init__(*args, **kwargs)
+
+ def __del__(self):
+ pass
+
return DummySimulator
# thread local storage for storing whether we are executing a script
flag = threading.local()
-compiled_filename = ''
def is_executing():
return getattr(flag, 'executing', False)
-def determine_line_number():
- '''Checks stack trace to determine the line number we are currently at.
+def determine_line(filename):
+ """Checks stack trace to determine the line number we are currently at.
The filename argument should be the filename given to the code when
it was compiled (with compile())
- '''
+ """
exc_type, exc_value, exc_traceback = sys.exc_info()
if exc_traceback is not None:
ex_tb = traceback.extract_tb(exc_traceback)
for fn, line, function, code in reversed(ex_tb):
- if fn == compiled_filename:
+ if fn == filename:
return line
# if we can't find it that way, parse the text of the stack trace
# note that this is required for indentation errors and other syntax
# problems
trace = traceback.format_exc()
- pattern = 'File "%s", line ' % compiled_filename
+ pattern = 'File "%s", line ' % filename
index = trace.find(pattern)
- if index >=0:
+ if index >= 0:
text = trace[index + len(pattern):].split('\n', 1)[0]
if ',' in text:
text = text.split(',', 1)[0]
@@ -91,6 +87,7 @@ def __init__(self, filename, allow_sim=False):
self.directory = os.path.dirname(filename)
self.added_directory = None
self.allow_sim = allow_sim
+
def __enter__(self):
if self.directory is not None and self.directory not in sys.path:
sys.path.insert(0, self.directory)
@@ -109,6 +106,7 @@ def __enter__(self):
for mod in discover_backends().values():
self.simulators[mod] = mod.Simulator
mod.Simulator = make_dummy(mod.Simulator)
+ return self
def __exit__(self, exc_type, exc_value, traceback):
for mod, cls in self.simulators.items():
diff --git a/nengo_gui/gui.py b/nengo_gui/gui.py
index 5245768f..d78f1ab9 100644
--- a/nengo_gui/gui.py
+++ b/nengo_gui/gui.py
@@ -1,46 +1,383 @@
-"""Classes to instantiate and manage the life cycle of the nengo_gui
-backend."""
+"""Classes to instantiate and manage the life cycle of nengo_gui."""
from __future__ import print_function
+import json
+import mimetypes
+import os
+import logging
+import pkgutil
import select
import signal
+import ssl
import sys
import threading
+import time
import webbrowser
+from timeit import default_timer
-import nengo_gui
-from nengo_gui.guibackend import GuiServer
+from nengo_gui import exec_env, paths, server
+from nengo_gui.client import ClientConnection, FastClientConnection
+from nengo_gui.compat import unquote
+from nengo_gui.editor import AceEditor, NoEditor
+from nengo_gui.page import Page
+from nengo_gui.server import (
+ HtmlResponse, HttpRedirect, ServerShutdown, WebSocketFrame)
-class ServerShutdown(Exception):
- """Causes the server to shutdown when raised."""
- pass
+logger = logging.getLogger(__name__)
+
+
+class Context(object):
+ """Provides context information to the page.
+
+ This can include the locals dictionary, the filename and whether
+ this model can (or is allowed) to be written to disk.
+ """
+
+ def __init__(self,
+ model=None,
+ locals=None,
+ filename=None,
+ filename_cfg=None,
+ writeable=True,
+ backend="nengo"):
+ self.writeabel = writeable
+ self.filename_cfg = filename_cfg
+ self.filename = filename
+ self.backend = backend
+
+ if model is None and locals is not None:
+ model = locals.get('model', None)
+
+ if model is None and filename is None:
+ raise ValueError("No model.")
+
+ self.model = model
+ self.locals = locals
+
+ @property
+ def filename(self):
+ return self._filename
+
+ @filename.setter
+ def filename(self, value):
+ self._filename = value
+
+ if value is None:
+ self.writeable = False
+ else:
+ try:
+ self._filename = os.path.relpath(value)
+ except ValueError:
+ # happens on Windows if filename is on a different
+ # drive than the current directory
+ pass
+
+ if self.filename_cfg is None:
+ self.filename_cfg = "%s.cfg" % (self._filename,)
+
+
+class ServerSettings(object):
+ __slots__ = ('listen_addr',
+ 'auto_shutdown',
+ 'password_hash',
+ 'ssl_cert',
+ 'ssl_key',
+ 'session_duration')
+
+ def __init__(self,
+ listen_addr=('localhost', 8080),
+ auto_shutdown=2,
+ password_hash=None,
+ ssl_cert=None,
+ ssl_key=None,
+ session_duration=60 * 60 * 24 * 30):
+ self.listen_addr = listen_addr
+ self.auto_shutdown = auto_shutdown
+ self.password_hash = password_hash
+ self.ssl_cert = ssl_cert
+ self.ssl_key = ssl_key
+ self.session_duration = session_duration
+
+ @property
+ def use_ssl(self):
+ if self.ssl_cert is None and self.ssl_key is None:
+ return False
+ elif self.ssl_cert is not None and self.ssl_key is not None:
+ return True
+ else:
+ raise ValueError("SSL needs certificate file and key file.")
+
+
+class GuiRequestHandler(server.AuthenticatedHttpWsRequestHandler):
+
+ @server.AuthenticatedHttpWsRequestHandler.http_route('/browse')
+ @server.RequireAuthentication('/login')
+ def browse(self):
+ r = [b'
']
+ d = unquote(self.db['dir'])
+ root = self.db['root'] if 'root' in self.db else '.'
+ ex_tag = '//examples//'
+ ex_html = b'built-in examples'
+ if d == root:
+ r.append(b'