All Downloads are FREE. Search and download functionalities are using the official Maven repository.

models.examples.puzzles.hanoi.als Maven / Gradle / Ivy

module examples/puzzles/hanoi

/*
 * Towers of Hanoi model
 *
 * Description of problem from http://www.cut-the-knot.com/recurrence/hanoi.html
 *
 * The Tower of Hanoi puzzle was invented by the French mathematician Edouard Lucas
 * in 1883. We are given a tower of eight disks, initially stacked in decreasing size on
 * one of three pegs. The objective is to transfer the entire tower to one of the other
 * pegs, moving only one disk at a time and never a larger one onto a smaller.
 *
 * The Alloy model below is written so that a solution to the model is a complete
 * sequence of valid moves solving an instance of the problem.  We define constraints
 * for the initial state (all discs on left stake), the final state (all discs on right stake),
 * and each pair of adjacent states (the top disc is moved from one stake to another,
 * not putting larger discs on smaller discs), and let Alloy Analyzer solve for the
 * sequence of states satisfying these constraints.  Since each adjacent pair of states is
 * constrained to be related by a single move, it is easy to see the sequence of moves
 * once you have the sequence of states.
 *
 * For n discs, 2^n states are needed for a solution
 *   (including the initial state and the final state).
 *
 * Performance: currently, the problem can be solved for up to 5 discs; this takes
 * several minutes with the Chaff solver.
 *
 * author: Ilya Shlyakhter
 */

open util/ordering[State] as states
open util/ordering[Stake] as stakes
open util/ordering[Disc] as discs

sig Stake { }

sig Disc { }

/**
 * sig State: the complete state of the system --
 * which disc is on which stake.  An solution is a
 * sequence of states.
 */
sig State {
  on: Disc -> one Stake  // _each_ disc is on _exactly one_ stake
  // note that we simply record the set of discs on each stake --
  // the implicit assumption is that on each stake the discs
  // on that stake are ordered by size with smallest disc on top
  // and largest on bottom, as the problem requires.
}

/**
 * compute the set of discs on the given stake in this state.
 * ~(this.on) map the stake to the set of discs on that stake.
 */
fun discsOnStake[st: State, stake: Stake]: set Disc {
  stake.~(st.on)
}

/**
 * compute the top disc on the given stake, or the empty set
 * if the stake is empty
 */
fun topDisc[st: State, stake: Stake]: lone Disc {
  { d: st.discsOnStake[stake] | st.discsOnStake[stake] in discs/nexts[d] + d }
}

/**
 * Describes the operation of moving the top disc from stake fromStake
 * to stake toStake.  This function is defined implicitly but is
 * nevertheless deterministic, i.e. the result state is completely
 * determined by the initial state and fromStake and toStake; hence
 * the "det" modifier above.  (It's important to use the "det" modifier
 * to tell the Alloy Analyzer that the function is in fact deterministic.)
 */
pred Move [st: State, fromStake, toStake: Stake, s': State] {
   let d = st.topDisc[fromStake] | {
      // all discs on toStake must be larger than d,
      // so that we can put d on top of them
      st.discsOnStake[toStake] in discs/nexts[d]
      // after, the fromStake has the discs it had before, minus d
      s'.discsOnStake[fromStake] = st.discsOnStake[fromStake] - d
      // after, the toStake has the discs it had before, plus d
      s'.discsOnStake[toStake] = st.discsOnStake[toStake] + d
      // the remaining stake afterwards has exactly the discs it had before
      let otherStake = Stake - fromStake - toStake |
        s'.discsOnStake[otherStake] = st.discsOnStake[otherStake]
   }
}

/**
 * there is a leftStake that has all the discs at the beginning,
 * and a rightStake that has all the discs at the end
 */ 
pred Game1 {
   Disc in states/first.discsOnStake[stakes/first]
   some finalState: State | Disc in finalState.discsOnStake[stakes/last]

   // each adjacent pair of states are related by a valid move of one disc
   all preState: State - states/last |
       let postState = states/next[preState] |
          some fromStake: Stake | {
             // must have at least one disk on fromStake to be able to move
             // a disc from fromStake to toStake
             some preState.discsOnStake[fromStake]
             // post- results from pre- by making one disc move
             some toStake: Stake | preState.Move[fromStake, toStake, postState]
          }
}

/**
 * there is a leftStake that has all the discs at the beginning,
 * and a rightStake that has all the discs at the end
 */
pred Game2  {
   Disc in states/first.discsOnStake[stakes/first]
   some finalState: State | Disc in finalState.discsOnStake[stakes/last]

   // each adjacent pair of states are related by a valid move of one disc
   all preState: State - states/last |
       let postState = states/next[preState] |
          some fromStake: Stake |
             let d = preState.topDisc[fromStake] | {
               // must have at least one disk on fromStake to be able to move
               // a disc from fromStake to toStake
               some preState.discsOnStake[fromStake]
               postState.discsOnStake[fromStake] = preState.discsOnStake[fromStake] - d
               some toStake: Stake | {
                 // post- results from pre- by making one disc move
                 preState.discsOnStake[toStake] in discs/nexts[d]
                 postState.discsOnStake[toStake] = preState.discsOnStake[toStake] + d
                // the remaining stake afterwards has exactly the discs it had before
                let otherStake = Stake - fromStake - toStake |
                    postState.discsOnStake[otherStake] = preState.discsOnStake[otherStake]
                }
             }
      }

run Game1 for 1 but 3 Stake, 5 Disc, 32 State expect 1
run Game2 for 1 but 3 Stake, 3 Disc, 8 State expect 1




© 2015 - 2025 Weber Informatics LLC | Privacy Policy