Skip to content

Replication and Replication Guide Part 1

Yu Ke edited this page Jul 14, 2016 · 4 revisions

1. Introduction

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.

2. Replication Computation

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,

  1. 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 input x. Note the result is still in a list.

  2. 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:

  1. 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}.
  2. If there are two or more dk > 0, do Zip Replication on all those arguments on the corresponding positions, and reduce dk by 1 (that is, dk = dk - 1). Repeat this process until only one or no dk > 0.
  3. If there is a dk > 0, do Cartesian Replication on that argument and and reduce dk by 1 (that is, dk = dk - 1). Repeat this process until no more dk > 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
}

3. Examples

Example 1

Node: BoundingBox.ByGeometry(geometry: Geometry[])

  • The rank of parameter geometry is 1.
  • Input: pts = {{p1, p2}, {p3, p4}}, the rank of argument pts is 2.

Replication steps:

  1. d(geometry) = 2 - 1 = 1
  2. As there is only one parameter and d(geometry) > 0, do Cartesian Replication on pts, which is equivalent to execute {BoundingBox.ByGeometry({p1, p2}), BoundingBox.ByGeometry({p3, p4})}.

Table below shows this process:

Example 2

Node: Point.ByCoordinates(x: double, y: double)

  • The rank of parameter x is 0, the rank of parameter y is 0.
  • Input: xs = {{1, 2}, {3, 4}}, ys = {5, 6}, so the rank of argument xs is 2, the rank of argument ys is 1.

Replication steps:

  1. {dx = 2 - 0 = 2, dy = 1 - 0 = 1}
  2. As both rank delta values are larger than 0, do Zip Replication on xs and ys 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}
  3. 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} for Point.ByCoordinates({1, 2}, 5} and {3, 4} Pont.ByCoordinates({3, 4}, 6) from step 2.

Table below shows this process:

Example 3

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):

Releases

Roadmap

How To

Dynamo Internals

Contributing

Python3 Upgrade Work

Libraries

FAQs

API and Dynamo Nodes

Clone this wiki locally