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 at the following address: https://doi.org/10.1287/ited.4.1.78 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 - 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 at the following address: https://doi.org/10.1287/ited.4.1.78 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-2020 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.