Control flow and operator
units
In many cases, the simple linear left-to-right execution of the units in
a visual program is too limiting. In this chapter you will learn how to
structure
the control flow in a visual program, particularly
How to set up repetitive loops
A group of units can be executed a fixed number of times by putting a for_op
unit in front of them. The for_op unit is surrounded by a "scope
rectangle" that can be stretched with the mouse pointer to enclose
those successor units that shall become part of the iteration loop (try
it with example#1). Alternatively,
the desired number of successor units ("operands") can also be specified
as the first parameter in the for_op's creation dialog window. The
second parameter initializes the number of desired iterations. The actual
value can be changed at the single input pin of the for_op (but
note that the initializer value for this pin is a creation parameter and
stays fixed). A single execution of the for_op repeats its operands
(the units enclosed in the scope rectangle) for the number of times given
at the input field of the for_op. After each iteration, the output of the
for_op is incremented to the current iteration count. A break_unit
among the operands will lead to a premature termination of the loop, but
only if the break_unit is activated by a non-zero input.
When a unit becomes the operand of a for_op (or any other NST
operator) unit, responsibility for its execution passes to the for_op.
That means, when the for_op becomes executed during a "Step" command
(and thereby iterates its operands), execution will not continue with the
first operand of the for_op (which would incorrectly contribute a further
iteration to the loop) but instead continues with the first non-operand
unit. As a result, when nesting several for_op's (or other operands), it
makes no difference whether the scope rectangle of an outer for_op encloses
only the nested for_op or also some of its operands (the scope rectangle
is for each iteration like a "Step" command for the operands). Try it out
with example#2.
How to execute
a group of units conditionally
The if_op unit is
another operator unit that, however, executes its operands only once and
only if an expression formed from its input values gives a non-zero (logical
true) result. The expression is defined by a string that may contain up
to 10 variables named x0..9. For each different variable name
that occurs, the if_op unit's input connector will have a corresponding
scalar input pin to provide a value. If the expression evaluates to zero,
the if_op acts like a for_op with zero iterations, i.e., execution
of its operands is omitted. The output of the if_op provides the
value of the logical expression.
Example#3
provides
an illustration.
If only the value of this expression, without any further side effects
is desired, one can use an if_op with 0 (no) operands. Nesting of
if_ops
follows analogous rules as explained for the for_op.
The
use of names to make units callable from other units
Often, one wishes to use the same instance of a unit in several contexts
(e.g., testing the same trained neural network in different environments).
To do this requires to give the to-be-called instance a (call) name
(cf.
below). When a unit has been named in this way, the use_method
unit allows to create a "proxy" for the named instance (see example#4).The
proxy will have the same interface and appear like a copy of the original
instance, however, each invocation of the proxy invokes the original instance
(it first transmits the proxy's current input values to the original instance,
then executes the original instance and finally copies the output pin values
back into the proxy's outputs). To name a unit, select it and then select
the "Name" button in the command menu. You can now specify a name
(or accept a suggested default). After "OK" the selected unit will
display its new name on its icon. To distinguish named units from units
bearing only labels (names and labels are different concepts
in Neo; labels have no referencing significance and are just aiding
the readability of a circuit) a name is always displayed underlined, while
a label is not.
A similar unit is the use_named
unit. The difference to the use_method unit is that the use_method
unit automatically inherits the full interface of the named unit it references,
whereas the use_named unit requires an explicit specification. Usually,
this is more inconvenient, but it may become useful when only a subset
of the connectors of the referenced unit shall be accessed.
It is recommended to choose name such that they are legal C-identifiers
(this may be convenient for future extensions, but it is not enforced currently).
Already now a name must not contain a '#' character. An underscore
'_'
is a legal part of a name, but when it is present, Neo will try it as a
hyphenation position to make the name fit on the Neo icon. When the name
is hyphenated at an underscore,the underscore will not be displayed on
the icon (although it is internally kept as a part of the name). The last
special character is the '|'. It may be specified in a name; like the underscore,
it is used as a hyphenation hint, but - unlike the underscore - it is never
considered to be part of the name (i.e., any callback specifications can
be made without it).
How to display
the original types of named units
When a unit has been named, the name will replace the previous label or
type inscription on the unit's icon. Therefore, one may wish to use names
that reflect the original type of a unit. However, this is not always possible.
Also, the use_method or use_named units always carry the referenced name
and never their original type as their inscription. Since this can be confusion,
Neo allows to toggle its usual display with a second display mode which
always displays the original unit types as inscriptions on the icons (together
with a unique referencing number; the latter may be useful for debugging
purposes). This alternative display mode is obtained by pressing the 'A'
key on the keyboard (a second press leads back to the original display
- try it).
The prog_unit as
an operator unit
You can make the prog_unit
into an operator unit by using the special routine exec_opnds()
in the prog_unit source code. In this case, the prog_unit icon will
have an adjustable scope rectangle, with which you can enclose a number
of successor units as operands for the prog_unit.
Each occurrence of exec_opnds() in the prog_unit program will
then cause an execution of the so selected operand units of the prog_unit.
I.e., the following line would simulate a for_op unit with 10 iterations:
for (i=0; i<10; i++) exec_opnds();
Note also, that before each call of exec_opnds() the prog_unit
will update its current output connectors, so that any operand units, when
connected to the prog_unit, will see the latest variable values (such as,
e.g., the iteration count i, if it happens to be an OUT variable).
Likewise, after exec_opnds(), the prog_unit code may
use values from input connectors which may contain result values from the
just executed operands. Therefore, with the prog_unit you can implement
very powerful operator units that can, e.g., do something with a set of
objects offered in the form of a number of operands (e.g, one ormore functions
that may again be implemented as prog_units).
There is also a related call exec_opnd(arg), where are
is either a number or the name of a named unit. In the former case, the
call will execute the arg-th successor to the prog_unit (the recursive
call arg=0 is ignored). In the latter case, the call will execute the NST
unit with name arg.
Note, however, that exec_opnd(1) is not entirely equivalent to
exec_opnds()
with one operand: the exec_opnd() call will not treat its argument
as an
operand but as a "callback":
a callback is just executed
in the normal fashion, without any further side effects on the callback
unit. In contrast, you might remember that
operands are always "shielded"
from being executed a second
time by "Step" if they were
already
executed by their operator as a result of the same "Step" command).
Therefore, during a "Step", exec_opnd(1) would execute the successor, and
then, after the execution of the prog_unit, the successor unit would be
executed a second time.
The example#5 illustrates
this difference: here, the prog_unit calls its first successor as
an operand, and its second successor
as a callback. If you execute the prog_unit directly,
both, the operand and the callback unit will be executed once. However,
if you use the Step command, the callback unit will become executed
twice.
How to
protect units from execution: the no_op unit
Sometimes (as in the case explained in the previous paragraph) it is desireable
to prevent one or several units from being executed by a "Step"
command. The no_op
operator unit allows to achieve this: it acts like a for_op with
0 iterations, i.e., during a "Step" command, execution will skip
to the next unit after the last operand of no_op. Note that this
protection works only for "Step" or the access of other operand
units. Non-operator calls, such as from a use_named unit, or from a exec_opnd(arg)
call in a prog_unit, still will work.