Loop - EndLoop
The four valid loop constructions are:
Indexed Loop
While Loop
For Loop
ForEach Loop
The lines of FISH between loop and endloop are executed repeatedly until certain conditions are met. Specific details of each loop option are given below:
Loops may be nested to any depth.
Indexed Loop
In the first form, which uses a numeric counter, var is given the value of expr1 initially, and is incremented by expr3 at the end of each loop execution until it reaches a value greater than expr2. If expr3 is not given, a value of 1 is used by default.
The local statement may optionally be used before var to create var as a variable local to the function.
Note that expr1, expr2, and expr3 (which may be arbitrary expressions) are evaluated at the start of the loop; redefinition of their component variables within the loop has no effect on the number of loop executions.
Also note that the comparison of var with the result of expr2 is done at the end of the loop, so the interior of the loop will always be executed at least once regardless of the values.
var is a genaral FISH variable; it may be used in expressions within the loop (even in functions called from within the loop, if it is a global variable), and may even be redefined. The increment of the value designated by expr3 is done in terms of the general FISH + operator, and the comparison with expr2 is done with the < operator. Any type(s) compatibile with those operators may be used,although most commonly the type used is an integer or a real.
The code snippet below demonstrates basic usage of this form to sum all integers below a specified value.
fish define sum(n) local s = 0 loop local i (1,n) s += i endloop sum = s end [s = sum(10)] fish list [s] fish define sum_even(n) local s = 0 loop local i (0,n,2) s += i endloop sum_even = s end [s2 = sum_even(10)] fish list [s2]
With n`=:lint:`10, the end result of the functions is s = 55 and s2 = 30. Note that the loop value may be an integer or a real, and by using a negative value for expr3 it may go from a larger to a smaller value.
While Loop
In the second form of the loop structure, the loop body is executed while the test expression evaluates to true; otherwise control passes to the next line after the endloop statement.
The expressions may involve floating-point variables as well as integers; the use of Boolean, strings and pointers is also permitted under the same conditions that apply to the if statement.
The code snippet below demonstrate basic usage of this form to sum all even integers below a specified value.
fish define sum_even(n) local s = 0 local i = 0 loop while i <= n s += i i +=2 endloop sum_even = s end [s2 = sum_even(10)] fish list [s2]
For Loop
The main function of the third form of the loop structure is to repeat execution of the loop body while the test expression is true, similar to the loop while form. But, in addition, the loop for construct provides specific locations to contain an initialize field and a modify field. So this loop is specially designed to perform a repetitive action with a counter which is initialized and modified on each iteration. It works in the following way.
Initialization is executed. Generally it is an initial value setting for a counter variable (which can be local or global). This is executed only once.
Condition is checked. If it is true, the loop continues; otherwise, the loop ends and the loop body is not executed.
Loop body is executed.
Finally, whatever is specified in the modify field is executed, and the loop goes back to step 2.
The code snippet below demonstrate basic usage of this form to sum all odd integers below a specified value.
fish define sum_odd(n) local s= 0 loop for (local i=1, i <=n, i+=2) s += i endloop sum_odd = s end [s_odd = sum_odd(10)] fish list [s_odd]
With n = 10, the end result of the sum is 25.
The first three forms of the loop statement may be contrasted. In the first, the test is done at the end of the loop (so there will be at least one pass through the loop); in the second and third, the test is done at the start of the loop (so the loop will be bypassed if the test is false initially).
ForEach Loop
The fourth form of the loop statement is a specialized syntax to allow iterating through all objects inside a given iterable object.
In this case, expr1 must evaluate to an iterable object, most commonly a pointer to a container or a list.
For example, all scalars in the list of user-defined scalars are returned by the FISH intrinsic data.scalar.list
.
As with the first loop form, var may be preceded by the local statement to indicate that a local variable is to be created instead of a global one. var will be assigned to each value in the iterable object consecutively.
If the object being looped over is a pointer to a container, and var is deleted during processing of the loop, the loop will continue normally as long as var is not referenced again within the same loop after deletion. If other items in the container are deleted (for example, if a function that deletes the contents of the container is called), then the loop might exit prematurely.
fish define scalar_location loop foreach local pnt data.scalar.list io.out(data.scalar.pos(pnt)) endloop end
exit loop
and continue
Loop Control Statements
Within the loop, the exit loop
statement may be used to break out of the loop and continue processing after the endloop
statement.
In addition, the continue
statement may be used to stop processing a current loop iteration and continue to the next iteration, without executing code that may lie between it and the endloop
statement.
Object Iteration: Example and Discussion
The following example illustrates use of the loop
statements above to iterate over the objects of a model, while including, for comparison, discussion of the use of FISH splitting and FISH operators (both multithreaded methods) to perform iteration, as described in Splitting: Using Multiple Threads in FISH.
For a range of reasons, it is often necessary to query or modify values associated with all, or some of, the objects in a model: to measure results like stress or displacement, to assign a calculated extra variable for plotting, or to adjust a property value.
Iteration of aggregate types—objects—may be performed using loops, splitting, and operators. Respectively, each of these methods is syntactically more succinct, and faster. The apparent redundancy is a matter of historical precedence.
In the earliest versions of Itasca software, aggregate types were stored in linked lists. The standard approach was to use a while
loop to traverse all the pointers of any object.
Next, the foreach
loop was introduced in FISH to simplify iteration. In contexts requiring traversal of two object types (e.g., blocks and zones in 3DEC) foreach
eliminates nested loops that are required when using the while
approach. Note that if you are using the “converto” tool in 3DEC or FLAC3D, any While Loops will be retained, although with updated syntax (e.g., zone_head will become zone.head), and not converted to ForEach Loops. CS: is preceding statement needed anymore?
With the advent of multi-threaded FISH, splitting and operators were introduced. FISH splitting uses an operator (::) to allow repeated execution of a function on each element of an aggregate type (a list, array, container of objects, etc.). FISH operators are a special class of function designed to be executed in a multi-threaded environment.
Example
Note
The project file for this example is available to be viewed/run in FLAC3D.[1] The project’s main data file is shown at the end of this example.
This example reproduces HoekBrownSlope.dat from the FLAC3D example Simple Slope in Hoek-Brown Material (FLAC3D). In that example, a constant value of 30 MPa (see the second zone property
command in the main data file) is assigned across the model to the Hoek-Brown intact rock strength parameter (σci — the Hoek-Brown constitutive model property “constant-sci” in FLAC3D).
In the current variation, the command model random
9999
is added, where 9999 is the seed value. This ensures that the same pseudo-random values will be generated in the model each time it is run.
Using four different iterative methods in turn (while
, foreach
, splitting, and a FISH operator), the constant-sci property value in each zone is adjusted using a pseudo-random uniform distribution of 30 ±10 MPa, providing a degree of rock strength variability across the model, as shown in the figure below.

Figure 1: Slope model showing the value of the Hoek-Brown intact rock strength property after a uniform distribution has been assigned using loops, splitting, or operators.
while
Loop
The following FISH function uses a while
loop to produce, for each zone, a random value using the intrinsic FISH function math.random.uniform
, which pseudo-randomly generates a value between 0 and 1. Multiplying this value by the lower desired strength and adding the difference between the upper and lower strengths gives a distribution of values ranging from 20 to 40 MPa. Properties are assigned using the intrinsic zone.prop
, where fred
is the zone pointer and the property name is a string, in this case “constant-sci.” Property names are found by referencing the documentation for the constitutive model assigned to the model zones (c hoek-brown in this case).
fish define random_Sci_LW
fred = zone.head ;assign fred as the zone pointer
loop while fred # null ;# = not
local rnd_constantsci = ( 20. + 20.*( math.random.uniform() ) )
zone.prop(fred,'constant-sci') = rnd_constantsci
fred = zone.next(fred) ;advance to the next pointer
endloop
end
foreach
Loop
In the following FISH function (random_Sci_FE), the foreach
loop is used. This syntax is more compact than the while
loop, and is available for practically every object; In the following FISH function, the loop traverses the zone.list
, which contains all the zone pointers, again assigned to the variable fred
. Note that the use of the local keyword before any variable is optional and provides flexibility to reuse the same variable name(s) in multiple functions without another function overriding another. Random values are generated and assigned to the constant-sci property in each zone as before.
fish define random_Sci_FE
loop foreach local fred zone.list
local rnd_constantsci = ( 20. + 20.*( math.random.uniform() ) )
zone.prop(fred,'constant-sci') = rnd_constantsci
endloop
end
Splitting
Splitting can be used as an alternative to loop statements to perform actions on many objects in a very clear and concise manner. In order to make a split call, the split operator ‘::’ must prefix one or more arguments of the function, operator, or library call. Arguments that are not split will be the same in every execution of the function. As you can see in the following FISH function (random_Sci_LIST), splitting performs in a single line what takes six lines in the while
loop example. On one line, zone properties randomly generated on the right-hand side of the equation are assigned to the zones in the list. Without the ::=, a single random value would be assigned to each zone “constant-sci” property. It is also critical that the number of zones be specified within the math.random.uniform
intrinsic FISH function so that a similar number of values as there are zones are generated. This can be done easily using the intrinsic FISH function list.size
.
fish define random_Sci_LIST
zone.prop(::zone.list,'constant-sci') ::= ...
20. + 20.*(math.random.uniform(list.size(zone.list)))
end
Effectively using splitting requires a certain change of perspective and approach from traditional sequential programming. But once the user becomes comfortable, the reward is being able to very quickly and relatively efficiently perform operations on large quantities of data using a relatively small amount of code.
Operators
While splitting is very convenient, it is, in general, not as efficient in a multi-threaded environment as a FISH operator that does multiple calculations on a single object at the same time using a single split. If speed is important (as is generally the case for functions executing during cycling, for example), it is almost always worth the effort to create an operator instead of using multiple splitting implementations on existing FISH intrinsics.
FISH operators are a special class of function designed to be executed in a multi-threaded environment.
Operators are created using the fish operator command, with arguments following just like a regular FISH function using the fish define command. The only difference is that there is no loop; the zone pointer is simply an input to the operator.
In the following FISH operator example (also called random_Sci_LIST), the operator is declared by specifying fish operator
(rather than fish define
). An input (fred) is given as an argument. The zone “constant-sci” property is pseudo-randomly generated and assigned as in the loop examples. However, when the operator is called, the operator input fred
is defined by splitting the zone.list
.
fish operator random_Sci_LIST(fred)
zone.prop(fred,'constant-sci') = (20. + 20.*(math.random.uniform()))
end
On a typical modern multi-core computer and a large set of data, this approach can result in a very significant increase in speed.
Performance
This example project was clocked with a timing test for each of the FISH functions above by calling the time.clock
intrinsic function at the start and end of the function and calculating the difference. The hardware configuration for the test was an Intel(R) Core(TM) Ultra 9 185H, 2500 Mhz, 16 cores with 22 logical processors. The following figure shows the average time required for each approach discussed above (in hundredths of a second) for a model with 228,000 zones.
Figure 2: Time (in hundredths of a second) required for each of the four iterative approaches discussed above.
While any of the methods reviewed above are effectively instantaneous in this model, the performance will become more important for very large models with millions or tens of millions of objects (zones, blocks, etc.), and/or if such functions are being called during cycling (i.e., each step). As such, the advantage of a method (operators) that is three times faster than another (loop while
) is apparent.
zone property-distribution
Command
It should be noted that the effect of each of the above FISH functions is also achieved using the zone property-distribution
command.
zone property-distribution constant-sci 30 deviation-uniform 10
For this Hoek-Brown slope example, using the above command would be the best way to achieve a random uniform property distribution for “constant-sci”. Commands are compiled C++ code and will likely execute faster than any function.
loops-splitting-operators.dat
model new
model random 9999
model large-strain off
; --- geometry ---
zone create brick point 0 ( 0,0,0) point 1 (40,0, 0) ...
point 2 ( 0,1, 0) point 3 ( 0,0, 8) size 800 1 160
zone create brick point 0 (15,0,8) point 1 (40,0, 8) ...
point 2 (15,1, 8) point 3 (25,0,18) ...
point 4 (40,1,8) point 5 (25,1,18) ...
point 6 (40,0,18) point 7 (40,1,18) size 500 1 200
zone face skin ; Label model boundaries
; --- Assign model and properties
zone cmodel assign hoek-brown
zone property density 2.5e-3 young 5000 poisson 0.3 tension 1e10
zone property constant-mb 0.067 constant-s 2.5e-5 ...
constant-a 0.619 constant-sci 30
zone property flag-evolution 1 flag-fos 0
; --- boundary conditions ---
zone face apply velocity-normal 0 range position-x 0
zone face apply velocity-normal 0 range position-x 40
zone face apply velocity-normal 0 range position-y 0
zone face apply velocity-normal 0 range position-y 1
zone face apply velocity-normal 0 range position-z 0
; --- settings ---
model gravity 9.81
model save 'initial'
;LOOP WHILE APPROACH | ** deprecated for this application
model restore 'initial'
fish define random_Sci_LW
fred = zone.head ;assign fred as the zone pointer
loop while fred # null ;# = not
local rnd_constantsci = ( 20. + 20.*( math.random.uniform() ) )
zone.prop(fred,'constant-sci') = rnd_constantsci
fred = zone.next(fred) ;advance to the next pointer
endloop
end
[random_Sci_LW]
model save 'whileloop'
;LOOP FOREACH APPROACH
model restore 'initial'
fish define random_Sci_FE
loop foreach local fred zone.list
local rnd_constantsci = ( 20. + 20.*( math.random.uniform() ) )
zone.prop(fred,'constant-sci') = rnd_constantsci
endloop
end
[random_Sci_FE]
model save 'foreachloop'
;SPLITTING APPROACH
model restore 'initial'
fish define random_Sci_LIST
zone.prop(::zone.list,'constant-sci') ::= ...
20. + 20.*(math.random.uniform(list.size(zone.list)))
end
[random_Sci_LIST]
model save 'splitlist'
;OPERATOR APPROACH
model restore 'initial'
fish operator random_Sci_LIST(fred)
zone.prop(fred,'constant-sci') = (20. + 20.*(math.random.uniform()))
end
[random_Sci_LIST(::zone.list)]
model save 'operator'
;*** Try the command approach by executing the following line:
model restore 'initial'
zone property-distribution constant-sci 30 deviation-uniform 10
model save 'command'
Endnotes
Was this helpful? ... | Itasca Software © 2024, Itasca | Updated: Apr 04, 2025 |