Expressions

Special variables and values

Certain variables have special meanings in steve. Their values are managed automatically by the breve engine and should not be assigned manually.

  • self (object).

    This variable always contains the value of the instantiation in which the current method is being run. The variable is used most frequently to call other methods within the same instantiation. For example, an object of class Mobile could move itself with the call:

        self move to (10, 10, 10).
    
  • super (object).

    This special value refers to the parent, or super-instance. This is used to invoke a parent class implementation of a method, instead of the current class implementation.

    For example, in the Controller's iterate method, the superclass iterate method is often called: super iterate. This is because custom subclasses of Control typically preform simulation specific tasks, but must then call on the superclass implementation (Control) to actually step the physical simulation forward and update the state of the world. Anytime you wish to implement a custom object behavior in addition a parent class behavior, you should invoke the parent class method as well.

  • controller (object).

    The controller variable is defined for all instances. It always refers to the simulation's controller instance.

Assignments

The most simple expressions are simple assignments of literal values to variables. It may help to refer to the documentation on steve types before continuing.

Below are a few examples of this type of expression. In each case, the variable on the left will take the value of the expression on the right. If the expression on the right is not the correct type, it will be automatically converted if possible. If the conversion is not possible, an error will occur.

myInt = 4.
myDouble = 12.345.
myString = "Hello!".
    
# If we assign a double to an int, it will be automatically converted by 
# truncating the decimal portion.  In this example, myInt will take the value 
# 4:  
    
myInt = 4.8.
    
# Likewise if we assign a string to a double or int.  The variable will take 
# the number value of the string, according to the atoi() or atof() ANSI C 
# functions.  Here the double will get the value 10000: 
     
myDouble = "10000 miles away.".

Mathematical Expressions

Mathematical operators in steve function almost exactly the same as they do in C, although there are some additions to account for vector and matrix types.

The following binary operators are valid for int and double types (the descriptions refer to x and y, as though they were used in an expression: x operator y):

  • +, addition

  • -, subtraction

  • *, multiplication

  • /, division

  • %, modulus (the remainder of x when divided by y)

  • ^, power (x raised to the power of y)

Their functions should be self-explanatory, with the possible exception of modulus, which cannot be used with double types in C. When used with doubles, the result is calculated using the ANSI fmod() function.

The following operators are valid for use with two vectors:

  • +, vector addition

  • -, vector subtraction

The following operators are valid for a vector and a double (although an int is automatically promoted to a double in this case):

  • *, vector times scalar multiplication

  • /, vector divided by scalar division

As in many languages, parenthesis are used to group expressions when the default order of operations does not compute the desired result:

# this is the default order, but we can make it explicit with parens
x = x + (4 / y).                    # computes 4 / y first...

# this is NOT the default order -- we need to force it
x = (x + 4) / y.                    # computes x + 4 first

Mathematical expressions are often used in conjunction with assignments in order to modify the value of a variable. A few examples of using mathematical expressions in conjunction with assignments follow:

    x = x + 4.

    y = (1, 2, 3) / x.                  # assumes x is an int or double

    z = z + x^2.

Mathematical Assignment Expressions

In addition to the mathematical assignment operators above, steve also support mathematical assignment operators. Mathematical assignment operators are used as shortcuts to perform a calculation and update the value of a variable simultaneously, instead of as two separate steps. These expressions are useful, but since they are only shortcuts for other expressions, understanding them is not critical.

The following mathematical assignment operators are available:

  • +=

  • -=

  • *=

  • /=

  • %=

  • ^=

These operators are simply shortcuts for cases in which the left operand of the mathematical expression is also the location where the output of the expression will be stored. For example, the following expression pairs are equivalent:

a = a + b.               # "a equals a plus b" can also be written as...
a += b.                  # "a plus equals b"

a = a - (2, 2, 2).
a -= (2, 2, 2).

steve also has "increment" and "decrement" assignment operators:

  • ++

  • --

As in languages like C and Perl, these operators increment and decrement a variable by one, respectively. Unlike C and Perl, these operators may only be placed after the variable. As an example, the following expression pairs are equivalent:

x = x + 1.          # updates the variable x by adding 1
x++.

x += 1.             # does the same...
x++.

y = (x += 1).       # a little confusing, but sets both x and y to (x + 1)
y = x++.            # as does this.

Mathematical Functions

A number of internal functions (which are otherwise typically not used in breve simulations) are available for math-related expressions. Internal functions are called just like C functions: functionName, (arguments).

  • sin(input) gives the sine of the radian angle input.

  • cos(input) gives the cosine of the radian angle input.

  • tan(input) gives the tangent of the radian angle input.

  • asin(input) gives the radian angle arc sine of input.

  • acos(input) gives the radian angle arc cosine of the input.

  • atan(input) gives the radian angle arc tangent of input.

  • sqrt(input) gives the float square root of input.

  • angle(a, , b) gives the float angle in radians between vectors a and b.

  • max(a, , b) gives the maximum of floatsa and b.

  • min(a, , b) gives the minimum of floatsa and b.

  • cross(v1, , v2) gives the vector cross product of vectors v1 and v2.

  • dot(v1, , v2) gives the float dot product of vectors v1 and v2.

  • log(input) gives the float natural log of input.

  • randomGauss() gives a float random number with a Gaussian distribution.

  • transpose(input) gives the transpose of the matrix input.

The following internal functions are used for testing float variables for special values which have meaning primarily to developers and plugin authors.

  • isnan(input) returns 1 if the input is a "not-a-number" float value, 0 otherwise.

  • isinf(input) returns 1 if the input is a float value representing infinity, 0 otherwise.

Random Numbers

Random numbers are available in steve using the command random. The syntax is random[ expression ], where expression is an expression of either int (the section called “The int type”), float (the section called “The float type”) or vector (the section called “The vector Type”). The value returned is always the same type as the expression. In the case of int or float, the returned value is a random value between 0 and the expression. In the case of a vector expression, the returned value is a vector in which each element is between 0 and the corresponding value of the expression. For example, a call to random[(10, 10, 20)] returns a vector with X and Y elements between 0 and 10, and the Z element between 0 and 20.

Note that random[intValue] returns a value between 0 and intValue, inclusive, as opposed to the behavior that many C programmers expect in which the returned value is between 0 and intValue - 1.

Because many simulations use the origin (0, 0, 0) as the "center" of their world, it is often useful to obtain a random vector centered around (0, 0, 0). For example, if we want to move agents somewhere within an imaginary box surrounding the origin, we might use the expression random[(40, 40, 40)] - (20, 20, 20). This convention gives us a vector with each element between -20 and 20. This type of expression appears frequently in simulations.

The values are produced using the standard C library random() routine. The library is seeded with the current time when the breve application is launched. To explicitly set the random seed, you may call the internal function randomSeed( value ), where value is an integer.

In the event that multiple breve simulations are launched simultaneously (typically only relevant in cluster environments), it may be necessary to pick unique random seeds for each simulation to prevent each of the simulations from giving the same results. Refer to the Controller method set-random-seed-from-dev-random for more information on automatically picking unique random seeds on systems which support it.

Method Calls

As discussed in the section on defining class methods, each method is identified by a name and may have any number of input arguments. The most simple method call to a method with no arguments is simply:

instancemethodName.

If the method takes arguments, each argument is associated with a keyword: the keyword identifies which argument will follow and what it's type is. This is somewhat different from C where the argument's type and meaning is specified by its order relative to other arguments. The steve method is more similar to Objective C and allows a more natural language structure to method calls and protects against arguments being passed in the wrong order.

To pass arguments to the method, simply precede the argument value with the keyword name. Consider a method move-object which takes a keyword to:

myObject move-object to (1, 2, 3). 

If the method takes more than a single argument, the convention is the same—just add the next argument afterwards. Note that the order in which the arguments are passed does not affect the method call, though it may affect the readability of the code. For example, the Control object implements a method to point the camera at a certain vector location from a vector offset—first we'll see the method definition, then how it's called:

# if the method is defined using:
    
+ to point-camera at location (vector) from offset (vector):
    ...

# then from another method, we can call point-camera using the code below.
# these two method calls are equivalent, though the first reads more 
# naturally.

+ to do-something-else:
    self point-camera at (0, 0, 0) from (100, 100, 100).
    self point-camera from (100, 100, 100) at (0, 0, 0). 

If you wish to call a method for multiple objects, you can use the method call syntax with a list of objects. Note, however, that the arguments to the list are computed for item in the list separately. This makes a difference in the following example:

# assume that mobileList is of type list

mobileList = 10 new Mobile.

# The random statement is evaluated for each instance, meaning that all the 
# instance go to different random locations, not to a single random location.

mobileList move to random[(20, 20, 20)].

The all Expression

You can find all instances of a given class using the all expression. all is given the name of a class, and returns a list containing all objects of that type.

# get a list of all mobile objects in the simulation

myMobile = all Mobiles

Printing information with print and printf

The print and printf statements are used to print output from a simulation to the output log. Both of these statements accept any type of expression, as well as multiple expressions separated by commas. Also, since strings may contain embedded variables, you can format the output of variables however you'd like. See the section on strings (the section called “The string Type”) for more information.

The only difference between print and printf is that printf does not automatically print a newline character. This means that subsequent prints will continue on the same line (as though the "return" key was never hit). This can be useful if you're trying to produce output in a specific format, but is typically not desirable. If in doubt, stick to print

Here are some examples of print and printf:

# print two variables, side by side.

print (self get-time), populationSize.

# use a variable embedded in a string.

print "the population size is $populationSize".

# the following statements would produce the text:
# A B C
# D E F

print "A B C ".
print "D E F"

# the following statements would produce the text:
# A B C D E F

printf "A B C ".
printf "D E F"

Using Subexpressions

As in C, of course, users can use subexpressions as part of larger expressions. For example, you can use a mathematical expression as part of a method call, or a method call as part of a mathematical expression. Because of the syntax of steve , however, subexpressions frequently need to be parenthesized in situations where it would not be required in C. The following important rules apply to using subexpressions: If a method call is not the entire statement, it must be parenthesized. If you wish to assign the result of a method call, use it in a mathematical expression or use it as an argument for another method, for example:

myInt = self get-speed.                   # incorrect
myInt = (self get-speed).                 # correct

myInt = self get-speed + 5.               # incorrect
myInt = (self get-speed) + 5.             # correct

self set-speed to neighbor get-speed.     # incorrect 
self set-speed to (neighbor get-speed).   # correct

All method arguments must be a single "unit"—arguments which are not simply a variable or literal value must be parenthesized.

This means that if you use mathematical expressions, instantiations or other method calls as input arguments to a method, they must be parenthesized. The first rule about method calls, of course, still applies:

self set-location to ((neighbor get-location) + (10, 10, 10)). # correct
self set-location to (neighbor get-location) + (10, 10, 10).   # incorrect

Internal Function Calls

[Note] For developer use only

Internal function calls are for use by breve and plugin developers only.

A final expression type not discussed above is an internal functions. Internal function calls look just like C calls:

methodName(arg1,arg2, ... argN)

Though internal function calls are the most direct access to the breve libraries and features, the included class hierarchy provides a formal interface to the internal functions such that user simulations should never use these internal functions. The only exception to this is for certain mathematical functions.