The WASA project |
|
||||||||||||||||||||||||||||||||
Here is a short cookbook showing you how to solve a very simple CLP (Constraint Language Programming) problem using the WASA framework. Presenting the problemThe goal is to place N queens on a N * N chessboard so that no two queens attack each other. This means that two queens cannot be placed on the same line / column / diagonal. The code snippets belows are taken out from fr.jussieu.gla.wasa.samples.queens.NaiveQueens. Presenting the algorithmIn order to be solved by WASA, the problem should be expressed as follows :
A Configuration represents the state of all queens on the board. The WASA solver will "shake" the configurations in order to find the "best" (as expressed by Constraints) as follows :
Creating Buisness ObjectsThe n-queens problem is modelled by a very simple Buisness Object : In order to simplify the problem, we'll declare each queen on a different line. This will implicitly satisfy the Constraint "two queens cannot be placed on the same line". The board itself can be simply modelled by a Queen array : Queen[] queens = new Queen[ boardSize ] ; for( int i = 0 ; i < boardSize ; i++ ) { queens[ i ] = new Queen( i , i ) ; } Creating the ProblemThe fr.jussieu.gla.wasa.core.Problem is the placeholder for the problem definition. All problem elements (Var, Constraint, Explorer...) require a valid Problem instance in their constructor. Problem problem = new Problem() ; Creating the VarsEach Buisness Object property which appears in a Constraint should be mapped by a fr.jussieu.gla.wasa.core.Var object. We'll use an utility class which uses reflection to map a var to a Buisness Object property, given the property name. Remember we'll only apply Constraints on queens' columns, so we only map the Column property for each Queen instance : final Var[] varColumns = new Var[ boardSize ] ; for( int i = 0 ; i < boardSize ; i++ ) { varColumn[ i ] = VarFactory.INSTANCE.createPropertyVar( columnExplorer , queens[ i ] , "Column" ) ; } We'll explain the role of the columnExplorer later, just remember that it is an Explorer instance, required by Var constructor. Creating the ConstraintsA fr.jussieu.gla.wasa.core.Constraint expresses how Vars "good" are. Constraints rely on Buisness Objects properties to determine the error to assign to Vars. Let's take the constraint of different columns. This should be expressed as "if two queens are on the same column, increment their error respectively" : Constraint columnsAllDifferent = new Constraint( problem ) { public void evaluate() { for( int i = 0 ; i < boardSize - 1 ; i++ ) { for( int j = i + 1 ; j < boardSize ; j++ ) { if( queens[ i ].getColumn() == queens[ j ].getColumn() ) { varColumns[ i ].incError( 1 ) ; varColumns[ j ].incError( 1 ) ; } } } } } ; It is also possible to give a name to the constraint for debugging purpose : columnsAllDifferent.setName( "N->S" ) ; The constraints on the diagonals follow the same idea. Creating the ExplorerA fr.jussieu.gla.wasa.core.Explorer
provides new meaningful values for the worst Var of a given Configuration.
The set of modified Configurations is called a Neighbourhood. Explorer columnExplorer = new Explorer( problem ) { protected boolean hasNeighbours( int exploreIndex ) { return exploreIndex < boardSize ; } public boolean explore( int exploreIndex ) { int varRank = getVar().getRank() ; if ( exploreIndex == 0 ) { return false ; } else { int theColumn = queens[ varRank ].getColumn() ; int newColumn = ( theColumn + exploreIndex ) % boardSize ; queens[ varRank ].setColumn( newColumn ) ; return true ; } } } ; Please note that the code above accesses Buisness Objects directly. It just uses Explorer.getVar in order to retrieve the current worst Var (according to the latest Constraints evaluation). The rank of the Var means it was the nth Var created for the Problem. Let's see what happens when the Explorer is being called by the WASA solver :
The Explorer instance is called several times by WASA, with the following results :
At the end of the exploration, the best configuration will be retained by the WASA solver as the next start Configuration. If no improvement was found, a random Configuration is set. Creating the Random ConfiguratorDue to the stochastic approach, the WASA solver may "shake" Buisness Objects when a dead-end (aka : local minimum) is detected. An instance of fr.jussieu.gla.wasa.core.RandomConfigurator provides a Buisness Object-dependant way to "shake" them : RandomConfigurator randomConfigurator = new RandomConfigurator( problem ) { public void configure( float randomRatio ) { IntRandomIterator randomQueenChoice = new IntRandomIterator( 0, boardSize -1 ) ; int bound = (int) ( randomRatio * boardSize ) ; for( int i = 0 ; i < bound ; i++ ) { queens[ randomQueenChoice.nextInt() ].setColumn( (int) ( Math.random() * boardSize ) ) ; } } } ;Each time the configure method is called, the randomRatio determines the rate of vars which will be shaken. They are randomly choosen once and only once, by the fr.jussieu.gla.wasa.util.IntRandomIterator . Once the var is selected, it's randomly set a column. Putting it all togetherThe Problem will be solved by a fr.jussieu.gla.wasa.core.Engine which runs the Adaptative Search Algorithm : Engine engine = new Engine( problem ) ; engine.runQuiet( 1000, 0.0f ) ; engine.restoreBest() ; The engine will search a configuration with an error of 0.0f on constraints, by executing at most 1000 steps. Then, our queens are set to columns which represent the best configuration found. (The snippet above is located in fr.jussieu.gla.wasa.samples.queens.NaiveQueensSample) ConclusionIn this document, we hid some implementation details, like problem-dependant tuning of the Engine, or solution display. The source code is available in fr.jussieu.gla.wasa.samples.queens.NaiveQueens. |
|||||||||||||||||||||||||||||||||