Friday, February 22, 2013

Dynamo: Preventing Unnecessary Evaluation (Part 2)

In my last blog post, I spoke about how we could use graph analysis in Dynamo to avoid duplicating code when compiling Dynamo workflows down to the FScheme engine. I am pleased to announce that this has now been implemented and is live on GitHub!

Of course, nothing ever goes right the first time. Hours after pushing all the new code to GitHub, during a moment of quiet reflection, I discovered why the approach I originally outlined was flawed. As with most issues in programming--I find--the problem has to do with mutation.

So what's wrong?

Let's use the following workflow as an example:



If we were to optimize this using the original approach, the resulting FScheme code would look like this:

(λ (path)
  (let ([a (read-file path)])
    (list (begin (write-file path "text")
                a
                path)
          a)))

(Note that this is a definition for a new node, so it gets compiled down to a function. Hence the λ at the beginning.)

At first glance, the problem may not seem immediately obvious. The Read File node is connected to two inputs, Perform All and List. We use the lowest single ancestor algorithm to find where we can place a let binding: in this case, it's around the List node. We evaluate the Read File node, store it in a new identifier a, and then in both places that it's used, we refer to a.

...so what's wrong?

FScheme--like most Schemes--is an eager language. This means that it will evaluate all that it can as soon as it can. When traversing the above code, first FScheme will first evaluate (read-file path) and store it in a. Then it will evaluate the body of the let binding, where (write-file path "text") will be evaluated. The problem here is that Perform All (which compiles to the begin expression) must evaluate its inputs in the order they are listed, otherwise side-effects could occur in the wrong order. Since the order in which side-effects occur matters a lot, this is a big problem.

What we really want is to not evaluate (read-file path) until we normally would, after (write-file path "text"). Then, we want to store that value somewhere and reference it later when we need it. In more traditional imperative programming languages, this is familiar. Take a look at the following snippet of C#:

delegate (string path)
{
  string a;
  writeFile(path, "text");
  a = readFile(path);
  return new List<string>() { path, a };
};

Notice how we store a after we perform writeFile, and then later we just refer to a again. Well, we can do something similar in Scheme:

(λ (path)
  (let ([a (begin)])
    (list (begin (write-file path "text")
                 (begin (set! a (read-file path))
                        a)
                 path)
          a)))

This may look strange, but we're following the same pattern as in C#:  we declare a new identifier a that's uninitialized (the (begin) code returns a value that, when used, will result in an error), then where a would normally first be referenced, we evaluate (set! a (read-file path)) which stores an actual value in a. In all other places, we can just refer to a which now contains an actual value.

Putting it all together

The full algorithm is as follows:
  1. For each node X that has multiple outputs connected:
    1. Assign a unique string ID to be used as a storage variable
    2. Look up the lowest single ancestor LSA(X)
  2. Compile the dynamo graph starting from the entry point (node with no connected output).
    1. When reaching LSA(X), insert a let binding, binding ID to a (begin)
    2. The first time X is reached, insert a begin that first binds the compiled form of X to ID, and then returns ID.
    3. All subsequent times X is reached, simply insert ID

Sunday, February 17, 2013

Dynamo: Preventing Unnecessary Evaluation

Hello again! Today I'm pleased to present some progress with automatic removal of duplicate code, an issue that has plagued Dynamo workflows for a long time.

What's the problem?

Take a look at the following workflow:


Notice that several of the nodes in the workflow have their outputs connected to multiple places. Therein lies the heart of the problem: as a user, one would expect the output of a node to be passed to all inputs connected to it. Dynamo does this, but up until now, it did this by duplicating code.

You can see this in the compiled form of the above workflow:

(list (+ (square 3) 3)
      (+ (square 3) 3) 
      (* (square 3) (square 4)) 
      (+ (square 4) (square 4)))

When Dynamo evaluates this expression, it will run all of the duplicated code: (square 3)for example, will be run 3 times. This is two more times than necessary.


That doesn't seem like big deal

It's true that for basic calculations such as in this example, the code that's duplicated doesn't make a big difference. However, if the code that's duplicated takes a long time to run--or worse, performs a mutation such as adding geometry to a Revit document--then the duplication turns out to be a very big deal.

Fortunately there was a way to work around this: the only nodes where duplication never really mattered were ones that had no inputs themselves, such as numbers or variables. This is because it's just as fast to "calculate" the output of these nodes as it would be to lookup a cached result. So what you could do is create a new node and pass in the results you want to cache as inputs. The results are then cached as the node's variables, and then you can access them as many times as you like.

To remove duplication from the above workflow using this method, the compiled expression would look like this:

((lambda (a)
  ((lambda (b)
     ((lambda (c)
        ((lambda (d)
           (list c c (* b d) (+ d d)))
         (square 4))) 
      (+ b a))) 
   (square a))) 
 3)

In the actual Dynamo UI, this would require four separate user-defined nodes, and is obviously too painful to have to do. Ideally, all of this would be done under the hood for us.

There is a better way

Scheme has a built-in construct called let for dealing with caching values, without having to use intermediate helper functions. Using let* (shorthand for nested lets), the above can be rewritten as:

(let* ([a 3]
       [b (square a)]
       [c (+ b a)]
       [d (square 4)])
  (list c c (* b d) (+ d d)))

It's actually significantly easier to have Dynamo generate a let* then having to create all of the intermediate functions. When passed to the underlying FScheme engine, it will eventually all be converted to these helper functions anyway.

So how does this happen?

Lots of graph analysis. When compiling the Expression used for evaluation, Dynamo will now check for duplicated code by looking for nodes with an output being used in more than one place. It figures out dependencies among these nodes (so that they are ordered properly in the let*) and calculates the extent of where the code will be used (to determine the "entry-point" of the let*).

For any node with more than one output, in order to determine where a let* will be placed, the lowest single ancestor for the node must be calculated. In graph theory, the LSA for a node v is another node l that lies on all paths from the root of a directed acyclic graph (which is what a Dynamo workflow is) to v, and none of l's descendants also have this property (otherwise the root node would always satisfy the problem). I found a great paper by Fischer and Huson outlining how to implement an LSA lookup.

Tuesday, January 8, 2013

Dynamo - FScheme Refactor Progress

I've been spending the past few weeks working on various improvements to FScheme, the core scripting engine of Dynamo. Originally I was focusing on performance, but I ended up adding a bunch of things to bring the language closer to actual Scheme. The core language is getting there, but the library is still missing a lot of what's in the R5RS Scheme standard.

What's FScheme?

Way back when Ian first created Dynamo, it used an event structure to notify nodes that their inputs have been evaluated. This was slow already, and the fact that it couldn't work inside of one Revit API transaction meant that each node had it's own transaction (something that present-day Dynamo does in Debug mode) meant that updates to Dynamo could take forever to propagate to Revit.

Starting in March of 2012, I began working on modifying the Dynamo engine so that the node interface could be converted to a Scheme-like expression which could then be evaluated, thus making Dynamo itself as full-featured a programming language as Scheme (in theory). In May, I committed these changes to GitHub.

FScheme began as a prototype in Python that I made in order to demonstrate that you can imperatively create Scheme expressions and then execute them. The idea is that the imperative interface could be wrapped by a GUI. I then set out to port the prototype to .NET so that it would be compatible with the Revit API and the existing Dynamo code. Originally, I was going to write the engine in C#, but after doing some research on tail call optimization in the language, I realized this wouldn't work. After doing a little more research, I came to the conclusion that F# would be the best candidate: it has full tail call optimization (even for x86) and it compiles to CLR code, meaning that it is not interpreted. The fact that F# code is callable from C# means that I could access this engine directly from the existing Dynamo code.

The problem with writing it in F# was that I didn't know anything about the language; the closest language to F# that I had used in the past was Haskell, and I had only used it briefly.  Fortunately, I found this great tutorial about writing a Scheme interpreter in F#, and--even better--the code was open source on GitHub. After doing some basic modifications to facilitate some features that Dynamo should support (with respect to performance and dynamic updating), I had a basic Scheme language that would be hidden to the end user behind Dynamo's UI.

So What's New?

It turns out that there were a ton of problems with the FScheme language outlined by the tutorial. The first problem was that lexical scope was broken and would prevent tail calls from being optimized properly. Macros were also first-class and evaluated at runtime, which on the one hand was cool because you can then pass macros as arguments just like you can pass functions, but it made writing library functions in F# a total headache. It also greatly messed up evaluating lists, since the evaluator made no distinction between a list data-structure and a syntax-list used for function calls.

After attempting to fix some of these things, I quickly realized that it would be simpler to rewrite the language from the ground up. So that's what I did. FScheme is now much closer to normal Scheme, except it retains the features I need for interoperability with Dynamo.

Unfortunately, a lot of these changes are not user-facing. Macros are now evaluated at compile-time, and so are no longer first class. Fortunately people don't usually pass if statements as arguments to functions, so this won't be missed. I also had to remove call/cc since nobody uses it and it was bogging down performance. Finally, I added a (simple) compiler that performs a bunch of optimizations and produces quick F# code.

For a basic benchmark, I ran the following expression in both the old FScheme implementation and the new one, and compared the execution times:

(begin (define counter (lambda (x) (if (<= x 0) x (counter (- x 1))))) (counter 1000000))

Avg. Old FScheme Execution Time: 5130ms
Avg. New FScheme Execution Time: 2539ms

Admittedly, this test only really benchmarks function call speed, but since that's the most common operation in FScheme, the fact that it runs over twice as fast is a significant accomplishment.

You can see all the new changes to the FScheme language in the fscheme-improvements branch on GitHub.

Tuesday, December 18, 2012

Dynamo - Manual Transactions

Hey there, kids! I hope you're all getting ready for the holiday festivities, but maybe some of you are using some of your downtime to tinker with Dynamo on the side. Today, I'll be talking about the Transaction node in Dynamo, and what it can do for your holiday experimentation.

How Dynamo evaluates nodes

I mentioned in my previous blog post that one of Dynamo's internal modes of interacting with the Revit API via Transactions is Manual Transaction mode, and Dynamo only evaluates in this mode when there is a Transaction node in the execution workflow. To summarize again: Dynamo starts evaluating the right-most nodes (nodes with nothing connected to the output). If a node has any inputs, then it first evaluates its inputs, and then passes the evaluated inputs to the node, which is then used to evaluate the node.

Take a look at the following image, in which I've labeled the order nodes are evaluated:


First, Dynamo tries to evaluate the Reference Point node. It sees that the node has an input, so it then tries to evaluate the input, which is the GraphFunction node. That also has inputs, so it start evaluating those, starting with the first (top-most). The multiplication node also has inputs, so it evaluates those. This brings us to the first Number node. It has no inputs, so it can be evaluated as-is. The number -1 is evaluated. Now the other input to the multiplication node needs to be evaluated. The pi node has no inputs, so it just returns 3.4159... Now, the inputs to the multiplication node are satisfied, and we can evaluate the node, which multiplies them. Dynamo will now start evaluating the second input to the GraphFunction node, and the same process will occur.

What the Transaction node does

The Transaction node hooks into this evaluation tree-traversal in order to wrap some of the evaluation in a Revit API transaction. Let's say we had a Transaction node in between the division node and the GraphFunction node. When it would normally come time to attempt to evaluate the division node, it would first run into the new Transaction node. The Transaction node inserts some extra code in the process that initializes a new Revit API transaction, and the continues with the evaluation process. This means that the division node and its inputs will be evaluated with a transaction active. Once the division node has finished being evaluated, it's time for the Transaction node to be evaluated. For its evaluation, it ends the Transaction it created, and then passes its only input along as the result of the evaluation.

Caveats of the Transaction node

When a Transaction node is in the evaluation tree, Dynamo can no longer automatically manage the transactions of its nodes, since the user has explicitly determined when a transaction begins and--more importantly--ends. This means that any node which requires a transaction must be manually connected to a Transaction node by the user. This will prove to be rather inconvenient if you're using a third-party node that uses a transaction internally. However, it mirrors how writing any code for the Revit API involving transactions works, so if you're used to working with it in code, code with it in Dynamo should be familiar.

So when should I use a Transaction node?

Any time you need discreet control of a transaction in a Dynamo workflow. Without a Transaction node in the workflow, Dynamo will automatically detect if any of the nodes require a transaction, and if they do, it will run the entire workflow inside of one. If this functionality cannot accomplish what you need, that's when you use a Transaction node.

As mentioned in my previous blog post, a great example is the Solar Radiation Optimizer sample. It first adjust the value of a family instance parameter, then needs to recalculate the amount of incident solar radiation with the new value applied. The Solar Radiation plugin cannot recalculate until the parameter change has been applied, which requires the transaction the parameter change occurred in to have ended. When I wrote the Solar Radiation Optimizer sample, I used a Transaction node in order to set the new family instance parameter value. After the value was set, the transaction would end, the solar radiation plugin would update and spit out its results to a file, Dynamo would pick up on the change to the file and read in the new data, then compare the data with what it has stored, and then repeat the process until the end of the loop.

Tuesday, November 27, 2012

Dynamo - Revit Transaction Modes

I'm sitting at an airport bar waiting for my flight back to Boston to start boarding, so I figure now is as good a time as any to discuss how Dynamo interacts with the Revit API via transactions.

All Dynamo nodes know whether or not they require a transaction in order to evaluate. This is necessary in order to make sure that they are run within a transaction, but also so that they aren't run in a transaction if they don't need to be.

Dynamo has three internal "transaction modes" which are set based on various factors.

Automatic

The default mode. If any node in the workflow requires a transaction to evaluate then the whole Dynamo expression is run in a transaction in the Revit idle thread. Otherwise, it is run in its own thread.

Manual

This mode is active if the workflow contains a Transaction node. In this mode, it is up to the user to make sure that all nodes which require a transaction are connected to a Transaction node (either directly or through one or more "parent" nodes). This is desired if you would like to see intermediate results in Revit or if you need a complete Transaction before continuing to evaluate. A good example is the Solar Radiation Optimizer sample, which has to end a Transaction in order for Solar Radiation to update and feed data back into Dynamo.

Debug

Activated when the Debug box is checked in the UI, every node which requires a transaction is evaluated in its own transaction. This allows you to see every node's effect on the document, but is also really slow. Fun fact: this was the first (and for a while, the only) transaction mode.

Monday, November 26, 2012

Dynamo - Sliders

The End Result


Slider nodes now have a text box that displays the current value underneath the slider as you move it.

Thoughts

I feel like any technology substantially cool also makes me want to pull my hair out.

Sometimes I'm amazed at how well WPF can get things done. I can usually link together how I'd like a UI to work using abstract notions that don't translate well to paper, let alone code, but WPF somehow has the ability to do this translation as simply as possible.

That doesn't mean that there isn't a lot of trial and error, however!

One long requested feature for Dynamo was the ability to see the exact value of a slider. The reason why this is useful is obvious, but because I didn't really know how I'd like the UX to work, I put the feature off for a while. Besides, I'm more of an engine guy and not so much into UI (ironic, considering I work on a programming language who's defining feature is a UI).

This weekend, in a alcohol turkey-induced haze, I came up with a decent idea:  have a TextBox appear when the user's mouse is over the slider, and have it disappear when the mouse is removed. "But wait Stephen," you exclaim excitedly, "Why don't you just use a tool tip? It does almost exactly that!" You see, Dynamo has tool tips for nodes reserved for node descriptions and error messages, and there is no clean way to override that functionality. Also, that's not really what tool tips are for, in my (humble, UX-lacking) experience.

OK. So how was it implemented?

Early in Dynamo's life, Ian Keough added a Canvas to the UI for Dynamo nodes. This Canvas was used to draw the chrome on the nodes, as well as the title text. I was able to use the Canvas in order to position a TextBox at an arbitrary coordinate relative to the node. Then, it was simply a matter of hooking the appropriate events to some logic that controls when the TextBox is displayed and hidden, and setting up a Binding that binds the TextBox's Text property to the current value of the slider.

Unfortunately for fans of XAML (but fortunately for myself, who isn't one), this has to be done programmatically in C#, due to how Dynamo nodes are initialized. This is what the code looks like:
1:  Slider tb_slider;  
2:  dynTextBox mintb;  
3:  dynTextBox maxtb;  
4:  TextBox displayBox;  
5:    
6:  public dynDoubleSliderInput()  
7:  {  
8:    tb_slider.ValueChanged += delegate  
9:    {  
10:      var pos = Mouse.GetPosition(elementCanvas);  
11:      Canvas.SetLeft(displayBox, pos.X);  
12:    };  
13:      
14:    tb_slider.PreviewMouseDown += delegate  
15:    {  
16:      if (this.IsEnabled && !elementCanvas.Children.Contains(displayBox))  
17:      {  
18:        elementCanvas.Children.Add(displayBox);  
19:    
20:        var pos = Mouse.GetPosition(elementCanvas);  
21:        Canvas.SetLeft(displayBox, pos.X);  
22:      }  
23:    };  
24:      
25:    tb_slider.PreviewMouseUp += delegate  
26:    {  
27:      if (elementCanvas.Children.Contains(displayBox))  
28:        elementCanvas.Children.Remove(displayBox);  
29:    };  
30:    
31:    displayBox = new TextBox()  
32:    {  
33:      IsReadOnly = true,  
34:      Background = Brushes.White,  
35:      Foreground = Brushes.Black  
36:    };  
37:    Canvas.SetTop(displayBox, this.Height);  
38:    Canvas.SetZIndex(displayBox, int.MaxValue);  
39:    
40:    var binding = new System.Windows.Data.Binding("Value")  
41:    {  
42:      Source = tb_slider,  
43:      Mode = System.Windows.Data.BindingMode.OneWay,  
44:      Converter = new DoubleDisplay()  
45:    };  
46:    displayBox.SetBinding(TextBox.TextProperty, binding);  
47:  }  
48:    
49:  [ValueConversion(typeof(double), typeof(String))]  
50:  private class DoubleDisplay : IValueConverter  
51:  {  
52:    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)  
53:    {  
54:      return ((double)value).ToString("F4");  
55:    }  
56:    
57:    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)  
58:    {  
59:      return null;  
60:    }  
61:  }  

(Note that I've removed code that's irrelevant to this explanation, which I'll summarize quickly: the initialization and configuration of the fields were removed, as well as the initialization code for the inherited Dynamo node UI.)

On line 8: When the slider's ValueChanged event occurs, I get the current mouse position relative to the Canvas, which the TextBox is a child of, and then use the X-coordinate to place the TextBox in line with the mouse.

On line 14: When the user clicks on the slider, first I check to see if the TextBox should be displayed. If it actually should, then I add the TextBox to the Canvas, and set it's X position in the Canvas just like I did in the previous event handler.

On line 25: When the user releases the mouse button over the slider, then I check if the TextBox is currently being displayed, and if it is, I stop displaying it.

On line 31: I initialize and configure the TextBox. Note that on line 37, I set the Y coordinate to the height of the node, thus making sure that it is always displayed right below the node.

On line 40: I create a Binding that ties what text that the TextBox displays to the value of the slider. Note that I set an explicit Converter. I wan't familiar with programmatically making WPF Bindings, but fortunately MSDN has a great tutorial on the subject.

On line 52: I create a new implementation of an IValueConverter, that converts double to String. This is used to truncate the number of decimals to 4, as opposed to the default, which was a lot more.

And that's all there is to it!