Initializing help system before first use

Column generation for cutting-stock with in-memory data I/O


Type: Cutting stock
Rating: 4 (medium-difficult)
Description: This example shows several options of in-memory data exchange between a Mosel model and the calling C application.
  • paper.c, paperm.mos: original version (defining a static module)
  • paperio.c, paperio.mos: using I/O drivers for data exchange in memory
  • paperiom.c, paperiom.mos: keeping the static module for pattern printing, data input/output in memory via I/O drivers
File(s): paper.c, paperm.mos (submodel), paperio.c, paperio.mos (submodel), paperiom.c, paperiom.mos (submodel)

paper.c
/*******************************************************
   Mosel Example Problems
   ====================== 

   file paper.c
   ````````````
   Declaring a "static" module and initializing
   a Mosel array from data stored in C.
   
   (c) 2008 Fair Isaac Corporation
       author: S. Heipcke, 2002
********************************************************/

#include <stdio.h>
#include <stdlib.h>
#include "xprm_mc.h"
#include "xprm_ni.h"

/* Initialization function of the module 'cutstkdata' */
static int cutstkdata_init(XPRMnifct nifct, int *interver,int *libver,
		XPRMdsointer **interf);

/*****************/
/* Main function */
/*****************/
int main()
{
 XPRMmodel mod;
 int result;
 char params[80];
 static int tabdemand[] = {150, 96,   48, 108,  227};
 static double tabwidth[] ={17, 21, 22.5,  24, 29.5};
 int nwidths = 5;

 if(XPRMinit()) return 1;

 /* Register 'cutstkdata' as a static module (=stored in the program) */
 if(XPRMregstatdso("cutstkdata", cutstkdata_init)) return 2;

 /* Parameters: the addresses of the data tables and their sizes */
 sprintf(params, "DDATA='%p',DSIZE=%d,WDATA='%p',WSIZE=%d,NWIDTHS=%d",
         tabdemand, sizeof(tabdemand)/sizeof(int), tabwidth, 
         sizeof(tabwidth)/sizeof(double), nwidths);

 /* Execute the model */
 if(XPRMexecmod("", "paperm.mos", params, &result, &mod)) return 3;
 
 return result;
}

/********************** Body of the module 'cutstkdata' ******************/

static int getcutstkdata_int(XPRMcontext ctx,void *libctx);
static int getcutstkdata_dbl(XPRMcontext ctx,void *libctx);
static int printpattern(XPRMcontext ctx,void *libctx);

static XPRMdsofct tabfct[]=
        {
         {"getcutstkdata",1000,XPRM_TYP_NOT,3,"AI.isi",getcutstkdata_int},
         {"getcutstkdata",1001,XPRM_TYP_NOT,3,"AI.rsi",getcutstkdata_dbl},
         {"printpat",1002,XPRM_TYP_NOT,3,"rAI.iAI.r",printpattern}
        };

static XPRMdsointer dsointer=
        {
         0,NULL,
         sizeof(tabfct)/sizeof(XPRMdsofct),tabfct,
         0,NULL,
         0,NULL
        };

static XPRMnifct mm;             /* To store the mosel function table */

/*****************************************/
/* Initialization function of the module */
/*****************************************/
static int cutstkdata_init(XPRMnifct nifct, int *interver, int *libver,
		XPRMdsointer **interf)
/* The following header is required to compile 'cutstkdata' as a DSO file:
DSO_INIT cutstkdata_init(XPRMnifct nifct, int *interver, int *libver,
		XPRMdsointer **interf)
*/
{
 mm=nifct;                      /* Save the table of functions */
 *interver=XPRM_NIVERS;         /* The interface version we are using */
 *libver=XPRM_MKVER(0,0,1);     /* The version of the module: 0.0.1 */
 *interf=&dsointer;             /* Our interface */

 return 0;
}

/************************************************************/
/* Initialize an array of integer with data held in C:      */
/*  getcutstkdata(array(range) of integer, string, integer) */
/************************************************************/
static int getcutstkdata_int(XPRMcontext ctx,void *libctx)
{
 XPRMarray arr;
 XPRMstring adr_s;
 XPRMset ndxset;
 int *adr,siz,index[1],last,i;

 arr=XPRM_POP_REF(ctx);             /* The array */
 adr_s=XPRM_POP_STRING(ctx);        /* Data location (as a string) */
 siz=XPRM_POP_INT(ctx);             /* Data size */
 sscanf(adr_s,"%p",&adr);           /* Get the address from the string */

 mm->getarrsets(arr,&ndxset);
 index[0]=mm->getfirstsetndx(ndxset);
 last=mm->getlastsetndx(ndxset);
 for(i=0;(i<siz) && (index[0]<=last);i++,index[0]++)
  mm->setarrvalint(ctx,arr,index,adr[i]);
 return XPRM_RT_OK;
}

/**********************************************************/
/* Initialize an array of double with data held in C:     */
/*  getcutstkdata(array(range) of real, string, integer)  */
/**********************************************************/
static int getcutstkdata_dbl(XPRMcontext ctx,void *libctx)
{
 XPRMarray arr;
 XPRMstring adr_s;
 XPRMset ndxset;
 int siz,index[1],last,i;
 double *adr;

 arr=XPRM_POP_REF(ctx);             /* The array */
 adr_s=XPRM_POP_STRING(ctx);        /* Data location (as a string) */
 siz=XPRM_POP_INT(ctx);             /* Data size */
 sscanf(adr_s,"%p",&adr);           /* Get the address from the string */

 mm->getarrsets(arr,&ndxset);
 index[0]=mm->getfirstsetndx(ndxset);
 last=mm->getlastsetndx(ndxset);
 for(i=0;(i<siz) && (index[0]<=last);i++,index[0]++)
  mm->setarrvalreal(ctx,arr,index,adr[i]);
 return XPRM_RT_OK;
}

/******************************************************************/
/* Print the new pattern found:                                   */
/*  printpat(real, array(range) of integer, array(range) of real) */
/******************************************************************/
static int printpattern(XPRMcontext ctx,void *libctx)
{
 XPRMarray varr,warr;
 int v,index[1];
 double dj,w,tw=0;

 dj=XPRM_POP_REAL(ctx); 
 varr=XPRM_POP_REF(ctx);                /* The value array */
 warr=XPRM_POP_REF(ctx);                /* The widths array */

 mm->printf(ctx,"new pattern found with marginal cost %g\n", dj);
 mm->printf(ctx,"   Widths distribution: ");
 mm->getfirstarrentry(varr,index);      /* Get the first index tuple */
 do
 {
  mm->getarrval(varr,index,&v);
  mm->getarrval(warr,index,&w);
  mm->printf(ctx,"%g:%d  ",w,v);
  tw+=v*w;
 } while(!mm->getnextarrentry(varr,index));
 mm->printf(ctx,"Total width: %g\n", tw);
  
 return XPRM_RT_OK;
}


paperm.mos
(!*******************************************************
   Mosel User Guide Examples
   =========================

   file paperm.mos
   ```````````````
   Column generation algorithm for finding (near-)optimal
   solutions for a paper cutting example.
   
   Data in- and output via a static module.
   Model can only be executed from the file paper.c,
   and not in standalone mode.
   
   Heuristic generation of patterns at start.
   
   (c) 2008 Fair Isaac Corporation
       author: S. Heipcke, 2002, rev. Dec. 2017
*******************************************************!)

model Papermill
 uses "mmxprs"
 uses "cutstkdata"                     ! User module for in-memory data input

 parameters
  NWIDTHS = 5                          ! Number of different widths
  WDATA=''                             ! Location of data in memory
  WSIZE=0                              ! Size of the data block (nb of reals)
  DDATA=''                             ! Location of data in memory
  DSIZE=0                              ! Size of the data block (nb of integers)
 end-parameters

 forward procedure column_gen
 forward function knapsack(C:array(range) of real, 
                           A:array(range) of real, 
                           B:real, 
                           D:array(range) of integer, 
                           xbest:array(range) of integer,
                           pass: integer): real
 forward function pat_heur:integer

(!
The column generation algorithm requires the solution of a knapsack problem.
We define the constraints of the knapsack problem globally because Mosel 
does not delete them at the end of a function (due to its principle of incremental model formulation).
!)
 
 declarations
  WIDTHS = 1..NWIDTHS                  ! Range of widths
  RP: range                            ! Range of cutting patterns
  MAXWIDTH = 94                        ! Maximum roll width
  EPS = 1e-6                           ! Zero tolerance

  WIDTH: array(WIDTHS) of real         ! Possible widths
  DEMAND: array(WIDTHS) of integer     ! Demand per width
  PATTERNS: array(WIDTHS, range) of integer  ! (Basic) cutting patterns

  use: dynamic array(RP) of mpvar      ! Rolls per pattern
  soluse: dynamic array(RP) of real    ! Solution values for variables `use' 
  Dem: array(WIDTHS) of linctr         ! Demand constraints
  MinRolls: linctr                     ! Objective function
 
  KnapCtr, KnapObj: linctr             ! Knapsack constraint+objective
  x: array(WIDTHS) of mpvar            ! Knapsack variables
 end-declarations

! WIDTH::  [ 17, 21, 22.5,  24, 29.5] 
! DEMAND:: [150, 96,   48, 108,  227]
 getcutstkdata(WIDTH, WDATA, WSIZE)
 getcutstkdata(DEMAND, DDATA, DSIZE)
                                       ! Make basic patterns
 forall(j in WIDTHS)  PATTERNS(j,j) := floor(MAXWIDTH/WIDTH(j))
(! Alternative starting patterns, particularly for small demand values:
 forall(j in WIDTHS)
   PATTERNS(j,j) := minlist(floor(MAXWIDTH/WIDTH(j)),DEMAND(j)) 
 forall(j in WIDTHS)  PATTERNS(j,j) := 1
!)

 forall(j in WIDTHS) do
  create(use(j))                       ! Create NWIDTHS variables `use'
  use(j) is_integer                    ! Variables are integer and bounded
  use(j) <= integer(ceil(DEMAND(j)/PATTERNS(j,j)))
 end-do

 setparam("zerotol", EPS)              ! Set Mosel comparison tolerance
 
 bnd:=pat_heur                         ! Create heuristic solution + 
                                       ! new non-dominated patterns

 MinRolls:= sum(j in WIDTHS) use(j)    ! Objective: minimize no. of rolls 

                                       ! Satisfy all demands
 forall(i in WIDTHS) 
  Dem(i):= sum(j in WIDTHS) PATTERNS(i,j) * use(j) >= DEMAND(i)  

 column_gen                            ! Column generation at top node

 minimize(MinRolls)                    ! Compute the best integer solution
                                       ! for the current problem (including
                                       ! the new columns) 
 writeln("Best integer solution: ", getobjval, " rolls")
 write("  Rolls per pattern: ")
 forall(i in RP) write(getsol(use(i)),", ")
 writeln   

!************************************************************************
!  Column generation loop at the top node:                               
!    solve the LP and save the basis                                     
!    get the solution values                                             
!    generate new column(s) (=cutting pattern)                           
!    load the modified problem and load the saved basis                  
!************************************************************************
 procedure column_gen
  declarations
   dualdem: array(WIDTHS) of real 
   xbest: array(WIDTHS) of integer
   ubnd, zbest, objval: real 
   bas: basis                          ! LP basis
  end-declarations

  setparam("XPRS_CUTSTRATEGY", 0)      ! Disable automatic cuts 
  setparam("XPRS_PRESOLVE", 0)         ! Switch presolve off
  npatt:=NWIDTHS
  npass:=1

  while(true) do
    minimize(XPRS_LIN, MinRolls)       ! Solve the LP 

    savebasis(bas)                     ! Save the current basis 
    objval:= getobjval                 ! Get the objective value      

                                       ! Get the solution values 
    forall(j in 1..npatt)  soluse(j):=getsol(use(j))
    forall(i in WIDTHS)  dualdem(i):=getdual(Dem(i))
                                       ! Solve a knapsack problem
    zbest:= knapsack(dualdem, WIDTH, MAXWIDTH, DEMAND, xbest, npass) - 1.0

    write("Pass ", npass, ": ")   
    if zbest = 0 then
      writeln("no profitable column found.\n")
      break
    else 
      printpat(zbest, xbest, WIDTH)    ! Print the new pattern
      npatt+=1
      create(use(npatt))               ! Create a new var. for this pattern
      use(npatt) is_integer

      MinRolls+= use(npatt)            ! Add new var. to the objective
      ubnd:=0
      forall(i in WIDTHS)                 
        if xbest(i) > 0 then
         Dem(i)+= xbest(i)*use(npatt)  ! Add new var. to demand constr.s
         ubnd:= maxlist(ubnd, ceil(DEMAND(i)/xbest(i) )) 
        end-if
      use(npatt) <= ubnd               ! Set upper bound on the new var.

      loadprob(MinRolls)               ! Reload the problem 
      loadbasis(bas)                   ! Load the saved basis 
    end-if
    npass+=1
  end-do

  writeln("Solution after column generation: ", objval, " rolls, ",
          getsize(RP), " patterns")
  write("   Rolls per pattern: ")
  forall(i in RP) write(soluse(i),", ")
  writeln   
  
 end-procedure

!************************************************************************
!  Solve the integer knapsack problem                              
!    z = max{cx : ax<=b, x<=d, x in Z^N}                                     
!  with b=MAXWIDTH, N=NWIDTHS, d=DEMAND
! 
! We reset the knapsack constraints to 0 at the end of this function
! so that they do not unnecessarily increase the size of the main 
! problem. The same is true in the other sense: hiding the demand 
! constraints while solving the knapsack problem makes life easier
! for the optimizer, but is not essential for getting the correct 
! solution.
!************************************************************************
 function knapsack(C:array(range) of real,
                   A:array(range) of real, 
                   B:real, 
                   D:array(range) of integer, 
                   xbest:array(range) of integer,
                   pass: integer):real

! Hide the demand constraints
  forall(j in WIDTHS) sethidden(Dem(j), true)

! Define the knapsack problem
  KnapCtr := sum(j in WIDTHS) A(j)*x(j) <= B
  KnapObj := sum(j in WIDTHS) C(j)*x(j)

! Integrality condition
  if(pass=1) then
   forall(j in WIDTHS) x(j) is_integer
   forall(j in WIDTHS) x(j) <= D(j)
  end-if

  maximize(KnapObj)
  returned:=getobjval
  forall(j in WIDTHS) xbest(j):=round(getsol(x(j)))

! Reset knapsack constraint and objective, unhide demand constraints 
  KnapCtr := 0
  KnapObj := 0  
  forall(j in WIDTHS) sethidden(Dem(j), false)

 end-function

!**********************************************************************
! This heuristic assumes that widths are given in ascending order
! * Calculate the theoretical minimum number `minpat' of rolls
! * Assign all items to rolls, starting with the largest items and
!   `minpat' rolls. If an item does not fit, start a new roll.
! * If the resulting number of rolls is equal to `minpat': stop,
!   optimal solution found.
! * Otherwise, complete new patterns to non-dominated patterns by
!   adding the largest-possible item to every new pattern
! * Create the variables `use' for the new patterns 
!**********************************************************************
 function pat_heur:integer
  declarations
   NI: integer
  end-declarations
  
  NI:= sum(j in WIDTHS) DEMAND(j) 

  declarations
   CurCont: array(range) of real
   ALLITEM: array(1..NI) of real
   INDALLITEM: array(1..NI) of integer
   mincont:real
  end-declarations
   
! Calculate the theoretical minimum number of rolls
  largest:=WIDTH(NWIDTHS)
  smallest:=WIDTH(1)
  newpat:=ceil((sum(j in WIDTHS) WIDTH(j)*DEMAND(j))/MAXWIDTH)
  minpat:=newpat
  returned:=newpat
  writeln("Lower bound: ", minpat)

! Copy all widths into the array ALLITEM, according to their occurrences
  ct:=1
  forall(j in WIDTHS)
   forall(k in 1..DEMAND(NWIDTHS-j+1)) do 
    ALLITEM(ct):= WIDTH(NWIDTHS-j+1)
    INDALLITEM(ct):= NWIDTHS-j+1
    ct+=1
   end-do 
   
! Assign the largest items, one to every roll
  forall(i in 1..newpat) do
   PATTERNS(INDALLITEM(i),NWIDTHS+i):=1
   CurCont(i)+=ALLITEM(i)
  end-do  

! Add the remaining items to the rolls
  ct:=newpat+1
  while(ct<=NI) do
   mincont:=MAXWIDTH
   indi:=0
   forall(i in 1..newpat)                    ! Find the least filled roll `indi'
    if(CurCont(i)<mincont) then
     mincont:=CurCont(i)
     indi:=i
    end-if
   if(MAXWIDTH-mincont>=ALLITEM(ct)) then    ! Add the item to roll `indi'
    PATTERNS(INDALLITEM(ct),NWIDTHS+indi)+=1
    CurCont(indi)+=ALLITEM(ct)
   else                                      ! Start a new roll
    newpat+=1
    PATTERNS(INDALLITEM(ct),NWIDTHS+newpat)+=1
    CurCont(newpat)+=ALLITEM(ct)
   end-if  
   ct+=1
  end-do

! Stop if optimal solution found
  if(newpat=minpat) then
   writeln("Heuristic solution is optimal: ", newpat, " patterns.")
   forall(k in 1..newpat) do
    forall(j in WIDTHS) 
     write( if(PATTERNS(j,NWIDTHS+k)>0, " "+WIDTH(j)+":"+PATTERNS(j,NWIDTHS+k),
       ""))
    writeln(" ",sum(j in WIDTHS) PATTERNS(j,NWIDTHS+k)*WIDTH(j))
   end-do 

   exit(3)
  end-if 
   
! Complete to non-dominated patterns  
  forall(k in 1..newpat) do
   totwidth:=MAXWIDTH-sum(j in WIDTHS) PATTERNS(j,NWIDTHS+k)*WIDTH(j)
   if(totwidth >=smallest) then           ! If roll is not filled
    repeat
     if(totwidth>largest) then            ! As long as the largest item fits:
      r:=integer(ceil(NWIDTHS*random))    ! add a randomly chosen item
      PATTERNS(r,NWIDTHS+k)+=1
      totwidth-=WIDTH(r)
     else                                 ! Add the largest possible item
      ct:=0
      while(ct<NWIDTHS and WIDTH(ct+1)<=totwidth) ct+=1
      if(ct>0) then
       PATTERNS(ct,NWIDTHS+k)+=1
       totwidth-=WIDTH(ct)
      end-if
     end-if 
    until (totwidth<smallest) 
   end-if
  end-do
  
(! Create the variables `use' for the new patterns
  ubnd:=max(k in WIDTHS) DEMAND(k)
  forall(k in 1..newpat) do
   create(use(NWIDTHS+k))               ! Create additional variables `use'
   use(NWIDTHS+k) is_integer            ! Variables are integer and bounded
   use(NWIDTHS+k) <= ubnd
  end-do
!)
  writeln("Heuristic solution: ", newpat," patterns")

 end-function

end-model


paperio.c
/*******************************************************
   Mosel Example Problems
   ====================== 

   file paperio.c
   ``````````````
   Initializing a Mosel array from data stored in C 
   using the I/O drivers, and retrieving data from the
   model.
   
   (c) 2008 Fair Isaac Corporation
       author: S. Heipcke, 2002, rev. Feb. 2017
********************************************************/

#include <stdio.h>
#include <stdlib.h>
#include "xprm_mc.h"

/*****************/
/* Main function */
/*****************/
int main()
{
 XPRMmodel mod;
 XPRMalltypes rvalue;
 XPRMmemblk *memblk;
 int result;
 char params[200];
 static int tabdemand[] = {150, 96,   48, 108,  227};
 static double tabwidth[] ={17, 21, 22.5,  24, 29.5};
 static double *tabsol;
 int i, type, sizesol;
 int nwidths = 5;

 if(XPRMinit()) return 1;

 /* Parameters: the addresses of the data tables and their sizes */
 sprintf(params, 
      "DDATA='noindex,mem:%p/%d',WDATA='noindex,mem:%p/%d',NWIDTHS=%d",
      tabdemand, (int)sizeof(tabdemand), tabwidth, 
      (int)sizeof(tabwidth), nwidths);

 /* Execute the model */
 if(XPRMexecmod("", "paperio.mos", params, &result, &mod)) return 2;

 if((XPRMgetprobstat(mod)&XPRM_PBRES)!=XPRM_PBOPT)
  return 3;                             /* Test whether a solution is found */
  
 /* Retrieve solution data */
 type=XPRMfindident(mod,"soltab",&rvalue);     /* Get model object 'soltab' */
 if(XPRM_STR(type)!=XPRM_STR_MEM)              /* Check the type */
  return 4;
  
 memblk=rvalue.memblk;
 tabsol=(double *)(memblk->ref);
 sizesol=(int)(memblk->size/sizeof(double));

 /* Print out the solution */ 
 printf("Best integer solution: %g rolls\n", XPRMgetobjval(mod));
 printf("  Rolls per pattern: ");
 for(i=0;i<sizesol;i++) printf("%g, ", tabsol[i]);
 printf("\n");
 
 return result;
}

paperio.mos
(!*******************************************************
   Mosel User Guide Examples
   =========================

   file paperio.mos
   ````````````````
   Column generation algorithm for finding (near-)optimal
   solutions for a paper cutting example.
   
   Data in- and output using the 'raw' driver.
   Model can only be executed from the file paperio.c,
   and not in standalone mode.
   
   Heuristic generation of patterns at start.
   
   (c) 2008 Fair Isaac Corporation
       author: S. Heipcke, 2005, rev. Dec. 2017
*******************************************************!)

model "Papermill IO"
 uses "mmxprs"

 parameters
  NWIDTHS = 5                          ! Number of different widths
  WDATA=''                             ! Location of data in memory
  DDATA=''                             ! Location of data in memory
 end-parameters

 forward procedure column_gen
 forward function knapsack(C:array(range) of real, 
                           A:array(range) of real, 
                           B:real, 
                           D:array(range) of integer, 
                           xbest:array(range) of integer,
                           pass:integer): real
 forward function pat_heur:integer
 forward procedure printpat(zbest:real, xbest:array(range) of integer, 
		            W:array(range) of real)

(!
The column generation algorithm requires the solution of a knapsack problem.
We define the constraints of the knapsack problem globally because Mosel 
does not delete them at the end of a function (due to its principle of incremental model formulation).
!)
 
 declarations
  WIDTHS = 1..NWIDTHS                  ! Range of widths
  RP: range                            ! Range of cutting patterns
  MAXWIDTH = 94                        ! Maximum roll width
  EPS = 1e-6                           ! Zero tolerance

  WIDTH: array(WIDTHS) of real         ! Possible widths
  DEMAND: array(WIDTHS) of integer     ! Demand per width
  PATTERNS: array(WIDTHS, range) of integer  ! (Basic) cutting patterns

  use: dynamic array(RP) of mpvar      ! Rolls per pattern
  soluse: dynamic array(RP) of real    ! Solution values for variables `use' 
  Dem: array(WIDTHS) of linctr         ! Demand constraints
  MinRolls: linctr                     ! Objective function
 
  KnapCtr, KnapObj: linctr             ! Knapsack constraint+objective
  x: array(WIDTHS) of mpvar            ! Knapsack variables
 end-declarations

! WIDTH::  [ 17, 21, 22.5,  24, 29.5] 
! DEMAND:: [150, 96,   48, 108,  227]

 initializations from "raw:"
  WIDTH as WDATA
  DEMAND as DDATA
 end-initializations
 
                                       ! Make basic patterns
 forall(j in WIDTHS)  PATTERNS(j,j) := floor(MAXWIDTH/WIDTH(j))
(! Alternative starting patterns, particularly for small demand values:
 forall(j in WIDTHS)
   PATTERNS(j,j) := minlist(floor(MAXWIDTH/WIDTH(j)),DEMAND(j)) 
 forall(j in WIDTHS)  PATTERNS(j,j) := 1
!)

 forall(j in WIDTHS) do
  create(use(j))                       ! Create NWIDTHS variables `use'
  use(j) is_integer                    ! Variables are integer and bounded
  use(j) <= integer(ceil(DEMAND(j)/PATTERNS(j,j)))
 end-do

 setparam("zerotol", EPS)              ! Set Mosel comparison tolerance
 
 bnd:=pat_heur                         ! Create heuristic solution + 
                                       ! new non-dominated patterns

 MinRolls:= sum(j in WIDTHS) use(j)    ! Objective: minimize no. of rolls 

                                       ! Satisfy all demands
 forall(i in WIDTHS) 
  Dem(i):= sum(j in WIDTHS) PATTERNS(i,j) * use(j) >= DEMAND(i)  

 column_gen                            ! Column generation at top node

 minimize(MinRolls)                    ! Compute the best integer solution
                                       ! for the current problem (including
                                       ! the new columns) 

                                       ! Save solution to memory
 forall(i in RP) soluse(i):= getsol(use(i))

 initializations to "raw:noindex"
  soluse as "mem:soltab"
 end-initializations

!************************************************************************
!  Column generation loop at the top node:                               
!    solve the LP and save the basis                                     
!    get the solution values                                             
!    generate new column(s) (=cutting pattern)                           
!    load the modified problem and load the saved basis                  
!************************************************************************
 procedure column_gen
  declarations
   dualdem: array(WIDTHS) of real 
   xbest: array(WIDTHS) of integer
   ubnd, zbest, objval: real 
   bas: basis                          ! LP basis
  end-declarations

  setparam("XPRS_CUTSTRATEGY", 0)      ! Disable automatic cuts 
  setparam("XPRS_PRESOLVE", 0)         ! Switch presolve off
  npatt:=NWIDTHS
  npass:=1

  while(true) do
    minimize(XPRS_LIN, MinRolls)       ! Solve the LP 

    savebasis(bas)                     ! Save the current basis 
    objval:= getobjval                 ! Get the objective value      

                                       ! Get the solution values 
    forall(j in 1..npatt)  soluse(j):=getsol(use(j))
    forall(i in WIDTHS)  dualdem(i):=getdual(Dem(i))
                                       ! Solve a knapsack problem
    zbest:= knapsack(dualdem, WIDTH, MAXWIDTH, DEMAND, xbest, npass) - 1.0

    write("Pass ", npass, ": ")   
    if zbest = 0 then
      writeln("no profitable column found.\n")
      break
    else 
      printpat(zbest, xbest, WIDTH)    ! Print the new pattern
      npatt+=1
      create(use(npatt))               ! Create a new var. for this pattern
      use(npatt) is_integer

      MinRolls+= use(npatt)            ! Add new var. to the objective
      ubnd:=0
      forall(i in WIDTHS)                 
        if xbest(i) > 0 then
         Dem(i)+= xbest(i)*use(npatt)  ! Add new var. to demand constr.s
         ubnd:= maxlist(ubnd, ceil(DEMAND(i)/xbest(i) )) 
        end-if
      use(npatt) <= ubnd               ! Set upper bound on the new var.

      loadprob(MinRolls)               ! Reload the problem 
      loadbasis(bas)                   ! Load the saved basis 
    end-if
    npass+=1
  end-do

  writeln("Solution after column generation: ", objval, " rolls, ",
          getsize(RP), " patterns")
  write("   Rolls per pattern: ")
  forall(i in RP) write(soluse(i),", ")
  writeln   
  
 end-procedure

!************************************************************************
!  Solve the integer knapsack problem                              
!    z = max{cx : ax<=b, x<=d, x in Z^N}                                     
!  with b=MAXWIDTH, N=NWIDTHS, d=DEMAND
! 
! We reset the knapsack constraints to 0 at the end of this function
! so that they do not unnecessarily increase the size of the main 
! problem. The same is true in the other sense: hiding the demand 
! constraints while solving the knapsack problem makes life easier
! for the optimizer, but is not essential for getting the correct 
! solution.
!************************************************************************
 function knapsack(C:array(range) of real,
                   A:array(range) of real, 
                   B:real, 
                   D:array(range) of integer, 
                   xbest:array(range) of integer,
                   pass: integer):real

! Hide the demand constraints
  forall(j in WIDTHS) sethidden(Dem(j), true)

! Define the knapsack problem
  KnapCtr := sum(j in WIDTHS) A(j)*x(j) <= B
  KnapObj := sum(j in WIDTHS) C(j)*x(j)

! Integrality condition and bounds
  if(pass=1) then
   forall(j in WIDTHS) x(j) is_integer
   forall(j in WIDTHS) x(j) <= D(j)
  end-if

  maximize(KnapObj)
  returned:=getobjval
  forall(j in WIDTHS) xbest(j):=round(getsol(x(j)))

! Reset knapsack constraint and objective, unhide demand constraints 
  KnapCtr := 0
  KnapObj := 0  
  forall(j in WIDTHS) sethidden(Dem(j), false)

 end-function

!**********************************************************************
! This heuristic assumes that widths are given in ascending order
! * Calculate the theoretical minimum number `minpat' of rolls
! * Assign all items to rolls, starting with the largest items and
!   `minpat' rolls. If an item does not fit, start a new roll.
! * If the resulting number of rolls is equal to `minpat': stop,
!   optimal solution found.
! * Otherwise, complete new patterns to non-dominated patterns by
!   adding the largest-possible item to every new pattern
! * Create the variables `use' for the new patterns 
!**********************************************************************
 function pat_heur:integer
  declarations
   NI: integer
  end-declarations
  
  NI:= sum(j in WIDTHS) DEMAND(j) 

  declarations
   CurCont: array(range) of real
   ALLITEM: array(1..NI) of real
   INDALLITEM: array(1..NI) of integer
   mincont:real
  end-declarations
   
! Calculate the theoretical minimum number of rolls
  largest:=WIDTH(NWIDTHS)
  smallest:=WIDTH(1)
  newpat:=ceil((sum(j in WIDTHS) WIDTH(j)*DEMAND(j))/MAXWIDTH)
  minpat:=newpat
  returned:=newpat
  writeln("Lower bound: ", minpat)

! Copy all widths into the array ALLITEM, according to their occurrences
  ct:=1
  forall(j in WIDTHS)
   forall(k in 1..DEMAND(NWIDTHS-j+1)) do 
    ALLITEM(ct):= WIDTH(NWIDTHS-j+1)
    INDALLITEM(ct):= NWIDTHS-j+1
    ct+=1
   end-do 
   
! Assign the largest items, one to every roll
  forall(i in 1..newpat) do
   PATTERNS(INDALLITEM(i),NWIDTHS+i):=1
   CurCont(i)+=ALLITEM(i)
  end-do  

! Add the remaining items to the rolls
  ct:=newpat+1
  while(ct<=NI) do
   mincont:=MAXWIDTH
   indi:=0
   forall(i in 1..newpat)                    ! Find the least filled roll `indi'
    if(CurCont(i)<mincont) then
     mincont:=CurCont(i)
     indi:=i
    end-if
   if(MAXWIDTH-mincont>=ALLITEM(ct)) then    ! Add the item to roll `indi'
    PATTERNS(INDALLITEM(ct),NWIDTHS+indi)+=1
    CurCont(indi)+=ALLITEM(ct)
   else                                      ! Start a new roll
    newpat+=1
    PATTERNS(INDALLITEM(ct),NWIDTHS+newpat)+=1
    CurCont(newpat)+=ALLITEM(ct)
   end-if  
   ct+=1
  end-do

! Stop if optimal solution found
  if(newpat=minpat) then
   writeln("Heuristic solution is optimal: ", newpat, " patterns.")
   forall(k in 1..newpat) do
    forall(j in WIDTHS) 
     write( if(PATTERNS(j,NWIDTHS+k)>0, " "+WIDTH(j)+":"+PATTERNS(j,NWIDTHS+k),
       ""))
    writeln(" ",sum(j in WIDTHS) PATTERNS(j,NWIDTHS+k)*WIDTH(j))
   end-do 

   exit(3)
  end-if 
   
! Complete to non-dominated patterns  
  forall(k in 1..newpat) do
   totwidth:=MAXWIDTH-sum(j in WIDTHS) PATTERNS(j,NWIDTHS+k)*WIDTH(j)
   if(totwidth >=smallest) then           ! If roll is not filled
    repeat
     if(totwidth>largest) then            ! As long as the largest item fits:
      r:=integer(ceil(NWIDTHS*random))    ! add a randomly chosen item
      PATTERNS(r,NWIDTHS+k)+=1
      totwidth-=WIDTH(r)
     else                                 ! Add the largest possible item
      ct:=0
      while(ct<NWIDTHS and WIDTH(ct+1)<=totwidth) ct+=1
      if(ct>0) then
       PATTERNS(ct,NWIDTHS+k)+=1
       totwidth-=WIDTH(ct)
      end-if
     end-if 
    until (totwidth<smallest) 
   end-if
  end-do
  
(! Create the variables `use' for the new patterns
  ubnd:=max(k in WIDTHS) DEMAND(k)
  forall(k in 1..newpat) do
   create(use(NWIDTHS+k))               ! Create additional variables `use'
   use(NWIDTHS+k) is_integer            ! Variables are integer and bounded
   use(NWIDTHS+k) <= ubnd
  end-do
!)
  writeln("Heuristic solution: ", newpat," patterns")

 end-function

!**********************************************************************
 procedure printpat(zbest:real, xbest:array(range) of integer, 
		    W:array(range) of real)
  declarations 
   dw: real
  end-declarations
  
  writeln("New pattern found with marginal cost ", zbest)
  write("   Widths distribution: ")
  dw:=0
  forall(i in WIDTHS) do 
    write(W(i), ":", xbest(i), "  ")
    dw += W(i)*xbest(i)
  end-do 
  writeln("Total width: ", dw)
 end-procedure	
 	    
end-model


paperiom.c
/*******************************************************
   Mosel Example Problems
   ====================== 

   file paperio.c
   ``````````````
   Declaring a "static" module and initializing
   a Mosel array from data stored in C using the
   I/O drivers.
   
   (c) 2008 Fair Isaac Corporation
       author: S. Heipcke, 2002, rev. Feb. 2017
********************************************************/

#include <stdio.h>
#include <stdlib.h>
#include "xprm_mc.h"
#include "xprm_ni.h"

/* Initialization function of the module 'cutstkdata' */
static int cutstkdata_init(XPRMnifct nifct, int *interver,int *libver,
		XPRMdsointer **interf);

/*****************/
/* Main function */
/*****************/
int main()
{
 XPRMmodel mod;
 XPRMalltypes rvalue;
 XPRMmemblk *memblk;
 int result;
 char params[200];
 static int tabdemand[] = {150, 96,   48, 108,  227};
 static double tabwidth[] ={17, 21, 22.5,  24, 29.5};
 static double *tabsol;
 int i, type, sizesol;
 int nwidths = 5;

 if(XPRMinit()) return 1;

 /* Register 'cutstkdata' as a static module (=stored in the program) */
 if(XPRMregstatdso("cutstkdata", cutstkdata_init)) return 2;

 /* Parameters: the addresses of the data tables and their sizes */
 sprintf(params, 
      "DDATA='noindex,mem:%p/%d',WDATA='noindex,mem:%p/%d',NWIDTHS=%d",
      tabdemand, (int)sizeof(tabdemand), tabwidth, 
      (int)sizeof(tabwidth), nwidths);

 /* Execute the model */
 if(XPRMexecmod("", "paperiom.mos", params, &result, &mod)) return 3;

 if((XPRMgetprobstat(mod)&XPRM_PBRES)!=XPRM_PBOPT)
  return 4;                             /* Test whether a solution is found */
  
 /* Retrieve solution data */
 type=XPRMfindident(mod,"soltab",&rvalue);     /* Get model object 'soltab' */
 if(XPRM_STR(type)!=XPRM_STR_MEM)              /* Check the type */
  return 5;
  
 memblk=rvalue.memblk;
 tabsol=(double *)(memblk->ref);
 sizesol=(int)(memblk->size/sizeof(double));
 
 printf("Best integer solution: %g rolls\n", XPRMgetobjval(mod));
 printf("  Rolls per pattern: ");
 for(i=0;i<sizesol;i++) printf("%g, ", tabsol[i]);
 printf("\n");
 
 return result;
}

/********************** Body of the module 'cutstkdata' ******************/

static int printpattern(XPRMcontext ctx,void *libctx);

static XPRMdsofct tabfct[]=
        {
         {"printpat",1002,XPRM_TYP_NOT,3,"rAI.iAI.r",printpattern}
        };

static XPRMdsointer dsointer=
        {
         0,NULL,
         sizeof(tabfct)/sizeof(XPRMdsofct),tabfct,
         0,NULL,
         0,NULL
        };

static XPRMnifct mm;             /* To store the mosel function table */

/*****************************************/
/* Initialization function of the module */
/*****************************************/
static int cutstkdata_init(XPRMnifct nifct, int *interver, int *libver,
		XPRMdsointer **interf)
/* The following header is required to compile 'cutstkdata' as a DSO file:
DSO_INIT cutstkdata_init(XPRMnifct nifct, int *interver, int *libver,
		XPRMdsointer **interf)
*/
{
 mm=nifct;                      /* Save the table of functions */
 *interver=XPRM_NIVERS;         /* The interface version we are using */
 *libver=XPRM_MKVER(0,0,1);     /* The version of the module: 0.0.1 */
 *interf=&dsointer;             /* Our interface */

 return 0;
}

/******************************************************************/
/* Print the new pattern found:                                   */
/*  printpat(real, array(range) of integer, array(range) of real) */
/******************************************************************/
static int printpattern(XPRMcontext ctx,void *libctx)
{
 XPRMarray varr,warr;
 int v,index[1];
 double dj,w,tw=0;

 dj=XPRM_POP_REAL(ctx); 
 varr=XPRM_POP_REF(ctx);                /* The value array */
 warr=XPRM_POP_REF(ctx);                /* The widths array */

 mm->printf(ctx,"new pattern found with marginal cost %g\n", dj);
 mm->printf(ctx,"   Widths distribution: ");
 mm->getfirstarrentry(varr,index);      /* Get the first index tuple */
 do
 {
  mm->getarrval(varr,index,&v);
  mm->getarrval(warr,index,&w);
  mm->printf(ctx,"%g:%d  ",w,v);
  tw+=v*w;
 } while(!mm->getnextarrentry(varr,index));
 mm->printf(ctx,"Total width: %g\n", tw);
  
 return XPRM_RT_OK;
}


paperiom.mos
(!*******************************************************
   Mosel User Guide Examples
   =========================

   file paperio.mos
   ````````````````
   Column generation algorithm for finding (near-)optimal
   solutions for a paper cutting example.
   
   Data in- and output using the 'raw' driver.
   Model can only be executed from the file paperiom.c,
   and not in standalone mode.
   
   Heuristic generation of patterns at start.
   
   (c) 2008 Fair Isaac Corporation
       author: S. Heipcke, 2005, rev. Dec. 2017
*******************************************************!)

model "Papermill IO"
 uses "mmxprs"
 uses "cutstkdata"                     ! User module for in-memory data input

 parameters
  NWIDTHS = 5                          ! Number of different widths
  WDATA=''                             ! Location of data in memory
  DDATA=''                             ! Location of data in memory
 end-parameters

 forward procedure column_gen
 forward function knapsack(C:array(range) of real, 
                           A:array(range) of real, 
                           B:real, 
                           D:array(range) of integer, 
                           xbest:array(range) of integer,
                           pass: integer): real
 forward function pat_heur:integer

(!
The column generation algorithm requires the solution of a knapsack problem.
We define the constraints of the knapsack problem globally because Mosel 
does not delete them at the end of a function (due to its principle of incremental model formulation).
!)
 
 declarations
  WIDTHS = 1..NWIDTHS                  ! Range of widths
  RP: range                            ! Range of cutting patterns
  MAXWIDTH = 94                        ! Maximum roll width
  EPS = 1e-6                           ! Zero tolerance

  WIDTH: array(WIDTHS) of real         ! Possible widths
  DEMAND: array(WIDTHS) of integer     ! Demand per width
  PATTERNS: array(WIDTHS, range) of integer  ! (Basic) cutting patterns

  use: dynamic array(RP) of mpvar      ! Rolls per pattern
  soluse: dynamic array(RP) of real    ! Solution values for variables `use' 
  Dem: array(WIDTHS) of linctr         ! Demand constraints
  MinRolls: linctr                     ! Objective function
 
  KnapCtr, KnapObj: linctr             ! Knapsack constraint+objective
  x: array(WIDTHS) of mpvar            ! Knapsack variables
 end-declarations

! WIDTH::  [ 17, 21, 22.5,  24, 29.5] 
! DEMAND:: [150, 96,   48, 108,  227]

 initializations from "raw:"
  WIDTH as WDATA
  DEMAND as DDATA
 end-initializations
 
                                       ! Make basic patterns
 forall(j in WIDTHS)  PATTERNS(j,j) := floor(MAXWIDTH/WIDTH(j))

 forall(j in WIDTHS) do
  create(use(j))                       ! Create NWIDTHS variables `use'
  use(j) is_integer                    ! Variables are integer and bounded
  use(j) <= integer(ceil(DEMAND(j)/PATTERNS(j,j)))
 end-do

 setparam("zerotol", EPS)              ! Set Mosel comparison tolerance
 
 bnd:=pat_heur                         ! Create heuristic solution + 
                                       ! new non-dominated patterns

 MinRolls:= sum(j in WIDTHS) use(j)    ! Objective: minimize no. of rolls 

                                       ! Satisfy all demands
 forall(i in WIDTHS) 
  Dem(i):= sum(j in WIDTHS) PATTERNS(i,j) * use(j) >= DEMAND(i)  

 column_gen                            ! Column generation at top node

 minimize(MinRolls)                    ! Compute the best integer solution
                                       ! for the current problem (including
                                       ! the new columns) 

                                       ! Save solution to memory
 forall(i in RP) soluse(i):= getsol(use(i))

 initializations to "raw:noindex"
  soluse as "mem:soltab"
 end-initializations

!************************************************************************
!  Column generation loop at the top node:                               
!    solve the LP and save the basis                                     
!    get the solution values                                             
!    generate new column(s) (=cutting pattern)                           
!    load the modified problem and load the saved basis                  
!************************************************************************
 procedure column_gen
  declarations
   dualdem: array(WIDTHS) of real 
   xbest: array(WIDTHS) of integer
   ubnd, zbest, objval: real 
   bas: basis                          ! LP basis
  end-declarations

  setparam("XPRS_CUTSTRATEGY", 0)      ! Disable automatic cuts 
  setparam("XPRS_PRESOLVE", 0)         ! Switch presolve off
  npatt:=NWIDTHS
  npass:=1

  while(true) do
    minimize(XPRS_LIN, MinRolls)       ! Solve the LP 

    savebasis(bas)                     ! Save the current basis 
    objval:= getobjval                 ! Get the objective value      

                                       ! Get the solution values 
    forall(j in 1..npatt)  soluse(j):=getsol(use(j))
    forall(i in WIDTHS)  dualdem(i):=getdual(Dem(i))
                                       ! Solve a knapsack problem
    zbest:= knapsack(dualdem, WIDTH, MAXWIDTH, DEMAND, xbest, npass) - 1.0

    write("Pass ", npass, ": ")   
    if zbest = 0 then
      writeln("no profitable column found.\n")
      break
    else 
      printpat(zbest, xbest, WIDTH)    ! Print the new pattern
      npatt+=1
      create(use(npatt))               ! Create a new var. for this pattern
      use(npatt) is_integer

      MinRolls+= use(npatt)            ! Add new var. to the objective
      ubnd:=0
      forall(i in WIDTHS)                 
        if xbest(i) > 0 then
         Dem(i)+= xbest(i)*use(npatt)  ! Add new var. to demand constr.s
         ubnd:= maxlist(ubnd, ceil(DEMAND(i)/xbest(i) )) 
        end-if
      use(npatt) <= ubnd               ! Set upper bound on the new var.

      loadprob(MinRolls)               ! Reload the problem 
      loadbasis(bas)                   ! Load the saved basis 
    end-if
    npass+=1
  end-do

  writeln("Solution after column generation: ", objval, " rolls, ",
          getsize(RP), " patterns")
  write("   Rolls per pattern: ")
  forall(i in RP) write(soluse(i),", ")
  writeln   
  
 end-procedure

!************************************************************************
!  Solve the integer knapsack problem                              
!    z = max{cx : ax<=b, x<=d, x in Z^N}                                     
!  with b=MAXWIDTH, N=NWIDTHS, d=DEMAND
! 
! We reset the knapsack constraints to 0 at the end of this function
! so that they do not unnecessarily increase the size of the main 
! problem. The same is true in the other sense: hiding the demand 
! constraints while solving the knapsack problem makes life easier
! for the optimizer, but is not essential for getting the correct 
! solution.
!************************************************************************
 function knapsack(C:array(range) of real,
                   A:array(range) of real, 
                   B:real, 
                   D:array(range) of integer, 
                   xbest:array(range) of integer,
                   pass: integer):real

! Hide the demand constraints
  forall(j in WIDTHS) sethidden(Dem(j), true)

! Define the knapsack problem
  KnapCtr := sum(j in WIDTHS) A(j)*x(j) <= B
  KnapObj := sum(j in WIDTHS) C(j)*x(j)

! Integrality condition and bounds
  if(pass=1) then
   forall(j in WIDTHS) x(j) is_integer
   forall(j in WIDTHS) x(j) <= D(j)
  end-if

  maximize(KnapObj)
  returned:=getobjval
  forall(j in WIDTHS) xbest(j):=round(getsol(x(j)))

! Reset knapsack constraint and objective, unhide demand constraints 
  KnapCtr := 0
  KnapObj := 0  
  forall(j in WIDTHS) sethidden(Dem(j), false)

 end-function

!**********************************************************************
! This heuristic assumes that widths are given in ascending order
! * Calculate the theoretical minimum number `minpat' of rolls
! * Assign all items to rolls, starting with the largest items and
!   `minpat' rolls. If an item does not fit, start a new roll.
! * If the resulting number of rolls is equal to `minpat': stop,
!   optimal solution found.
! * Otherwise, complete new patterns to non-dominated patterns by
!   adding the largest-possible item to every new pattern
! * Create the variables `use' for the new patterns 
!**********************************************************************
 function pat_heur:integer
  declarations
   NI: integer
  end-declarations
  
  NI:= sum(j in WIDTHS) DEMAND(j) 

  declarations
   CurCont: array(range) of real
   ALLITEM: array(1..NI) of real
   INDALLITEM: array(1..NI) of integer
   mincont:real
  end-declarations
   
! Calculate the theoretical minimum number of rolls
  largest:=WIDTH(NWIDTHS)
  smallest:=WIDTH(1)
  newpat:=ceil((sum(j in WIDTHS) WIDTH(j)*DEMAND(j))/MAXWIDTH)
  minpat:=newpat
  returned:=newpat
  writeln("Lower bound: ", minpat)

! Copy all widths into the array ALLITEM, according to their occurrences
  ct:=1
  forall(j in WIDTHS)
   forall(k in 1..DEMAND(NWIDTHS-j+1)) do 
    ALLITEM(ct):= WIDTH(NWIDTHS-j+1)
    INDALLITEM(ct):= NWIDTHS-j+1
    ct+=1
   end-do 
   
! Assign the largest items, one to every roll
  forall(i in 1..newpat) do
   PATTERNS(INDALLITEM(i),NWIDTHS+i):=1
   CurCont(i)+=ALLITEM(i)
  end-do  

! Add the remaining items to the rolls
  ct:=newpat+1
  while(ct<=NI) do
   mincont:=MAXWIDTH
   indi:=0
   forall(i in 1..newpat)                    ! Find the least filled roll `indi'
    if(CurCont(i)<mincont) then
     mincont:=CurCont(i)
     indi:=i
    end-if
   if(MAXWIDTH-mincont>=ALLITEM(ct)) then    ! Add the item to roll `indi'
    PATTERNS(INDALLITEM(ct),NWIDTHS+indi)+=1
    CurCont(indi)+=ALLITEM(ct)
   else                                      ! Start a new roll
    newpat+=1
    PATTERNS(INDALLITEM(ct),NWIDTHS+newpat)+=1
    CurCont(newpat)+=ALLITEM(ct)
   end-if  
   ct+=1
  end-do

! Stop if optimal solution found
  if(newpat=minpat) then
   writeln("Heuristic solution is optimal: ", newpat, " patterns.")
   forall(k in 1..newpat) do
    forall(j in WIDTHS) 
     write( if(PATTERNS(j,NWIDTHS+k)>0, " "+WIDTH(j)+":"+PATTERNS(j,NWIDTHS+k),
       ""))
    writeln(" ",sum(j in WIDTHS) PATTERNS(j,NWIDTHS+k)*WIDTH(j))
   end-do 

   exit(3)
  end-if 
   
! Complete to non-dominated patterns  
  forall(k in 1..newpat) do
   totwidth:=MAXWIDTH-sum(j in WIDTHS) PATTERNS(j,NWIDTHS+k)*WIDTH(j)
   if(totwidth >=smallest) then           ! If roll is not filled
    repeat
     if(totwidth>largest) then            ! As long as the largest item fits:
      r:=integer(ceil(NWIDTHS*random))    ! add a randomly chosen item
      PATTERNS(r,NWIDTHS+k)+=1
      totwidth-=WIDTH(r)
     else                                 ! Add the largest possible item
      ct:=0
      while(ct<NWIDTHS and WIDTH(ct+1)<=totwidth) ct+=1
      if(ct>0) then
       PATTERNS(ct,NWIDTHS+k)+=1
       totwidth-=WIDTH(ct)
      end-if
     end-if 
    until (totwidth<smallest) 
   end-if
  end-do
  
(! Create the variables `use' for the new patterns
  ubnd:=max(k in WIDTHS) DEMAND(k)
  forall(k in 1..newpat) do
   create(use(NWIDTHS+k))               ! Create additional variables `use'
   use(NWIDTHS+k) is_integer            ! Variables are integer and bounded
   use(NWIDTHS+k) <= ubnd
  end-do
!)
  writeln("Heuristic solution: ", newpat," patterns")

 end-function

end-model