Initializing help system before first use

Packages

Topics covered in this chapter:

A package is a library written in the Mosel language (this feature was introduced by Mosel 2.0). Its structure is similar to models, replacing the keyword model by package. Packages are included into models with the uses statement for dynamic loading (the package BIM needs to be present for model execution), in the same way as this is the case for modules (DSO). Alternatively, packages can be loaded statically via imports in which case they get included in the model BIM file (this option is not available for modules that are always dynamic). Unlike Mosel code that is included into a model with the include statement, packages are compiled separately, that is, their contents are not visible to the user.

Typical uses of packages include

  • development of your personal `tool box'
  • model parts (e.g., reformulations) or algorithms written in Mosel that you wish to distribute without disclosing their contents
  • add-ons to modules that are more easily written in the Mosel language

Packages may define new constants, subroutines, types, and parameters for the Mosel language as shown in the following examples (the first two examples correspond to the first two module examples of the Mosel Native Interface User Guide).

Please note the following:

  1. Package name: compiling a package will result in a file packagename.bim. This package is invoked in a Mosel model by the statement
    uses "packagename"
    The name of the Mosel package source file (.mos file) may be different from the name given to the BIM file.
  2. Internal package name: the name given in the Mosel file after the keyword package is the internal name of the package. It must be a valid Mosel identifier (and not a string). This name may be different from the name given to the BIM file, but it seems convenient to use the same name for both.
  3. Package location: for locating packages Mosel applies similar rules as for locating modules; it first uses the list of prefixes defined by the compiler option -bx or the environment variable MOSEL_BIM, it then searches in the directory dso of the Xpress installation (see Section 2.3.1 Directive uses of the Mosel Language Reference for further detail), and in the directories pointed to by the environment variable MOSEL_DSO. The contents of the enviroment variables MOSEL_BIM and MOSEL_DSO can be set freely by the user.
    To try out the package examples in this chapter, you may simply include the current working directory ('.') in the locations pointed to by MOSEL_DSO, so that packages in the current working directory will be found, for example:
    Windows: set MOSEL_DSO=.
    Unix/Linux, C shell: setenv MOSEL_DSO .
    Unix/Linux, Bourne shell: export MOSEL_DSO; MOSEL_DSO=.
    Alternatively, you can use the compilation option -bx to indicate the location of package files (this option does not apply to DSOs):
    mosel exe -bx ./ mymodel.mos
    In general, and in particular for the deployment of an application, it is recommended to work with absolute paths in the definition of environment variables.

Definition of constants

The following package myconstants defines one integer, one real, one string, and two boolean constants.

package myconstants

 public declarations
  MYCST_BIGM = 10000          ! A large integer value
  MYCST_TOL = 0.00001         ! A tolerance value
  MYCST_LINE =                ! String constant
     "----------------------------------------------------------------"
  MYCST_FLAG = true           ! Constant with value true
  MYCST_NOFLAG = false        ! Constant with value false
 end-declarations

end-package

The structure of a package is similar to the structure of Mosel models, with the difference that we use the keyword package instead of model to mark its beginning and end.

After compiling our package with the standard Mosel command (assuming the package is saved in file myconstants.mos)

mosel comp myconstants

it can be used in a Mosel model (file myconst_test.mos):

model "Test myconstants package"
 uses "myconstants"

 writeln(MYCST_LINE)
 writeln("BigM value: ", MYCST_BIGM, ", tolerance value: ", MYCST_TOL)
 writeln("Boolean flags: ", MYCST_FLAG, " ", MYCST_NOFLAG)
 writeln(MYCST_LINE)

end-model

Having made sure that Mosel is able to find our package myconstants.bim (see the notes about package location at Packages), executing the test model above will produce the following output:

----------------------------------------------------------------
BigM value: 10000, tolerance value: 1e-05
Boolean flags: true false
----------------------------------------------------------------

When comparing with the C implementation of the module example myconstants in the Mosel Native Interface User Guide we can easily see that the package version is much shorter.

Definition of subroutines

We now show a package (file solarraypkg.mos) that defines several versions of a subroutine, solarray, which copies the solution values of an array of decision variables of type mpvar into an array of real of the same size. For each desired number (1–3) and type (integer or string) of array indices we need to define a new version of this subroutine.

package solarraypkg

! **** Integer indices (including ranges) ****
 public procedure solarray(x:array(R:set of integer) of mpvar,
                           s:array(set of integer) of real)
  forall(i in R) s(i):=getsol(x(i))
 end-procedure

 public procedure solarray(x:array(R1:set of integer,
                                   R2:set of integer) of mpvar,
                           s:array(set of integer,
                                   set of integer) of real)
  forall(i in R1, j in R2) s(i,j):=getsol(x(i,j))
 end-procedure

 public procedure solarray(x:array(R1:set of integer,
                                   R2:set of integer,
                                   R3:set of integer) of mpvar,
                           s:array(set of integer,
                                   set of integer,
                                   set of integer) of real)
  forall(i in R1, j in R2, k in R3) s(i,j,k):=getsol(x(i,j,k))
 end-procedure

! ****String indices ****
 public procedure solarray(x:array(R:set of string) of mpvar,
                           s:array(set of string) of real)
  forall(i in R) s(i):=getsol(x(i))
 end-procedure

 public procedure solarray(x:array(R1:set of string,
                                   R2:set of string) of mpvar,
                           s:array(set of string,
                                   set of string) of real)
  forall(i in R1, j in R2) s(i,j):=getsol(x(i,j))
 end-procedure

 public procedure solarray(x:array(R1:set of string,
                                   R2:set of string,
                                   R3:set of string) of mpvar,
                           s:array(set of string,
                                   set of string,
                                   set of string) of real)
  forall(i in R1, j in R2, k in R3) s(i,j,k):=getsol(x(i,j,k))
 end-procedure
end-package

Using the package in a Mosel model (file solarrpkg_test.mos):

model "Test solarray package"
 uses "solarraypkg", "mmxprs"

 declarations
  R1=1..2
  R2={6,7,9}
  R3={5,-1}
  x: array(R1,R2,R3) of mpvar
  sol: array(R1,R2,R3) of real
 end-declarations

! Define and solve a small problem
 sum(i in R1, j in R2, k in R3) (i+j+2*k) * x(i,j,k) <= 20
 forall(i in R1, j in R2, k in R3) x(i,j,k)<=1
 maximize(sum(i in R1, j in R2, k in R3) (i+2*j+k) * x(i,j,k))

! Get the solution array
 solarray(x,sol)

! Print the solution
 forall(i in R1, j in R2, k in R3)
  writeln(" (", i, ",", j, ",", k, ") ", sol(i,j,k), " ", getsol(x(i,j,k)))
 writeln(sol)

end-model

Output produced by this model:

 (1,6,-1) 1 1
 (1,6,5) 0 0
 (1,7,-1) 1 1
 (1,7,5) 0 0
 (1,9,-1) 1 1
 (1,9,5) 0 0
 (2,6,-1) 0.166667 0.166667
 (2,6,5) 0 0
 (2,7,-1) 0 0
 (2,7,5) 0 0
 (2,9,-1) 0 0
 (2,9,5) 0 0
[1,0,1,0,1,0,0.166667,0,0,0,0,0] 

This example may be classified as a `utility function' that eases tasks occurring in a similar way in several of your models. Another example of such a utility function could be a printing function that simply outputs the solution value of a decision variable with some fixed format (if you apply write/writeln to a decision variable of type mpvar you obtain the pointer of the variable, and not its solution value).

If we again make the comparison with the implementation as a module we see that both ways have their positive and negative points: the implementation as a module is clearly more technical, requiring a considerable amount of C code not immediately related to the implementation of the function itself. However, at the C level we simply check that the two arguments have the same index sets, without having to provide a separate implementation for every case, thus making the definition more general.

Implementation alternative using union types and iterator

A generic implementation of the subroutine solarray (see file solarranypkg.mos) can be achieved with the help of union types in combination with the iterator functionality provided by the module mmreflect that makes it possible to enumerate arrays of dense or sparse format without any explicit knowledge about their indexing sets. As a first step, this routine needs to check whether the two arguments are indeed arrays of the expected types (mpvar and real respectively), followed by a comparison of the index sets, similarly to the C implementation as a module. This new implementation accepts any type and number of index sets and is therefore considerably more generic than any set of explicitly defined overloaded subroutine versions.

package solarrayanypkg
 uses "mmreflect"

 ! Utility routine checking whether 2 arrays have the same index set types
 function checkindices(x:any, s:any): boolean
   returned:= x.array.nbdim=s.array.nbdim and
     and(i in 1.. x.array.nbdim)
       x.array.index(i).eltype=s.array.index(i).eltype
 end-function

 ! Generic implementation of solarray routine
 public procedure solarray(x:any, s:any)
   declarations
     it: iterator
   end-declarations
   if x is array of mpvar and s is array of real then
     if checkindices(x,s) then
       inititer(it,x.array)
       reset(s.array)
       while (nextcell(it)) s.array(it).real:= x.array(it).mpvar.sol
     else
       writeln("SOLARRAY: array indices don't match")
     end-if
   else
     writeln("SOLARRAY: arguments must be arrays of type mpvar / real")
   end-if
 end-procedure

end-package

This subroutine solarray provided by the package solarranypkg is used exactly in the same way as what we have seen earlier for the package solarrpkg.

Definition of types

In Section Initialization of records from file we have seen the example arcs.mos that defines a record to represent arcs of a network. If we wish to use this data structure in different models we may move its definition into a package 'arcpkg' to avoid having to repeat it in every model.

Such a package may look as follows (file arcpkg.mos):

package arcpkg

 public declarations
  arc = public record                   ! Arcs:
   Source,Sink: string                  !   Source and sink of arc
   Cost: real                           !   Cost coefficient
  end-record
 end-declarations

end-package  

which is used thus from the model file:

model "Arcs2"
 uses "arcpkg"

 declarations
  NODES: set of string                  ! Set of nodes
  ARC: array(ARCSET:range) of arc       ! Arcs
 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  

At this place, the use of the keyword public may call for some explanation. Here and also in the example `myconstants' the whole declarations block is preceded by the public marker, indicating that all objects declared in the block are public (i.e., usable outside of the package definition file). If only some declarations are public and others in the same block are private to the package, the public marker needs to preceed the name of every object within the declarations that is to become public instead of marking the entire block as public.

The second occurrence of the public marker in the definition of package `arcpkg' is immediately in front of the keyword record, meaning that all fields of the record are public. Again, it is possible to select which fields are accessible from external files (for example, you may wish to reserve some fields for special flags or calculations within your package) by moving the keyword public from the record definition in front of every field name that is to be marked as public.

A definition of package `arcpkg' equivalent to the one printed above therefore is the following.

package arcpkg2

 declarations
  public arc = record                   ! Arcs:
   public Source,Sink: string           !   Source and sink of arc
   public Cost: real                    !   Cost coefficient
  end-record
 end-declarations

end-package  

Definition of parameters

Mosel parameters are scalars of one of the four basic types (real/integer/string/boolean). Packages can define new parameters by declaring their names and type in the parameters section. The package needs to store the current values of the parameters in separate model entities and will usually initialize default values for the parameters.

package parpkg

 ! Specify parameter names and types
 parameters
  "p1":real
  "p2":integer
  "p3":string
  "p4":boolean
 end-parameters

 ! Entities for storing current parameter values
 declarations
  myp1: real
  myp2: integer
  myp3: string
  myp4: boolean
 end-declarations

 ! Set default values for parameters
 myp1:=0.25
 myp2:=10
 myp3:="default"
 myp4:=true

 !... Parameter access routines ...
end-package

The access routines for the four parameter types have a fixed format, namely packagename∼get[r|i|s|b]param and packagename∼set[r|i|s|b]param as shown in the code extract below.

 ! Get value of a real parameter
 public function parpkg~getrparam(p:string):real
  case p of
   "p1": returned:=myp1
  end-case
 end-function

 ! Get value of an integer parameter
 public function parpkg~getiparam(p:string):integer
  case p of
   "p2": returned:=myp2
  end-case
 end-function

 ! Get value of a string parameter
 public function parpkg~getsparam(p:string):string
  case p of
   "p3": returned:=myp3
  end-case
 end-function

 ! Get value of a boolean parameter
 public function parpkg~getbparam(p:string):boolean
  case p of
   "p4": returned:=myp4
  end-case
 end-function

 ! Set value for real parameters
 public procedure parpkg~setparam(p:string,v:real)
  case p of
   "p1": myp1:=v
  end-case
 end-procedure

 ! Set value for integer parameters
 public procedure parpkg~setparam(p:string,v:integer)
  case p of
   "p2": myp2:=v
  end-case
 end-procedure

 ! Set value for string parameters
 public procedure parpkg~setparam(p:string,v:string)
  case p of
   "p3": myp3:=v
  end-case
 end-procedure

 ! Set value procedure for boolean parameters
 public procedure parpkg~setparam(p:string,v:boolean)
  case p of
   "p4": myp4:=v
  end-case
 end-procedure

A model using the package 'parpkg' will access the package parameters via Mosel's standard getparam and setparam routines ( the parameter names are not case-sensitive and their names can be preceded by the package name).

model "Packages with parameters"
 uses 'parpkg'

 ! Display default parameter values
 writeln("Default values:",
   " p1=", getparam("parpkg.P1"), " p2=", getparam("P2"),
   " p3=", getparam("parpkg.p3"), " p4=", getparam("p4"))

 ! Change values
 setparam("p1",133)
 setparam("parpkg.p2",-77)
 setparam("P3","tluafed")
 setparam("parpkg.P4",not getparam("parpkg.P4"))

end-model

Namespaces

A namespace is a group of identifiers in a program that is distinguished by a common name (prefix). When working with mutiple packages it can be helpful to introduce namespaces in order to structure the data and to determine which model entities are accessible to other (all or preselected) packages or models.

A fully qualified entity name in Mosel is of the form

nspc~ident

where nspc is a namespace name and ident an identifier in the namespace. Namespaces and their access are specified via specific compiler directives at the start of the model or package. The package example mynspkg1 below defines three namespaces ('ns1', 'ns3', and 'ns3∼ns31'), two of which are restricted to a namespace group that comprises a second package mynspkg2, and the namespace 'ns3' is visible to all packages and models. The package further states via the nssearch directive that any unqualified entity names employed in the package should be searched for in the namespace 'ns1', meaning that the names belonging to this namespace can be used without the namespace prefix ns1∼.

package mynspkg1
 namespace ns1, ns3, ns3~ns31         ! This package defines 3 namespaces:
 nsgroup ns1: "mynspkg2"              !  * ns1 + ns3~ns31 restricted to pkg2
 nsgroup ns3~ns31: "mynspkg2"         !  * ns3 is visible to all
 nssearch ns1                         ! 'ns1' can be used without prefix

 declarations
   ns3~R = 1..10
   ns1~Ar: array(ns3~R) of integer    ! Array with index set in another namespace
   vi, ns3~vi, ns3~ns31~vi: integer   ! 3 different entities
 end-declarations

 public declarations
   vp: integer                        ! This entity is visible to all
 end-declarations

 procedure ns1~proc1(val:integer)     ! Subroutine in a namespace
   ns3~vi:=val; ns3~ns31~vi:=2*val; vi:=val; vp:=val
   Ar(5):=val                         ! No prefix: 'ns1' is in search list
   writeln(" In ns1~proc1: ", vi)
 end-procedure

 public procedure proc2(val:integer)  ! Public subroutine
   writeln(" In proc2: ", val)
 end-procedure

 procedure proc3(val:integer)         ! Private subroutine
   writeln(" In proc3: ", val)
 end-procedure
end-package 

The package mynspkg1 shows some examples of entity and subroutine defintions for the three cases: private (vi, proc3), in a namespace (ns3∼R, ns1∼Ar, ns3∼vi, ns3∼ns31∼vi, ns1∼proc1), and public (vp, proc2).

The second package mynspkg2 that uses functionality from mynspkg1 needs to state which namespaces are used, either via a namespace or a nssearch directive.

package mynspkg2
 uses 'mynspkg1'
 namespace ns3~ns31                   ! Namespace used in this package
 nssearch ns1                         ! 'ns1' can be used without prefix

 public procedure frompkg2(val: integer)
   proc1(val)                         ! Procedure in namespace 'ns1'
   writeln("  frompkg2:",ns3~ns31~vi) ! Namespace 'ns3~ns31' is not searched
   writeln("  vp=", vp)               ! Public symbol of pkg1
   writeln("  Ar(5)=", Ar(5))         ! Contained in 'ns1' (prefix optional)
 end-procedure
end-package 

Any model or further package using the previous two packages can access the namespace 'ns3' and also define new namespaces of its own, but it is not allowed to access the other two namespaces that are restricted to this group of packages.

model "mynstest"
 uses 'mynspkg1', 'mynspkg2'
 namespace ns2     ! A new namespace
 nssearch ns3      ! Symbols from 'ns3' can be used without prefix

 frompkg2(5)                           ! Public routine from package mynspkg1
 writeln("n3~vi:", vi, " vp:", vp)     ! Display values of n3~vi and vp

 proc2(4)                              ! Public subroutine from mynspkg1

end-model 

An interesting feature of namespaces is that an entire namespace can be saved via initializations to simply by indicating its name and the stored information can subsequently be used to initialize entities in some other namespace with matching names and types.

 declarations
   ns2~vi: integer
   I, ns2~R: range
 end-declarations

 ! Store contents of namespace 'ns3'
 initializations to "mem:mynsav"
   ns3
 end-initializations

 ! Initialize entities with matching names from the saved namespace
 initializations from "mem:mynsav"
   ns2 as "ns3"
 end-initializations
 writeln("ns2~vi:", ns2~vi)            ! Has received the value of ns3~vi

 ! Read an individual entity from the saved namespace
 initializations from "mem:mynsav"
   I as "ns3~R"
 end-initializations
 writeln("I:", I)  

Package version management

Packages, just like Mosel models, can use the version compiler directive to define a version number of the format MMM.mmm.ppp (that is, up to 3 digits for each, major, minor, and patch numbers). Dynamic package loading relies on this number to decide whether the available package version is compatible with the required package version information recorded in a BIM file using this package. Mosel applies as a default compatibility rule that the (available/found) package version ap.bp.cp is compatible with the required package version ar.br.cr if ar=ap and bp>=br. For many cases, this rule is too restrictive (in particular, if you are not using any newer package functionality, a model might be expected to continue working with more recent package versions than the one it has been compiled with, as long as newer package versions continue to provide the original functionality).

Consider the following example of a package file versionedpkg.mos with three versions that incrementally add new (public) subroutines.

Initial version (1.0):

package versionedpkg
 version 1.0

!@mc.version.since 1.0.0
 public procedure showpkgver
   writeln("Using version ", getparam("model_version"),
     " of package 'versionedpkg'.")
 end-procedure

end-package 

Second version (2.0):

package versionedpkg
 version 2.0
!@mc.version.compatible 1.0.0

!@mc.version.since 2.0
 public procedure myproc2
  writeln("Output from proc2")
 end-procedure

!@mc.version.since 2.0
 public procedure myoldproc
  writeln("This subroutine is deprecated with v3.0.1")
 end-procedure

!@mc.version.since 1.0.0
 public procedure showpkgver
   writeln("Using version ", getparam("model_version"),
     " of package 'versionedpkg'.")
 end-procedure

end-package 

Current version (3.1):

package versionedpkg
 version 3.1
!@mc.version.compatible 1.0.0
!@mc.version.atleast 3.0

!@mc.version.since 2.0
 public procedure myproc2
  writeln("Output from proc2")
 end-procedure

!@mc.version.since 3.0.2
 public procedure myproc3
  writeln("Output from proc3")
 end-procedure

!@mc.version.deprecated 3.0.1
!@mc.version.since 2.0
 public procedure myoldproc
  writeln("This subroutine is deprecated with v3.0.1")
 end-procedure

! Unversioned subroutine (defaults to current package version)
 public procedure mynewproc
  writeln("Output from newproc")
 end-procedure

!@mc.version.since 1.0.0
 public procedure showpkgver
   writeln("Using version ", getparam("model_version"),
     " of package 'versionedpkg'.")
 end-procedure

end-package 

These three versions of the package 'versionedpkg' specify version management annotations to define version compatibility rules and deprecation markup for subroutines. (The concept of annotations is explained more in detailed in Chapter Annotations.) A Mosel program using package functionality might look as follows:

model "test package versioning"
 uses "versionedpkg"

 ! Calling a subroutine defined since the initial package version
 showpkgver

end-model 

If we have used the initial package version (1.0.0) for compiling this model we shall see in the output of Mosel's exam command on the resulting BIM file versionedpkg_test.bim the following dependency information:

Model `test package versioning' version 0.0.0
 file: .\versionedpkg_test.bim
 sys. com.: `versionedpkg_test.mos',mc6.4.0
(...)
 pkg. req.: versionedpkg (1.0.0)

Procedures and Functions:
 procedure showpkgver [reqmt]

This means, that the compiled model can be run with a BIM file generated from any of the package versions above, since all of them are marked as compatible with version 1.0.0. The actual package version that is in use when running the model file will be displayed by the routine 'showpkgver', for example, with the third package version we will see the output:

Using version 3.1.0 of package 'versionedpkg'.

If we now recompile the model file with a BIM file generated with the third version (3.1.0), we see the required package version in the output from the exam command change to 3.0.0 (we are not using any functionality from this version, but this package is tagged as requiring at least version 3.0). If we now add a call to the subroutine 'myproc3' into the model, the minimum required version will be raised to 3.0.2. Using the subroutine 'mynewproc' that does not have any version annotation will further raise the minimum version requirement to the current package version (3.1.0).

If we call the subroutine 'myoldproc' along with 'myproc3' from our model file, we shall see a deprecation warning displayed since this routine is marked as deprecated since 3.0.1, which is in conflict with the minimum version 3.0.2 that is induced by the use of 'myproc3':

Mosel: W-241 at line 39 of `versionedpkg_test.mos': Using deprecated subroutine `myoldproc'.

Packages vs. modules

The possibility of writing packages introduces a second form of libraries for Mosel, the first being modules (see the `Mosel Native Interface User Guide' for further detail). The following list summarizes the main differences between packages and modules.

  • Definition
    • Package
      • library written in the Mosel language
    • Module
      • dynamic library written in C that obeys the conventions of the Mosel Native Interface
  • Functionality
    • Package
      • define
        • symbols
        • subroutines
        • types
        • control parameters
    • Module
      • extend the Mosel language with
        • constant symbols
        • subroutines
        • operators
        • types
        • control parameters
        • I/O drivers
  • Efficiency
    • Package
      • like standard Mosel models
    • Module
      • faster execution speed
      • higher development effort
  • Use
    • Package
      • making parts of Mosel models re-usable
      • deployment of Mosel code whilst protecting your intellectual property
    • Module
      • connection to external software
      • time-critical tasks
      • definition of new I/O drivers and operators for the Mosel language

As can be seen from the list above, the choice between packages and modules depends largely on the contents and intended use of the library you wish to write.


© 2001-2024 Fair Isaac Corporation. All rights reserved. This documentation is the property of Fair Isaac Corporation (“FICO”). Receipt or possession of this documentation does not convey rights to disclose, reproduce, make derivative works, use, or allow others to use it except solely for internal evaluation purposes to determine whether to purchase a license to the software described in this documentation, or as otherwise set forth in a written software license agreement between you and FICO (or a FICO affiliate). Use of this documentation and the software described in it must conform strictly to the foregoing permitted uses, and no other use is permitted.