Symbols and expressions

Symbol substitution is one of the most important and useful tools found within FELIX. Symbols are used in much the same way as variables in algebra; you can define processing algorithms that incorporate a symbol, and later on, you can decide on a value for the symbol. Alternatively, you can store important numbers in symbols so that they may be quickly recalled later.

FELIX symbols are simply text strings that are evaluated at command execution time. Symbol names may be up to eight characters long and may contain any alphanumeric character. Any single symbol value can contain up to 128 characters. To display the current symbol definitions and their values, use the list command (lis). The lis command makes use of the wild card character (*), for example:

     lis h* 

shows values for only those symbols beginning with the letter h. The result is displayed in the FELIX parent window. The output from the above example might look like:

1 hafwid 2
2 hardx0 0
3 hardxs 0
4 hardy0 0
5 hardys 0
6 hardmo 21
7 hilim1 0
8 .... ....
9 harddv temp.plt

To delete or purge one or more of the user symbols, uses the pur command. This command also makes use of the wildcard character (*), for example:

     pur count

deletes the user symbol count, and:

     pur loop*

deletes all user symbols that begin with the letters "loop". Giving the pur command with no parameters does nothing; however, be aware that:

pur *

deletes all user symbols. This should only be done very carefully, if at all.

Reserved symbols

An important subset of the symbols used by FELIX is referred to as reserved symbols. FELIX uses several reserved symbols to hold information about the internal structure of the program and data. Some reserved symbols define the current graphical state of the program, and others remember the current datafile names and display features. By referring to the proper reserved symbols, your macros can find out such things as whether a matrix is open, how large it is, the last contour level drawn, and whether the last macro command executed successfully. Reserved symbols generally fall into one of two classes: those that affect a subsequent command, and those that are updated as a result of a command's execution.

An example of reserved symbols that define the graphical state of the program are those symbols that are related to the contour plot command (cp). For each contour plot, certain reserved symbols define how the contour plot appears. These reserved symbols include those that define the contour level (level), the number of levels (nlevel), the contour level mode (clmode). Whenever the contour plot command is executed, FELIX looks at the current values of specific reserved symbols to determine what should be plotted. Similarly, when you try to read a 1D datafile with the re or rn command, FELIX looks at the values of the symbols datfil and datpfx to determine the current datafile's name and the current data directory. The values of the reserved symbols described above are usually set within control panels, but they may also be defined with the def command (see below).

The second class of reserved symbol is exemplified by the datsiz symbol. This symbol contains the current data size as a number of points. Although you can change the value of datsiz using control panels or the def command (see below), the symbol's value is automatically updated to the current data size after reading a datafile or loading a 1D vector from a matrix. Likewise, when FELIX reads a 1D datafile, the reserved symbol datype is changed automatically to reflect the appropriate datatype, whether it is real or complex. Other reserved symbols of this class include d1size (the size of the current matrix in the D1 dimension) and matfil (the name of the current matrix). These are changed when you open a matrix.

A complete list and definitions of the reserved symbols are given in Appendix B, Symbol Reference. In addition, each command in Appendix A, Command Reference, describes the reserved symbols that affect that command and those symbols that are updated by execution of the command.

User-defined symbols

You may also define user symbols. In general, user symbols are used to save specific values so that they may be recalled later. In addition, user symbols are used to remember the current state of the menu interface. For example, when a toggle or switch value is set, user symbols are used to remember that state or value.

FELIX includes several distinct ways to explicitly define symbols and their values. One is to use the define symbol command (def). For example, to set the symbol filnam to the string "test", you would simply enter:

 > def filnam test

This mechanism is used throughout the macros, especially in the macro routines used for data processing. The def command may also be used to define reserved symbols such as level, which defines the lowest plot level for subsequent 2D plots.

The cdf command (conditional define) lets a macro define a symbol only when that symbol does not yet exist. This gives greater flexibility when writing macros, because you can make sure a symbol exists and has a valid value without changing its previously defined value. Thus, you can make a macro that is executed many times, yet only the needed symbols initializes the first time through, without any special effort.

Another way to define symbols is the eva command. It evaluates arithmatic expressions, mathematical functions, and database functions and stores the resulting value in a symbol. More information on expressions and functions appears later in this chapter.

Many FELIX commands accept a symbol name as the last parameter on their command line and define that symbol's value to reflect the answer or status of that command's result. For example, the command:

 > xpk who &pksent -1 whoitm

returns the item number of the cross peak that the user selected with the cross-hair cursor. The symbol whoitm gets defined to the value of a cross-peak item number. If no cross peak was selected, whoitm gets the value zero.

Another example shows how to convert a data position in points into ppm:

 > ppm 0 0 512.0 ppmval

This command calculates the ppm position for datapoint 512 in the current 1D workspace and defines the symbol ppmval as that value.

A third example shows how to extract the reference information for one dimension of a matrix using the rmx command:

rmx -2 rfreq rwid raxis rpnt rval rtext

This command statement extracts the reference information for D2 from the current matrix. It defines six symbols with values corresponding to spectrometer frequency, spectrum width, etc.

The most common mechanism for defining symbol values is from within control panels. These graphical interface components prompt you for symbol values by using text explanations, switches, toggles, buttons, and scrolling list boxes. The intent is to show you all the available choices in a format that simplifies making a selection, while minimizing the possibility of allowing invalid input. Almost every element in a control panel defines a symbol to reflect its state or contents. A more detailed description of defining symbols using control panels is found in Chapter 5. Menus and control panels.

Symbol substitution

Symbol substitution is one of the most important and useful tools within FELIX. To access and use the value of a symbol, you must precede the symbol name with an ampersand (&). Whenever FELIX encounters an ampersand in a command line, it attempts to perform symbol substitution on the text string that follows the ampersand. For example, if the symbol filnam currently has the value "test", the command line:

     rn &filnam

is interpreted as:

     rn test

before it is executed. In this example, FELIX attempts to read a datafile named test.dat. The symbol filnam has been replaced by the text string "test" as far as the macro command is concerned.

You can use the value of the reserved symbol datsiz in a command, as follows:

     sb &datsiz 0

which applies a zero-degree shifted sinebell function over the number of points specified by the symbol datsiz.

Some applications require more complex symbol substitutions. The following examples illustrate a few of the many possibilities.

First, a symbol can be concatenated with normal text or numbers. For example:

     rn test&number

reads a file named test1.dat if the value for the symbol number is equal to the number one. In a macro, the symbol number could be changed at strategic points, allowing you to read a series of datafiles with a single read statement containing a symbol variable. Such use of symbols may be necessary when transforming 3D data, because the initial data in these large matrices may be contained within more than one serial file.

Second, two symbols can be concatenated simply by placing the symbols adjacent to each other. If the symbol filnam had a value "abc" and the symbol number had a value "001", the command statement:

ty &filnam&number

would be equivalent to ty abc001. In this manner, several symbolic components can be combined into a single string.

Finally, symbol substitution is recursive, as illustrated by the following example. If the symbol filnam had a value of "abc" and the symbol abc had a value of "hello", the following command statement:

ty &&filnam

would produce "hello". In this case, the sequence of substitution is: first find the string for the symbol &filnam and replace it with its value "abc", thereby leaving "&abc" on the command line; then, since an ampersand remains on the command line, do another symbol substitution. In the example shown, the "&abc" string is replaced by the value of the symbol abc, namely "hello".

It is also possible to delay symbol substitution until later, by adding an exclamation point (!) to the beginning of a symbol name. For example, the command statement:

def test !&filnam

defines the symbol test to literally be &filnam. The most common use of this symbol-definition method is in combination with the macro put command for building macros from the FELIX command language.

Local symbols

In previous versions of FELIX, all symbols were global. One had to be careful to not use the same symbol name in two different macros if one macro called the second, for fear of unintentionally having the second macro change the value of a symbol that the first macro was still using. Furthermore, any symbols that were defined stayed defined until the program was terminated.

Since version 2.3 of FELIX, local symbols can be defined. Local symbols are used only in the macro in which they are defined and are automatically purged when that macro is finished. In addition, multiple macros can use the same (local) symbol names, without any risk of changing another macro's local symbols.

Distinguishing local from global symbols is simple, since all local symbols begin with an underscore, "_". This is the only difference. Defining and using local symbols is the same as for global symbols. The following example revises the simple macro test1.mac to use a local symbol instead:


1 c**test1.mac
2 for _count 1 5
3 ty Count = &_count
4 next
5 ex &return
6 end

As soon as this macro is finished, the symbol _count becomes undefined. In writing macros, all loop counters and other symbols that are used locally in the macro should be defined as local symbols. This keeps the size of the symbol table from getting too large and minimizes the chance of macros causing unintended side effects on other macros.

Global symbols

FELIX now supports two classes of user symbols. By default, the value of a symbol is local to the graphics frame where it is defined. This feature allows a frame to act independently, without affecting the content or behavior of other frames. To share symbols between frames, it is necessary to explicitly export or import the symbol values to or from another frame. While this notion of local symbols is good for working with multiple independent frames, it can cause significant problems when working with multiple connected frames all operating on a common dataset.

To address this problem, FELIX now supports global user symbols in addition to local reserved symbols and local user symbols. All global user symbols are always known to all frames, and defining the value in any one frame defines that symbol globally in all frames. This feature greatly enhances the flexibility of working with multiple connected frame layouts.

To identify a symbol as global rather than local, begin the symbol name with the character string "g_". All symbol names beginning with "g_" are global, and all other symbols are local. There are no global reserved symbols, only global user symbols. All the rules for symbol substitution are exactly the same for global symbols, and a global symbol may be used anywhere a local symbol may be used.

For optimal speed when using FELIX, do not use more global symbols than needed. Local user symbols have somewhat faster lookup access, and too many global user symbols can slow down all symbol lookup access. A good rule of thumb is to limit the number of global symbols to no more than ten percent of all user symbols.

Arithmetic expressions

FELIX allows arithmetic expressions to replace any numeric parameter. An arithmetic expression may contain the arithmetic operators plus (+), minus (-), multiply (*), divide (/), and power (^), with left and right parentheses being used to define the order of operations. The syntax for expressions within FELIX is exactly the same as in FORTRAN or BASIC. FELIX also supports the unary (-) operator, which negates the variable, symbol, or expression immediately to its right.

When used for numeric parameters, arithmetic expressions must be enclosed in parentheses. This allows FELIX to evaluate the expression before passing the parameter value on; otherwise, the parameter would contain non-numeric characters and be uninterpretable. Sometimes you may want to force evaluation of an arithmetic expression: the eva (evaluate) command is useful for doing this. For example:

     eva temp (4*3+1)

forces the value of the symbol temp to be 13. Remember, symbol values are just character strings and do not get interpreted as numbers until they are used as parameters by a command.

Expressions are even more powerful when they are combined with user-defined symbols. For example:

     eva temp (4*3+&shift)

determines a number for the symbol temp, whose value depends on the current value of the symbol shift. The ability to evaluate arithmetic expressions in combination with symbols provides a very powerful method of data manipulation.

Expressions are most often used within FELIX for determining data characteristics. For example, to determine the number of points currently displayed on the screen, the following expression can be used:

     eva scnsiz (&last-&first+1)

The value for the symbol scnsiz is the difference between the symbols last (the last-displayed datapoint) and first (the first-displayed datapoint). After the above expression, your macro would usually contain a sequence of commands using the value of scnsiz.

A slightly more complex expression finds the value of the midpoint displayed on the screen:

     eva midpt ((&first+&last)/2)

Another use for expression evaluation is to manipulate values found in the database. For instance, specific elements can be loaded into symbols, with the resulting symbols being evaluated via expressions. A simple example is to find the separation between two resonances found in a 1D spectrum. First, load the first two resonance positions into the symbols cen1 and cen2 using the standard database-access methodology:

     dba element load pic:1d_picks.1.cenpnt cen1

dba element load pic:1d_picks.2.cenpnt cen2

Next, evaluate their separation, using symbol evaluation:

     eva separ (&cen2-&cen1)

To view the resulting symbol for the separation, use the lis command:

     lis separ

Or within a macro, use the ty command to print comments along with symbol values to the parent window:

     ty Separation between item 1 and 2 is: &separ

In a macro, you may also check to ensure that the resultant value is a positive number, and if it's not, take its absolute magnitude:

     if &separ lt 0 then

eva separ (abs(&separ))

A more complicated example of expression evaluation makes use of an exponential or power evaluation. The example shown below uses NOESY cross-peak volumes to measure distance constraints. This example is used for measuring proton-proton distances from an NOE volume at a single mixing time by using the sixth-power dependence ratio as it relates to a fixed distance (dissum) and volume (volsum):

     dba element load &volent.10.vol1 volum1

eva dist ((((&dissum)^6.0)*&volsum/&voluml)^(1.0/6.0))

The distance may be further modified to reflect upper and lower bounds:

     eva lower (&dist-(&dist*0.1))

eva upper (&dist+(&dist*0.1))
ty Lower bound is: &lower
ty Upper bound is: &upper

Integer vs. real expressions

FELIX supports both integer and floating-point arithematic expressions. When any of the operands in the expression has a decimal point, then the expression is evaluated as real and the result is real. If no decimal points are present in the operands, then the expression is evaluated as integer and the result is integer. Thus there is a difference between the following two values:

     eva int (17/2)

eva real (17.0/2.0)

The value of int is 8, and the value of real is 8.5. The benefit of supporting both integer and real expression evaluation may not be obvious, but several algorithms require integer division.

Complex mathematical functions

FELIX also allows arithmetic functions to be evaluated and saved as a symbol. These mathematical functions can appear anywhere that simple expressions may be used. The arithmetic functions currently supported are:

abs - absolute value

acos - arccosine

asin - arcsine

atan - arctangent

cos - cosine

exp - exponential

float - turn integer to real

int - truncate real to integer

log - natural logarithm

log10 - logarithm base 10

nint - round real to nearest integer

ran - random number generator

sqrt - square root

sin - sine

tan - tangent

The trigonometric functions require all angles to be in degrees. These functions are combined using the standard evaluation nomenclature to calculate resultant values.

The ran function returns a random value between zero and the value of its argument. For example, ran(1.0) returns a value between zero and one, and ran(500.0) returns a value between zero and five hundred. It may be used in any arithmetic expressions and in the eva command:

     eva value (ran(1.0)

Working with the example begun above for finding the absolute value of a negative peak separation, we can use the following sequence of commands in a macro:

     1	...

2 if &separ lt 0 then
3 eva separ (abs(&separ))
4 eif
5 ...

Or more simply, the above "if/then/else" sequence could be replaced by the absolute value calculation, since only positive values are acceptable in this instance.

Another example is used to ensure that each dimension of a new matrix has size of root 2:

     1	...

2 eva n2size (log(&d2size) / log (2.0))
3 eva n2int (nint(&n2size))
4 if &n2size eq &n2int onward
5 ...

Again, examples such as these may be added to your own macros.

Database functions

FELIX supports one other class of functions that evaluate the state of database components. These functions are only valid in the eva command and cannot be combined on the same command line with other functions or expressions. The form of all database functions is db$nnnn(arg), where nnnn is the function name and arg is the argument passed to the function. More information can be found in Chapter 7, The Database.

The function:

eva open db$open()

determines whether a database is open. The symbol open is set to zero (not open) or one (open).

The function:

eva exist db$entity(xpk:peaks)

determines if an entity with the name xpk:peaks exists in the current database. The symbol exist is set to zero if no entity with that name exists and to non-zero if the entity does exist.

The complete list of database functions is: