Skip to content
Gijs Molenaar edited this page Feb 13, 2014 · 5 revisions

How does a TDL script run?

A TDL script is nothing more and nothing less that a Python module that makes use of a bunch of syntactic sugar from Timba.TDL to define trees.

When you select a TDL script from the browser (use the "Load and run" option of the TDL menu), the following things happen:

  1. The TDL host program (currently, the meqbrowser) imports the script as a module. NB: in the first implementation, the host is the browser. In later versions it is planned to move TDL hosting directly into the kernel, which should make tree definition even faster.
  2. The script must define a global define_forest() function. This is the entrypoint called by the host program.
  3. The host creates a TDL.NodeScope object representing the global node scope, and calls the script's define_forest() function with the node scope as the argument.
  4. The script is expected to populate the node scope with a forest definition, and return control back to the host. Errors should be reported by throwing exceptions.
  5. The TDL host checks the forest definition for consistency, and creates the actual forest (the browser does this by telling the kernel to create nodes).
  6. If the script defines a global test_forest() function, this is added to the "Jobs" menu.
  7. Any additional functions whose names begin with tdl_job_ are also added to the "Jobs" menu. This is a convenient way to run pre-packaged queries on the forest.

A simple example

Let's look at a very simple TDL script first (PyApps/test/tdl_test.tdl. It is a good exercise to run this script from the browser, and look at it side-by-side with the tree.

from Timba.TDL import *# Timba.TDL.Settings.forest_state is a standard TDL name. # This is a record passed to Set.Forest.State. Settings.forest_state.cache_policy = 100;# Make sure our solver root node is not cleaned upSettings.orphans_are_roots = True;```
This imports TDL functions and changes the default forest state. See [[TDLTutorial]] for an explanation of the second setting. 


```python
def define_forest (ns):  ns['solver'] << Meq.Solver(    num_iter=5,debug_level=10,solvable="x",    children = Meq.Condeq(      Meq.Parm([0,1]),      ns.x << Meq.Parm(0,tiling=record(time=1),node_groups='Parm')    ),  );```
Now, this is the real meat of the script. What's happening here? Recall that `define_forest()` is called with a `NodeScope` argument. This is the `ns` parameter here. A node scope can be thought of as a _node generator_. Its main purpose is to create **node stubs**. A node stub eventually represents a complete node, but when you first create it, it contains a name and nothing more. The code above invokes stubs for nodes "solver" and "x", while two other nodes are defined but left unnamed, 


### Naming nodes

Stubs are created as follows: 

* by referring to an attribute of `ns`, as `ns.`_nodename_. This is how the "x" node above is named. This is handy for assigning fixed, static names. 
* should you ever need to "compute" a name on-the-fly (say, return it from a function), you can use the second form, `ns[`_expression_`]`. This is how the "solver" name above is created. Of course this is a contrived example, since the name is static, and it would have been easier to just say `ns.solver` to begin with. 

### Binding node definitions to names

To complete a node, we need to associate the named stub with a **node definition**. This is done by creating a definition and **binding** it to a stub via the **`<<`** operator above. Node definitions are created by the **`Meq`** object (`TDL.Meq`). Any call of the form: 

* `Meq.`_Nodeclass_`(`_children,[initfield=value,...]_`)` 
...creates a definition for a node of class "[[MeqNodeclass|MeqNodeclass]]", using the supplied arguments to override defaults in the init-record. Note that TDL knows very little about specific node classes, you could say `Meq.NoSuchClass(no_such_field=0)`, and it will dutifully try to create the node for you (eventually failing of course). Some popular or complex classes, however, may have predefined "shortcut" constructors. `Meq.Parm` is an example -- if you look at the code above, you'll realize there's a polc funklet being implicitly constructed... but `Meq.Parm(0)` is much less of a mouthful than `Meq.Parm(default_funklet=meq.polc(0)).` 


### Specifying children

The node definition may include children. There are three ways to supply them: 

* simply by listing them as unnamed arguments in the node definition call. E.g. the call to Meq.Condeq above. 
* unnamed arguments in Python must precede keyword arguments. For the sake of clarity, you may want to specify children after the node's named arguments. In this case, you can use the `children=` keyword, as is done for Meq.Solver above. 
* you can also specify labelled children as keywords, see [[AdvancedTDL]]. 
Now, what exactly is a "child"? In TDL, it may be one of several things: 

* An unbound node definition (see the first Meq.Parm above) creates an _anonymous_ child.    The child node will get a name assigned automatically. 
* A node stub. In the example above, this is the second Meq.Parm. Note that the `<<` operator returns the bound node stub itself, so you can specify a child and initialize it in one statement. You can also initialize a child ahead of time: ```
  ns.x << Meq.Parm(0,tiling=record(time=1),node_groups='Parm');
  Meq.Condeq(...,ns.x);
"""]]or even afterwards: ```
  Meq.Condeq(...,ns.x);
  ns.x << Meq.Parm(0,tiling=record(time=1),node_groups='Parm');

Other ways to specify children will be covered in AdvancedTDL.

Testing the forest

If your TDL script supplies a test_forest() method, the host program should offer the user the option of running it automatically once the forest has been successfully created. Here's a fairly typical test method:

def test_forest (mqs,parent):  from Timba.Meq import meq  # run tests on the forest  cells = meq.cells(meq.domain(0,1,0,1),num_freq=6,num_time=4);  request = meq.request(cells,eval_mode=0);  mqs.meq('Node.Execute',record(name='x',request=request));  mqs.meq('Save.Forest',record(file_name='tile_test.forest.save'));  # execute request on solver  request = meq.request(cells,eval_mode=1);  mqs.meq('Node.Execute',record(name='solver',request=request));```
And finally, at the bottom of the script, you will see the following code snippet: 


```python
if __name__ == '__main__':  Timba.TDL._dbg.set_verbose(5);  ns = NodeScope();  define_forest(ns);  # resolves nodes  ns.Resolve();```
  

This is a very useful thing to have at the bottom of any TDL script. It allows you to run the script on its own (i.e. without any hosting program) via `python `_script_`.tdl`, for a quick check of syntax and logical consistency. You can even do this on a machine without a build of Timba -- see [[StandAloneTDL]]. 

Now please see [[TDLTutorial]] for a more complete example. 
Clone this wiki locally