-
Notifications
You must be signed in to change notification settings - Fork 633
Replication and Replication Guide Part 1
One open problem in visual programming is how to represent iteration. As an iteration normally has condition and iteration body, representing them usually results in a large number of nodes with tangled connections. Researchers on visual programming have come up with a lot of ideas to reduce cognitive overhead, but most solutions are still mimics of WHILE-loop or FOR-loop in normal programming languages. Users have to spent more time on thinking about the details of iteration. In other word, user has to think about “how to do” instead of “what to do”.
For most practical purposes, Dynamo represents data in multidimensional lists. Most nodes in Dynamo (e.g. +
node) are able to handle single values (e.g. 2 + 3
) as well as multidimensional lists (e.g. { 2, 3 } + { 4, 5 }
). In such case, iteration is not necessary. But there is a hidden requirement: one computation outcome shouldn’t affect the outcome of the other computation. This kind of iteration is called horizontally parallel iteration. In other words, it is about doing some operation for all elements in a set for a certain number times and each operation is independent. The good thing is most iterations fall in this category.
The concept for a function that accepts single value can also accept array values is not something new. [APL] (https://en.wikipedia.org/wiki/APL_(programming_language)) is one of the earliest [array programming language] (https://en.wikipedia.org/wiki/Array_programming ). For Dynamo, this ability to accept list in place of a single value is called replication, and it is built right in the heart of DesignScript -- the programming language that powers Dynamo.
Basically, replication is just about unpacking inputs of a node to a series of values that a node expects to work with, so the execution of node will be expanded to a series of execution and all results will be aggregated and returned.
There are two kinds of replication. For a node f with a series of input xs1, xs2, ..., xsn
,
-
Cartesian Replication is about iterating all elements in an input and for each element, execute the node with that element together with other inputs. Iteration is just for top level elements. For example, if we are going to iterate through
{{{1}, {2}}, {{3}, {4}}}
, we iterate first element{{1}, {2}}
and then the second element{{3}, {4}}
. For user who has some Python experience, Cartesian replication be expressed in the following Python list comprehension. Note the enclosing{}
puts the result back into a list:{ f(x, xs2, ..., xsn) for x in xs1 }
For example, in the following picture,
Point.ByCoordinates
replicates on inputx
. Note the result is still in a list. -
Zip Replication is to iterate two or more inputs simultaneously and to execute the node with these elements together with other inputs. It can expressed in the following Python list comprehension (this case do zip replication on all inputs)
{ f(x1, x2, ..., xn) for x1, x2, ..., xn in zip(xs1, xs2, ..., xsn) }
For example, in the following picture,
Point.ByCoordinates
does zip replication on inputs of x and y. Note the result is put in a list.
Note replication can be nested. That is, inside a Cartesian Replication we could do another Zip Replication or Cartesian Replication. Each replication creates an extra “layer”, or in Dynamo term, a list.
Now you may ask, how does Dynamo decide whether to do replication or not? If do replication, do Cartesian Replication or do Zip Replication?
The answer is in the signature of a node:
In the info bubble, Point.ByCoordinates(x: double = 0, y: double = 0, z: double = 0): Point
specifies what kinds of inputs that this node expect to work with. Here x: double
specifies the expected type of input x
(we call x
a parameter). double
is a type, means a double-precision floating-point value like 1.2, 3.14 and so on.
If a node expects to work with one dimensional list of some type T
(T
can be any type like int
, bool
, Point
, Line
, etc), the type of that parameter would be T[]
. Similarly, two dimensional list of type T
is T[][]
, three dimensional list is T[][][]
and so on. A special case is arbitrary dimensional list, which is T[]..[]
. In the dimension of a list, in DesignScript term, we call rank. So the rank of T
is 0, the rank of T[]
is 1, the rank of T[][]
is 2, the rank of T[]..[]
is a special value called arbitrary rank.
Similarly, the real input (we call it argument) has its rank as well.
Replication depends on the difference between the rank of parameter that node expects and the rank of real input (argument). It is computed in the following steps:
- Compute the rank delta
dk = rank of parameter at position k - rank of argument at position k
for each parameter and argument pair but skip those parameters that have arbitrary ranks (that is,[]..[]
); then we get a list of delta ranks:{d1, d2, ..., dn}
. - If there are two or more
dk > 0
, do Zip Replication on all those arguments on the corresponding positions, and reducedk
by 1 (that is,dk = dk - 1
). Repeat this process until only one or nodk > 0
. - If there is a
dk > 0
, do Cartesian Replication on that argument and and reducedk
by 1 (that is,dk = dk - 1
). Repeat this process until no moredk > 0
.
If you prefer to read code than read text, following pesudo-code shows this process:
while (two or more dk > 0)
{
Zip Replication on argument at position k for {dk | dk > 0}
for each dk, dk = dk - 1
}
while (any dk > 0)
{
Cartesian Replication on argument at position k
dk = dk - 1
}
Node: BoundingBox.ByGeometry(geometry: Geometry[])
- The rank of parameter
geometry
is 1. - Input:
pts = {{p1, p2}, {p3, p4}}
, the rank of argumentpts
is 2.
Replication steps:
d(geometry) = 2 - 1 = 1
- As there is only one parameter and
d(geometry) > 0
, do Cartesian Replication onpts
, which is equivalent to execute{BoundingBox.ByGeometry({p1, p2}), BoundingBox.ByGeometry({p3, p4})}
.
Table below shows this process:
Node: Point.ByCoordinates(x: double, y: double)
- The rank of parameter
x
is 0, the rank of parametery
is 0. - Input:
xs = {{1, 2}, {3, 4}}, ys = {5, 6}
, so the rank of argumentxs
is 2, the rank of argumentys
is 1.
Replication steps:
{dx = 2 - 0 = 2, dy = 1 - 0 = 1}
- As both rank delta values are larger than 0, do Zip Replication on
xs
andys
firstly. It is equivalent to execute node with{Point.ByCoordinates({1, 2}, 5}, Point.ByCoordinates({3, 4}, 6)}
. After this step,{dx = 2 - 1 = 1, dy = 1 - 1 = 0}
- As there is only one rank delta value is larger than 0, do Cartesian Replication on the result that comes from step 2. That is, do Cartesian Replication on argument
{1, 2}
forPoint.ByCoordinates({1, 2}, 5}
and{3, 4}
Pont.ByCoordinates({3, 4}, 6)
from step 2.
Table below shows this process:
Define a function foo()
showed below and call this function. Note the rank of y
is arbitrary (that is, []..[]
), so it will be excluded from replication computation.
Table below shows the replication computation process (dk = -1
means exclude this parameter from replication computation):
Looking for help with using the Dynamo application? Try dynamobim.org.
- Dynamo 2.0 Language Changes Explained
- How Replication and Replication Guide work: Part 1
- How Replication and Replication Guide work: Part 2
- How Replication and Replication Guide work: Part 3