NB: This text may be out-of-date. The latest version is always avalable in CVS as PyApps/test/tdl_tutorial.tdl (or use [WWW] this link).

   1 # A TDL tutorial
   2 # NB: work in progress!!!
   3 # This script is meant as both a tutorial and a test of most of the
   4 # features of TDL. If something is missing, please feel free to add.
   5 # The CVS path is Timba/PyApps/test/tdl_tutorial.tdl.
   6 
   7 # standard preamble, every TDL script must start with this
   8 from Timba.TDL import *
   9 
  10 # Initial forest state may be modified by assigning to 
  11 # Timba.TDL.Settings.forest_state. Since we import all names from Timba.TDL
  12 # above, the record is available as Settings.forest_state
  13 Settings.forest_state.cache_policy = 100;
  14 
  15 # If False (the default), orphan nodes not assigned to anything (such as a 
  16 # root group, e.g. ns.ROOT) will be deleted.
  17 # If True, orphan nodes are always retained. This is what we want here.
  18 Settings.orphans_are_roots = True;
  19 
  20 
  21 def define_forest (ns):
  22   """define_forest() is a standard TDL name. When a forest script is
  23   loaded by, e.g., the browser, this method is automatically called to
  24   define the forest. The 'ns' argument is a NodeScope object in which
  25   the forest is to be defined, usually this is simply the global scope.
  26   """;
  27   # So, this is where we start defining our forest
  28 
  29   # A node can be defined as follows: 
  30   ns.a << Meq.Parm(cache_policy=10);
  31   # This creates a MeqParm named 'a'. The thingy on the left ('ns.a') is
  32   # called a "node stub" (hence 'ns' can be thought of both as "node scope"
  33   # and "node stub"), and it determines the name of the node. The thingy
  34   # on the left ('Meq.Parm()') is a "node def", which determines the class
  35   # of the node and its init-record. The init-record can be given as keyword
  36   # parameters to Meq.Parm(). The "<<" operator binds the two together and 
  37   # results in a real node being created.
  38 
  39   # Other variations on this theme. Here's nodes with a default init-record:
  40   ns.a1 << Meq.Parm();
  41   ns.a2 << Meq.Parm;      # this works too
  42   ns.a3 = Meq.Parm;
  43   # assignment works too but note the following: the result of operator <<
  44   # is the node stub itself, and it that may be reused (see below). 
  45   # By contrast, Python won't let you reuse the result of operator = 
  46   # (i.e. you can say a=b=1 but not a=(b=1)+1.) The importance of this 
  47   # will become obvious below.
  48 
  49   # node names can also have arbitrary qualifiers:
  50   ns.b1(1,2,'x',y=2) << Meq.Parm;  # creates node named 'b1:1:2:x:y=2'
  51 
  52   # a name may also be computed, the [] operator comes in handy for this
  53   name = 'b2';
  54   ns[name](1,2) << Meq.Parm;   # creates 'b2:1:2'
  55 
  56   # in fact, even the class itself may be computed:
  57   classname = 'Parm';
  58   ns.b3 << Meq[classname](cache_policy=10);
  59 
  60   # note also that a node def may be reused to create multiple nodes.
  61   # Suppose we want to create a bunch of MeqParms, each with a 2x1 polc
  62   # in it, with c00=1 and c01=0 We can save the def into a variable:
  63   P22 = Meq.Parm([1,0]);
  64   # and reuse it several times:
  65   ns.b4 << P22;
  66   ns.b5 << P22;
  67   ns.b6 << P22;
  68 
  69   # Qualifiers are particularly useful when creating whole series of nodes 
  70   # in a loop:
  71   stations = range(1,5);
  72   P0 = Meq.Parm(0);      # saved def 
  73   for st in stations:
  74     x0 = ns.x0(st) << P0;
  75     y0 = ns.y0(st) << P0;
  76     z0 = ns.z0(st) << P0;
  77     ns.xyz0(st) << Meq.Composer(x0,y0,z0);
  78   # This loop creates a set of subtrees (i=1..4) of the form:
  79   #   xyz0:i (MeqComposer)
  80   #     +-- x0:i (MeqParm)
  81   #     +-- y0:i (MeqParm)
  82   #     +-- z0:i (MeqParm)
  83   # So, children to a node may be specified by passing node stubs as 
  84   # arguments to its definition. (Note that stubs can be assigned to variables
  85   # like any other Python object). But, remember that operator << 
  86   # returns the node stub. So, we can use it in-place to create the whole
  87   # subtree with a single statement:
  88   for st in stations:
  89     ns.xyz1(st) << Meq.Composer(ns.x1(st)<<P0,ns.y1(st)<<P0,ns.z1(st)<<P0);
  90   # or here's an even more concise way to do the same subtree, using Python 
  91   # list comprehension syntax, and passing in the argument list via *:
  92   coords = 'xyz';
  93   for st in stations:
  94     ns.xyz(st) << Meq.Composer(*[ns[a](st) << P0 for a in coords]);
  95 
  96   # Now, can you guess what tree this going to generate?
  97   ns.d1 << Meq.Composer(1,-2,5);
  98   # The answer is, also a subtree:
  99   #   d1 (MeqComposer)
 100   #    +-- c1  (MeqConstant value=1)
 101   #    +-- c-2 (MeqConstant value=-2)
 102   #    +-- c5  (MeqConstant value=5)
 103   # So, using a constant where a node is expected implicitly creates a 
 104   # Meq.Constant node. And here too:
 105   ns.d2 << 0;  # d2 is a MeqConstant with value=0
 106 
 107   # Of course, children may be nested to any depth. Here's another subtree:
 108   ns.solver << Meq.Solver(
 109     num_iter=5,debug_level=10,solvable="x",
 110     # an alternative way to specify children is via the 'children' keyword.
 111     # this is simply a matter of taste, since you may want to give the node's
 112     # init-record first, as we do here.
 113     # The value of children may be a list of children, a dict (see below), 
 114     # or a single child as here:
 115     children = Meq.Condeq(  # a condeq, with 2 children.
 116       # What is this child going to be called? We see here a node def but
 117       # no stub. This is an unnamed node. A name for it will be generated
 118       # automatically, probably something of the form "parmN".
 119       Meq.Parm([0,1]),
 120       # What about here? Of course, a Meq.Add node, with two children
 121       # of its own: a Meq.Constant with value 5, and a Parm named 'x'.
 122       Meq.Add(5,ns.x << Meq.Parm(0,node_groups='Parm'))
 123     ),
 124     # stepchildren are specified via this keyword. A list or a single
 125     # stepchild may be supplied.
 126     stepchildren = [ Meq.Parm(0),Meq.Parm(1) ]
 127   );
 128 
 129   # As a final wrinkle on specifying children, note that some nodes
 130   # may use child labels. For example, MeqUVW has children labelled 
 131   # 'radec_0' (phase center), 'xyz_0' (reference position), and 'xyz' (antenna
 132   # position). MeqUVW has two children labelled 'radec_0' and 'radec'.
 133   # If we defined a UVW node as:
 134   #   ns.uvw << Meq.UVW(ns.radec0,ns.xyz0,ns.xyz)
 135   # we'd never be sure if the order of the children is right. This is where 
 136   # child labels help avoid mistakes. The first way to use labels is to 
 137   # pass in a dict of children:
 138   phasecen = ns.phasecen << Meq.Constant([0.,0]);
 139   srcpos = ns.srcpos << Meq.Constant([.1,.1]);
 140   ns.lmn = Meq.LMN(children={'radec_0':phasecen,'radec':srcpos});
 141   # This syntax is a  bit cumbersome. Fortunately, you can also do it 
 142   # like this:
 143   for st in stations:
 144     ns.uvw(st) << Meq.UVW(radec=phasecen,xyz=ns.xyz(st),xyz_0=ns.xyz0(st));
 145   # There's some potential for confusion here because both labelled children
 146   # and initrec fields are specified via keyword arguments. How does TDL
 147   # know the difference? The answer is, a keyword argument that is a node stub
 148   # or a node def is treated as a child, and everything else is assumed to be 
 149   # an initrec field. Hence,
 150   #     ns.lmn << Meq.LMN(radec0=Meq.Constant([0,0]),radec=ns.position)
 151   # ...correctly specifies two children, but
 152   #     ns.lmn << Meq.LMN(radec0=0,radec=ns.position)
 153   # specifies an initrec field, initrec.radec0=0. So, you can use implicit
 154   # constant nodes for regular children, but not for labelled ones.
 155 
 156   # More examples of automatic node names.
 157   # Now here's another interesting subtree:
 158   ns.d3 << (ns.d4 << Meq.Parm(1)) * Meq.Parm + 5;
 159   # This creates something like:
 160   # 'd3' (MeqAdd)
 161   #   +-- 'multiply(d4,parm3)' (MeqMultiply)
 162   #   |      +-- 'd4'          (MeqParm)
 163   #   |      +-- 'parm3'       (MeqParm)
 164   #   +-- 'c5'     q            (MeqConstant)
 165   # This illustrates two things: 
 166   # 1. Simple arithmetic (+ - * /) with nodes automatically expands into trees
 167   # 2. Unnamed function nodes (such as the MeqMultiply here) are assigned 
 168   #    names based on their child names.
 169 
 170   # Note that (quite obviously if you think about it)
 171   ns.d5 << 1 + 2
 172   # does not define a subtree, but just one constant node. This is because
 173   # Python interprets and computes '1+2' directly. 
 174 
 175   # Subscopes
 176   # Subscopes are a useful way to modularize subtrees. Consider a function
 177   # that creates a subtree. 
 178   def subtree (ns):
 179     ns.d6 << ( (ns.d7 << Meq.Parm) + 5 );
 180   # of course we can call it only once, subsequent calls will redefine 
 181   # the same node names
 182   subtree(ns);
 183   # how do we create the same subtree multiple times with different names?
 184   # Using qualifiers is one idea:
 185   def subtree1 (ns,*q,**kq):
 186     ns.d6(*q,**kq) << ( (ns.d7(*q,**kq) << Meq.Parm) + 5 );
 187   subtree1(ns,'a');   # d6:a  d7:a  
 188   subtree1(ns,'b');   # d6:b  d7:b
 189   # this however is cumbersome, and may get in the way of using qualifiers
 190   # for something else (e.g. stations, etc.). Subscopes come to the rescue:
 191   ns1 = ns.Subscope('a');   # ns1 is also a node scope object
 192   subtree(ns1);   # creates 'a::d6', 'a::d7'
 193   ns2 = ns.Subscope('b',1,x=2);    # subscopes can have qualifiers
 194   subtree(ns2);   # creates 'b:1:x=2::d6', 'b:1:x=2::d7'
 195 
 196   # More fun with automatic node naming. First, note that we can assign
 197   # a node def directly to the 'ns' object to create an auto-named node
 198   # without specifying a stub.
 199   ns << Meq.Cos(ns.x(1)-ns.y(1));
 200   # now, note that node qualifiers are retained: the above creates
 201   # 'cos(sub(x,c1)):1', with a single '1' qualifier retained
 202   # Compare this with
 203   ns << Meq.Add(Meq.Sqr(ns.x(1)),Meq.Sqr(ns.y(1)),Meq.Sqr(ns.z(2)));
 204   # this creates 'add(sqr(x),sqr(y),sqr(z)):1:2', so the rule is:
 205   # identical qualifiers are merged, and differing qualifiers are accumulated
 206 
 207   # note the treatment of keyword qualifers
 208   ns.e(a=1) << 0;
 209   ns.f(a=1) << 1;
 210   ns.f(a=2) << 1;
 211   ns.f(b=1) << 2;
 212   ns << ns.e(a=1) + ns.f(a=1);   # creates 'add(e,f):a=1'
 213   ns << ns.e(a=1) + ns.f(a=2);   # creates 'add(e,f):a=1,2'
 214   ns << ns.e(a=1) + ns.f(b=1);   # creates 'add(e,f):a=1:b=1'
 215 
 216   # Finally, a word on orphans and node groups. Remember the orphans_are_roots 
 217   # setting above which we set to True. If we had set it to False, all 
 218   # the nodes defined above would have been deleted afterwards as 
 219   # "orphaned". A node (or a branch) is considered orphaned when the 
 220   # following two conditions are met:
 221   #   1. It has no parents (i.e. it is a root node)
 222   #   2. The node stub is not referred to from anywhere else in Python
 223   #     (i.e. it is not assigned to any variables or containers)
 224   # Automatic orphan cleaning is handy if you want to create a bunch of
 225   # subtrees in advance, and only leave behind the ones that are actually
 226   # used. This means that you must explicitly hold refs to the true "root"
 227   # nodes by, e.g., assigning them to a container that does not go out
 228   # of scope when our define_forest() returns.
 229 
 230   # A NodeGroup provides a convenient container for this. It is, in fact, 
 231   # nothing more than a dict that redefines operator << so that
 232   #     group << node
 233   # is equivalent to
 234   #     group[node.name] = node;
 235   # and also redefines the "in" operator so that saying 'node in group' is
 236   # equivalent to 'node.name in group'.
 237   # Every NodeScope object has a predefined "root" group available as
 238   # ns.ROOT. So, all we need to do to mark nodes as true roots and not orphans
 239   # is say:
 240   ns.ROOT << ns.e(a=1);
 241   # You can define a node and assigns it to roots in one statement, but
 242   # take care with the parentheses (<< binds left to right)
 243   ns.ROOT << ( ns << ns.e(a=1) + ns.f(b=1) );   # creates 'add(e,f):a=1:b=1'
 244 
 245 
 246 def test_forest (mqs,parent):
 247   """test_forest() is a standard TDL name. When a forest script is
 248   loaded by, e.g., the browser, and the "test" option is set to true,
 249   this method is automatically called after define_forest() to run a 
 250   test on the forest. The 'mqs' argument is a meqserver proxy object.
 251   """;
 252   from Timba.Meq import meq
 253   # run tests on the forest
 254   cells = meq.cells(meq.domain(0,1,0,1),num_freq=6,num_time=4);
 255   request = meq.request(cells,eval_mode=0);
 256   mqs.meq('Node.Execute',record(name='x',request=request));
 257   mqs.meq('Save.Forest',record(file_name='tile_test.forest.save'));
 258   # execute request on solver
 259   request = meq.request(cells,eval_mode=1);
 260 #  mqs.meq('Node.Set.Breakpoint',record(name='solver'));
 261 #  mqs.meq('Debug.Set.Level',record(debug_level=100));
 262   mqs.meq('Node.Execute',record(name='solver',request=request));
 263 
 264 def tdl_job_test1 (mqs,parent):
 265   """this is a test job. You can see it appear in the menu automatically."""
 266   pass;
 267 
 268 def tdl_job_test2 (mqs,parent):
 269   """this is another test job. You can see it appear in the menu automatically."""
 270   pass;
 271 
 272 
 273 # this is the testing branch, executed when the script is run directly
 274 # via 'python script.py'
 275 
 276 if __name__ == '__main__':
 277 #  from Timba.Meq import meqds 
 278 
 279   Timba.TDL._dbg.set_verbose(5);
 280 
 281   ns = NodeScope(test=True);
 282 
 283   define_forest(ns);
 284 
 285   # resolves nodes
 286   ns.Resolve();
 287 
 288 #  test_forest(None);
 289 
 290 #  # feeds repository to back-end to create the forest
 291 #  MqsBackend.create_forest(ns.repository(),mqs);
 292 #
 293 #  test_forest(meqds.mqs());

TDLTutorial (last edited 2005-07-29 16:29:53 by OlegSmirnov)