| While the details of recording and playing back macros are
described elsewhere, macros can also be edited or written manually with any text editor. This allows to add instructions that control the execution flow of the macro using variables,
for/while loops, if/else statements etc. The syntax of these control operations
(the Yanaconda macro language) is described in this section. With Yanaconda, you can program anything from basic data analysis to multimedia presentations.
Why yet another macro language? Why not use Python directly,
as it is possible in YASARA plugins? There are two simple reasons:
- Many users prefer not to use the graphical interface, but type the commands
directly in the console to save time. In this case, every keystroke is a precious
resource, and Yanaconda has been designed to minimize the number of keystrokes
while maximizing the readability of the code.
So if you quickly want to color all CA and CB atoms blue,
you type: Because the command above is not a valid Python syntax,
you would have to type:
ColorAtom("ca cb","blue")
In the first case, it takes 20 keystrokes, in the second case
33 (counting SHIFTs), that's 65% more. The quick way is possible because Yanaconda is not case sensitive and the command
'ColorAtom' knows its argument types and does not expect quotes around strings.
When writing a macro that will be used again, you would invest more time and choose a more readable form of the command,
with the same result:
- Not everyone knows how to (or likes to) program in Python, and Yanaconda
can be seen as a minimal common set of instructions that everyone knowing at least
one other programming language can memorize in ten minutes. (If Yanaconda is
your first 'programming language' it may take 30 minutes).
The only language the processor can understand is machine code. The low-level Assembly language is just a way to make machine code readable for humans. High-level languages like C/C++ must be translated to machine code by a compiler,
or to an intermediate 'byte-code' that can be executed by an interpreter (Python,Perl).
Yanaconda is one additional level 'higher' (and slower, which is not an issue because virtually all of the time is spent in YASARA commands anyway). The source code is reinterpreted continuously and can modify itself using
'explicit evaluators' described below. This revival of 'self-modifying code' allows for surprising shortcuts that can increase readability and efficiency at the same time.
When reading Yanaconda source code, there is one single golden rule:
EVERYTHING ENCLOSED IN PARENTHESES ( ) IS EVALUATED AND REPLACED BY THE RESULT
Example:
Name = 'Yami'
Print 'Hi I am (Name)!'
Hi I am Yami!
And a more extreme example:
Name = 'Yami'
Function = 'Print'
Argument = 'Hi I am (Name)!'
(Function) (Argument)
Hi I am Yami!
In short, the pair of opening and closing parentheses is called an
'explicit evaluator', because it explicitly forces the evaluation of an expression at any location in the source code. The entire expression including the parentheses is replaced by the value of the expression.
Much of Yanaconda's syntax has been kept compatible with Python,
so that you can immediately use syntax highlighting in your favorite editor. It helps to add the file extension for Yanaconda macros
'.mcr' to the editor settings. You can also use Python style comments with a double cross
'#' for one line comments and triple quotes """ for multi-line comments.
Yanaconda knows four different datatypes,
which are described below.  | Integers are numbers without a decimal point. Contrary to other programming languages,
they can be defined with leading zeroes and remember them. Examples:
a = 5
b = -4003
c = 000
d = 000005
To wheat your appetite, here is an example which takes away some things explained later,
but illustrates the point. Imagine you want to load 10 PDB files, named Model001 to Model010.
As a C programmer, you would type:
char filename[9];
int i;
for (i=1;i<=10;i++)
{ sprintf(filename,"Model%03d",i);
LoadPDB(filename); }
As a Python programmer, you save a lot and earn percents and a mysterious
'11':
for i in range(1,11):
LoadPDB("Model%03d"%i)
As a Yanaconda programmer, your life is easy:
for i=001 to 010
LoadPDB Model(i)
Floats are numbers with a decimal point and an optional exponent. If you specify an exponent or just a dot without decimal digits,
calculations will be done at maximum precision. Otherwise the result will be rounded to the number of digits after the decimal point.
Examples: |
a = 5.000
| A float with a precision of three decimal digits |
b = 4e0
| A float with maximum precision (4*10^0 = 4) |
c = 3.
| Just a dot without decimal digits also gives maximum precision |
d = -2.5e3
| A float with maximum precision (-2.5*10^3 = -2500) |
e = 7.345e-4
| Another float with maximum precision (7.345*10^-4 = 0.0007345) |
f = 0.0034
| A float with a precision of four decimal digits |
|
You already met some weak strings in the section about
explicit evaluators. They are called weak because of two reasons:
- Explicit evaluators inside the string are indeed evaluated.
- When a weak string is explicitly evaluated, it loses the single quotes.
Example:
a = 2
b = 3
# Assign '5' to MyWeakString
MyWeakString = '(a+b)'
Print 'Result is (MyWeakString)'
Result is 5
Note that the result is 5, not '5', not (a+b) and not
'(a+b)'. Strong strings are somehow complementary to weak strings:
- Explicit evaluators inside the string are ignored.
- When a strong string is explicitly evaluated, it keeps the double quotes.
Example:
a = 2
b = 3
MyStrongString = "(a+b)"
Print 'Result is (MyStrongString)'
Result is "(a+b)"
| It is easily possible to perform an operation with two operands of different datatypes. In this case,
the result gets the datatype of the left operand. Example:
a = 2
b = 1.7
# Result is a float
Print (b+a)
3.7
# Result is an integer, 3.7 rounded to 4
Print (a+b)
4
This simple rule permits a large number of useful applications:
pi = 3.14159265359
Print (0.00+pi)
3.14
pi = 3.14159265359
a = 0+pi
Print (a)
3
And another example to check if a rounded number is odd or even,
which considers the fact that the modulo operator '%' can only be applied to integers:
a = 7.6
if (0+a)%2
print 'a is odd'
else
print 'a is even'
a is even
- Convert to and from string
a = "2"
b = 5+a
Print (b)
7
a = 2
b = "5"+a
Print (b)
"52"
a = 5
b = "2"*a
Print (b)
"22222"
Remember to be careful when writing more complicated mathematical expressions:
- DEFINE FLOATS WITH AN EXPONENT TO GAIN MAXIMUM PRECISION
- PUT INTEGERS AND LOW PRECISION FLOATS ON THE RIGHT SIDE TO PREVENT THEM FROM CONVERTING OTHER FLOATS
Examples for calculating the average of two numbers:
If b and c are integers and you want to obtain a float result,
put the 0.5 on the left side:
a=0.5*(b+c)
If b and c are high precision floats, put the
0.5 on the right side (or use 5e-1) to prevent it from reducing the precision of the result to one decimal:
Most programming languages share a common set of operators. Some differences become apparent if you use multiple operators in one expression without explicitly ordering them using parentheses. Then evaluation order depends on the predefined operator priority. The operators you can use in Yanaconda are listed in the following sections. Operators of the same priority are evaluated from left to right.
 | This operator switches true
(anything except 0) to false (0) and vice versa. You can use '!' or 'not', whichever you prefer.
a = 3
b = 0
Print (!a)
0
Print (not b)
1
Here are some examples for the six operators with the second highest priority. Note that the modulo operator
'%' requires an integer on the left side. The '//' operator is mainly known to Python programmers,
it is a division with truncation of the result's fractional part. The normal division operator
'/' rounds the result to the closest integer if the requested result data type is an integer.
a = 2
b = 3.
Print (a*b)
6
Print (b*a)
6.0000000000e0
Print (a/b)
1
Print (a//b)
0
Print (b/a)
1.5000000000e0
Print (a%b)
2
Print (b%a)
Error - unsupported operator
The '//' operator is additionally helpful to truncate floating point numbers without rounding:
The shift operators '<<' and '>>' do bitwise binary shifts to the left or right,
they require an integer on the left side. A left shift corresponds to a multiplication with
2, a right shift to a division by 2. That's why they have the same priority as '*' and
'/'.
a = 2
b = 3.000
Print (a<<1)
4
Print (a<<b)
16
Print (a>>1)
1
Print (b>>a)
Error - unsupported operator
a = 2
b = 3.000
Print (a+b)
5
Print (b+a)
5.000
These operators do a bitwise 'and', 'or' and 'xor', there must be an integer on the left side. They are rarely if ever needed in Yanaconda. Unlike in the C programming language,
their priority is higher than the comparisons, just like in Python. All comparison operators have the third lowest priority. They return
1 if the comparison is true, 0 otherwise. Note that '!=' stands for 'is not equal to'.
a = 2
b = 3.000
Print (a>b)
0
Print (a<b)
1
Print (a==b)
0
Print (a!=b)
1
The 'in' operator determines if a variable is in a list:
anglelist='Alpha','Beta','Gamma'
angle='Beta'
if angle in anglelist
Print 'Yes'
Yes
Logical operators are used most of the time in if/else statements to combine the results of comparisons.
a = 2
b = 3
if a==1 or b==3
Print "Hello World!"
Hello World!
| Python introduced a very smart concept which is taken over by Yanaconda: instead of using explicit code block markers like braces
'{' and '}' in C/C++ or 'begin' and 'end' in Pascal, blocks of code that belong together are identified by their indentation level. Taking away the section about conditional execution below,
here is an example:
a = 2
b = 3
if a==3
Print "A equals 3!"
if b==3
Print "and B too!"
else
Print "but B does not"
else
Print "sorry, A does not equal 3"
if b==3
Print "but therefore B does!"
else
Print "and neither does B"
sorry, A does not equal 3
but therefore B does!
When compared to the language C, the benefits are enormous: you have to type less,
do not waste space with braces, and do not spend your time searching where you forgot a brace.
However, as soon as you want to use code written by someone else,
it is payback time. Python programmers know the nightmare: You receive a code-snippet by e-mail,
paste it into your editor, save, run and crash. Then you spend your time hunting down little indentation problems,
because the original code was written with a mixture of tabs and spaces, got additionally modified on its way through the various e-mail clients and is dead on arrival.
To keep these troubles away from Yanaconda users and promote the quick and easy exchange of macros,
Yanaconda insists that indentation is done with multiples of two spaces and no tabs are used
(which is the standard Python convention anyway). If you like to use the 'Tab' key,
just configure your editor to replace each tab with two spaces while typing. (Virtually any source code editor with syntax highlighting can do that.) If you are very unhappy about that and would prefer a different indentation style,
be assured that most of the time, you will not need any indentation at all, due to the simple nature of Yanaconda macros.
An example for conditional execution
has been given above, the syntax is the same as in Python, 'elif' is used instead of
'else if'. Note that you cannot continue on the same line after 'if', 'elif' or
'else', you always have to start a new (indented) line.
a = 2
if a==1
Print 'A equals 1!'
elif a==2
Print 'A equals 2!'
else
Print 'A equals neither 1 nor 2!'
A equals 2!
Equally important as conditional execution is the ability to repeat a set of instructions for a given number of cycles or while a condition is true.
 | Use a for loop to cycle over a fixed range of numbers.
for i = 1 to 3
Print (i)
1
2
3
for i = 5 to 12 step 2
Print (i)
5
7
9
11
for i = 20.0 to 15.0 step -1.5
Print (i)
20.0
18.5
17.0
15.5
For testing purposes, you can of course also type a loop directly into the console. Just make sure to keep the indentation
(the first loop iteration will execute while you type) and close the loop by typing a command without indentation:
>for i=1 to 3
> Print (i)
1
> Print (i*4)
4
>Print 'Done'
2
8
3
12
Done
If you have a fixed number of elements that are either strings or non-sequential numbers,
use this form of the for loop. This syntax matches Python.
for i in 5,10,3,-8
Print (i)
5
10
3
-8
for text in 'Hi!','I am','Yami..'
Print (text)
Hi!
I am
Yami..
And you can also loop over the content of a file:
for id in file /home/yasara/pdb_id.txt
Clear
LoadPDB (id)
CountRes all
In the example above, the file pdb_id.txt contains a list of PDB IDs to loop over. In this simple text file,
you can use either commas or linefeeds to separate the elements:
# Example for valid loop-file:
# Line starting with '#' are ignored.
# Using linefeeds to separate
1CRN
5TIM
# Using commas to separate
1SOL, 1STY,1THV
# By default, strings are weak strings, single quotes are not essential:
'1RIS', '1RWT'
# Double quotes are needed to specify strong strings:
"1VII","1YAS"
# Integers and floats are also allowed:
1,2,3
5.7, -1e60
In addition to the Python syntax, Yanaconda supports the C-like do
.. while X style. In the latter case, the loop is run at least once, even if the expression at the end is false.
i = 3
while i<50
Print (i)
i = i*2
3
6
12
24
48
i = 60
do
Print (i)
i = i*2
while i<50
60
Like C and Python, Yanaconda supports the 'break' statement to stop a loop. In addition,
it is possible to specify the number of nested loops to stop right after the 'break'.
i = 0
while 1
Print (i)
if i==5
break
i = i+1
0
1
2
3
4
5
for i=1 to 3
for j=10 to 15
Print (i)-(j)
if j==12
break 2
1-10
1-11
1-12
The 'continue' statement has also been borrowed from C and Python. Again,
you can specify the number of the loop to continue with.
for i=1 to 3
for j=10 to 15
if j==12
continue 2
Print (i)-(j)
1-10
1-11
2-10
2-11
3-10
3-11
| Having a list of something (strings, numbers) can be very handy.
for i = 1 to 5
MyList(i) = i*10
for i = 5 to 1
Print (MyList(i))
50
40
30
20
10
Note that lists are not a special datatype but 'faked' using
explicit evaluators. The first for
loop creates five variables named 'MyList1' to 'MyList5', and the second for
loop simply prints them out in reversed order. If you assign more than one value to a variable,
it is assumed to be a list, and the 'fake' variables Name1,Name2...NameN are created automatically. You can document the fact that MyList is a list by appending two parentheses.
MyList() = 4,7,8,9,5
for i in MyList
Print (i)
for i = 1 to 5
Print 'Element (i): (MyList(i))'
Print 'Accessing list elements:'
MyList(3) = MyList(3)+1
Print (MyList(3))
MyList3 = MyList3+1
Print (MyList3)
4
7
8
9
5
Element 1: 4
Element 2: 7
Element 3: 8
Element 4: 9
Element 5: 5
Accessing list elements:
9
10
To delete a list, use:
MyList()=0
This expression will set MyList1 to 0 and delete all other variables with the same root name
'MyList'.  | The same concept holds for multidimensional lists,
but care must be taken that the list indices do not interfere, best by separating them with an underscore
'_':
for i=1 to 12
for j=1 to 12
wrongmatrix(i)(j)=i*j
rightmatrix(i)_(j)=i*j
Why is the first matrix wrong? Simply because the matrix elements
1,12 and 11,2 (like several others) map to the same variable name: wrongmatrix112. The underscore makes sure that the indices stay separated: rightmatrix1_12 and rightmatrix11_2 are indeed different names.
If you know the number of columns and rows in advance, you can use leading zeroes to avoid the underscore:
for i=01 to 12
for j=01 to 12
rightmatrix(i)(j)=i*j
In the above case, rightmatrix0112 and rightmatrix1102 correctly map to different variable names.
As lists are just variables with the same root name and a sequential number at the end,
the length of a list can be determined by counting the number of names that match a certain pattern. This is done by the
'count' function:
MyList() = 4,8,5,2
for i = 1 to count MyList
Print 'Element (i): (MyList(i))'
Element 1: 4
Element 2: 8
Element 3: 5
Element 4: 2
If no variable name matches, 'count' returns 0 to indicate that the list is empty
/ does not exist. These five functions scan all variable names that match the given
root name and return the smallest, largest,
summed up and average value, respectively. The datatype of the result is either an integer or float,
depending on the list elements. The standard deviation is calculated using the formula:
StdDev^2 = Sum((ElementX-Mean)^2) / Elements
Example for a simple list:
MyList() = 1,4,-5,10,8,2
Print (count MyList)
Print (min MyList)
Print (max MyList)
Print (sum MyList)
Print (mean MyList)
Print (stddev MyList)
Print (join MyList)
6
-5
10
20
3.33333333333333e+00
4.88762609953839e+00
1 4 -5 10 8 2
The Yanaconda language does not contain a single function to sort a list, since most of the time one wants to sort something else based on the numbers stored in a list.
The following example assumes that the simulation cell contains one protein multiple times in
different, non-overlapping conformations
. This macro sorts these conformations based on their energy (the cell is assumed to be the last object):
energylist() = EnergyObj all
do
sorted=1
for i=1 to Objects-2
if energylist(i)>energylist(i+1)
SwapObj (i),(i+1)
swap=energylist(i)
energylist(i)=energylist(i+1)
energylist(i+1)=swap
sorted=0
while not sorted
| Some variables are predefined to make life easier,
they are just normal variables and can be overwritten. | | View |
1 if the stage of your YASARA is at least View, always true | | Model
| 1 if the stage of your YASARA is at least Model (true for Model,
Dynamics, Structure), 0 otherwise | | Dynamics |
1 if the stage of your YASARA is at least Dynamics (true for Dynamics,
Structure), 0 otherwise | | Structure |
1 if the stage of your YASARA is at least Structure (true for Structure only), 0 otherwise |
| Twinset | 1 if you have the Twinset installed |
| Movie | 1 if you are running YASARA Movie |
| Linux | 1 if the operating system is Linux |
| MacOS | 1 if the operating system is MacOSX |
| Windows | 1 if the operating system is Windows |
| MacroDir | Directory where the current macro resides |
| MacroTarget | Target set for this macro with the MacroTarget command |
| Atoms | Number of atoms in the soup,
see also Count | | Objects
| Number of objects in the soup , see also Count
| | FirstObj | Number of first active object, -1 if no object is active |
| LastObj | Number of last active object
, -1 if no object defined | | EnergyUnit | Current
EnergyUnit, either 'kcal/mol' or 'kJ/mol' |
| SpeedMax | The current speed of the fastest atom in the simulation in m/s |
| Pi | The value of Pi, 3.14159265359 |
| Pid | The ID of the current process
(helpful if you create temporary files) | | PDBDir
| Directory of the local PDB as specified in yasara.ini |
| LeftButton | 1 if the left mouse button is currently pressed |
| MiddleButton | 1 if the middle mouse button is currently pressed |
| RightButton | 1 if the right mouse button is currently pressed |
| ContinueButton | 1 if the command 'ShowButton Continue' has been issued and the button is pressed |
| FirstName | Your first name, type Print
'Hi (FirstName)!' to greet yourself. | | LastName |
Your last name | | ScrSizeX | The width of the YASARA window in pixels |
| ScrSizeY | The height of the YASARA window in pixels |
| PixToA | Conversion factor from pixels to Angstroms |
| EyeDis | Distance between the eye/camera and the viewplane in Angstroms |
|
Sometimes a certain macro is needed at more than one place. Instead of copying macros manually between files,
you can use the 'include' statement to let a macro include other macros:
# Solve an NMR structure
# First fold it from the stretched out conformation
include nmr_fold
# Then refine it in vacuo
include nmr_refinevac
# And then in water
include nmr_refinewat
- The 'include' statement does not have to be placed in the first
column (YASARA will instead automatically indent the included file as well).
- YASARA includes the other macros BEFORE running the
macro, just like a C preprocessor includes header files before compilation.
As a consequence, the line numbers reported
in case of errors refer to the complete macro, with all the others included.
Also Yanaconda language features like explicit evaluators cannot be resolved.
- The path specified after 'include' may be absolute or relative. In the latter
case it is taken relative to the directory containing the including macro, but not
relative to the current working directory.
Yanaconda supports a number of built-in functions,
mainly for doing math. These functions can appear anywhere in an expression. If the function argument is itself an expression,
you must enclose it in parentheses. Remember to leave a space between the function name and the opening parenthesis.
for i = 0 to 180 step 60
j = sqrt i
k = cos (i)
Print 'Sqrt of (i) = (j), Cos of (i) = (k)'
Sqrt of 0 = 0.00000000000000e+00, Cos of 0 = 1.00000000000000e+00
Sqrt of 60 = 7.74596669241483e+00, Cos of 60 = 5.00000000000015e-01
Sqrt of 120 = 1.09544511501033e+01, Cos of 120 = -4.99999999999969e-01
Sqrt of 180 = 1.34164078649987e+01, Cos of 180 = -1.00000000000000e+00
Unlike C and Python, trigonometric functions expect their arguments in normal degrees.
If a string is passed to the rnd function, it will randomize each alphanumeric character such that the replacement has a lower alphanumeric ASCII code. E.g. when passing the string
'ffffff', the result will be a random RGB color. The following functions are available:
| | abs X
| Absolute value of X | | acos X
| Arc cosine of X, returned in degrees | | asin X
| Arc sine of X, returned in degrees | | atan X
| Arc tangent of X, returned in degrees | | cos X
| Cosine of X, X in degrees | | count X
| The number of occurrences of variables with root name X and different terminal numbers |
| cube X | Volume of a cube with length X
= X*X*X | | curt X | Cubic root |
| dirname X | Get the directory name of the path in string X
(discarding additional filename or slash) | | exp X |
e (the base of natural logarithms) raised to the power X |
| log X | Natural logarithm of X |
| log10 X | Base-10 logarithm of X |
| join X | Join all variables with root name X to a single weak string,
currently in random order | | max X | The maximum value in list X
(all variables with root name X) | | mean X | The mean value of list X
(all variables with root name X) | | min X | The minimum value in list X
(all variables with root name X) | | onein X | A random element in list X
(all variables with root name X) | | ord X | The ASCII code of string X,
summed up if string contains more than one character | | rnd X
| A random number in the range [0..X[. If the argument is a string,
each character is randomized | | round X | X rounded to the closest integer |
| sign X | The sign of X, either 1, 0 or
-1. | | sin X | Sine of X, X in degrees |
| sqr X | Area of a square with length X
= X*X | | sqrt X | Square root |
| stddev X | The standard deviation of list X
(all variables with root name X) | | sum X | The sum of all values in list X
(all variables with root name X) | | tan X | Tangent of X,
X in degrees | | trunc X | The integer part of X,
rounded towards zero. | |
The most important part of any macro are the YASARA
commands. Yanaconda is merely used to guide the control flow and call YASARA
commands with the right parameters. Almost every example in this section used YASARA's
'Print' command to print results. YASARA commands
know which arguments to expect. E.g. the DelAtom command expects a selection. To save your time,
selections are not enclosed by any quotes. This will obviously delete all CA (=Calpha) atoms. Now let's put the atom name in a variable:
Name = 'CA'
DelAtom (Name)
Note that you must use an explicit evaluator so that
(Name) is replaced with CA, otherwise YASARA would delete all atoms named 'Name'. If you look at previous examples,
you can see that all Print commands that printed variables used explicit evaluators to insert the content of the variable. Otherwise just its name would have been printed.
You can of course also use variables for more complicated selections,
e.g. for salt-bridges:
selection = 'Lys Atom NZ with distance < 4 from Asp Glu'
ShowRes (selection)
ColorRes (selection),blue
This brings us to the final golden rule for Yanaconda programmers:
ALWAYS USE EXPLICIT EVALUATORS AROUND YANACONDA EXPRESSIONS THAT APPEAR AS ARGUMENTS TO YASARA COMMANDS.
Some YASARA commands return values that can be assigned to variables. This example stores the X/Y/Z coordinates of atom
500 in three variables x,y and z: If a command returns more results than variables present,
the additional return values will be lost. In this case, the Y and Z coordinates are discarded:
It is also possible to put the return values in a list by appending two parentheses to the variable name:
pos() = PosAtom 500
Print X=(pos(1)) Y=(pos(2)) Z=(pos(3))
This feature is especially useful to store selections returned by the List command,
e.g. the command ColorAtom CA,Red can be expressed as:
selection() = ListAtom CA
for atom in selection
ColorAtom (atom),Red
If you want to know if a command returns something, look at the examples given for the command.
Normally YASARA executes each command issued by the macro just as if it had been created via the graphical user interface. This includes printing the command in the console and updating the graphics display. If the macro issues hundreds of YASARA commands,
this approach may become too slow. In this case simply take full control of YASARA by switching off the console:
YASARA will then only redraw the screen when the macro explicitly requests it via the
Wait command. More details about this approach
can be found here. |