UDEC Modeling • Tutorials

Tutorial: Working with FISH

This section is intended for people who have run UDEC (at least for simple problems) but have not used the FISH language—no programming experience is assumed. To get the maximum benefit from the examples given here, you should try them out with UDEC running interactively. The short programs may be typed directly into the command line, accessed via the console pane. After running an example, give the UDEC command model new, to “wipe the slate clean,” ready for the next example. Alternatively, the more lengthy FISH functions may be typed into data files using the the built-in editor; these can be executed directly in the editor or program call in the console.

Type the lines in the example below at the UDEC’s command prompt, pressing <Enter> at the end of each line.

Defining a FISH Function

fish def abc
  abc = 22 * 3 + 5
end

Note

We will use purple courier boldface to identify user-defined FISH functions and declared variables; FISH statements are similar, but bold and darker.

Note that the command prompt changes to Def> after the first line has been typed in; it changes back to the usual prompt when the end statement is entered. This change in prompt lets you know whether you are using commands or if you are defining a FISH function. All lines following the fish define statement are taken as part of the definition of a FISH function (until the end statement is entered). However, if you type in a line that contains an error (e.g., you type the = sign on an empty line), then you will get the udec> prompt again with an error message. In this case, one must redefine the FISH function again from scratch, starting with the fish define statement. Because it is very easy to make mistakes, FISH functions are often typed into data files using the built-in editor. These can either be executed directly from the editor or can be executed via the program call command just like a UDEC data file not containing FISH. We will describe this process later; for now, we’ll continue to work from the command line. Assuming that you typed in the preceding lines without error, and that you now see the udec> prompt, you can “execute” the function named fname defined in the example above by typing the line

@abc

This line causes the arithmetic operations defined in the function to be executed. This is an “assignment statement.” If an equal sign is present, the expression on the right-hand side of the equal sign is evaluated and given to the variable on the left-hand side. Note that arithmetic operations follow the usual conventions: addition, subtraction, multiplication and division are done with the signs +, -, *, and /, respectively. The sign ^ denotes “raised to the power of.”

The actions above create two objects: a function object that can be called at the user’s discretion, and a variable called abc, where abc = 71 after the function has been executed. One can see abc FISH function in the global symbols control set. In addition, one can print the value of abc to the command line by invoking an inline FISH expression via square brackets:

[abc]

The message

71 (integer)

will appear on the command prompt. The function abc can also be executed with the inline FISH syntax as outlined below. Once the symbol abc is defined, we can now refer to it in many ways using UDEC commands. A FISH function can have any name, but in order for a value to be returned by the function, the function name should be identical to a value name updated inside the function, as outlined below.

Variables

We now type a slightly different program (using the model new command to erase the old function and reset the model state).

Using a variable

model new
fish def abc
  hh = 22
  abc = hh * 3 + 5
end

Here we introduce a “variable,” hh, which is given the value of 22 and then used in the next line. If we execute this function via inline FISH (i.e., [abc]), then exactly the same output as in the previous case appears. However, we now have two FISH symbols. The two symbols both have values, but one (abc) is known as a “function,” and the other (hh) as a variable. The distinction is as follows:

When a FISH symbol name is mentioned, the associated function is executed if the symbol corresponds to a function. However, if the symbol is not a function name, then the current value of the symbol is used.

The mechanism of naming a FISH function to be identical to a variable set inside the function allows one to provide a return value from the execution of the FISH function. This mechanism is frequently used, especially when FISH histories (using the fish history command) are needed.

The following experiment may help to clarify the distinction between variables and functions. Before doing the experiment, note that inline FISH can be used to set the value of any user-defined FISH symbol, independent of the FISH function in which the symbol was introduced. Now type the following lines without giving the model new command, since we want to keep our previously entered program in memory:

[abc=0]
[hh=0]
[hh]
[abc]
[hh]

The values of abc and hh are set to 0 in the first two lines. The function abc is not executed, as the first line is an assignment operation. Note that abc still refers to the same FISH function even though its value has been modified. abc will always refer to the defined FISH function unless it is redefined with the fish define command. Following the two assignment operations, the value of hh is printed. The line [abc] executes the FISH function and prints the updated value corresponding to that execution on the command line. The function is executed, as abc refers to a function and the value of abc is not being assigned. Thus the value of abc has been changed along with the value of hh, as the last line demonstrates.

Though we highly recommend using the inline FISH syntax to manipulate FISH variables, an alternate syntax to achieve the same effect is given below:

fish set @abc=0 @hh=0
fish list @hh
fish list @abc
fish list @hh

In this case, the second fish list command causes abc to be executed, as abc is the name of a function, causing the values of both hh and abc to be updated.

As a test of your understanding, type in the slightly modified sequence shown in the next example and figure out why the displayed answers are different.

Test your understanding of function and variable names

model new
fish def abc
  abc = hh * 3 + 5
end
fish set @hh=22
fish list @abc
fish set @abc=0 @hh=0
fish list @hh
fish list @abc
fish list @hh

Using FISH for Custom Calculations

The following example shows how the stress in a zone can be stored as a history for later inspection.

Capturing the history of a FISH variable

model new
block create polygon 0 0 0 10 10 10 10 0
block zone gen edge 10
block property material 1 density 1000 bulk 1e9 shear 0.7e9
block gridpoint apply velocity-y 0.0 range position-x 0 10 position-y -.1 .1
model gravity 0 -10
;
fish define stress_y
  zoneIdx = block.zone(block.head)
  stress_y = block.zone.stress.yy(zoneIdx)
end
;
fish history @stress_y
block cycle 200

The FISH zone variable block.zone.stress.yy is an example of a zone quantity that is available within a FISH program and is termed a FISH intrinsic; see Zone Functions for a complete list of the zone-related intrinsics. Often times, FISH intrinsics are predefined model quantities but, in other cases, they define more complex model operations.

In the example, the FISH intrinsic block.zone.stress.yy provides the \(y\)-component of the zone stress; each instance of block.zone.stress.yy must be followed by a zone index. The index is found with the function block.zone(block_head), which uses the FISH block intrinsic block.head to find the index of the first block and then the index of the first zone in the block. By creating a history, the FISH function stress_y is executed when histories are updated during cycling (see the history interval command to change the history update frequency). Since the value of stress_y is updated in the function (i.e., the function name and update variable name are the same), the history contains updated values each time it is called. At the end of the run, one can simply plot the history of stress_y (history 1) just like a predefined UDEC history. Similarly, we may use FISH functions to post-process or output the computed history for further analysis.

In addition to the above-mentioned FISH intrinsics for zones, there are a plethora of intrinsics available for use in FISH. block-specific intrinsics are given here and structural element intrinsics are given here. A catalog of general intrinsics is given here. Using math, for instance, enables things like sines and cosines to be calculated from within a FISH function. The list below samples some of the functions available from the set of math intrinsics.

math.abs(n) absolute value of n
math.cos(n) cosine of n (n is in radians)
math.log(n) base-ten logarithm of n
math.max(n1, n2) returns maximum of n1, n2
math.sqrt(n) square root of n

A more complex example using a number of intrinsics will be presented later, but now we must discuss one more way a UDEC data file can make use of user-defined FISH names:

Wherever a value is expected in a UDEC command, you may substitute the name of a FISH variable or function using inline FISH notation ([]) or prefixed by @.

This simple statement is the key to a very powerful feature of FISH that allows such things as ranges, applied stresses, properties, etc., to be computed in a FISH function and used in UDEC commands in symbolic form. Hence, parameter changes can be made very easily, without the need to change many numbers in an input file.

As an example, let us assume that we know the Young’s modulus and Poisson’s ratio of a material. If the user wishes to use the bulk and shear moduli, these may be derived with a FISH function, using the equations below:

\[G={E\over2(1+\nu)}\]
\[K={E\over3(1-2\nu)}\]

Coding these equations into a FISH function (called derive) can then be done as shown in this example:

FISH functions to calculate bulk and shear moduli

model new
fish def derive(y_mod,p_ratio)
  s_mod = y_mod / (2.0 * (1.0 + p_ratio))
  b_mod = y_mod / (3.0 * (1.0 - 2.0 * p_ratio))
end

[derive(5e8,0.25)]
[b_mod]
[s_mod]

Note that we execute the function derive by giving its name with arguments inside parenthesis. In this case the arguments, the Young’s modulus and Poisson’s ratio, respectively, are used as temporary variables to compute the bulk and shear moduli; as a result, they do not appear as FISH variables after derive has been executed. The computed values of the bulk and shear moduli (b_mod and s_mod, respectively) can subsequently be used, in symbolic form, in UDEC commands as shown in the following example:

Using symbolic variables in UDEC input

block create polygon 0,0  0,10  10,10  10,0
block zone gen edge 10
block zone cmodel assign elastic
block zone property bulk [b_mod] shear [s_mod]
block zone list property bulk
block zone list property shear

The result of these operations can be checked by listing bulk and shear in the usual way (e.g., using the block zone list property command).

FISH Rules, Syntax, and Statements, Illustrated

There is great flexibility in choosing names for FISH variables and functions. The underscore character ( _ ) may be included in a name. Names must begin with an alphabetical letter (i.e., cannot be a number or a special character) and must not contain any of the arithmetic operators (+, -, /, *, or ^). A chosen name cannot be the same as the name of one of the built-in FISH intrinsics. FISH variable names are not case sensitive, so that aVariable and AVARiable are the same variable.

In addition to inspecting FISH variable/function names in the global symbols control set, one can also obtain a list of all current variables and functions using the fish list command. A printout of all current values, sorted alphabetically by name, is produced as a result of this command.

fish list

We now examine ways decisions can be made, and repeated operations done, via FISH programming. The following FISH statements allow specified sections of a program to be repeated many times:

loop var (a1, a2)

endloop

The words loop and endloop are FISH statements, the symbol var stands for the loop variable, and a1, a2 stand for expressions (or single variables) that bound the loop. The next example shows the use of a loop (or repeated sequence) to produce the sum and product of the first ten integers:

Controlled loop in FISH

model new
fish def xxx
  sum  = 0
  prod = 1
  loop n (1,10)
    sum  = sum + n
    prod = prod * n
  end_loop
end
@xxx
fish list @sum
fish list @prod

In this case, the loop variable n is given successive values from 1 to 10, and the statements inside the loop (between the loop and endloop statements) are executed for each value. As mentioned, variable names or an arithmetic expression could be substituted for the numbers 1 or 10. Note that the exit statement can be used to break out of a FISH loop and the continue statement can be used to skip the remaining instructions in the loop, moving to the next sequence of the loop.

It is important to note that this formulation of looping is different from a for loop in most high-level programming languages. For instance, one cannot easily control the ending condition (i.e., loop from 1 to 10 excluding 10) or the incrementing mechanism (i.e., loop from 1 to 10 by twos or loop backward). A standard for loop is also available in FISH to provide for additional loop control.

Traditional for loop in FISH

model new
fish def xxx
  sum  = 0
  prod = 1
  loop for (n = 1, n<= 10, n = n+1)
    sum  = sum + n
    prod = prod * n
    io.out('The sum is ' + string(sum) +  ...
           ' and the product is ' + string(prod))
  end_loop
end
@xxx

Besides standard looping as depicted above, one can easily loop over sets of model objects (i.e., zones, gridpoints, structural element nodes, etc.) using the loop while construct. A practical use of the loop while construct is to install a nonlinear initial distribution of elastic moduli in a UDEC grid. Suppose that the Young’s modulus at a site is given by this equation:

\[E=E_\circ+c\sqrt{y}\]

where \(y\) is the depth below surface, and c and E are constants. We write a FISH function to install appropriate values of bulk and shear modulus in the grid, as in this example:

Applying a nonlinear initial distribution of moduli

model new
;
block tolerance corner-round-length 0.1
block create polygon 0 -30 0 0 30 0 30 -30
block cut joint-set angle 36 0 trace 50 0 gap 0 0 spac 8 0
block cut joint-set angle -58 0 trace 50 0 gap 0 0 spac 12 0
block zone gen edge 1
block zone cmodel assign mohr-c
;
fish define install
  ; smoothness is the elevation tolerance on changing moduli
  bi = block.head
  loop while bi # 0
    zi = block.zone(bi)
    loop while zi # 0
      z_depth = float(int(-block.zone.pos.y(zi)/smoothness))
      y_mod = y_zero + cc * math.sqrt(z_depth)
      _shear = y_mod / (2.0*(1.0+p_ratio))
      _bulk  = y_mod / (3.0*(1.0-2.0*p_ratio))
      block.zone.prop(zi,'bulk')  = _bulk
      block.zone.prop(zi,'shear') = _shear
      zi = block.zone.next(zi)
    end_loop
    bi = block.next(bi)
  endloop
end
;
fish set @p_ratio=0.25 @y_zero=1e7 @cc=1e8 @smoothness=6.0
block zone property density 2000
block zone property bulk 1e8
block domain property material 1 capillary-gamma 1e7
@install
model save 'ex4_09.sav'
return

Again, you can verify correct operation of the function by printing or plotting shear and bulk moduli.

In the function install, the looping takes place over all blocks in the global list of blocks, while the inner loop scans over all zones in each block. Inside the loop, the \(y\)-coordinate of each zone centroid is used to calculate the Young’s modulus, given in the equation above. We assume that the datum (or ground surface reference point) is at \(y\) = 0. The variables bl.zone.pos.y(index) and block.zone.prop(index, 'young') are zone intrinsics. Here, we set properties directly from within a FISH function, rather than with a block zone property command. Note that the looping here assumes that the end of the list in each case is a zero index. This will be true for most list of objects. However, there are some lists that are circular (such as block.boundary). In these cases, the loop must test to see if the original starting point has been found again.

Having seen several examples of FISH functions, let’s briefly examine the question of FISH syntax and style. A complete FISH statement occupies one line. However, a line may be typed across two or more lines as long as each line but the ultimate is terminated with the continuation character ( … ). Use of temporary variables as hinge points to concatenate lengthy formulas can also be handy. The following example shows how this can be done:

Splitting lines

model new
fish def long_sum  ;example of a sum of many things
  local temp1 = v1 + v2 + v3 + v4 + v5 + v6 + v7 + v8 + v9 + v10
  long_sum = temp1 + v11 + v12 + v13 + v14 + v15
end

In this case, the sum of fifteen variables is split into two parts. The local designation for temp means that it is used just during the execution of long_sum and discarded afterward. By default, FISH variables are global, meaning that they stick around in memory space until a model new command is given. One can specify a variable as global with the global statement. It is good practice, and may be more efficient, to designate all FISH variables that will not be used by other functions or commands as local. For instance, one can make a loop using a local variable as follows:

loop local n(1,10)

Also note the use of the semicolon after the definition of long_sum; this indicates a comment. Any characters that follow a semicolon are ignored by the FISH compiler, but they are echoed to the log file. It is good programming practice to annotate programs with informative comments. Some of the programs have been shown with indentation (i.e., space inserted at the beginning of some lines to denote a related group of statements). Any number of space characters may be inserted (optionally) between variable names and arithmetic operations to make the program more readable. Again, it is good programming practice to include indentation to indicate things like loops, conditional clauses, and so on. Spaces in FISH are “significant” in the sense that space characters may not be inserted into a variable or function name.

One other topic that should be addressed now is that of variable type. You may have noticed, when printing out variables from the various program examples, that numbers are either printed without decimal points or in “E-format” (i.e., as a number with an exponent denoted by “E”). At any instant in time, a FISH variable or function name is classified as one of various (and growing) types: array, Boolean, integer, matrix, pointer, real, string, structure, tensor, 2D vector, or 3D vector. These types may change dynamically, depending on context, but the casual user should not normally have to worry about the type of a variable, since it is set automatically. Consider the following example:

Variable types

model new
fish def types
  v1 = 2
  v2 = 3.4
  v3 = 'Have a nice day'
  v4 = aa * bb
  v5 = cc + ', old chap'
  v6 = vector(1,2)
  v7 = matrix(vector(1,2))
  v8 = true
end
@types
fish list

The resulting screen display looks like this:

           Name    Value
           ------- --------------------
(function) types 0 (integer)
           v1    2 (integer)
           v2    3.400000000000000e+00 (real)
           v3    'Have a nice day' (string)
           v4    6.800000000000000e+00 (real)
           v5    'Have a nice day, old chap' (string)
           v6    (1.000000000000000e+00,2.000000000000000e+00) (vector2)
           v7    3 x 1 (matrix)
           v8    true (boolean)

The variables v1, v2, and v3 are converted to integer, float (or real), and string, respectively, corresponding to the numbers (or strings) that were assigned to them. Integers are exact numbers (without decimal points) but are of limited range; floating-point numbers have limited precision (about 15 decimal places), but are of much greater range; string variables are arbitrary sequences of characters; and indexes are used to address UDEC model components or other internal UDEC objects. There are various rules for conversion between the types. For example, v4 becomes a floating-point number, because it is set to the product of a floating-point number and an integer; the variable v5 becomes a string because it is the sum (concatenation) of two strings.

There is another language element in FISH that is commonly used: the if then else statement. The following three statements allow decisions to be made within a FISH program:

if a1 test a2 then

else

endif

These statements allow conditional execution of FISH function segments; else and then are optional. The item test consists of one of the following symbols or symbol pairs:

=   #   >   <   >=   <=

The meanings are standard except for #, which means “not equal.” The items expr1 and expr2 are any valid expressions or single variables. If the test is true, then the statements immediately following if are executed until else or endif is encountered. If the test is false, the statements between else and endif are executed if the else statement exists; otherwise, the program jumps to the first line after endif. The action of these statements is illustrated in the next example:

Action of the if else endif construct

model new
fish def abc(xx)
  if xx > 0 then
    abc = 33
  else
    abc = 11
  end_if
end
[abc(1)]
[abc(-1)]

The displayed value of abc in this example depends on the argument provided to abc when it is executed. You should experiment with different test symbols (e.g., replace > with <).

Until now, our FISH functions have been invoked from UDEC, either by using the square brackets [] of inline FISH, by giving the function name prepended with the the @ character, or by using the fish list command. It is also possible to do the reverse, to give UDEC commands from within FISH functions. Most valid UDEC commands can be embedded between the following FISH statements:

command

endcommand

There are two main reasons for eliciting UDEC commands from a FISH program. First, it is possible to use a FISH function to perform operations that are not possible using the predefined FISH intrinsics mentioned above. Second, we can control a complete UDEC run with FISH. As an illustration of the first use of the command endcommand statement, we can write a FISH function to install a number of cable elements at different depths in a material. The number of cables and number of segments are provided as arguments to the function. When many cable elements are required, it becomes tedious to type many separate structure cable create commands, each with different grid values. However, with FISH, we can elicit UDEC commands from within a loop and assign the location of the cable ends automatically during the loop, as illustrated in this example:

Automated placing of cable elements

model new
;
fish define setup
; Create vars for later use
  xCentre = 0.0         ; x-coord of tunnel centre
  yCentre = 0.0         ; y-coord of tunnel centre
  theta1  = 10.0        ; starting angle for cables
  theta2  = 160.0       ; ending angle for cables
                        ; (don't wrap back on theta1)
  radius1 = 8.0         ; radius of tunnel
  radius2 = 16.0        ; ending radius for remote end of cables
end
;
fish define place_cables(nCables)
  ; This example places cable elements along a given arc of tunnel.
  ; number of cables
  ; calculate angle increment between successive cables
  theta1 = math.degrad * theta1
  theta2 = math.degrad * theta2
  _angInc = (theta2 - theta1) / float(nCables - 1)
  _ang = theta1

  ; get endpoint coordinates
  loop ii (1, nCables)
    _x1 = radius1 * math.cos(_ang) + xCentre
    _y1 = radius1 * math.sin(_ang) + yCentre
    _x2 = radius2 * math.cos(_ang) + xCentre
    _y2 = radius2 * math.sin(_ang) + yCentre

    ; place the cable
    command
      block structure cable create begin @_x1 @_y1 end @_x2 @_y2 ...
        segments 5 material-steel 2 material-grout 3
    endcommand
    _ang = _ang + _angInc

  endloop

end
;
@setup
;
block create polygon -25.0 -25.0 -25.0 25.0 25.0 25.0 25.0 -25.0
block cut split begin 0.0 -25.0 end 0.0 25.0
block cut split begin -25.0 0.0 end 25.0 0.0
block cut tunnel 0.0, 0.0, @radius1, 16
block zone gen edge 10
;
block delete range annulus center 0.0, 0.0 rad  0  8
;
@place_cables(15)
model save 'ex4_13.sav'

return

After entering these statements, you should list and plot the cable data to verify that cables have been created, and that they are located at the appropriate positions in the grid.

Arrays and Maps

It is often the case that one would like to store a list of objects that they will loop over in the future. These may be computed values from zones, for instance, or specific gridpoint pointers themselves. FISH has two containers to use in these circumstances, termed arrays and maps.

An array holds a list of FISH variables of any type that can be looped over or accessed by the integer index of the element of the array. Arrays can be multidimensional and do not resize dynamically. The simple example below shows how one can create an array of integers and then sum the values.

Array example

model new
fish define array_operation
    ;create and populate an array with products of 2
    arr = array.create(10)
    loop local n(1,10)
        arr[n] = 2*n
    end_loop
      
    ;compute the sum and product of elements in the array
    sum = 0
    prod = 1
    local i = 1
    loop while (i <= array.size(arr,1))
        sum = sum + arr[i]
        prod = prod * arr[i]
        i = i + 1
    end_loop
    io.out('The sum is ' + string(sum) + ...
           ' and the product is ' + string(prod))
end
@array_operation

In this example, an array is created and filled with numbers. The loop while construct is used to loop over the array entries and the sum and product are computed and output.

A map, on the other hand, is an associative container, meaning that one can access the members of a map by an integer or string used to insert a value in the map. Maps can dynamically be resized and added to one another (appending maps together), and are the preferred constructs for storing lists of FISH variables for later access.

Map example

model new
fish define map_operation
    ;create and populate a map with products of 2
    my_map = map(1,2)
    loop local n(2,10)
        map.add(my_map,n,2*n)
    end_loop
      
    ;compute the sum and product of elements in the map
    sum = 0
    prod = 1
    loop foreach n my_map
        sum = sum + n
        prod = prod * n
    end_loop
    io.out('The sum is ' + string(sum) + ...
           ' and the product is ' + string(prod))
end
@map_operation

Unlike with arrays, maps can be looped through using the loop foreach construct. In this case, n is the value held in each map entry, not the integer name of the object in the map. Likewise, instead of using integers to insert objects into the map, one could use strings such as first, second, etc. This allows one to easily and efficiently store and access FISH variables by a user-defined name.

Formated Output

Sometimes it is need to produce output of values that are not available in the provided list commands. FISH can be used to produce output in a user controlled format. The FISH function string

Using FISH to list contents of an array in a formated list

project new
model new
fish def afill          ; fill matrix with random numbers
  array var(4,3)
  loop m (1,4)
    loop n (1,3)
      var(m,n) = math.random.uniform
    end_loop
  end_loop
end
fish def ashow          ; display contents of matrix
  loop m (1,4)
    hed = '   '
    msg = '  '+string(m)
    loop n (1,3)
      hed = hed + '             '+string(n)
      msg = msg + '  '+string(var(m,n),12,' ',4,'e')
    end_loop
    if m = 1
      dum = io.out(hed)
    end_if
    dum = io.out(msg)
  end_loop
end
@afill
@ashow

Upon execution, the output is

             1            2            3
1  5.7713E-001  6.2307E-001  7.6974E-001
2  8.3807E-001  3.3640E-001  8.5697E-001
3  6.3214E-001  5.4165E-002  1.8227E-001
4  8.5974E-001  9.2797E-001  9.6332E-001

Calling FISH functions in response to an event

FISH functions may be called from several places in the UDEC program while it is executing. The fish callback is used to manage registering FISH functions with specific callback events; the “tuto_callbacks” tutorial discusses this mechanism in detail.

Attaching a FISH function to a callback event causes the FISH function to be executed by UDEC in response to a specific event.

One can list the set of FISH functions registered with callback events via the fish list callbacks command.

The fish callback command can be given with the remove keyword to un-register FISH functions. For example:

fish callback 1 remove xxx

removes the association between function xxx and event 1.

Note that the whilestepping expression inside a FISH function automatically inserts the FISH function into the cycle sequence at event 1 (start of cycling).

The following data file illustrates the use of a fishcall. Two blocks are moved toward each other at a relatively high velocity. When the blocks touch, the applied velocity is removed; this prevents a contact overlap error from occurring. The FISH function \(stop loading\) is invoked with a fishcall that is triggered when a contact is created, and sets the block velocities to zero.

Example of a FISH callback*

model new
call 'fishcall.fis'
block tolerance corner-round-length 0.001
block create polygon (-0.05,-0.1)  (-0.05,0.1)  (0.25,0.1)  (0.25,-0.1)
block cut crack -1 0 1 0
block cut crack  0 0.1  0 0
block cut crack  0.2 0.1  0.2 0
block cut crack  0.1 0.1  0.1 0
block cut crack  0.105 0.1 0.105 0
block del  range pos-x -0.05 0   pos-y 0 0.1
block del  range pos-x 0.2 0.25  pos-y 0 0.1
block del  range pos-x 0.1 0.105 pos-y 0 0.1
;
block zone gen quad 0.4  0.11 range pos-x 0 1 pos-y -1 0
block zone gen quad 0.07 0.11 range pos-x 0 1 pos-y  0 1
;
block prop  mat=1   d=2.60e-3  k=45000    g=30000
block contact prop mat=1   stiffness-normal=40000  ...
  stiffness-shear=40000 friction=30
;
block contact cmodel assign area
block contact cmodel default area
block contact property stiffness-normal=40000  ...
  stiffness-shear=40000 friction=30
;
bl grid app  velocity-x=0  range pos-x -0.06,-0.04 pos-y -1,1
bl grid app  velocity-x=0  range pos-x  0.24,0.26  pos-y -1,1
bl grid app  velocity-y=0  range pos-x    -1,1     pos-y -0.11,-0.09
bl edg app stress (0,0,-10) range  pos-x -1,1  pos-y 0.09,0.11
;
block mech hist solve-rat
model display hist 1
bloc solve
;
; apply shear load by imposing x-velocity on top blocks
bl grid app  velocity-x=0.1  range pos-x -.01,.101 pos-y -.01,.11
bl grid app velocity-x =-0.1 range pos-x .104,.21  pos-y -.01,.11
fish def stop_loading(contact)
   ii=io.out(' ')
   ii=io.out(' Contact created! '+string(contact))
   command
      bou velocity-x=0.0  range atblock .025 .05
      bou velocity-x=0.0  range atblock .150 .05
      joint  stiffness-normal=40000  stiffness-shear=40000 friction=30
   endcommand
end
fish callback @FC_CONT_CREATE @stop_loading
;fishcall @stop_loading @FC_CONT_CREATE
block gridpoint history vel-x  0.1,0.1
block gridpoint history disp-x  0.2,0.1
block cycle 10000

Determining Failure States of Zones

The program stores a state variable using bitwise values that can be used to represent distinct failure states. Some of the states that are used by constitutive models in UDEC are:

Table 3: Failure states
State State Value
Failure in shear now 1
Failure in tension now 2
Failure in shear in the past 4
Failure in tension in the past 8
Failure in joint shear now 16
Failure in joint tension now 32
Failure in joint shear in the past 64
Failure in joint tension in the past 128
Failure in volume now 256
Failure in volume in the past 512

The numbers listed above are given symbolic FISH names in file \(STATES.FIS,\) the contents of which are listed below. The symbolic names should be used instead of actual numbers, so that assignments can be changed in the future without the need to change existing FISH functions.

File `states.fis` which is used to assign fish variables to state values

fish define _States
shearnow          = 1        ; 1
tensionnow        = 2        ; 2
shearpast         = 4        ; 3
tensionpast       = 8        ; 4
jointshearnow     = 16       ; 5
jointtensionnow   = 32       ; 6
jointshearpast    = 64       ; 7
jointtensionpast  = 128      ; 8
volumenow         = 256      ; 9
volumepast        = 512      ; 10
;
; Quick reference
;         Model                      States used
;        ------                      -----------
;      
;      Burger-Mohr                     1 - 4
;      Cap-Yield                       1 - 4
;      Cap-Yield-Simplified            1 - 4
;      Double-Yield                    1 - 4, 9 - 10
;      Drucker-Prager                  1 - 4
;      Hoek-Brown                      1 , 3
;      Hoek-Brown-PAC                  1 , 3
;      Modified-Cam-Clay               1 , 3
;      Mohr-Coulomb                    1 - 4
;      Power-Mohr                      1 - 4
;      Softening-Ubiquitous            1 - 8
;      Strain-Hardening                1 - 4
;      Wipp-Drucker                    1 - 4
;      Ubiquitous-Joint                1 - 8
;      Wipp-Salt                       1 - 4
;
end
;
@_States

The data file below illustrates the process of determining failure state of zones in a model using the FISH function block.zone.state.

Example of listing state values of zones

model new
program log-file = 'myfile.log'
log on
block tolerance corner-round-length .01
block create polygon 0,0 0,1 1,1 1,0
block zone gen edge 1
;
block change model 3
block property mat 1 bulk 2e6 shea 2e3 coh 2e4 ten 2e3 dens 2000
block contact prop mat = 1 stiffness-normal 1e3 stiffness-shear 1e3
block mechanical gravity 0,-10
;
bl edg app stress 0,9,-3e2 range pos-x  -.1 1.1 pos-y 0.9 1.1
bl grid app vel-y 0 range pos-x  -.1 1.1 pos-y -.1 .1
;
block cycle 100
;
call 'states.fis'  ; failure states defined as fish variables
fish def _querystate
  ;
  iab = block.head
  loop while iab # 0
    iaz = bl.zone(iab)
    loop while iaz # 0
      i_mess = 'zone '+string(iaz)
      curr_state = bl.zone.state(iaz)
      if math.and(curr_state, shearnow) # 0 then
        i_mess = i_mess+' shear'
      else
        if math.and(curr_state, shearpast) # 0 then
          i_mess = i_mess+' shear_past'
        endif
      endif
      if math.and(curr_state, tensionnow) # 0 then
        i_mess = i_mess+' tension'
      else
        if math.and(curr_state, tensionpast) # 0 then
          i_mess = i_mess+' tension_past'
        endif
      endif
      if math.and(curr_state, jointshearnow) # 0 then
        i_mess = i_mess+' joint_shear'
      else
        if math.and(curr_state, jointshearpast) # 0 then
          i_mess = i_mess+' joint_shear_past'
        endif
      endif
      if math.and(curr_state, jointtensionnow) # 0 then
        i_mess = i_mess+' joint_tension'
      else
        if math.and(curr_state, jointtensionpast) # 0 then
          i_mess = i_mess+' joint_tension_past'
        endif
      endif
      if math.and(curr_state, volumenow) # 0 then
        i_mess = i_mess+' volume'
      else
        if math.and(curr_state, volumepast) # 0 then
          i_mess = i_mess+' volume_past'
        endif
      endif
      ii = io.out(i_mess)
      iaz = bl.zone.next(iaz)
    endloop
    iab = bl.next(iab)
  endloop
END
@_querystate
log off
return

FISH File Routines

The set of FISH functions described in this section allow data to be written to, and read from, a file. There are three modes: an “ASCII” mode that allows a FISH program to exchange data with other programs, and a “FISH” mode that allows data to be passed between FISH functions and binary mode. In FISH and binary mode, the data are written in binary, without loss of precision; numbers written out in ASCII form may lose precision when read back into a FISH program. In FISH mode, the value of the FISH variable, not the name of the variable, is written to the file.

Table 4: FISH File Functions
file.close
file.open
file.open.pointer
file.open.pos
file.read
file.write

The following FISH intrinsic functions do not perform file operations, but can be used to extract items from ASCII data that are derived from a file.

Table 5: String functions to Parse Input from Files
string.char
string.len
string.sub
string.token
string.token.type

The following example demonstrates the use of the FISH file functions

fish def setup
  a_size = 20
  IO_READ  = 0
  IO_WRITE = 1
  IO_FISH  = 0
  IO_ASCII = 1
  filename = 'junk.dat'
end
@setup
;
fish def io
  array aa(a_size) bb(a_size)
;
  ; ASCII I/O TEST ------------------
  status = file.open(filename, IO_WRITE, IO_ASCII)
  aa(1)  = 'Line 1 ... Fred'
  aa(2)  = 'Line 2 ... Joe'
  aa(3)  = 'Line 3 ... Roger'
  status = file.write(aa,3)
  status = file.close
  status = file.open(filename, IO_READ, IO_ASCII)
  status = file.read(bb, a_size)
  if status # 3 then
    oo = out(' Bad number of lines')
  endif
  status = file.close
;
  ; now check results...
  loop n (1,3)
    if string.token(bb(n), 2) # n then
      oo = io.out(' Bad 2nd item in loop ' + string(n))
      exit
    endif
  endloop
;
  if string.token.type(bb(3), 4) # 3 then
    oo = io.out(' Not a string')
    exit
  endif
;
  ; FISH I/O TEST -----------------
  status = file.open(filename, IO_WRITE, IO_FISH)
  funny_int   = 1234567
  funny_float = 1.2345e6
  aa(1)  = '---> All tests passed OK'
  aa(2)  = funny_int
  aa(3)  = funny_float
;
  status = file.write(aa,3)
  status = file.close
  status = file.open(filename, IO_READ, IO_FISH)
  status = file.read(bb, 3)
  status = file.close
;
  ; now check results...
  if type(bb(1)) # 3 then
    oo = io.out(' Bad FISH string read/write')
      exit
    endif
  if bb(2) # funny_int then
    oo = io.out(' Bad FISH integer read/write')
    exit
  endif
  if bb(3) # funny_float then
    oo = io.out(' Bad FISH float read/write')
    exit
  endif
  oo = io.out(bb(1)) ; (should be a good message)
  command
    sys 'del junk.dat'
  endcommand
end
;
@io

Directly Communicating with Other Programs

FISH contains the option to allow data to be exchanged between two or more Itasca codes running as separate processes, using socket connections (as used for TCP/IP transmission over the Internet). It is possible to pass data between two or more instances of the same code (e.g., two instances of UDEC), but the main use is anticipated to be coupling of dissimilar codes such as UDEC and PFC2D. The data contained in FISH arrays may be passed in either direction between two codes. The data are transmitted in binary with no loss of precision. Up to six data channels may be open at any one time; these may exist between two codes, or may connect several codes simultaneously. The following FISH intrinsics are provided.

Table 6: FISH Functions for Socket Communication
socket.close
socket.create
socket.delete
socket.open
socket.read
socket.read.array
socket.write
socket.write.array

The following example demonstrates the server side of the Communication

fish def serve
  array arr(3)
  arr(1) = 1234
  arr(2) = 57.89
  arr(3) = 'hello from the server'
  oo = socket.open(1,1)
  oo = socket.write(arr,3,1)
  oo = socket.read(arr,1,1)
  oo = socket.close(1)
  oo = io.out(arr(1))
end
@serve

The following example demonstrates the client side of the Communication

fish def client
  array arr(3)
  oo = socket.open(0,1)
  oo = socket.read(arr,3,1)
  oo = io.out(' Received values ... ')
  oo = io.out('   '+string(arr(1)))
  oo = io.out('   '+string(arr(2)))
  oo = io.out('   '+string(arr(3)))
  arr(1) = 'greetings from the client'
  oo = socket.write(arr,1,1)
  oo = socket.close(1)
end
;   Received values should be...
;     1234
;     5.7890E+01
;     hello from the server
@client

Further Information

We have now covered some aspects of the FISH language and how it interacts with UDEC. A complete guide to the language, including language rules, statements, intrinsic functions, and examples, is provided in the FISH Scripting section of this Help file.