Arrays, sets, lists, and records



The Mosel language defines the structured types set, array, list, and record. So far we have worked with arrays and sets relying on an intuitive understanding of what is an `array' or a `set'. More formally, we may define an array as a collection of labeled objects of a given type where the label of an array entry is defined by its index tuple.

A set collects objects of the same type without establishing an order among them (as opposed to arrays and lists). Set elements are unique: if the same element is added twice the set still only contains it once.

A list groups objects of the same type. Unlike sets, a list may contain the same element several times. The order of the list elements is specified by construction.

Mosel arrays, sets and lists may be defined for any type, that is the elementary types (including the basic types integer, real, string, boolean and the MP types mpvar and linctr), structured types (array, set, list, record), and external types (contributed to the language by a module).

A record is a finite collection of objects of any type. Each component of a record is called a field and is characterized by its name and its type.

This chapter first presents in a more systematic way the different possibilities of how arrays and sets may be initialized (all of which the reader has already encountered in the examples in the first part), and also shows more advanced ways of working with sets. We then introduce lists, showing how to initialize and access them, and finally give some examples of the use of records.

Arrays

In the first part of this manual we have already encountered many examples that make use of arrays. The most important points are summarized in this section and here is an overview of the topics explained with other examples:

Array declaration

Here are some examples of array definition:

 declarations
  A1: array(1..3) of integer         ! Fixed size array
  F = {"a","b","c"}
  A2: array(F) of real               ! Fixed size array
  A3: array(R:range) of integer      ! Implicitly dynamic
  A4: dynamic array(F) of real       ! Dynamic array
 end-declarations

 writeln("A1:", A1, " A2:", A2, " A3:", A3, " A4:", A4)

! Using the array initialization operator
 A1::[10,20,30]                      ! Range indices are known
 A2::(["a","b","c"])[1.1, 2.5, 3.9]  ! String indices must be stated
 A3::(1..3)[10,20,30]                ! Indices are not konwn

 A2("a"):=5.1                        ! Redefine an entry

 setrandseed(3)
 forall(f in F) A4(f):= 10*random    ! Value assignment
 delcell(A4("a"))                    ! Deleting an array entry
 
 writeln("A1:", A1, " A2:", A2, " A3:", A3, " A4:", A4) 

The output produced by this model (file arraydef.mos) is the following.

A1:[0,0,0] A2:[0,0,0] A3:[] A4:[]
A1:[10,20,30] A2:[5.1,2.5,3.9] A3:[(1,10),(2,20),(3,30)] A4:[(`b',7.6693),(`c',5.89101)]

Arrays A1 and A2 are fixed size arrays: their size (i.e. the total number of objects/cells they contain) is known at their declaration because all their indexing sets are of fixed size (i.e. either constant or finalized). All the cells of fixed size arrays are created and initialized immediately, using default initialization values that depend on the array type. For Mosel's basic types these are the following values.
real, integer: 0
boolean: false
string: '' (i.e. the empty string)

Array A4 is explicitly marked as dynamic array using the qualifier dynamic. Dynamic arrays are created empty. Their cells are created explicitly (see Paragraph create below) or when they are assigned a value, that is, the array size will grow `on demand'. It is also possible to delete some or all cells of a dynamic array using the procedure delcell on an entry or the whole array. The value of a cell that has not been created is the default initial value of the type of the array.

Array A3 is created empty since its indexing set is not known at the time of its declaration, but this array is not the same as a dynamic array. Mosel uses a dedicated internal representation that tries to determine the most efficient way of handling the array depending on its usage. This automatic finalization mechanism is explained with some more detail in Appendix Finalizing sets and dynamic arrays.

Multiple indices

Arrays with multiple indices are defined and accessed as follows:

 declarations
  C: array(range, set of string, set of real) of integer
  D: array(1..5) of array(1..10) of real
 end-declarations
 
 C(5,"a",1.5):= 10
 D(1,7):= 2.8 

As shown in the example, in order to access (or 'dereference') the cell of an array of arrays, the list of indices for the second array has to be appended to the list of indices of the first array.

create

Special care needs to be taken in the case of dynamic arrays of decision variables (and indeed with any types that do not have an assignment operator). Writing x:=1 is a syntax error if x is of type mpvar. If an array of such a type is defined as dynamic or the size of at least one of its indexing sets is unknown at declaration time (i.e. empty set), the corresponding cells are not created. The entries of the array must be created explicitly by using the procedure create since they cannot be defined by assignment. Let us simply recall here the example from Section A transport example.

 declarations
  REGION: set of string                 ! Set of customer regions
  PLANT: set of string                  ! Set of plants
  TRANSCAP: dynamic array(PLANT,REGION) of real
                                        ! Capacity on each route plant->region
  flow: dynamic array(PLANT,REGION) of mpvar    ! Flow on each route
 end-declarations
 
 initializations from 'transprt.dat'
  TRANSCAP
 end-initializations
 
! Create the flow variables that exist
 forall(p in PLANT, r in REGION | exists(TRANSCAP(p,r)) ) create(flow(p,r))

For a more detailed discussion of decision variable creation please see Section Conditional variable creation and create.

Array initialization from file

When working with arrays, we distinguish between dense and sparse data formats. Dense data format means that only the data values are represented (see also Section A blending example); in sparse format each data entry is accompanied by its index tuple. Dense format data uses less storage space but this format can only be used if all indices are defined in the model and if no ambiguity results from the omission of the indices when transferring data. In all other cases sparse data format must be used and it is particularly recommended to use this representation if only few entries of a multidimensional array are actually defined.

 declarations
  A: array(1..2,1..3) of real          ! Can use dense format
  B: array(R:range,T:range) of real    ! Requires sparse format
  D: dynamic array(set of string, range) of real   ! Requires sparse format
  S: set of string
  M: dynamic array(S) of integer       ! Requires sparse format
  N: dynamic array(S) of string        ! Requires sparse format
 end-declarations
 
 initializations from "arrayinit.dat"
  A  B           
  D as "SomeName"                      ! Data label different from model name
  D as "SomeName2"                     ! Add some more data to 'D'
  [M,N] as "MNData"                    ! 2 arrays read from the same table
 end-initializations 
 
 writeln("A:", A, " B:", B, "\nD:", D, "\nM:", M, "\nN:", N)

With this contents of the data file arrayinit.dat

A: [2 4 6 8 10 12]
B: [(1 1) 2 (1 2) 4 (1 3) 6 (2 1) 8 (2 2) 10 (2 3) 12]
SomeName: [("a" 1) 2 ("a" 2) 4 ("b" 3) 6 ("c" 4) 8 ("b" 5) 10]
SomeName2: [("a" 3) 12 ("b" 2) 14 ("b" 5) 16]
MNData: [ ("A") [2 "a"] ("B") [* "b"] 
          ("C") [6 *]   ("D") [8 "c"]
          ("E") [10 "b"] ] 

we see the following output display when executing the model arrayinit.mos shown above:

A:[2,4,6,8,10,12] B:[2,4,6,8,10,12]
D:[(`a',1,2),(`a',2,4),(`a',3,12),(`b',2,14),(`b',3,6),(`b',5,16),(`c',4,8)]
M:[(`A',2),(`C',6),(`D',8),(`E',10)]
N:[(`A',a),(`B',b),(`D',c),(`E',b)] 

By default, Mosel expects that data labels are the same as the model names. For array D we show how to read data using different labels. The contents of the second set of data labeled SomeName2 is added to what is read from SomeName. Note that the entry (b,5) is contained in both sets, and the corresponding array entry takes its value from the last label that is read.

Arrays such as M and N, that share the same index sets (but not necessarily the same entries) can be read from a single label/data table. The `*' in certain entries of MNData indicates that the entry does not exist in one of the arrays.

The syntax of initializations blocks remains the same when switching to other data sources. Sections Reading data from spreadsheets and databases and Excel spreadsheets discuss examples of using databases or spreadsheets instead of text files for array initialization. For further detail on data I/O using different data sources the reader is refered to the Xpress whitepaper Using ODBC and other database interfaces with Mosel.

Automatic arrays: the array operator

The keyword array can be used as an aggregate operator in order to create an array that will exist only for the duration of the expression. This automatic array may be used wherever a reference to an array is expected, for instance, in function calls or in initializations blocks.

In the following example we use the array operator to extract the (1-dimensional) rows and column arrays from a 2-dimensional array, we further generate a subarray with a selection of entries and the transposed (inversed indices) array.

model "Automatic arrays"

 declarations
  B: array(S:set of string, I:set of real) of integer
 end-declarations
 
 B::(["a","b"], [3,1.5,7])[1,2,3,4,5,6]
 writeln("B: ", B)

 forall(s in S) writeln("Row ", s, ": ", array(i in I) B(s,i))
 forall(i in I) writeln("Column ", i, ": ", array(s in S) B(s,i))

 writeln("B filtered: ", array(s in S,i in I | s<>"a" and i<5) B(s,i))

 writeln("Transpose: ", array(i in I, s in S) B(s,i)) 
end-model 

And this is the output generated by the model autoarr.mos.

B: [(`a',3,1),(`a',1.5,2),(`a',7,3),(`b',3,4),(`b',1.5,5),(`b',7,6)]
Row a: [(3,1),(1.5,2),(7,3)]
Row b: [(3,4),(1.5,5),(7,6)]
Column 3: [(`a',1),(`b',4)]
Column 1.5: [(`a',2),(`b',5)]
Column 7: [(`a',3),(`b',6)]
B filtered: [(`b',3,4),(`b',1.5,5)]
Transpose: [(3,`a',1),(3,`b',4),(1.5,`a',2),(1.5,`b',5),(7,`a',3),(7,`b',6)] 

On the topic of output to file using initializations to, see Chapter Output, and particularly the note on solution output using arrays generated 'on the fly' in combination with evaluation of in Section Solution output with initializations to.

Initializing sets

In the revised formulation of the burglar problem in Chapter Some illustrative examples and also in the models in Chapter More advanced modeling features we have already seen different examples for the use of index sets. We recall here the relevant parts of the respective models.

Constant sets

In the Burglar example the index set is assigned directly in the model:

 declarations
  ITEMS={"camera", "necklace", "vase", "picture", "tv", "video",
         "chest", "brick"}
 end-declarations

Since in this example the set contents is set in the declarations section, the index set ITEMS is a constant set (its contents cannot be changed). To declare it as a dynamic set, the contents needs to be assigned after its declaration:

 declarations
  ITEMS: set of string
 end-declarations

 ITEMS:={"camera", "necklace", "vase", "picture", "tv", "video",
         "chest", "brick"}

Set initialization from file, finalized and fixed sets

In Chapter Integer Programming the reader has encountered several examples how the contents of sets may be initialized from data files.

The contents of the set may be read in directly as in the following case:

 declarations
  WHICH: set of integer
 end-declarations

 initilizations from 'idata.dat'
  WHICH
 end-initializations  

Where idata.dat contains data in the following format:

WHICH: [1 4 7 11 14]

Unless a set is constant, arrays that are indexed by this set are created as dynamic arrays. Since in many cases the contents of a set does not change any more after its initialization, Mosel provides the finalize statement that turns a (dynamic) set into a constant set. Consider the continuation of the example above:

 finalize(WHICH)

 declarations
  x: array(WHICH) of mpvar
 end-declarations

The array of variables x will be created as a static array, without the finalize statement it would be dynamic since the index set WHICH may still be subject to changes. Declaring arrays in the form of static arrays is preferable if the indexing set is known before because this allows Mosel to handle them in a more efficient way.

Index sets may also be initialized indirectly during the initialization of dynamic arrays:

 declarations
  REGION: set of string
  DEMAND: array(REGION) of real
 end-declarations

 initializations from 'transprt.dat'
  DEMAND
 end-initilizations  

If file transprt.dat contains the data:

DEMAND: [(Scotland) 2840 (North) 2800 (West) 2600 (SEast) 2820 (Midlands) 2750]

then printing the set REGION after the initialization will give the following output:

{`Scotland',`North',`West',`SEast',`Midlands'}

Once a set is used for indexing an array (of data, decision variables etc.) it is fixed, that is, its elements can no longer be removed, but it may still grow in size.

The indirect initialization of (index) sets is not restricted to the case that data is input from file. In the following example (model chess3.mos) we add an array of variable descriptions to the chess problem introduced in Chapter Getting started with Mosel. These descriptions may, for instance, be used for generating a nice output. Since the array DescrV and its indexing set Allvars are dynamic they grow with each new variable description that is added to DescrV.

model "Chess 3"
 uses "mmxprs"

 declarations
  Allvars: set of mpvar
  DescrV: array(Allvars) of string
  small, large: mpvar
 end-declarations

 DescrV(small):= "Number of small chess sets"
 DescrV(large):= "Number of large chess sets"
 
 Profit:=  5*small + 20*large
 Lathe:=   3*small + 2*large <= 160
 Boxwood:=   small + 3*large <= 200
  
 maximize(Profit)

 writeln("Solution:\n Objective: ", getobjval)
 writeln(DescrV(small), ": ", getsol(small))
 writeln(DescrV(large), ": ", getsol(large))

end-model

The reader may have already remarked another feature that is illustrated by this example: the indexing set Allvars is of type mpvar. So far only basic types have occurred as index set types but as mentioned earlier, sets in Mosel may be of any elementary type, including the MP types mpvar and linctr.

Working with sets

In all examples of sets given so far sets are used for indexing other modeling objects. But they may also be used for different purposes.

The following example (model setops.mos) demonstrates the use of basic set operations in Mosel: union (+), intersection (*), and difference (-):

model "Set example" 

 declarations 
  Cities={"rome", "bristol", "london", "paris", "liverpool"}
  Ports={"plymouth", "bristol", "glasgow", "london", "calais", 
              "liverpool"}
  Capitals={"rome", "london", "paris", "madrid", "berlin"}
 end-declarations

 Places:= Cities+Ports+Capitals        ! Create the union of all 3 sets 
 In_all_three:= Cities*Ports*Capitals  ! Create the intersection of all 3 sets 
 Cities_not_cap:= Cities-Capitals      ! Create the set of all cities that are
                                       ! not capitals

 writeln("Union of all places: ", Places)
 writeln("Intersection of all three: ", In_all_three)
 writeln("Cities that are not capitals: ", Cities_not_cap)

end-model

The output of this example will look as follows:

  Union of all places:{`rome',`bristol',`london',`paris',`liverpool',
  `plymouth',`bristol',`glasgow',`calais',`liverpool',`rome',`paris',
  `madrid',`berlin'}
  Intersection of all three: {`london'}
  Cities that are not capitals: {`bristol',`liverpool}

Sets in Mosel are indeed a powerful facility for programming as in the following example (model prime.mos) that calculates all prime numbers between 2 and some given limit.

Starting with the smallest one, the algorithm takes every element of a set of numbers SNumbers (positive numbers between 2 and some upper limit that may be specified when running the model), adds it to the set of prime numbers SPrime and removes the number and all its multiples from the set SNumbers.

model Prime

 parameters
  LIMIT=100                     ! Search for prime numbers in 2..LIMIT
 end-parameters

 declarations
  SNumbers: set of integer      ! Set of numbers to be checked
  SPrime: set of integer        ! Set of prime numbers
 end-declarations

 SNumbers:={2..LIMIT} 
 
 writeln("Prime numbers between 2 and ", LIMIT, ":")

 n:=2
 repeat
   while (not(n in SNumbers)) n+=1
   SPrime += {n}                ! n is a prime number
   i:=n
   while (i<=LIMIT) do          ! Remove n and all its multiples
     SNumbers-= {i}
     i+=n
   end-do
 until SNumbers={}    

 writeln(SPrime)
 writeln(" (", getsize(SPrime), " prime numbers.)")
 
end-model

This example uses a new function, getsize, that if applied to a set returns the number of elements of the set. The condition in the while loop is the logical negation of an expression, marked with not: the loop is repeated as long as the condition n in SNumbers is not satisfied.

Set operators

The preceding example introduces the operator += to add sets to a set (there is also an operator -= to remove subsets from a set). Another set operator used in the example is in denoting that a single object is contained in a set. We have already encountered this operator in the enumeration of indices for the forall loop.

Mosel also defines the standard operators for comparing sets: subset (<=), superset (>=), difference (<>), end equality (=). Their use is illustrated by the following example (model setcomp.mos):

model "Set comparisons"

 declarations
  RAINBOW = {"red", "orange", "yellow", "green", "blue", "purple"}
  BRIGHT = {"yellow", "orange"}
  DARK = {"blue", "brown", "black"}
 end-declarations

 writeln("BRIGHT is included in RAINBOW: ", BRIGHT <= RAINBOW) 
 writeln("RAINBOW is a superset of DARK: ", RAINBOW >= DARK)
 writeln("BRIGHT is different from DARK: ", BRIGHT <> DARK)
 writeln("BRIGHT is the same as RAINBOW: ", BRIGHT = RAINBOW)

end-model

As one might have expected, this example produces the following output:

BRIGHT is included in RAINBOW: true 
RAINBOW is a superset of DARK: false
BRIGHT is different from DARK: true
BRIGHT is the same as RAINBOW: false

Initializing lists

Lists are not commonly used in the standard formulation of Mathematical Programming problems. However, this data structure may be useful for the Mosel implementation of some more advanced solving and programming tasks.

Constant list

If the contents of a list are specified at the declaration of the list, such as

 declarations
  L = [1,2,3,4,5,6,7,8,9,10]
 end-declarations

we have defined a constant list (its contents cannot be changed). If we want to be able to modify the list contents subsequently we need to separate the definition of the list contents from the declaration, resulting in a dynamic list:

 declarations
  L: list of integer
 end-declarations
 
 L:= [1,2,3,4,5,6,7,8,9,10] 

A two-dimensional array of lists may be defined thus (and higher dimensional arrays by analogy):

 declarations
  M: array(range,set of integer) of list of string
 end-declarations
 
 M:: (2..4,1)[['A','B','C'], ['D','E'], ['F','G','H','I']] 

List initialization from file

Similarly to what we have already seen for other data structures, the contents of lists may be initialized from file through initializations blocks. For example,

 declarations
  K: list of integer
  N: array(range,set of integer) of list of string
 end-declarations
 
 initializations from "listinit.dat"
  K  N
 end-initializations

 writeln("K: ", K)
 writeln("An entry of N: ", N(5,3)) 

Assuming the datafile listinit.dat contains these lines

K: [5,4,3,2,1,1,2,3,4,5]

N: [(3) ['B','C','A']
    (5) ['K','L']
    (5) ['D','E']
    (6) ['H','I','F','G']] 

we obtain the following output from the model fragment above:

K: [5,4,3,2,1,1,2,3,4,5]
An entry of N: [`K',`L',`D',`E']  

Working with lists

Enumeration

Similarly to the way we have used sets so far, lists may be used as loop indices for enumeration. The following enumerates a given list L from beginning to end:

 declarations
  L: list of integer
 end-declarations
 
 L:= [1,2,3,4,5] 

 forall(i in L) writeln(i) 

Since lists have an ordering we may choose, for instance, to reverse the order of list elements for the enumeration. The model listenum.mos below shows several possibilities for enumerating lists in inverse order: (1) reversing a copy of the list to enumerate, (2) reversing the list to enumerate. In the first case we obtain the reversed copy of the list with function getreverse, in the second case we modify the original list by applying to it the procedure reverse.

model "Reversing lists"

 declarations
  K,L: list of integer
 end-declarations
 
 L:= [1,2,3,4,5] 

! Enumeration in inverse order:
 ! 1. Reversed copy of the list (i.e., no change to 'L')
 K:=getreverse(L)
 forall(i in K) writeln(i)

 ! 2. Reversing the list itself
 reverse(L)
 forall(i in L) writeln(i) 

end-model 

List operators

Lists are composed by concatenating several lists or by truncating their extremities (refered to as head and tail). The operators += and + serve for concatenating lists. Their inverses (-= and -) may be used to remove the tail of a list—they will not remove the given sublist if it is not positioned at the end.

The following model listops.mos shows some examples of the use of list operators. Besides the concatenation operators + and += we also use the aggregate form sum. Another list operator used in this example is the comparison operator <> (the comparison operator = may also be used with lists).

model "List operators"

 declarations
  L,M: list of integer
 end-declarations
 
 L:= [1,2,3] + [4,5]; writeln("L (1): ", L) 
 L+= [6,7,8]; writeln("L (2): ", L) 
 L-= [1,2,3]; writeln("L (3): ", L) 

 M:= sum(l in L) [l*2]; writeln("M: ", M)
 
 writeln("L and M are different: ", L<>M)

end-model 

As can be seen in the output, the list [1,2,3] is not removed from L since it is not located at its tail:

L (1): [1,2,3,4,5]
L (2): [1,2,3,4,5,6,7,8]
L (3): [1,2,3,4,5,6,7,8]
M: [2,4,6,8,10,12,14,16]
L and M are different: true 

List handling functions

The Mosel subroutines for list handling form two groups, namely

The following example listmerge.mos merges two lists of integers K and L, the elements of which are ordered in increasing order of their values into a new list M that is ordered in the same way. The elements of the two original lists are added one-by-one to the new list using the concatenation operator +=. Whilst the elements of the list K are simply enumerated, we iteratively split off the first element from list L (using splithead with second argument 1 to take away just the first list element) so that this list will be empty at the end of the forall loop. If this is not desired, we need to work with a copy of this list.

model "Merging lists"

 declarations
  K,L,M: list of integer
 end-declarations
 
 K:= [1,4,5,8,9,10,13]
 L:= [-1,0,4,6,7,8,9,9,11,11] 

 forall(k in K) do
  while (L<>[] and k >= getfirst(L))  M += splithead(L,1)
  M+= [k]
 end-do  
 
 writeln(M)

end-model 

The resulting list M is:

[-1,0,1,4,4,5,6,7,8,8,9,9,9,10,11,11,13] 

List handling routines provide a powerful means of programming, illustrated by the following example euler.mos that constructs a Eulerian circuit for the network shown in Figure Network forming a Eulerian circuit (thick arrows indicate that the corresponding arc is to be used twice). This example is an alternative implementation of the Eulerian circuit algorithm described in Section 15.4 `Gritting roads' (problem j4grit) of the book 'Applications of optimization with Xpress-MP'.

MoselUG/j4grit2b

Figure 10.1: Network forming a Eulerian circuit

A Eulerian circuit is a tour through a network that uses every given arc exactly once. To construct such a circuit for a given set of arcs we may employ the following algorithm

model "Eulerian circuit"

 declarations
  NODES = 1..12                        ! Set of nodes
  UNUSED: array(NODES) of list of integer
  TOUR: list of integer
  NEWT, TAIL: list of integer
 end-declarations

 initializations from 'euler.dat'
  UNUSED
 end-initializations
 
 ct:=sum(i in NODES) getsize(UNUSED(i))

 TOUR:=[1]                            ! Choose node 1 as start point

 while(ct>0) do                       ! While there are unused arcs:
     ! Find first node in TOUR with unused outgoing arc(s) 
  node:=0
  forall(i in TOUR)
   if UNUSED(i) <> [] then
    node:=i
    break
   end-if

     ! Insertion position (first occurrence of 'node' in TOUR)  
  pos:= findfirst(TOUR, node) 

     ! Construct a new subtour starting from 'node'
  cur:=node                           ! Start with current node
  NEWT:=[]
  while(UNUSED(cur) <> []) do
   NEWT+=splithead(UNUSED(cur),1)     ! Take first unused arc
   cur:=getlast(NEWT)                 ! End point of arc is new current node
  end-do
  
     ! Stop if the subtour is not a closed loop (=> no Eulerian circuit)
  if cur<>node then                   ! Compare start and end of subtour
   writeln("Tour cannot be closed")
   exit(1)
  end-if 

     ! Add the new subtour to the main journey 
  TAIL:=splittail(TOUR, -pos)         ! Split off the tail from main tour
  TOUR += NEWT + TAIL                 ! Attach subtour and tail to main tour
  ct -= getsize(NEWT)
 end-do
 
 writeln("Tour: ", TOUR)              ! Print the result
 
end-model 

The data file euler.dat corresponding to the graph in Figure Network forming a Eulerian circuit has the following contents:

UNUSED: [(1) [2 5] (2) [3 5 6] (3) [2 4 4] (4) [3 8 8] 
         (5) [1 1 6 6] (6) [2 5 7 9 9 10] (7) [3 6 8 11] 
         (8) [4 11 12] (9) [5 10] (10) [6 6 7] 
         (11) [7 7 10] (12) [11] ] 

A Eulerian circuit for this data set is the tour

1 Maths/rightarrow.png 2 Maths/rightarrow.png 6 Maths/rightarrow.png 5 Maths/rightarrow.png 6 Maths/rightarrow.png 7 Maths/rightarrow.png 8 Maths/rightarrow.png 12 Maths/rightarrow.png 11 Maths/rightarrow.png 7 Maths/rightarrow.png 11 Maths/rightarrow.png 10 Maths/rightarrow.png 7 Maths/rightarrow.png 3 Maths/rightarrow.png 4 Maths/rightarrow.png 3 Maths/rightarrow.png 4 Maths/rightarrow.png 8 Maths/rightarrow.png 4 Maths/rightarrow.png 8 Maths/rightarrow.png 11 Maths/rightarrow.png 7 Maths/rightarrow.png 6 Maths/rightarrow.png 9 Maths/rightarrow.png 5 Maths/rightarrow.png 6 Maths/rightarrow.png 9 Maths/rightarrow.png 10 Maths/rightarrow.png 6 Maths/rightarrow.png 10 Maths/rightarrow.png 6 Maths/rightarrow.png 2 Maths/rightarrow.png 3 Maths/rightarrow.png 2 Maths/rightarrow.png 5 Maths/rightarrow.png 1 Maths/rightarrow.png 5 Maths/rightarrow.png 1

Records

Records group Mosel objects of different types. They may be used, for instance, to structure the data of a large-scale model by collecting all information relating to the same object.

Defining records

The definition of a record has some similarities with the declarations block: it starts with the keyword record, followed by a list of field names and types, and the keyword end-record marks the end of the definition. The definition of records must be placed in a declarations block. The following code extract defines a record with two fields (`name' and `values').

 declarations 
  R = 1..10
  D: record
   name: string
   values: array(R) of real
  end-record
 end-declarations

We need to define a name (e.g., `mydata') for the record if we want to be able to refer to it elsewhere in the model. For example:

 declarations 
  R = 1..10
  mydata = record
   name: string
   values: array(R) of real
  end-record
  D: mydata
  A: array(range) of mydata
 end-declarations

The fields of a record are accessed by appending .fieldname to the record, for instance:

 D.name:= "D"
 forall(i in R) D.values(i):= i
 writeln("Values of ", D.name, ": ", D.values)
 
 writeln("An entry of A: ", A(1))
 writeln("'name' of an entry of A: ", A(4).name)
 writeln("'values' of an entry of A: ", A(3).values)
 writeln("First entry of 'values': ", A(3).values(1)) 

Note: if a record field is an array, the index set(s) of the array must be either constant or be declared outside of the record definition. So, these are valid record definitions:

 declarations 
  R: range
  P: record
   values: array(R) of real
  end-record

  Q: record
   values: array(1..10) of real
  end-record
 end-declarations

whereas this form can not be used:

  Q: record
   values: array(range) of real
  end-record 

Initialization of records from file

The contents of a record may be assigned fieldwise within a model as shown above or else be read in from file using initializations. The data file must contain the data entries for the different record fields in their order of occurrence in the record definition. An array A of the record type mydata defined in the previous section is initialized with data from file in the obvious way (model recorddef.mos):

 declarations 
  A: array(T:range) of mydata
 end-declarations
 
 initializations from "recorddef.dat"
  A
 end-initializations

 writeln(A(1))
 forall(i in T | exists(A(i))) writeln(A(i).name) 

If the data file recorddef.dat has these contents:

A: [(1) ['A1' [(2) 2 (3) 3 (4) 4] ] 
    (3) ['A3' [(3) 6 (6) 9] ]
    (4) ['A4' [5 6 7 8] ]
    (7) ['A7']                      ! Define just the first field
    (6) [* [(6) 6] ]                ! Skip the first field
   ]  

we obtain the following output (the entry with index 6 is defined but has no name, which accounts for the empty line between 'A4' and 'A7'):

[name=`A1' values=[0,2,3,4,0,0,0,0,0,0]]
A1
A3
A4

A7  

An example of the use of records is the encoding of arcs and associated information such as for representing the network in Figure Network with costs on arcs.

MoselUG/arcs

Figure 10.2: Network with costs on arcs

A data file with the network data may look as follows (file arcs.dat):

ARC: [(1) ["A" "B" 2]
      (2) ["A" "D" 4]
      (3) ["A" "C" 7]
      (4) ["B" "F" 4]
      (5) ["B" "D" 3]
      (6) ["C" "B" 5]
      (7) ["C" "D" 1]
      (8) ["C" "E" 1]
      (9) ["D" "F" 2]
     (10) ["D" "E" 5]
     (11) ["E" "F" 8] ]  

We may then write our model arcs.mos thus

model "Arcs"

 declarations
  NODES: set of string                  ! Set of nodes
  ARC: array(ARCSET:range) of record    ! Arcs:
   Source,Sink: string                  !   Source and sink of arc
   Cost: real                           !   Cost coefficient
  end-record 
 end-declarations

 initializations from 'arcs.dat'
  ARC
 end-initializations

! Calculate the set of nodes
 NODES:=union(a in ARCSET) {ARC(a).Source, ARC(a).Sink}
 writeln(NODES)

 writeln("Average arc cost: ", sum(a in ARCSET) ARC(a).Cost / getsize(ARCSET) )

end-model 

The record definition may contain additional fields (e.g., decision variables) that are not to be initialized from file. In this case we need to specify in the initializations block which record fields are to be filled with data.

 declarations
  NODES: set of string                  ! Set of nodes
  ARC: array(ARCSET:range) of record    ! Arcs:
   flow: mpvar                          !   Flow quantity
   Source,Sink: string                  !   Source and sink of arc
   Cost: real                           !   Cost coefficient
  end-record 
 end-declarations

 initializations from 'arcs.dat'
  ARC(Source,Sink,Cost)
 end-initializations 

This functionality can also be used to read separately, and possibly from different sources, the contents of the record fields. For instance, the 'Cost' field of our record ARC could be initialized as follows:

 initializations from 'arcs.dat'
  ARC(Cost) as "COST"
 end-initializations 

where the data array 'COST' is given as

COST: [(1) 2 (2) 4 (3) 7 (4) 4 (5) 3 (6) 5 
       (7) 1 (8) 1 (9) 2 (10) 5 (11) 8] 

User types

In a Mosel model, the user may define new types that will be treated in the same way as the predefined types of the Mosel language. New types are defined in declarations blocks by specifying a type name, followed by =, and the definition of the type. The simplest form of a type definition is to introduce a new name for an existing type, such as:

declarations
  myint = integer
  myreal = real
 end-declarations 

In the section on records above we have already seen an example of a user type definition for records (where we have named the record `mydata'). Another possible use of a user type is as a kind of `shorthand' where several (data) arrays have the same structure, such as in the model blend.mos from Chapter Some illustrative examples, where, instead of

 declarations
  ORES = 1..2                    ! Range of ores

  COST: array(ORES) of real      ! Unit cost of ores
  AVAIL: array(ORES) of real     ! Availability of ores
  GRADE: array(ORES) of real     ! Grade of ores (measured per unit of mass)
 end-declarations 

we could have written

 declarations
  ORES = 1..2                    ! Range of ores

  myarray = array(ORES) of real  ! Define a user type

  COST: myarray                  ! Unit cost of ores
  AVAIL: myarray                 ! Availability of ores
  GRADE: myarray                 ! Grade of ores (measured per unit of mass)
 end-declarations 

without making any other modifications to the model.



If you have any comments or suggestions about these pages, please send mail to support@fico.com.

© Copyright 2001-2013 Fair Isaac Corporation. All rights reserved.