[PREVIOUS] [NEXT] [UP]

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.


[PREVIOUS] [NEXT] [UP]