TDI is the expression language for MDSplus. It is used to manipulate data within the Traverser, the Scope and anywhere data is accessed. It is used to access, process and store the data. It is sometimes referred to as an expression evaluator.
This tutorial is intended to provide familiarity with the syntax and some of the functions of TDI, and point to references for further information. The more advanced topics include various ways of building signals and calling routines developed in other languages from TDI.
$ tdic TDI> treeopen("pspec_pc",126725) 265388041 TDI> _inputsig = \Xeus_image ; TDI> _r = make_xeus_spectra( _inputsig);
IDL is a popular environment for working with MDSplus data, and it has built-in support for executing TDI expressions through the MDSVALUE function. An easier path is to use the TDIC program, distributed with MDSplus, which provides an interactive TDI console.
MDSVALUE
First, as with any MDSplus access from IDL, you must be connected to an MDSplus server, and have a tree open. Note, that you must be authorized to connect to the MDSplus server.
$ idl IDL> MDSCONNECT, 'skylark.pppl.gov:8501' ; unnecessary if on the server IDL> MDSOPEN, 'nstx', 104500 ; tree and shot (or ID) number IDL> tm1 = MDSVALUE( "\TMINUS1" ) ; \TMINUS1 is a tag in this tree IDL> help, tm1 TM1 FLOAT = -1.00000
In the following examples, the TDI expression is the quoted argument to MDSVALUE.
TDI supports numerical expressions:
IDL> print, MDSVALUE( "\TMINUS1 * 10 - 2" ) -12.0000 IDL> print, MDSVALUE( "\TMINUS1 - 2 * 10" ) -21.0000
The rules for precedence in TDI are well defined).
TDI supports a variety of functions (like many of those in FORTRAN-90 and C):
IDL> print, MDSVALUE( "(ABS(\TMINUS1)+1)^2" ) 4.00000
You may perform these operations on tags that return arrays, as well (\ip is an MDSplus tag in the WF tree for NSTX):
\ip
IDL> plot,MDSVALUE('\wf::ip/1000')
For efficiency, or code clarity, you may wish to store results from TDI expressions into TDI variables (which must begin with an underscore), e.g.,
IDL> dummy = MDSVALUE('_ip=\wf::ip/1000') IDL> print,MDSVALUE('maxval(_ip)') 1.16095 IDL> print,MDSVALUE('maxval(cos(_ip))') 1.00000
IDL> print,MDSVALUE("_val=7/4") 1 IDL> print,MDSVALUE("_val=7/float(4)") 1.75000
IDL> print,MDSVALUE("$PI") 3.14159 IDL> print,MDSVALUE("$SHOT-1") 104499
Find the full path from the top of the tree for nodes containing IP:
IDL> t=MDSVALUE('_t=getnci("...*IP*","fullpath","ANY")')
IDL> nodesFound = MDSVALUE( '_n=findtags($)','*IP*' )
IDL> print,MDSVALUE("_a=[6,5,4,3,2,1]") 6 5 4 3 2 1 IDL> print,MDSVALUE("_a=(6:1:-1)") ; alternate method 6 5 4 3 2 1
IDL> print,MDSVALUE("minloc(_a)") ; index of minimum value in _a 5 IDL> print,MDSVALUE("_a[ minloc(_a) ]") 1 IDL> print,MDSVALUE("ubound(_a)") ; highest index in _a 5 IDL> print,MDSVALUE("mean(_a*10)") 35 IDL> print,MDSVALUE("_a[5]") 1 IDL> print,MDSVALUE("mean(_a[0:2])") 5
IDL> print,MDSVALUE("_a[5]=3") % MDSVALUE: %TDI$-E-INV_OPC, Invalid operator code in a function %TDI Error in EQUALS(_a[5], 3) %TDI Error in EVALUATE(_a[5] = 3) %TDI Error in EXECUTE("_a[5]=3")
IDL> print,MDSVALUE("BUILD_RANGE(1,17,4)") 1 5 9 13 17 IDL> print,MDSVALUE("(1:17:4)") ; short-hand method 1 5 9 13 17
IDL> MDSOPEN, 'wf', 109070 IDL> sparseData = MDSVALUE( "DATA(\ip)[0:*:10]" )
IDL> MDSOPEN, 'particles', 113458 IDL> frame = MDSVALUE( "DATA(\sflip_3d)[*,*,10]" )
For non-signals, such as a dimension, DATA( ) is not necessary:
IDL> sparseTime = MDSVALUE("DIM_OF(\ip)[0:*:10]")
For example, the TDI for a signal composed of a few digitizer counts (made artificially small here for simplicity) on a 1 KHz clock might look like:
_sig = BUILD_SIGNAL( 5./4095 * $VALUE, /* expression using RAW */ [5,2,1,4,2], /* Raw data */ [0,1,2,3,4]/1000.); /* Dimension */
_sig = BUILD_SIGNAL( 5./4095 * $VALUE, /* expression using RAW */ [5,2,1,4,2], /* Raw data */ BUILD_RANGE(0.0, 0.004, 0.001)); /* Dimension */
$VALUE
If you don't wish to bother with the Raw field, you can build this signal as follows:
_sig = BUILD_SIGNAL( 5./4095*[5,2,1,4,2], *, /* No Raw data */ BUILD_RANGE(0.0, 0.004, 0.001)); /* Dimension */
IDL> s = MDSVALUE("_s=BUILD_SIGNAL( [5,2,1,4,2],"+ $ "*, BUILD_RANGE(0.0, 0.004, 0.001))" ) IDL> print,s 5 2 1 4 2 IDL> print, MDSVALUE("DIM_OF(_s)") 0.00000 0.00100000 0.00200000 0.00300000 0.00400000
IDL> str='BUILD_SIGNAL( BUILD_WITH_UNITS([5,2,1,4,2],"Volts"),' + $ ;DATA ',' + $ ;RAW 'BUILD_DIM( BUILD_WINDOW(0,4,0.0),' + $ ;DIMENSION 'BUILD_RANGE(*, *, BUILD_WITH_UNITS(0.001,"Sec") )' + $ ')' + $ ')' IDL> s = MDSVALUE("_s="+str) IDL> print,s 5 2 1 4 2 IDL> print, MDSVALUE("DIM_OF(_s)") 0.00000 0.00100000 0.00200000 0.00300000 0.00400000 IDL> print, MDSVALUE("UNITS_OF(_s)") Volts IDL> print, MDSVALUE("UNITS_OF(DIM_OF(_s))") Sec
IDL> str = "BUILD_SIGNAL( BUILD_WITH_UNITS(_image,'Photons'),," + $ "BUILD_WITH_UNITS(_x,'Sec'), BUILD_WITH_UNITS(_y,'cm') )" IDL> s = MDSVALUE(str)
IDL> MDSPUT, sigName, $ "BUILD_SIGNAL( BUILD_WITH_UNITS($,$),," + $ "BUILD_WITH_UNITS($,$), BUILD_WITH_UNITS($,$) )", $ data, sigUnits, xAxis, xUnits, yAxis, yUnits
When data types are mixed there are rules for the type of the result. E.g.,
IDL> dum = MDSVALUE('_f=[.1,.2,.3]') IDL> dum = MDSVALUE('_c=CMPLX(2,3)') IDL> prod = MDSVALUE('_f*_c') IDL> help,prod PROD COMPLEX = Array[3] IDL> print,prod ( 0.200000, 0.300000)( 0.400000, 0.600000) ( 0.600000, 0.900000)
The figure above is the setup screen for a scope signal (brought up by holding down the right mouse button over a scope plot, and selecting "Setup Data Source...").
TDI is used in the fields labeled "Y Axis:", "X Axis:", and "Title:". The Y-axis is the average of two signals, represented by tags \ip1 & \ip2. The X-axis just uses the dimensions of one of the signals. The Title includes the units (of one of the signals) and appends the shot number by using the constant intrinsic for the shot number, $SHOT. Note that double slashes (//) are used for concatenating strings in TDI.
testtag.fun
FUN PUBLIC testtag( IN _c ) { _tm1 = \TMINUS1; _result = _tm1 * _c; write(*,'_c=',_c); write(*,'_tm1=',_tm1); write(*,'_result=',_result); RETURN (_result); }
To use a new locally-written TDI function (.FUN file) in an MDSplus node or expression, it is necessary to put the file into the search-list. On VMS, this is defined by the logical name MDS$PATH; on Unix it is the environmental variable MDS_PATH. On VMS at PPPL, for example, the directory MDSplus$:[PPPL] is for user-written TDI of general interest. On VMS, the logical name MDS$USER_PATH is searched before MDS$PATH, so directories pointed to by it should be used for testing, or for routines that will only be used by the author.
Execute your TDI from IDL:
$ idl IDL> MDSCONNECT, 'europa.pppl.gov:8501' ; unnecessary if on the server IDL> MDSOPEN, 'nstx', 104500 ; tree and shot (or ID) number IDL> a=MDSVALUE("testtag(5)") _c= 5 _tm1= -1.0000E0 _result= -5.0000E0
You may wish to add routines written in other languages, such as FORTRAN or C, to a shared library so they can be called from TDI (or IDL). Sometimes this is done for speed, or so Scope can perform some specialized function. This section shows how to link in a routine written in FORTRAN.
You may wish to Test your Fortran first.
Before you can call such a routine from TDI or IDL, you must put it in a shared object module on Unix or a DLL on Windows.
On Unix, create a logical name or environmental variable to point to the shared library:
% setenv myshrdlib mydisk/mydir/myshrdlib.so
_sts = myshrdlib->integ( _nels, REF(_x), REF(_y), REF(_rInteg) );
rInteg = FLTARR( n_elements(x) ) result = CALL_EXTERNAL( 'myshrdlib', 'integ', LONG(n_elements(x)), x, y, rInteg)
If conventions are followed for a documentation header in a TDI routine, as well as being easier for users to find information, web pages can be automatically generated to document systems, just as with IDL routines.
For example:
/* SECONDS_SINCE.FUN ; PURPOSE: Return a floating point number of seconds ; given an input quadword time. ; CATEGORY: Data Conversion ; CALLING SEQUENCE: seconds = SECONDS_SINCE(quadword-time [,_since-time-string]) ; INPUTS: ; quadword-time - a VMS time or array of times. ; ; OPTIONAL INPUT PARAMETERS: -- ; _since-time-string - date to start from ; default '1-JAN-1992' ; OUTPUTS: -- ; OPTIONAL OUTPUT PARAMETERS: -- ; COMMON BLOCKS: -- ; SIDE EFFECTS: -- ; RESTRICTIONS: -- ; PROCEDURE: -- ; MODIFICATION HISTORY: ; JAS 14-OCT-1992 Initial coding. */ FUN PUBLIC SECONDS_SINCE(IN _time, OPTIONAL IN _since) { _DELTA = 0Q; _ANS = 0.0; if (not PRESENT(_since)) _since = '1-JAN-1992'; _stat = LIBRTL->LIB$CONVERT_DATE_STRING( DESCR(_since),REF(_DELTA)); _q_times = _time - _delta; _q_times = _q_times / 100000Q; /* hundredths of a second */ _ans = FLOAT(_q_times)/(100.); return(_ans); }
Where your TDI is executed can affect the speed of your call.
If, for example, you are getting a digitizer signal, and the TDI is not available on your remote host, the 16-bit values will be converted to floating point and sent as 32-bit numbers (or 64-bit for double precision), as will the time signal (DIM_OF). If the TDI is executed on your remote computer, rather than on the MDSplus host, just 2 bytes per data point will be transfered (rather than 4, or 8 if double-precision), and t0, delta t and number of points will be transfered, rather than the entire time array. So, approximately 1/4 the amount of data is transfered in this example if the conversion to floating point is done on your remote computer.
On the other hand, if the TDI expression includes a dozen different signals, executing the TDI code on the MDSplus server will result in substantially less data being transfered.
Network transfer speeds are typically the slowest part of data access, but your conditions may be different. The relative speed and load of the client and server computers may also be a consideration in deciding where to have your TDI executed.
To have TDI executed locally, define the enviromental variable _____ on your MDSplus client machine.
_c0 = map( \DIVBOLOM_IRRAD, make_range(0,*,4) ); _c1 = map( \DIVBOLOM_IRRAD, make_range(1,*,4) ); _c2 = map( \DIVBOLOM_IRRAD, make_range(2,*,4) ); _c3 = map( \DIVBOLOM_IRRAD, make_range(3,*,4) );
mdsopen,'efit',109070 d3 = mdsvalue('_d3=\PSIRZ') cols = mdsvalue('_COLS=SIZE(_d3,0)') rows = mdsvalue('_ROWS=SIZE(_d3,1)') d2 = mdsvalue('_d2=set_range( _ROWS, _COLS, map(_d3,make_range(5,*,*)) )')
_sum = SUM(_data[0:_nm1]); %TREE$-W-NOT_OPEN, tree not currently open %TDI Error in COMPILE("\t_sum = SUM(_data[0:_nm1]);") %TDI Syntax error near # marked region _sum = SUM(_data[0:_n##m1]##); %TDI Error in EXECUTE("\t_sum = SUM(_data[0:_nm1]);")
_sum = SUM(_data[0 : _nm1]);
_inX = D_FLOAT( DATA( DIM_OF( _Sig ) ) ); /* to make double precision */ _inY = F_FLOAT( DATA( _Sig ) ); /* to make single precision */
_i = 7/4; /* will result in _i=1 */
WRITE(*,'_data=',_data[0 : 10]);
_sum = _a + _b + _c + _d;