A first look at AIMMS+CP (AIMMS with Constraint Programming)
In the Summer of 2011 I had the opportunity to test an early beta version of AIMMS+CP (AIMMS with Constraint Programming), and then did a second test round in September 2012 (with a late beta version). Here are some of my comments of my experiences with AIMMS+CP during these tests. A little later, in October 2012, Paragon released version 13.1 to the public including AIMMS+CP.
My AIMMS+CP page links to both .aim files (which shows the textual representation of parameters, variables and constraints) and the .aimmspack file (a binary file containing the full model project including the GUI part and other settings). As usual I have implemented many of my "starting/learning" models to be able to compare them with other CP systems. I would like to thank especially Chris Kuip and Gertjan de Lange from Paragon for great support, suggestions and inspirations during these tests.
First a little about Paragon and their commercial product AIMMS, from the Overview:
AIMMS GUI tool was (and still is AFAIK) only for Windows, so I used VirtualBox under my Linux Ubuntu 12.04 when testing. Also, before the test, I have not used AIMMS at all.
For a person that knows AIMMS well and also know how CP works, the documentation is probably enough. But for someone like me that didn't know AIMMS before or someone who know AIMMS but not CP, the documentation is a little thin, especially since the examples are quite advanced to get started. I missed some of the standard CP examples such as N-queens, Send More Money, Sudoku, etc and examples of the basic stuff, such as how handle single variables, comparisons etc. Hopefully my AIMMS+CP implementations might be of some help.
The documentation don't say much about the CP solver and what options it has (and what they mean); though the is some documentation available with the Help button for the options. It is great that there are good support for scheduling and the example models are interesting, but - again - too complicated to start learning from.
I should mention that I have not read the complete documentation of AIMMS. Instead I started to code in AIMMS almost directly. This might have been a bad decision since I got stumped a couple of times with some of AIMMS basic concepts.
I later realized that AIMMS Blog contains tips and documentation regarding certain AIMMS features.
Below I will try to explain CP modeling in AIMMS+CP as well as some personal comments during my tests. So, let's start with the standard example: SEND+MORE=MONEY (or perhaps Sudoku is more standard nowadays?).
A word of caution: These models are tested with a beta version of AIMMS+CP (albeit a late beta), so some constraints/features might have been changed in the current version.
Compared to other CP systems, AIMMS use a little different concepts and approach when modeling, and here I will show this using the SEND+MORE=MONEY problem, where the object - of course - is to assign each letter to an integer so that the equation holds.
In this problem we define the array
All these definitions are done via AIMMS' GUI, the Model Explorer. Here is a picture how it looks like; the graphical I/O is shown to the right (the Page Manager):
Below is the full definition of the model except for the graphical part (compare with the .aim file SendMoreMoney2.aim). Note that one very important feature in AIMMS is that one define the set of the indices to be used in an array: here the index
Then we define the traditional constraints:
The procedure MainExecution is where the program is started and is essential to get things working. Here we just do the following:
It would be nice to have an easy way to actually showing all solutions, but I'm often quite happy with this feature.
One could note that AIMMS - with CPoptimizer as solver - shows the first solution of N=1000 in 3 seconds (422 failures).
My system setup for this benchmark: Running AIMMS in a Virtual Box running Windows 7, allotted 2Gb RAM, under a Linux Ubuntu 12.04, 64-bit with 12Gb RAM and 8 core Intel 930@2.80GHz.
Model: CoinsGridCPMIP.aim
This is originally a MIP problem but I use it to see how a CP solver handles the problem (often not very good so I'm yet to be surprised). I originally thought of doing two versions of this, one CP version and one MIP version. But it's so easy to change to MIP version so I first settled with just one project, hence the name CoinsGridCP.
Note: Chris Kuip was kind enought to later add a way to select between CP and MIP, see CoinsGridCPMIP.aim.
Model: Map2.aim
This is a simple Map coloring problem, with two variants: Map and Map2.
For the standard settings the model Map.aim shows this solution:
Symmetry breaking
Here is a simple symmetry breaking, where we state that Belgium has color 1 (whatever that color represents) with a restriction in the Index domain:
Since that worked quite well, I also tried another version of Map (Map2.aim) that use real color names instead of integers, and also use another representation of the connections between the countries. This is perhaps more in the spirit of AIMMS.
The set
As I have mentioned before, I'm always amazed how easy it is to state this problem in a CP system (cudos to these high level programming languages). Here is the problem specification (from Gecode's Minesweeper model):
Here's the solution to the problem shown above, where a "1" marks where the mines are.
Here is the way to fill the given hints:
AIMMS has excellent support for reification and I'm (happily) surprised that it worked as I expected. This is the constraint
This is a standard benchmark for theorem proving and I use it for testing how well a CP system supports boolean decision variables and reifications. The way I usually model this problem yields 8 solutions, all indicating the same murder (guess who).
This AIMMS model also yields 8 solutions, and the tables for
Here is the problem:
Some comments:
This problem is mainly used to test a CP system regarding two things:
This is a simple scheduling problem from Kim Marriott & Peter Stuckey: "Programming with constraints", page 112f, where the object is to optimize the tasks of moving some furnitures. In AIMMS the
It seems that we can assume that the End job requirement is automatically ensured by the cp::ParallelSchedule constraint, e.g. this constraint:
This is a optimization model where
In some variants of
Later note: The AIMMS Blog's blog post Scheduling example: Narrowing time window for smaller jobs explains a little how to use scheduling with AIMMS+CP.
Problem statement:
In the constraints
Fortunately Chris Kuip was kind enough to explain all this to me. And today he also wrote the blog post What is the element after the last? explaining this in some detail.
Model: MagicSquareWaterRetention.aim
The plain MagicSquare model is not so interesting, except perhaps that in the GUI one can select which symmetry breaking constraints to use. This is done via the four binary variables
Magic Square Water Retention problem
More interesting is MagicSquareWaterRetention.aim which is one of few model where I have tested with different search heuristics and other settings (see below for a little more about these). The object is to create a magic square where the numbers represents heights and retains as much water as possible in the "cavaties" that is created. More info about this problem is in Wikipedia article Water retention on mathematical surfaces. This AIMMS model also use the Frénicle standard form for symmetry breaking.
It seems that the big win is to use
Trying with N=5, my small allotted 2Gb for the VirtualBox Windows 7 was probably not enough since AIMMS unfortunately crashed. (Chris Kuip mentioned that this crasch is not reproducible in the released version of AIMMS+CP.)
[My best shot of this problem has so far been with a MiniZinc model where Gecode/fz solves N=4 in 0.8 seconds (48619 failures) using the heuristics "most_constrained"/"indomain_random".]
In this model we see some of the benefits and drawbacks of the AIMMS approach (as I perceive them):
There seems to be no way to set this option globaly, i.e. for all new projects created.
Later note: Guido Diepen (Paragon) blogged about this a year ago Exporting a section and importing it in another AIMMS project, though I didn't now that until very recently.
Don't forget to set Set
My AIMMS+CP page links to both .aim files (which shows the textual representation of parameters, variables and constraints) and the .aimmspack file (a binary file containing the full model project including the GUI part and other settings). As usual I have implemented many of my "starting/learning" models to be able to compare them with other CP systems. I would like to thank especially Chris Kuip and Gertjan de Lange from Paragon for great support, suggestions and inspirations during these tests.
First a little about Paragon and their commercial product AIMMS, from the Overview:
AIMMS offers an all-round development environment for the creation of high-performance decision support and advanced planning applications to optimize strategic operations. It allows organizations to rapidly improve the quality, service, profitability, and responsiveness of their operations. The AIMMS development environment possesses a unique combination of advanced features and design tools, such as the graphical model explorer, which allow you to build and maintain complex decision support applications and advanced planning systems in a fraction of the time required by conventional programming tools.One distinct feature of AIMMS is the integrated GUI where one both define the parameters, variable and constraints as well as the graphical input/output of the model. I should note that this is the first time in my CP experience that I've worked with this kind of tight integrated tool, which might explain some of the comments below.
AIMMS GUI tool was (and still is AFAIK) only for Windows, so I used VirtualBox under my Linux Ubuntu 12.04 when testing. Also, before the test, I have not used AIMMS at all.
Documentation, examples
There is a lot of documentations of how AIMMS work (PDF files available from the Help menu):- AIMMS Getting Started
- AIMMS User's Guide
- AIMMS Language Reference
- AIMMS Optimization Modeling
- AIMMS Function Reference
- AIMMS Tutorial
- etc
Variable
, Element Variable
) used in CP modeling.
For a person that knows AIMMS well and also know how CP works, the documentation is probably enough. But for someone like me that didn't know AIMMS before or someone who know AIMMS but not CP, the documentation is a little thin, especially since the examples are quite advanced to get started. I missed some of the standard CP examples such as N-queens, Send More Money, Sudoku, etc and examples of the basic stuff, such as how handle single variables, comparisons etc. Hopefully my AIMMS+CP implementations might be of some help.
The documentation don't say much about the CP solver and what options it has (and what they mean); though the is some documentation available with the Help button for the options. It is great that there are good support for scheduling and the example models are interesting, but - again - too complicated to start learning from.
I should mention that I have not read the complete documentation of AIMMS. Instead I started to code in AIMMS almost directly. This might have been a bad decision since I got stumped a couple of times with some of AIMMS basic concepts.
I later realized that AIMMS Blog contains tips and documentation regarding certain AIMMS features.
Modeling in AIMMS
Here I will focus on the modeling part. I have actually done very little extra with the graphics: it's just a Solve button, input fields and output fields/tables, i.e. nothing fancy at all. The models that is distributed with AIMMS is, on the other hand, much more elaborated and tasty and shows a huge number of general different possibilities in AIMMS; though - as mentioned above - there are not many CP specific models. In part, I wrote some of the detailed comments below with a hope to help newcomers in AIMMS+CP.Below I will try to explain CP modeling in AIMMS+CP as well as some personal comments during my tests. So, let's start with the standard example: SEND+MORE=MONEY (or perhaps Sudoku is more standard nowadays?).
A word of caution: These models are tested with a beta version of AIMMS+CP (albeit a late beta), so some constraints/features might have been changed in the current version.
SEND + MORE = MONEY: Some concepts
Model: SendMoreMoney2.aimCompared to other CP systems, AIMMS use a little different concepts and approach when modeling, and here I will show this using the SEND+MORE=MONEY problem, where the object - of course - is to assign each letter to an integer so that the equation holds.
In this problem we define the array
x
with the index domain i
(defined as a subset of Integers with the values {1,2,3,4,5,6,7,8}
. Then all the parameters
(constants) S,E,N,D,M,O,R,Y
are defined to be able to be index in x
, e.g.
such as 1000*x(S)+ 100*x(E)+ 10*x(N)+x(D)
(defining SEND) etc.
All these definitions are done via AIMMS' GUI, the Model Explorer. Here is a picture how it looks like; the graphical I/O is shown to the right (the Page Manager):
Below is the full definition of the model except for the graphical part (compare with the .aim file SendMoreMoney2.aim). Note that one very important feature in AIMMS is that one define the set of the indices to be used in an array: here the index
i
is defined via the set ii
(the name of the set is not so important except for documentation purposes). When defining the array (x
) the size is defined by the range of the index. So we define the array x(i)
(identifier: x
, index domain: (i)
) and makes it a Variable
with the range of {0..9}
(i.e. the domain of the values in x
).
Then we define the traditional constraints:
- The values of S and M must be > 0:
x(S) > 0 and x(M) > 0
- The main constraint SEND+MORE=MONEY is defined as usual:
(1000*x(S) + 100*x(E)+ 10*x(N) + x(D) +
1000*x(M) + 100*x(O)+ 10*x(R) + x(E)) =
(10000*x(M) + 1000*x(O) + 100*x(N) + 10*x(E) + x(Y))
View, Text Representation, All
.
DECLARATION SECTION SET: identifier : ii subset of : Integers index : i definition : {1,2,3,4,5,6,7,8} ; CONSTRAINT: identifier : SendMoreMoney definition : (1000*x(S) + 100*x(E)+ 10*x(N) + x(D) + 1000*x(M) + 100*x(O)+ 10*x(R) + x(E)) = (10000*x(M) + 1000*x(O) + 100*x(N) + 10*x(E) + x(Y)) ; MATHEMATICAL PROGRAM: identifier : SendMoreMoney2Plan direction : minimize constraints : AllConstraints variables : AllVariables type : CSP ; CONSTRAINT: identifier : SM definition : x(S) > 0 and x(M) > 0 ; VARIABLE: identifier : x index domain : (i) range : {0..9} ; PARAMETER: identifier : S definition : 1 ; PARAMETER: identifier : E definition : 2 ; PARAMETER: identifier : N definition : 3 ; PARAMETER: identifier : D definition : 4 ; PARAMETER: identifier : M definition : 5 ; PARAMETER: identifier : O definition : 6 ; PARAMETER: identifier : R definition : 7 ; PARAMETER: identifier : Y definition : 8 ; CONSTRAINT: identifier : AllDiff definition : cp::AllDifferent(i, x(i)) ; ENDSECTION ; PROCEDURE identifier : MainInitialization ENDPROCEDURE ; PROCEDURE identifier : MainExecution body : ShowProgressWindow; solve SendMoreMoney2Plan; ENDPROCEDURE ; ENDMODEL Main_SendMoreMoney2 ;The call
cp::AllDifferent(i, x(i))
works like this: The first parameter i
is the
"loop index" which is used to loop through the second argument x(i)
, i.e. it states that all
the elements in x
should be distinct. This is how for-loops are done in AIMMS and is a very
common idiom.
The procedure MainExecution is where the program is started and is essential to get things working. Here we just do the following:
-
ShowProgressWindow
: the Progress window shows how the model progress as well as the time, number of failures, etc. -
solve SendMoreMoney2Plan
which actuall start the solving process. This must be defined for all models. SendMoreMoney2Plan is of type mathematical program (select this type of identifier via type options labeled "...
"). For Constraint Satisfaction models like this, selectType: CSP
, and letDirection
be whatever (minimize or maximize). For optimization models, the type should beCOP
(Constraint OPtimization), and state the objective variable, and the direction. (Note: The objective variable must beFree
for some reason.)
Main Execution
(not
shown in the listing above since it belongs to the GUI.)
NQueens
The first naive version of the N-queens problem is NQueens.aim, but is slow for larger N:CONSTRAINT: identifier : C1 index domain : (i,j) definition : (i < j) and x(i) <> x(j) ; CONSTRAINT: identifier : C2 index domain : (i,j) definition : (i < j) and x(i) + i <> x(j) + j ; CONSTRAINT: identifier : C3 index domain : (i,j) definition : (i < j) and x(i) - i <> x(j) - j ; CONSTRAINT: identifier : AllDiff definition : cp::AllDifferent(i, x(i)) ;The more modern - and faster - version is shown in NQueens2.aim which use three
alldifferent
constraints:
CONSTRAINT: identifier : alldiff1 definition : cp::AllDifferent(i, x(i)) ; CONSTRAINT: identifier : alldiff2 definition : cp::AllDifferent(i, x(i)-i) ; CONSTRAINT: identifier : alldiff3 definition : cp::AllDifferent(i, x(i)+i) ;To see the number of solutions of a model, just add this line last in MainExecution:
DialogMessage(GMP::Solution::Count('ProjectPlan'));together with a large enough value of
Solution storage limit
in Setting, Project Settings, Specific Solvers, CPoptimizer 12.4, General
.
It would be nice to have an easy way to actually showing all solutions, but I'm often quite happy with this feature.
One could note that AIMMS - with CPoptimizer as solver - shows the first solution of N=1000 in 3 seconds (422 failures).
My system setup for this benchmark: Running AIMMS in a Virtual Box running Windows 7, allotted 2Gb RAM, under a Linux Ubuntu 12.04, 64-bit with 12Gb RAM and 8 core Intel 930@2.80GHz.
CoinsGrid
Model: CoinsGridCP.aimModel: CoinsGridCPMIP.aim
This is originally a MIP problem but I use it to see how a CP solver handles the problem (often not very good so I'm yet to be surprised). I originally thought of doing two versions of this, one CP version and one MIP version. But it's so easy to change to MIP version so I first settled with just one project, hence the name CoinsGridCP.
Note: Chris Kuip was kind enought to later add a way to select between CP and MIP, see CoinsGridCPMIP.aim.
Map, Map2
Model: Map.aimModel: Map2.aim
This is a simple Map coloring problem, with two variants: Map and Map2.
For the standard settings the model Map.aim shows this solution:
Country Colors ------------------ Belgium 2 Denmark 1 France 4 Germany 3 Netherlands 1 Luxembourg 1which is perfectly fine. Without any symmetry breaking there are 144 solutions.
Symmetry breaking
Here is a simple symmetry breaking, where we state that Belgium has color 1 (whatever that color represents) with a restriction in the Index domain:
Index domain: c1 | c1 = "Belgium" Colors(c1) = 1The solution is now:
Country Colors ------------------ Belgium 1 Denmark 1 France 4 Germany 3 Netherlands 2 Luxembourg 2which is also a correct solution.
Since that worked quite well, I also tried another version of Map (Map2.aim) that use real color names instead of integers, and also use another representation of the connections between the countries. This is perhaps more in the spirit of AIMMS.
The set
Colors
is defined as:
data {red, blue, green, yellow}It was also very easy to do the direct representation of the connections between the countries. AIMMS converts automatically to a boolean connection matrix (a nice feature):
DATA { (Belgium, [France, Germany, Netherlands, Luxembourg]), (Denmark, [Germany]), (France, [Belgium, Germany, Netherlands, Luxembourg]), (Germany, [Belgium, Denmark, France, Netherlands, Luxembourg]), (Netherlands, [Belgium, France, Germany]), (Luxembourg, [Belgium, Germany]) }However, it was a little bit harder to define the "DifferentColor" constraint using the color names, but after changing
CountryColors
to a ElementVariable
and Range: Colors
it worked as expected.
SET: identifier : Countries indices : c1, c2 definition : data {Belgium, Denmark, France, Germany, Netherlands, Luxembourg} ; SET: identifier : Colors index : c initial data : data {red, blue, green, yellow} ; ELEMENT VARIABLE: identifier : CountryColor index domain : (c1) range : Colors ; CONSTRAINT: identifier : DifferentColors index domain : (c1,c2) definition : if (c1 <> c2 and Connections(c1, c2) = 1) then CountryColor(c1) <> CountryColor(c2) endif ;And the solution:
Solution: Belgium blue Denmark red France yellow Germany gree Netherlands red Luxembourg red
Minesweeper
Model: Minesweeper.aimAs I have mentioned before, I'm always amazed how easy it is to state this problem in a CP system (cudos to these high level programming languages). Here is the problem specification (from Gecode's Minesweeper model):
A specification is a square matrix of characters. Alphanumeric characters represent the number of mines adjacent to that field. Dots represent fields with an unknown number of mines adjacent to it (or an actual mine). E.g. ..2.3. 2..... ..24.3 1.34.. .....3 .3.3..The main constraint states that the number in "this" cell (if > 0) is the sum of the number of mines surrounding this cell. The indices
i
and j
are defined as sets 1..#rows
and 1..#cols
, respectively. The four if
s in the Sum
constraint restricts the value of i+a
and j+b
to be
inside the matrix.
CONSTRAINT: identifier : C1 index domain : (i,j) definition : if (Problem(i,j) > 0) then Sum[(a,b), x(i+a, j+b) | (i+a > 0) and (j+b > 0) and (i+a <= R) and (j+b <= C)] = Problem(i,j) endif;However, this constraint is not enough since it don't rule out that a hint cell can contain a mine (or if a cell contains a hint, it cannot contain a mine). So we must add either of these two constraints
CONSTRAINT: identifier : C2 index domain : (i,j) definition : if (Problem(i,j) >= 0) then x(i,j) = 0 endif comment : "If a cell contains a hint (>= 0) then there can be no mine. Either C2 or C3 must be active." ; CONSTRAINT: identifier : C3 index domain : (i,j) definition : if (x(i,j) = 1) then Problem(i,j) = -1 endif comment : "If this cell contains a mine, then there can be no hint (i.e. >= 0). Either C2 or C3 must be active." ;I tend to prefer constraint C2.
Here's the solution to the problem shown above, where a "1" marks where the mines are.
1 0 0 0 0 1 0 1 0 1 1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 1 1 1 0 0 1 0 0 0 1 1(A more artistic modeler than me would show the result in a nicer way.)
Sudoku
Model: Sudoku2.aimHere is the way to fill the given hints:
CONSTRAINT: identifier : FillData index domain : (i,j) definition : if (Problem(i,j) > 0) then x(i,j) = Problem(i,j) endif ;Note that this can also be written without the
if
clause, which more show the
advantage of AIMMS modeling principle:
index domain: (i,j) | Problem(i,j) > 0 definition: x(i,j) = Problem(i,j)The Latin Square requirements are very easy to state, using two
alldifferent
constraints:
index: i cp::alldifferent(i, x(i,j))and
index: j cp::alldifferent(i, x(i,j))However the block constraint is a little more tricky (as in all CP systems). In AIMMS one has (as I understand it) to define a special parameter,
Blocks
to be used in the constraint:
PARAMETER: identifier : Blocks index domain : (i,j) definition : 1+N*Div(i-1,N)+Div(j-1,n) ; CONSTRAINT: identifier : C3 index domain : k definition : cp::AllDifferent( (i,j) | Blocks(i,j) = k, x(i,j) ) ;The unique solution to the problem instance is
7 9 5 1 4 6 8 2 3 6 4 3 8 2 5 7 9 1 2 1 8 3 9 7 4 6 5 5 2 7 9 6 1 3 8 4 8 6 4 7 5 3 2 1 9 1 3 9 2 8 4 6 5 7 4 5 1 6 7 2 9 3 8 9 7 6 5 3 8 1 4 2 3 8 2 4 1 9 5 7 6To ensure that this is a unique solution, simply counts the number of solutions: any value larger than 1 means that the model is wrong in some way. Here's to do it:
- Increase the number of
Solution storage limit
inSetting, Project Settings, Specific Solvers, CPoptimizer 12.4, General
to2
(the default is 1). - Show the number of solutions with this code in
MainExecution
:DialogMessage(GMP::Solution::Count('Sudoku2Plan') + " solutions");
AllDifferentExcept0
Model: AllDifferentExcept0.aimAIMMS has excellent support for reification and I'm (happily) surprised that it worked as I expected. This is the constraint
CAllDifferentExcept0
which states that all values in x
that are not 0 (zero) must be distinct (hence the name of the global constraint AllDifferentExcept0
).
if (i < j and x(i) <> 0 and x(j) <> 0) then x(i) <> x(j) endif;I also added an increasing constraint,
CIncreasing
:
if (ord(i) > 1) then x(i-1) <= x(i) endif;Though it might be more clear to write it as:
index domain : i | ord(i) > 1 definition : x(i-1) <= x(i)
WhoKilledAgatha: reification and Element variables
Model:WhoKilledAgatha.aimThis is a standard benchmark for theorem proving and I use it for testing how well a CP system supports boolean decision variables and reifications. The way I usually model this problem yields 8 solutions, all indicating the same murder (guess who).
This AIMMS model also yields 8 solutions, and the tables for
Hates
and Richer
are valid so I conclude that this model is correct (or rather: as correct as my
other versions of this problem).
Here is the problem:
Someone in Dreadsbury Mansion killed Aunt Agatha. Agatha, the butler, and Charles live in Dreadsbury Mansion, and are the only ones to live there. A killer always hates, and is no richer than his victim. Charles hates noone that Agatha hates. Agatha hates everybody except the butler. The butler hates everyone not richer than Aunt Agatha. The butler hates everyone whom Agatha hates. Noone hates everyone. Who killed Agatha?This was not as hard as I first thought it should be, though I had to experiment a little with
Element Variables
to get it to work.
Some comments:
- The two decision variables
Hates
andRicher
are set toElement Variables
to be able to handleElement
constraints such asHates(TheKiller, TheVictim) = 1 Richer(TheKiller, TheVictim) = 0
Note thatTheKiller
is a decision variable.
It's great that AIMMS support these kind of "matrix element constraint", even though one have to declare the variable as a special type.
- I like that AIMMS has the same syntax for
if-then else
whether or not decision variables or parameters are involved in theif
clause. This makes creating constraints much easier.
- When using other CP systems I will certainly miss AIMMS elegant ways of creating certain constraints.
Hates
) are are defined via the set (ij
), defining the indices and their domains (i
and j
).
PARAMETER: identifier : N definition : 3 ; SET: identifier : ij subset of : Integers indices : i, j definition : {1..N} ; VARIABLE: identifier : Hates index domain : (i,j) range : binary ; CONSTRAINT: identifier : KillerHatesAVictim definition : hates(TheKiller, TheVictim) = 1 ; CONSTRAINT: identifier : ButlerHaveEveryoneWhomAgataHates index domain : i definition : if (Hates(Agatha, i) = 1) then Hates(Butler, i) = 1 endif ;(Sometimes I don't like to have to name all constraints in the model, but right now when writing about them it's is quite useful.)
DivisibleBy9Through1
Model: DivisibleBy9Through1.aimThis problem is mainly used to test a CP system regarding two things:
- the
modulo
constraint, which is quite essential for some recreational mathematics problems. AIMMS has support for this (otherwise I would have tried to implement it). - how large integers can be handled
AIMMS can manage up to base 10 (which is quite normal). I have seen solutions of this problem for base 12 and 14 (found by the ECLiPSe CP system). Here are the solutions forN
<= 10 (the AIMMS model just show one of the alternative solutions):2: [1] 4: [1, 2, 3] [3, 2, 1] 6: [1, 4, 3, 2, 5] [5, 4, 3, 2, 1] 8: [3, 2, 5, 4, 1, 6, 7] [5, 2, 3, 4, 7, 6, 1] [5, 6, 7, 4, 3, 2, 1] 10: [3, 8, 1, 6, 5, 4, 7, 2, 9]
MainExecution
procedure
which saves the variables for the first solution to a file:
write AllVariables to file "DivisibleBy9Through1.wrt";
FurnitureMoving: simple scheduling problem
File: FurnitureMoving.aimThis is a simple scheduling problem from Kim Marriott & Peter Stuckey: "Programming with constraints", page 112f, where the object is to optimize the tasks of moving some furnitures. In AIMMS the
ParallelSchedule
constraint is used, which is much
like the traditional cumulative
constraint:
cp::ParallelSchedule( upperLimit, jobDomain, jobStart, jobDuration, jobEnd, jobHeight )Here are the specific values used in the model:
UpperLimit: 4 (resources, i.e. persons) UpperLimit2: 120 (minutes) jobDomain: j (index for domain Jobs: {1..UpperLimit2}) jobStart: Start (minutes, variable) jobDuration: Duration (minutes, may involve variables) jobEnd: End (minutes, variable) jobHeight: Resources (persons, may involve variables)The only trouble I had here was how to define
jobDomain
, but
after reading the definition a couple of times it's quite clear that
UpperLimit
, and jobHeight
should have the same unit (i.e.
resources), while the other are time (in minutes); that is, for this specific model.
It seems that we can assume that the End job requirement is automatically ensured by the cp::ParallelSchedule constraint, e.g. this constraint:
jobEnd(i) = jobStart(i) + jobDuration(i)(In many other CP systems one have to ensure this explicitly.)
This is a optimization model where
MaxEndTime
is to be minimized, stated
in the mathematical program (here called FurnitureMovingPlan
).
In some variants of
cumulative
(e.g. some of MiniZinc's solvers)
I've seen that the total number of required resources can be a
decision variable as well (and thus can be an objective variable), but this variant
is not supported in AIMMS.
Later note: The AIMMS Blog's blog post Scheduling example: Narrowing time window for smaller jobs explains a little how to use scheduling with AIMMS+CP.
Langford number problem: indices
Langford.aimProblem statement:
Langford's number problem (CSP lib problem 24)In this model I had some problems understanding how AIMMS' indices works, which emphasize that they don't work exactly as the "array based" indices that I'm used to from other CP systems.
Arrange 2 sets of positive integers 1..k to a sequence, such that, following the first occurence of an integer i, each subsequent occurrence of i, appears i+1 indices later than the last.
For example, for k=4, a solution would be 41312432
In the constraints
pos1
and pos3
shown below, it's important
to restrict the index j
so it don't go "over the edge" of the position
array, which is interpreted by AIMMS as an error in the model, i.e. it throws an
UNSAT when this happens. [Or perhaps it's the solver CPLEX CP Optimizer that does
this interpretation.] Using the traditional "array based" for-loops this would have
been easier, at least for me using a traditional for-loop. So beware of this gotcha.
(Note: When writing this now much later, this behaviour actually makes much more sense
than when I did the model.)
CONSTRAINT: identifier : pos1 index domain : j |j <= k definition : position(j) + j+1 = position(j+k) comment : "Note the index domain." ; CONSTRAINT: identifier : pos2 index domain : (i) definition : solution(position(i)) = i ; CONSTRAINT: identifier : pos3 index domain : j |j <= k definition : solution(position(k+j)) = j comment : "Note the index domain." ;A similar case is in the AllInterval where one of the constraints is to restrict the index
i
to not be the last
value of the Set i1
set (defined as the range 1..N
), so it
restricts i
in the DiffsK
constraint to not be N
.
SET: identifier : i1 subset of : Integers index : i definition : {1..N} ; ... CONSTRAINT: identifier : DiffsK index domain : i | i <> last( i1 ) definition : diffs(i) = Abs(x(i)-x(i+1)) comment : "Note the special handling of the index domain for this constraint." ;These kind of behaviour (different to what I'm used to) have caused some long debugging sessions.
Fortunately Chris Kuip was kind enough to explain all this to me. And today he also wrote the blog post What is the element after the last? explaining this in some detail.
Magic Square and Magic Square Water Retention problem
Model: MagicSquare.aimModel: MagicSquareWaterRetention.aim
The plain MagicSquare model is not so interesting, except perhaps that in the GUI one can select which symmetry breaking constraints to use. This is done via the four binary variables
UseSymmetry1
... UseSymmetry4
.
Magic Square Water Retention problem
More interesting is MagicSquareWaterRetention.aim which is one of few model where I have tested with different search heuristics and other settings (see below for a little more about these). The object is to create a magic square where the numbers represents heights and retains as much water as possible in the "cavaties" that is created. More info about this problem is in Wikipedia article Water retention on mathematical surfaces. This AIMMS model also use the Frénicle standard form for symmetry breaking.
It seems that the big win is to use
Search Type::Restart
instead of Automatic. Using this option solves the problem for N=4
in about 3 seconds (with 168662 failures) compared to 20 seconds (663751 failures) when using Search Type: Automatic
.
Trying with N=5, my small allotted 2Gb for the VirtualBox Windows 7 was probably not enough since AIMMS unfortunately crashed. (Chris Kuip mentioned that this crasch is not reproducible in the released version of AIMMS+CP.)
[My best shot of this problem has so far been with a MiniZinc model where Gecode/fz solves N=4 in 0.8 seconds (48619 failures) using the heuristics "most_constrained"/"indomain_random".]
In this model we see some of the benefits and drawbacks of the AIMMS approach (as I perceive them):
- benefit: It's quite easy and descriptive to state the
water retention constraint (first part). There is no explicit for-loop, just using the
defined indices and adjust for the border cases (1 and N).
(i,j) | i > 1 and j > 1 and i < N and j < N water(i,j) = Max(x(i,j), Min(water(i-1,j), water(i,j-1), water(i+1,j), water(i,j+1)))
- drawback: When defining the ranges (domains) - which in this model
are
1..N
,1..N*N
, and1..N*N*N
, it must be done with two extra help parameters (N2
, N*N, andN3
, N*N*N) since the Range Wizard for the variable cannot calculate formulas for the upper bound. - drawback: Defining the optimization problem the objective (
z
) must be declared as a Free Range variable (-inf .. inf) and not using the Range domain (1..N3
). Intuitively this seems to be a too large domain for the solver to handle, but perhaps the solver is intelligent in calculating the bounds.
Some other comments
Here are some more asorted comments about AIMMS+CP and my way of using it.Number of solutions
One of the drawback in AIMMS is that it's not easy to generate all the solutions. It is possible but I have not done that. However, it's easy to count the number of solutions using the following:- Change
Settings, Project Options...
selectSpecific solvers, CPoptimizer 12.4, General
and changeSolution storage limit
to a higher value, e.g. 1000. - Add this code to the MainExecution procedure:
DialogMessage("It was " + GMP::Solution::Count('ProjectPlan') + " solution(s).");
Data instances, data files
Many of models contain some data (as a parameter that might be changed when running the project). There is in principle two approaches for using data files in AIMMS:- enter the data via the
Parameter
definition and keep it there. - define a couple of data files and load the appropriate via
Data, Load Case, as active
Settings, Project, Startup & authorization,
Startup case
.
Automatically saving .aim files
Since I often want to look in other models when implementing a new (mostly done with the .aim files), I tend to set the projects to save the .aim file when closing the project: Settings, Autosave & Backups, Project, Create an AIM file at Project Close.There seems to be no way to set this option globaly, i.e. for all new projects created.
Heuristics
I like that there is a lot of different heuristics, though I have not explored them as much. They are defined inSettings, Project Option,
Specific solvers, CPoptimizer 12.4, Search
- Variable selection:
- Automatic
- Random
- Smallest domain size
- Largest domain size
- Smallest maximum domain value
- Largest maximum domain value
- Smallest minimum domain value
- Largest minimum domain value
- Smallest impact
- Largest impact
- Smallest impact last branch
- Largest impact last branch
- Smallest local impact
- Largest local impact
- Smallest success rate
- Largest success rate
- Value selection:
- Automatic
- Smallest impact
- Largest success rate
- Smallest value
- Largest value
Procedures
One drawback in AIMMS is that there is no way (I know of) to implement a user-defined "procedure" that is easy accessible in different models/projects. Instead one has to copy the constraint verbatim. I have mostly copied the definitions from the saved .aim files. There is also a way of viewing the textual representation in the current model (presented much like the .aim files). This is done viaView, Text Representational, All
when in the Model Explorer.
Later note: Guido Diepen (Paragon) blogged about this a year ago Exporting a section and importing it in another AIMMS project, though I didn't now that until very recently.
Some tips
Stop a calculation: Ctrl-Shift-SDon't forget to set Set
ii
to a subset of integers.
Pros / Cons: A kind of summary
And finally, here is a subjective list of pros/cons about AIMMS (as I have understood it). Pros- AIMMS has a very nice GUI to create and checking constraints and the related data. The online checking of constraints, indices, lengths etc is very handy.
- Certain constructs are nice and powerful, e.g. that there is no syntactic difference between "if" on plain boolean variables/conditions and on decision variables.
- Another nice thing is that some loops can be written quite naturally without "for" just via the index (i,j etc).
- Using a GUI makes it easy to connect different parts of the model, especially between data/variables and the output.
- For an "integer array based" constraint programmer like me it took some time to get used to the "items based" approach that is AIMMS' forte. Example: Some intuitions from "integer index based" modelling don't apply directly to AIMMS; e.g. see the dicussion about the Langford model above.
- Also, the focus on defining indices and their connection with arrays was a little uncommon for me, and took some time from the rest of the modeling.
- It's not very easy to generate and show all solutions to a problem. Though it's very easy to generate the number of solutions (see NQueens etc).
- Using a GUI as the main modeling is a bit strange to me since I'm almost always use a texteditor (Emacs) and then run via command line (under Linux). There are ways to use textfiles directly but since this is Windows I'm not very confortable which that.
- I used Windows 7 in VirtualBox under Ubuntu 12.04 and some things didn't work, for example copying backup files from my Linux box to the Windows' AIMMS' directory. This was - of course - not AIMMS' fault, but it made my experience sometimes a little frustrating...
My AIMMS+CP models
Here are some of my AIMMS+CP models, many originally written in the Summer 2011. Around mid-September 2012, I re-checked (and re-modelled) these with AIMMS version 3.13 (beta) and also added a few new. Thanks to Chris Kuip (Paragon) for comments, suggestions and improvements on many of these models.- AllDifferentExcept0.aimmspack: Alldifferent except 0, AllDifferentExcept0.aim
- AllInterval.aimmspack: All Interval, AllInterval.aim
- BinPackingTest.aimmspack: Binpacking test, BinPackingTest.aim
- CoinsGridCP.aimmspack Coins grid, CP only version, CoinsGridCP.aim li> CoinsGridCPMIP.aimmspack Coins grid, both CP and MIP version, CoinsGridCPMIP.aim
- Diet.aimmspack: Simple diet problem, Diet.aim
- DivisibleBy9Through1Chris.aimmspack: Divisible by 9 through 1, DivisibleBy9Through1.aim
- FurnitureMoving.aimmspack: Furniture moving, scheduling
- Langford.aimmspack: Langford, Langford.aim
- LeastDiff.aimmspack: Least Diff, LeastDiff.aim
- MagicSequence.aimmspack: Magic sequence, MagicSequence.aim
- MagicSquare.aimmspack: Magic square, MagicSquare.aim
- MagicSquareWaterRetention.aimmspack: Magic square Water Retention (with Frénicle normal form for symmetry breaking), MagicSquareWaterRetention.aim
- Map.aimmspack: Map problem, Map.aim
- Map2.aimmspack: Map problem, alternative version, Map2.aim
- Marathon.aimmspack: Marathon logic problem, Marathon.aim
- Marathon2.aimmspack: Marathon logic problem, different approach, Marathon2.aim
- Marathon3.aimmspack: Marathon logic problem, different approach suggested by Chris Kuip, Marathon3.aim
- Minesweeper.aimmspack: Minesweeper (updated 20121004), Minesweeper.aim
- NQueens.aimmspack: N-queens problem, decomposition, slower, NQueens.aim
- NQueens2.aimmspack: N-queens problem, 3 alldifferent approach, faster, NQueens2.aim
- PlaceNumberPuzzle.aimmspack: Place number puzzle, PlaceNumberPuzzle.aim
- QuasigroupCompletion.aimmspack: Quasigroup completion, QuasigroupCompletion.aim
- SendMoreMoney.aimmspack: SEND+MORE=MONEY, SendMoreMoney.aim
- SendMoreMoney2.aimmspack: SEND+MORE=MONEY, alternative version, SendMoreMoney2.aim
- SendMostMoney.aimmspack: SEND+MOST=MONEY and optimize MONEY, SendMostMoney.aim
- SequenceTest.aimmspack: Sequence test, SequenceTest.aim
- Seseman.aimmspack: Seseman problem, Seseman.aim
- SetCovering.aimmspack: Simple set covering problem (Winston), SetCovering.aim
- Sudoku2.aimmspack: Sudoku, alternative version, Sudoku2.aim
- SurvoPuzzle.aimmspack: Survo Puzzle, SurvoPuzzle.aim
- TableTest.aimmspack: A simple version of Table constraint, using FORALL, TableTest.aim
- Tomography.aimmspack: Discrete Tomography, Tomography.aim
- WhoKilledAgatha.aimmspack: Who Killed Agatha logic problem, WhoKilledAgatha.aim
- Xkcd.aimmspack: Xkcd "knapsack" problem, Xkcd.aim