#node = PCTNode()
#node.summary()
Nodes
Overview
A node comprises four functions, reference, perceptual, comparator and output. Executing the node will run each of the functions in the order indicated above and return the output value.
The functions can actually be a collection of functions, each executed in the order they are added. This allows a chain of functions in case pre-processing is required, or post-processing in the case of the output.
ControlUnitIndices
ControlUnitIndices (value, names=None, module=None, qualname=None, type=None, start=1)
An enumeration.
PCTNode
PCTNode (reference=None, perception=None, comparator=None, output=None, default=True, name='pctnode', history=False, build_links=False, mode=0, namespace=None, **pargs)
A single PCT controller.
PCTNodeData
PCTNodeData (name='pctnodedata')
Data collected for a PCTNode
Creating a Node
A node can be created simply.
= PCTNode()
node node.summary()
pctnode PCTNode be1f182d-0011-11f0-a177-8cf8c5b8669e
----------------------------
REF: constant Constant | 0
PER: variable Variable | 0
COM: subtract Subtract | 0 | links constant variable
OUT: proportional Proportional | gain 1 | 0 | links subtract
----------------------------
That creates a node with default functions. Those are, a constant of 1 for the reference, a variable, with initial value 0, for the perception and a proportional function for the output, with a gain of 10.
A node can also be created by providing a name, and setting the history to True. The latter means that the values of all the functions are recorded during execution, which is useful for plotting the data later, as can be seen below.
'pct.functions', 'Constant') dynamic_module_import(
= Constant(1)
reference =reference.namespace namespace
= PCTNode(name="mypctnode", history=True, reference = reference, output=Proportional(10, namespace=namespace), namespace=namespace)
node node.summary()
mypctnode PCTNode be2c2575-0011-11f0-b307-8cf8c5b8669e
----------------------------
REF: constant Constant | 1
PER: variable Variable | 0
COM: subtract Subtract | 0 | links constant variable
OUT: proportional Proportional | gain 10 | 0 | links subtract
----------------------------
Another way of creating a node is by first declaring the functions you want and passing them into the constructor.
UniqueNamer.getInstance().clear()= Variable(0, name="velocity_reference")
r = Constant(10, name="constant_perception")
p = Integration(10, 100, name="integrator")
o = PCTNode(reference=r, perception=p, output=o, name="integratingnode", history=True) integratingnode
Yet another way to create a node is from a text configuration.
= PCTNode.from_config({ 'name': 'mypctnode',
config_node 'refcoll': {'0': {'type': 'Proportional', 'name': 'proportional', 'value': 0, 'links': {}, 'gain': 10}},
'percoll': {'0': {'type': 'Variable', 'name': 'velocity', 'value': 0.2, 'links': {}}},
'comcoll': {'0': {'type': 'Subtract', 'name': 'subtract', 'value': 1, 'links': {0: 'constant', 1: 'velocity'}}},
'outcoll': {'0': {'type': 'Proportional', 'name': 'proportional', 'value': 10, 'links': {0: 'subtract'}, 'gain': 10}}})
# config_node = PCTNode.from_config({ 'name': 'mypctnode1',
# 'refcoll': {'0': {'type': 'Proportional', 'name': 'proportional', 'value': 0, 'links': {}, 'gain': 10}},
# 'percoll': {'0': {'type': 'Variable', 'name': 'velocity', 'value': 0.2, 'links': {}}},
# 'comcoll': {'0': {'type': 'Subtract', 'name': 'subtract', 'value': 1, 'links': {0: 'constant', 1: 'velocity'}}},
# 'outcoll': {'0': {'type': 'Proportional', 'name': 'proportional', 'value': 10, 'links': {0: 'subtract'}, 'gain': 10}}}, namespace=namespace)
Viewing Nodes
The details of a node can be viewed in a number of ways, which is useful for checking the configuration. The summary method prints to the screen. The get_config method returns a string in a JSON format.
integratingnode.summary()
integratingnode PCTNode be3abbc4-0011-11f0-a234-8cf8c5b8669e
----------------------------
REF: velocity_reference Variable | 0
PER: constant_perception Constant | 10
COM: subtract Subtract | 0 | links velocity_reference constant_perception
OUT: integrator Integration | gain 10 slow 100 | 0 | links subtract
----------------------------
#print(integratingnode.get_config())
assert integratingnode.get_config() == {'type': 'PCTNode', 'name': 'integratingnode', 'refcoll': {'0': {'type': 'Variable', 'name': 'velocity_reference', 'value': 0, 'links': {}}}, 'percoll': {'0': {'type': 'Constant', 'name': 'constant_perception', 'value': 10, 'links': {}}}, 'comcoll': {'0': {'type': 'Subtract', 'name': 'subtract', 'value': 0, 'links': {0: 'velocity_reference', 1: 'constant_perception'}}}, 'outcoll': {'0': {'type': 'Integration', 'name': 'integrator', 'value': 0, 'links': {0: 'subtract'}, 'gain': 10, 'slow': 100}}}
integratingnode.get_config()
{'type': 'PCTNode',
'name': 'integratingnode',
'refcoll': {'0': {'type': 'Variable',
'name': 'velocity_reference',
'value': 0,
'links': {}}},
'percoll': {'0': {'type': 'Constant',
'name': 'constant_perception',
'value': 10,
'links': {}}},
'comcoll': {'0': {'type': 'Subtract',
'name': 'subtract',
'value': 0,
'links': {0: 'velocity_reference', 1: 'constant_perception'}}},
'outcoll': {'0': {'type': 'Integration',
'name': 'integrator',
'value': 0,
'links': {0: 'subtract'},
'gain': 10,
'slow': 100}}}
A node can also be viewed graphically as a network of connected nodes.
import os
if os.name=='nt':
=2000, figsize=(8,4)) integratingnode.draw(node_size
Running a Node
For the purposes of this example we first create a function which is a very basic model of the physical environment. It defines how the world behaves when we pass it the output of the control system.
def velocity_model(velocity, force , mass):
= velocity + force / mass
velocity return velocity
= 50
mass = 0 force
In the following cell we start with a velocity of zero. The node is run once (second line), the output of which is the force to apply in the world velocity_model. That returns the updated velocity which we pass back into the node to be used in the next iteration of the loop.
=0
velocity= node()
force = velocity_model(velocity, force, mass)
velocity
node.set_perception_value(velocity)print(force)
assert force == 10
10
The node can be run in a loop as shown below. With verbose set to True the output of each loop will be printed to the screen.
= PCTNode(history=True)
pctnode "perception", "velocity")
pctnode.set_function_name("reference", "reference")
pctnode.set_function_name(
for i in range(40):
print(i, end=" ")
= pctnode(verbose=True)
force = velocity_model(pctnode.get_perception_value(), force, mass)
vel pctnode.set_perception_value(vel)
0 0.000 0.000 0.000 0.000
1 0.000 0.000 0.000 0.000
2 0.000 0.000 0.000 0.000
3 0.000 0.000 0.000 0.000
4 0.000 0.000 0.000 0.000
5 0.000 0.000 0.000 0.000
6 0.000 0.000 0.000 0.000
7 0.000 0.000 0.000 0.000
8 0.000 0.000 0.000 0.000
9 0.000 0.000 0.000 0.000
10 0.000 0.000 0.000 0.000
11 0.000 0.000 0.000 0.000
12 0.000 0.000 0.000 0.000
13 0.000 0.000 0.000 0.000
14 0.000 0.000 0.000 0.000
15 0.000 0.000 0.000 0.000
16 0.000 0.000 0.000 0.000
17 0.000 0.000 0.000 0.000
18 0.000 0.000 0.000 0.000
19 0.000 0.000 0.000 0.000
20 0.000 0.000 0.000 0.000
21 0.000 0.000 0.000 0.000
22 0.000 0.000 0.000 0.000
23 0.000 0.000 0.000 0.000
24 0.000 0.000 0.000 0.000
25 0.000 0.000 0.000 0.000
26 0.000 0.000 0.000 0.000
27 0.000 0.000 0.000 0.000
28 0.000 0.000 0.000 0.000
29 0.000 0.000 0.000 0.000
30 0.000 0.000 0.000 0.000
31 0.000 0.000 0.000 0.000
32 0.000 0.000 0.000 0.000
33 0.000 0.000 0.000 0.000
34 0.000 0.000 0.000 0.000
35 0.000 0.000 0.000 0.000
36 0.000 0.000 0.000 0.000
37 0.000 0.000 0.000 0.000
38 0.000 0.000 0.000 0.000
39 0.000 0.000 0.000 0.000
Save and Load
Save a node to file.
import json
"inode.json") integratingnode.save(
Create a node from file.
= PCTNode.load("inode.json")
nnode
nnode.summary()print(nnode.get_config())
integratingnode PCTNode beee8755-0011-11f0-b9d8-8cf8c5b8669e
----------------------------
REF: velocity_reference Variable | 0
PER: constant_perception Constant | 10
COM: subtract Subtract | 0 | links velocity_reference constant_perception
OUT: integrator Integration | gain 10 slow 100 | 0 | links subtract
----------------------------
{'type': 'PCTNode', 'name': 'integratingnode', 'refcoll': {'0': {'type': 'Variable', 'name': 'velocity_reference', 'value': 0, 'links': {}}}, 'percoll': {'0': {'type': 'Constant', 'name': 'constant_perception', 'value': 10, 'links': {}}}, 'comcoll': {'0': {'type': 'Subtract', 'name': 'subtract', 'value': 0, 'links': {0: 'velocity_reference', 1: 'constant_perception'}}}, 'outcoll': {'0': {'type': 'Integration', 'name': 'integrator', 'value': 0, 'links': {0: 'subtract'}, 'gain': 10, 'slow': 100}}}
print(nnode.get_summary())
0.000 10.000 0.000 0.000
Plotting the Data
As the history of the variable pctnode was set to True the data is available for analysis. It can be plotted with python libraries such as matplotlib or plotly. Here is an example with the latter.
The graph shows the changing perception values as it is controlled to match the reference value.
import plotly.graph_objects as go
= go.Figure(layout_title_text="Velocity Goal")
fig =pctnode.history.data['refcoll']['reference'], name="ref"))
fig.add_trace(go.Scatter(y=pctnode.history.data['percoll']['velocity'], name="perc")) fig.add_trace(go.Scatter(y
This following code is only for the purposes of displaying image of the graph generated by the above code.
from IPython.display import Image
='http://www.perceptualrobots.com/wp-content/uploads/2020/08/pct_node_plot.png') Image(url
