Initializing help system before first use

Fiveleaper (MIP model and graphics)


Type: Traveling Salesman Problem
Rating: 4 (medium-difficult)
Description: A fiveleaper travels over the chessboard by advancing in a single move either 5 squares in a straight line or three squares in a particular direction and 4 in an orthogonal direction. That is, with every move it travels a Euclidean distance of five squares. The problem of finding fiveleaper's tours on a chessboard is an instance of the Traveling Salesman Problem (TSP) and we adapt the subtour elimination algorithm described in the book 'Applications of optimization with Xpress-MP' (problem f5tour) for solving this problem.
You will find a description of this problem and a few other model versions in the following publication: M.J. Chlond, B. Daniel, S. Heipcke, Fiveleapers a-leaping, INFORMS Transactions on Education, Vol. 4, No. 1, 2003. http://ite.pubs.informs.org/Vol4No1/ChlondDanielHeipcke
The model version fiveleap_graph.mos displays a user graph within a webbrowser. The model fiveleap.mos simply prints out the results and will work on any platform for which Mosel is available.
File(s): fiveleap.mos, fiveleap_graph.mos


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

   file fiveleap.mos 
   `````````````````
   Fiveleaper problem.

   Closed TSP form of 5leaper for a rectangular chessboard

   Run with parameters NROW and NCOL (numbers of squares on the edges)
   e.g.   mosel exe fiveleap NROW=9 NCOL=6
   NROW=NCOL=8 are the smallest possible values for square boards.
 
   This is a variant of the problem in "f5tour.mos" from the book
   "Applications of optimization with Xpress-MP" 

   You will find a description of this problem and a few other 
   model versions at the following address:
   http://ite.pubs.informs.org/Vol4No1/ChlondDanielHeipcke

   A square (i,j) on the rectangle is labeled
     sq := j + NCOL*(i-1)
   Given a square label
     i := ceil(sq/NCOL)
     j := sq - NCOL*(i-1)
     
   (c) 2008 Fair Isaac Corporation
       authors: Bob Daniel, S. Heipcke, March 2003
*******************************************************!)

model 'FIVELEAP'
  uses 'mmxprs', 'mmsystem'

  parameters
    NCOL = 8                            ! Number of colums
    NROW = 8                            ! Number of rows
  end-parameters

  forward function break_subtour: integer
  forward procedure print_sol
  forward procedure find_subtour(start: integer, TOUR: set of integer)

  declarations
    NSQ = NCOL*NROW                     ! Number of squares
    RSQ = 1..NSQ                        ! Range of squares
    
    nmoves, ntours, niterations, ntourstotal: integer
    nmovesfrom: array(RSQ) of integer   ! Counts possible moves leaving

    x: dynamic array(RSQ,RSQ) of mpvar  ! 1 if we leap from sq1 to sq2
    NEXTX: array(RSQ) of integer        ! Next square after sq in the solution

    square1, square2, ii, jj: integer
  end-declarations

! For each pair of squares with a valid move...
  nmoves := 0
  forall(i1,i2 in 1..NCOL, j1,j2 in 1..NROW | (i1-i2)^2+(j1-j2)^2 = 25) do
    square1 := j1+NCOL*(i1-1)
    square2 := j2+NCOL*(i2-1)
    create(x(square1,square2))          ! Create the 'x' binary decision variable
    x(square1,square2) is_binary
    nmovesfrom(square1) += 1            ! Note a possible move from square
    nmoves += 1
  end-do
  writeln("There are ", nmoves, " possible moves")

! Check that each square is reachable
  forall(sq in RSQ | nmovesfrom(sq)=0) do
    ii := ceil(sq/NCOL)
    jj := sq-NCOL*(ii-1) 
    writeln("**** Cannot go anywhere from square (", ii, ",", jj, ")")
    exit(0)
  end-do

! Objective (arbitrary)
  AnyObj := sum(sq1,sq2 in RSQ | exists(x(sq1,sq2)) ) x(sq1,sq2)

! Each square precedes one other
  forall(sq1 in RSQ) sum(sq2 in RSQ | exists(x(sq1,sq2)) ) x(sq1,sq2) = 1

! Each cell is preceded by one other
  forall(sq2 in RSQ) sum(sq1 in RSQ | exists(x(sq1,sq2)) ) x(sq1,sq2) = 1

! Optimizer settings to speed up the iterations
  setparam("XPRS_CUTSTRATEGY", 0) 
  setparam("XPRS_PRESOLVE", 0) 

! Solve the problem

  starttime := gettime
  niterations := 0

  while (TRUE) do
    minimize(AnyObj)
  
    if (getprobstat <> XPRS_OPT) then
      writeln("Problem is infeasible.")
      exit(0)
    end-if

    forall(sq in RSQ) 
      NEXTX(sq):= integer(round(getsol(sum(sq2 in RSQ) sq2*x(sq,sq2) )))

    ntours := break_subtour

    if (ntours=1) then break; end-if

    ! Print a log every 10 iterations    
    niterations += 1
    ntourstotal += ntours
    if (niterations mod 10 = 0) then
      writeln(niterations, " (", gettime-starttime, " sec) tours eliminated: ",
              ntourstotal)
    end-if

  end-do
  
  print_sol

  writeln("Total time: ", gettime-starttime, " sec")
 
 
!-----------------------------------------------------------------
! Eliminate all subtours, and return number of (sub)tours
! - Find news subtours starting at each node in turn
! - If found complete tour, return 1
! - Otherwise add subtour elimination cut and continue
!
! Global variables required
!   RSQ: range of squares
!   NSQ: number of squares
!   NEXTX(i): square following i in current solution

  function break_subtour: integer
    declarations
      TOUR, ALLTOURS: set of integer
      ntours: integer
    end-declarations
 
    ALLTOURS := {}
    ntours := 0

    forall(startsquare in RSQ | startsquare not in ALLTOURS) do
      ! Find (sub)tour containing {startsquare}
      find_subtour(startsquare, TOUR)
      ALLTOURS += TOUR
      ntours += 1
      ! If found complete tour, return
      if (getsize(TOUR)>=NSQ) then break; end-if
      ! Add a subtour breaking constraint
      sum(sq in TOUR) x(sq,NEXTX(sq)) <= getsize(TOUR) - 1
    end-do
    
    returned := ntours
  end-function

!-----------------------------------------------------------------
! Find a subtour starting at square 'start'; result returned in TOUR

  procedure find_subtour(start: integer, TOUR: set of integer)
    declarations
      square: integer
    end-declarations

    TOUR := {}
    square := start
    repeat
      TOUR += {square}
      square := NEXTX(square)
    until (square=start)
  end-procedure


!-----------------------------------------------------------------
! Print the current solution
!
! Global variables required
!   RSQ: range of squares
!   NEXTX(i): square following i in current solution

  procedure print_sol
    declarations
      SEENSOFAR: set of integer
      square: integer
    end-declarations
   
    SEENSOFAR:={}
    forall(startsquare in RSQ | startsquare not in SEENSOFAR) do
      write(startsquare)
      square := startsquare
      repeat
        SEENSOFAR += {square}
        square := NEXTX(square)
        write(" - ", square)
      until (square=startsquare)
      writeln(' ')
    end-do        
  end-procedure

end-model



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

   file fiveleap_graph.mos 
   ```````````````````````
   Fiveleaper problem.

   Closed TSP form of 5leaper for a rectangular chessboard
   - Graphical representation of solutions with IVE -

   Run with parameters NROW and NCOL (numbers of squares on the edges)
   e.g.   mosel exe fiveleap NROW=9 NCOL=6
   NROW=NCOL=8 are the smallest possible values for square boards.
 
   This is a variant of the problem in "f5tour.mos" from the book
   "Applications of optimization with Xpress-MP" 

   You will find a description of this problem and a few other 
   model versions at the following address:
   http://ite.pubs.informs.org/Vol4No1/ChlondDanielHeipcke

   A square (i,j) on the rectangle is labeled
     sq := j + NCOL*(i-1)
   Given a square label
     i := ceil(sq/NCOL)
     j := sq - NCOL*(i-1)
     
   (c) 2008 Fair Isaac Corporation
       authors: Bob Daniel, S. Heipcke, March 2003, rev. Sep. 2017
*******************************************************!)

model 'FIVELEAPGR'
  uses 'mmxprs', 'mmsystem', 'mmsvg'

  parameters
    NCOL = 8                            ! Number of colums
    NROW = 8                            ! Number of rows
    DISPLAY = true                      ! `true' to see graphical output
  end-parameters

  forward function break_subtour: integer
  forward procedure print_sol
  forward procedure draw_sol
  forward procedure find_subtour(start: integer, TOUR: set of integer)

  declarations
    NSQ = NCOL*NROW                     ! Number of squares
    RSQ = 1..NSQ                        ! Range of squares
    
    nmoves, ntours, niterations, ntourstotal: integer
    nmovesfrom: array(RSQ) of integer   ! Counts possible moves leaving

    x: dynamic array(RSQ,RSQ) of mpvar  ! 1 if we leap from sq1 to sq2
    NEXTX: array(RSQ) of integer        ! Next square after sq in the solution

    square1, square2, ii, jj: integer
  end-declarations

! For each pair of squares with a valid move...
  nmoves := 0
  forall(i1,i2 in 1..NCOL, j1,j2 in 1..NROW | (i1-i2)^2+(j1-j2)^2 = 25) do
    square1 := j1+NCOL*(i1-1)
    square2 := j2+NCOL*(i2-1)
    create(x(square1,square2))          ! Create the 'x' binary decision variable
    x(square1,square2) is_binary
    nmovesfrom(square1) += 1            ! Note a possible move from square
    nmoves += 1
  end-do
  writeln("There are ", nmoves, " possible moves")

! Check that each square is reachable
  forall(sq in RSQ | nmovesfrom(sq)=0) do
    ii := ceil(sq/NCOL)
    jj := sq-NCOL*(ii-1) 
    writeln("**** Cannot go anywhere from square (", ii, ",", jj, ")")
    exit(0)
  end-do

! Objective (arbitrary)
  AnyObj := sum(sq1,sq2 in RSQ | exists(x(sq1,sq2)) ) x(sq1,sq2)

! Each square precedes one other
  forall(sq1 in RSQ) sum(sq2 in RSQ | exists(x(sq1,sq2)) ) x(sq1,sq2) = 1

! Each cell is preceded by one other
  forall(sq2 in RSQ) sum(sq1 in RSQ | exists(x(sq1,sq2)) ) x(sq1,sq2) = 1

! Optimizer settings to speed up the iterations
  setparam("XPRS_CUTSTRATEGY", 0) 
  setparam("XPRS_PRESOLVE", 0) 

! Solve the problem

  starttime := gettime
  niterations := 0

  while (TRUE) do
    minimize(AnyObj)
  
    if (getprobstat <> XPRS_OPT) then
      writeln("Problem is infeasible.")
      exit(0)
    end-if

    forall(sq in RSQ) 
      NEXTX(sq):= integer(round(getsol(sum(sq2 in RSQ) sq2*x(sq,sq2) )))

    if DISPLAY then 
      draw_sol
    ! Closing the graphical display will interrupt the algorithm
      if svgclosing then break; end-if
    end-if

    ntours := break_subtour

    if (ntours=1) then break; end-if

    ! Print a log every 10 iterations    
    niterations += 1
    ntourstotal += ntours
    if (niterations mod 10 = 0) then
      writeln(niterations, " (", gettime-starttime, " sec) tours eliminated: ",
              ntourstotal)
    end-if

  end-do
  
  print_sol

  writeln("Total time: ", gettime-starttime, " sec")
 
  if DISPLAY then 
    svgwaitclose("Close browser window to terminate model execution.", 1)
  end-if
 
!-----------------------------------------------------------------
! Eliminate all subtours, and return number of (sub)tours
! - Find news subtours starting at each node in turn
! - If found complete tour, return 1
! - Otherwise add subtour elimination cut and continue
!
! Global variables required
!   RSQ: range of squares
!   NSQ: number of squares
!   NEXTX(i): square following i in current solution

  function break_subtour: integer
    declarations
      TOUR, ALLTOURS: set of integer
      ntours: integer
    end-declarations
 
    ALLTOURS := {}
    ntours := 0

    forall(startsquare in RSQ | startsquare not in ALLTOURS) do
      ! Find (sub)tour containing {startsquare}
      find_subtour(startsquare, TOUR)
      ALLTOURS += TOUR
      ntours += 1
      ! If found complete tour, return
      if (getsize(TOUR)>=NSQ) then break; end-if
      ! Add a subtour breaking constraint
      sum(sq in TOUR) x(sq,NEXTX(sq)) <= getsize(TOUR) - 1
    end-do
    
    returned := ntours
  end-function

!-----------------------------------------------------------------
! Find a subtour starting at square 'start'; result returned in TOUR

  procedure find_subtour(start: integer, TOUR: set of integer)
    declarations
      square: integer
    end-declarations

    TOUR := {}
    square := start
    repeat
      TOUR += {square}
      square := NEXTX(square)
    until (square=start)
  end-procedure


!-----------------------------------------------------------------
! Print the current solution
!
! Global variables required
!   RSQ: range of squares
!   NEXTX(i): square following i in current solution

  procedure print_sol
    declarations
      SEENSOFAR: set of integer
      square: integer
    end-declarations
   
    SEENSOFAR:={}
    forall(startsquare in RSQ | startsquare not in SEENSOFAR) do
      write(startsquare)
      square := startsquare
      repeat
        SEENSOFAR += {square}
        square := NEXTX(square)
        write(" - ", square)
      until (square=startsquare)
      writeln(' ')
    end-do        
  end-procedure

!-----------------------------------------------------------------
! Draw the current solution
!
! Global variables required
!   RSQ: range of squares
!   NEXTX(sq): square following sq in current solution
!   NCOL: number of columns

  procedure draw_sol
   
    declarations
      SEENSOFAR: set of integer
      tour, square, gr, ii1, ii2, jj1, jj2: integer
    end-declarations

    if DISPLAY then
      svgerase
      svgsetgraphscale(25)
!      maxval:=maxlist(NCOL+1,NROW+2)
!      svgsetgraphviewbox(0,0,maxval,maxval)

    ! Draw the board
      svgaddgroup("B", "", SVG_BLACK)
      forall(i in 1..NROW+1) svgaddline(i-0.5, 0.5, i-0.5, NCOL+0.5)
      forall(j in 1..NCOL+1) svgaddline(0.5, j-0.5, NROW+0.5, j-0.5)

      SEENSOFAR := {}
      tour := 0
  
      forall(tour as counter, startsquare in RSQ | startsquare not in SEENSOFAR) do
        svgaddgroup("G"+tour, "Tour "+tour)
      
        square := startsquare
        repeat
          ii1 := ceil(square/NCOL)
          jj1 := square-NCOL*(ii1-1) 
          ii2 := ceil(NEXTX(square)/NCOL)
          jj2 := NEXTX(square)-NCOL*(ii2-1) 

          svgaddarrow(ii1, jj1, ii2, jj2)

          SEENSOFAR += {square}
          square := NEXTX(square)
        until (square=startsquare)
      end-do

      svgaddtext("B",1,NROW+1,"Number of (sub)tours: "+tour)
      svgrefresh

    ! Uncomment next line to pause at every iteration:
      if tour>1 then svgpause; end-if
    end-if

  end-procedure


end-model



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