Initializing help system before first use

Solution enumeration


Type: Facility location problem
Rating: 3 (intermediate)
Description:
  • solution pool configuration
  • setting solution enumeration strategy
  • retrieving multiple solutions from the pool
  • using pool solutions to warmstart MIP search
  • defining procedures
File(s): solutionpool.mos


solutionpool.mos
(!******************************************************
   Mosel Example Problems
   ======================

   file solutionpool.mos
   `````````````````````
   Capacitated facility location problem (CFLP) with
   single-sourcing, used to demonstrate the MIP solution
   pool functionality of the Xpress Optimizer:
    (a) collecting solutions during a normal solve;
    (b) warm-starting a re-solve from the pool after
        fixing a facility to be open;
    (c) enumerating the 5 best solutions overall via
        extended branch-and-bound;
    (d) enumerating the 5 best facility-assignment-only
        solutions (restricting enumeration to the
        facility-open variables).

   Model:
     Sets      I customers, J facilities
     Data      D_i demand of customer i
               F_j fixed cost of opening facility j
               Q_j capacity of facility j
               C_ij distance between customer i and
                    facility j
     Variables y_j =1 if facility j is open
               x_ij=1 if customer i is served by facility j
     Objective min sum(j) F_j y_j + sum(i,j) C_ij x_ij
     Constraints
       sum(j) x_ij = 1                       for every i
       sum(i) D_i x_ij <= Q_j y_j            for every j

   (c) 2026 Fair Isaac Corporation
       author: S. Heipcke, Sep. 2026
*******************************************************!)

model "Solution Pool"
 options noimplicit
 version 1.0.0
 uses "mmxprs", "mmsystem"

 declarations
  NCUST = 25                     ! Number of customers
  NFAC = 10                      ! Number of facilities
  Customers = 1..NCUST           ! Index set of customers
  Facilities = 1..NFAC           ! Index set of facilities

  CUSTX, CUSTY: array(Customers) of real    ! Customer coordinates
  DEM: array(Customers) of real             ! Customer demand
  FACX, FACY: array(Facilities) of real     ! Facility coordinates
  CAP: array(Facilities) of real            ! Facility capacity
  FCOST: array(Facilities) of real          ! Facility fixed opening cost
  DIST: array(Customers, Facilities) of real    ! Customer-facility distance

  open: array(Facilities) of mpvar              ! 1 if facility j is open
  serve: array(Customers, Facilities) of mpvar  ! 1 if customer i served by j
  MinCost: linctr                               ! Objective: total cost

 ! Forward declaration of subroutines defined later on
  procedure printsolutions
  procedure solve_collect_solutions
  procedure solve_warmstart_from_pool(fixed: integer)
  procedure enumerate_all_binaries
  procedure enumerate_facilities
 end-declarations

 ! Customer coordinates and demands (25 customers in 3 clusters + 2 outliers)
 CUSTX:: [20, 22, 5, 6, 13,             ! cluster A
          15, 20, 11, 3,
          70, 69, 82, 74, 77,           ! cluster B
          82, 72, 70,
          56, 53, 50, 39, 55,           ! cluster C
          43,
          46, 30]                       ! outliers
 CUSTY:: [9, 15, 13, 26, 14,
          19, 19, 7, 21,
          27, 33, 26, 30, 20,
          11, 25, 24,
          82, 74, 84, 81, 80,
          84,
          47, 55]
 DEM:: [26, 25, 37, 22, 35, 22, 43, 21, 41,
           29, 61, 32, 41, 40, 39, 19, 72,
           23, 40, 59, 27, 45, 25,
           21, 40]

 ! Facility coordinates, capacities and fixed costs (10 facilities)
 FACX:: [7, 12,        ! near cluster A
         76, 80,       ! near cluster B
         56, 52,       ! near cluster C
         45, 63,       ! central bridge
         3, 3]         ! peripheral
 FACY:: [15, 19,
         19, 21,
         88, 79,
         50, 48,
         93, 88]
 CAP:: [307, 98, 86, 298, 211, 190, 70, 205, 140, 165]
 FCOST:: [787, 233, 257, 756, 567, 517, 227, 535, 367, 445]

 ! Compute service costs (Euclidean distance from customer to facility)
 forall(i in Customers, j in Facilities)
  DIST(i,j):= sqrt((CUSTX(i)-FACX(j))^2 + (CUSTY(i)-FACY(j))^2)

 forall(j in Facilities) open(j) is_binary
 forall(i in Customers, j in Facilities) serve(i,j) is_binary

 ! Demand satisfaction constraints: each customer served by exactly one facility
 forall(i in Customers)
  sum(j in Facilities) serve(i,j) = 1

 ! Capacity and activation constraints: a facility can only serve
 ! customers, up to its capacity, if it is open 
 forall(j in Facilities)
  sum(i in Customers) DEM(i)*serve(i,j) <= CAP(j)*open(j)

 ! Objective: total fixed opening cost plus total service (distance) cost
 MinCost:= sum(j in Facilities) FCOST(j)*open(j) +
  sum(i in Customers, j in Facilities) DIST(i,j)*serve(i,j)

 ! Display solver log
 ! setparam("XPRS_VERBOSE", true)

 ! Solve the model and collect up to 10 solutions into the pool.
 solve_collect_solutions

 ! Force a facility to be opened and reoptimize, carrying the existing
 ! solutions through as warm-start solutions for the next solve.
 solve_warmstart_from_pool(3)

 ! Run an enumeration search to find the 5 best solutions.
 enumerate_all_binaries

 ! Run an enumeration search to find the 5 best facility assignments.
 enumerate_facilities

! ***********************************************************************

 (! **** Print the list of collected solutions: objective value, number of
    facilities opened, and maximum distance between a facility and a
    customer it serves. **** !)
 procedure printsolutions
  declarations
   psize: integer
   nopen: integer
   maxdist: real
  end-declarations

  psize:= getparam("XPRS_POOLSIZE")
  writeln("\n", psize, " solution(s) collected:")
  writeln("  Sol#     Objective    Facilities        Max dist")
  forall(p in 1..psize) do
   selectsol(p)
   nopen:= sum(j in Facilities) round(getsol(open(j)))
   maxdist:= 0
   forall(i in Customers, j in Facilities |
          getsol(serve(i,j))>0.5 and DIST(i,j)>maxdist)
    maxdist:= DIST(i,j)
   writeln(formattext("%6d%14.2f%5d %-13s%8.2f", p, MinCost.sol, nopen, 
     text(union(j in Facilities | round(getsol(open(j)))=1) {j}), maxdist))
  end-do
 end-procedure

 ! **** Solve the model and collect up to 10 solutions into the pool. ****
 procedure solve_collect_solutions
  ! Collect up to ten solutions during the MIP search. Solutions are
  !  stored from best to worst; index 1 always holds the best solution.
  setparam("XPRS_POOLLIMIT", 10)

  writeln("\nSolving problem to collect solutions")
  minimize(MinCost)
  if getprobstat<>XPRS_OPT then
   writeln("Failed to solve problem, status ", getprobstat)
  else
   writeln("Solved problem to optimality.")
   printsolutions
  end-if
 end-procedure

 (! **** Assumes that we have already solved the problem and that the pool
    contains a set of solutions. This procedure forces a single facility
    to be open and uses the pooled solutions to warm-start the next solve:
    If we open an additional facility, all existing solutions should remain 
    feasible - the presolver should take care of satisfying the fixed binary. 
    The warm-start solutions will not be optimal since no customers are served
    by the new facility. **** !)
 procedure solve_warmstart_from_pool(fixed: integer)
  declarations
   fixfac: integer
  end-declarations

  ! Make sure the problem is in its original state before we change it.
  ! If the problem is already in its original state, this call simply
  ! returns without doing anything.
  postsolve

  ! We need to set XPRS_POOLKEEPSOLS to transfer any existing solutions
  ! to the warm-start pool before we fix a variable, since any
  ! modifications will invalidate all collected solutions. 
  setparam("XPRS_POOLKEEPSOLS", -1)

  ! Force a facility to be open. The setub/setlb routines modify directly
  ! the problem definition in the solver. Any other modifications to the
  ! problem, such as addition/deletion/modification of constraints, will
  ! trigger reloading of the problem in the next call to 'minimize', in which
  ! case any pooled solutions are automatically cleared and the automatic
  ! warm-starting will not work.
  fixfac:=if(fixed not in Facilities, 3, fixed)
  setlb(open(fixfac), 1)

  writeln("\nReoptimizing with facility ", fixfac, " fixed open")
  minimize(MinCost)
  if getprobstat<>XPRS_OPT then
   writeln("Failed to resolve problem, status ", getprobstat)
  else
   writeln("Resolved problem to optimality.")
   printsolutions
  end-if

  ! Reset controls. We should do this before modifying the problem below
  ! to avoid automatically transferring the solutions when restoring the bound.
  setparam("XPRS_POOLKEEPSOLS", 0)    ! Reset to default value
  setlb(open(fixfac), 0)              ! Undo the fixed facility
 end-procedure

 (! **** Use the built-in solution enumeration to find the 5 best solutions 
    to the problem. The solver, by default, considers two solutions distinct
    if they differ in any binary-restricted variable. To speed up the
    solve, the search is limited to solutions within 50% of the best
    solution's objective value. **** !)
 procedure enumerate_all_binaries
  ! Collect up to 5 best solutions.
  setparam("XPRS_POOLLIMIT", 5)
  ! Enable automatic solution enumeration.
  setparam("XPRS_POOLENUMSTRATEGY", XPRS_POOLENUMSTRATEGY_FULL)
  ! We limit collection to solutions that are within 50% of the best solution.
  setparam("XPRS_POOLRELGAP", 0.50)

  ! Solve the problem, which will automatically trigger an extended 
  ! branch-and-bound search to find all 5 best solutions.
  writeln("\nSolving problem to find 5 best solutions")
  minimize(MinCost)
  if getprobstat<>XPRS_OPT then
   writeln("Failed to solve problem, status ", getprobstat)
  else
   writeln("Solved problem to optimality.")
   ! Print the collected solutions.
   printsolutions
  end-if

  ! Reset controls to default values
  setparam("XPRS_POOLLIMIT", 10)
  setparam("XPRS_POOLENUMSTRATEGY", XPRS_POOLENUMSTRATEGY_STANDARD)
  setparam("XPRS_POOLRELGAP", 0)
 end-procedure

 (! **** We want to find the 5 best combinations of opening facilities but 
    the demand sourcing should be optimal in each solution. To achieve this,
    we tell the solver to enumerate only through solutions that differ in
    facility variables, by specifying branching directives only on the
    facility variables. The solver will automatically treat two solutions
    with the same facility assignment as duplicates and keep only the
    best one, resulting in the optimal demand assignment for each
    facility assignment. **** !)
 procedure enumerate_facilities
  ! Make sure the problem is in its original state before we change it.
  ! If the problem is already in its original state, this call simply
  ! returns without doing anything.
  postsolve
  ! Collect up to 5 best solutions.
  setparam("XPRS_POOLLIMIT", 5)
  ! Enable automatic solution enumeration.
  setparam("XPRS_POOLENUMSTRATEGY", XPRS_POOLENUMSTRATEGY_FULL)

  ! Apply branching directives to the facility binaries. Binaries
  ! without a directive will not be enumerated.
  forall(j in Facilities) setmipdir(open(j), XPRS_BR)

  ! Solve the problem, which will automatically trigger an extended
  ! branch-and-bound search to find the 5 best facility assignments.
  writeln("\nSolving problem to find 5 best facility assignments")
  minimize(MinCost)
  if getprobstat<>XPRS_OPT then
   writeln("Failed to resolve problem, status ", getprobstat)
  else
   writeln("Resolved problem to optimality.")
   ! Print the collected solutions.
   printsolutions
  end-if

  ! Reset controls to default values
  setparam("XPRS_POOLLIMIT", 10)
  setparam("XPRS_POOLENUMSTRATEGY", XPRS_POOLENUMSTRATEGY_STANDARD)

  clearmipdir                            ! Clear branching directives
 end-procedure

end-model

© 2001-2026 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.