A first look at OscaR (Scala in OR), former Scampi
OscaR - (Scala in OR) is a constraint solver for Scala (former called Scampi). The source is here (Bitbucket).
From the Wiki:
I should perhaps mention that this project has been both learning OscaR and learning more about Scala. Scala is a very nice programming language with suite me quite well since it supports both imperative object oriented way of thinking as well as pure functional approach (though I have to confess that in some models this is not apparent at all).
Most examples shown in this blog post are from my OscaR page. There are other OscaR examples in the repo here.
For other constraints which don't return a single value, then one have to use a slightly different approach and add the CPSolver as a parameter to the function. Here is an example of defining the
OscaR use the following operators when using boolean operators and reifications:
Also,
An example that use a lot of reifications is WhoKilledAgatha.scala. Here's an extract:
When an expression (constraint, reification) has both an Int and an CPVarInt variable, then OscaR/Scala is picky about where to put the terms. For example, the above equation SEND+MORE=MONEY must be written with the CPVarInt at the left, i.e. as
If the equation would have been stated as
The way to indicate optimization of a variable is
In a very high level CP system, such as MiniZinc (langford2.mzn), it is stated a little more simple (and - in my book - more natural) as:
Here I will just mention some of the basic search methods (which I tend to use first). Let us here assume that
As indicated in the introduction, OscaR has more features besides support for Constraint Programming, such as Constraint Based Local Search (former Asteroid), Linear Programming, and Discrete Event Simulation, as well as Scheduling in CP (mentioned above). Also there is support for visualization. These features has not been tested but I hope to test some of them in the future.
Most of my own OscaR models where converted from either my JaCoP/Scala models or my or-tools/C# models; and this probably shows in some of them.
All these models (or at least the majority) is also be available from the OsacaR repo: https://bitbucket.org/oscarlib/oscar/src/tip/src/main/scala/oscar/examples/cp/hakank. Thanks to Pierre Schaus for improving several of these models, either directly or indirectly by adding functions (or syntactic sugar).
From the Wiki:
OscaR (Scala in OR) is a Scala toolkit to solve Operations Research problems. The techniques currently available in OscaR are:The part I have focused on is Constraint Programming which is presented in these links:
- Constraint Programming (former called Scampi)
- Constrained Based Local Search (former called Asteroid)
- Linear Programming
- Discrete Event Simulation
- Derivative Free Optimization
- Visualization
- Modeling
- Logical and Reified Constraints
- Search
- Large Neighborhood Search
- Implement your own constraint
- Scheduling with CP
- oscar/examples/cp
- oscar/examples/cp/scheduling examples of scheduling in CP
- oscar/examples/cp/hakank (my own examples at the bitbucket repo)
Modeling in OscaR
OscaR should be seen as a high level CP modeling language, since it has a lot of operator overloaded, see example of this below. (About the only thing I miss regarding this is a "sugared" version of element, i.e. x(y) where y is a decision variable. More about that later on...).I should perhaps mention that this project has been both learning OscaR and learning more about Scala. Scala is a very nice programming language with suite me quite well since it supports both imperative object oriented way of thinking as well as pure functional approach (though I have to confess that in some models this is not apparent at all).
Most examples shown in this blog post are from my OscaR page. There are other OscaR examples in the repo here.
First model: SEND+MORE=MONEY
Here is the full model of SEND+MORE=MONEY problem (it's also available at SendMoreMoney.scala, and - as mentioned - also in the OscaR repo, see below):package oscar.examples.cp.hakank import oscar.cp.modeling._ import oscar.cp.search._ import oscar.cp.core._ object SendMoreMoney { def main(args: Array[String]) { val cp = CPSolver() val S = CPVarInt(cp, 0 to 9) val E = CPVarInt(cp, 0 to 9) val N = CPVarInt(cp, 0 to 9) val D = CPVarInt(cp, 0 to 9) val M = CPVarInt(cp, 0 to 9) val O = CPVarInt(cp, 0 to 9) val R = CPVarInt(cp, 0 to 9) val Y = CPVarInt(cp, 0 to 9) val all = Array(S,E,N,D,M,O,R,Y) cp.solveAll() subjectTo { cp.add( S*1000 + E*100 + N*10 + D + M*1000 + O*100 + R*10 + E == M*10000 + O*1000 + N*100 + E*10 + Y) cp.add(S > 0) cp.add(M > 0) cp.add(alldifferent(all), Strong) } exploration { cp.binaryFirstFail(all) println(all.mkString("")) } println() cp.printStats() } }The output is:
9 5 6 7 1 0 8 2 (time(ms),36) (#bkts,5) (time in fix point(ms),18) (time in trail restore(ms),0) (max trail size,587)Some things to note:
- OscaR is quite influenced by the way Comet (and OPL) separate the constraint part (
cp.solveAll() subjectTo
) and the search (branching) and output part (exploration
). See below for a little more about search, and for a optimization example. - The arithmetic operators (
*,+,==
, and many more) are nicely overloaded which simplifies much of the modeling. I comment more about this below. - Additions of the constraint are done with
cp.add(constraint)
.
// ... val all = Array.fill(8)(CPVarInt(cp, 0 to 9)) val Array(s,e,n,d,m,o,r,y) = all cp.solveAll() subjectTo { // constraints cp.add( s*1000 + e*100 + n*10 + d + m*1000 + o*100 + r*10 + e = m*10000 + o*1000 + n*100 + e*10 + y) cp.add(s > 0) cp.add(m > 0) cp.add(alldifferent(all), Strong) } exploration { // ... }Another example is Minesweeper.scala which is an example of how to use
yield
to collect variables.
// ... val mines = Array.fill(r)(Array.fill(c)(CPVarInt(cp, 0 to 1))) cp.solveAll subjectTo { val tmp = List(-1,0,1) for(i <- 0 until r) { for(j <- 0 until c if game(i)(j) >= 0) { cp.add(sum( for{ a <- tmp; b <- tmp if ((i+a >= 0) && (j+b >= 0) && (i+a < r) && (j+b < c)) } yield mines(i+a)(j+b)) == game(i)(j) ) // end sum // redundant constraint if (game(i)(j) > X) { cp.add(mines(i)(j) == 0) } } } // ...
Running OscaR
Running OscaR requires - of course - the OscaR distribution and Scala. Compiling it from source also requires thesbt
tool. Obtaining a working version of OscaR can be done in two ways:
- Get the distribution from the bitbucket.org repo. A JAR file is available here.
- Or use Mercurial to do things manually, which basically consists of the following:
(ht clone), hg pull, hg update, sbt assembly, sbt test
Running OscaR models
Note that models that are in Scala packages - such as the models in the distribution and my own models - must first be compiled with scala/fsc, i.e. they cannot be run as Scala scripts (at least using the current Scala version). Here is how I run a OscaR model under Linux:
$ fsc -J"-Xmx2000m" -deprecation -cp /path/to/oscar/oscar.jar -P:continuations:enable oscar/examples/cp/hakank/SendMoreMoney.scala
$ time scala -J"-Xmx2000m" -cp /path/to/oscar/oscar.jar -P:continuations:enable oscar.examples.cp.hakank.SendMoreMoney
Defining constraints (decompositions)
To define a constraint (decomposition) there are two variants depending on the return value. For "simple" constraints which returns a single value, e.g. CPVarInt, a Constraint, or CPVarBool then it can be used quite "seam less" in the model ascp.add(myConstraint(x))
. An example from DeBruijn.scala is the constraint toNum
which channels between an array of CPVarInt's and a CPVarInt (e.g. the Array (4,2,3,1) is channeled with the number 4231):
def toNum(t: Array[CPVarInt], base: Int=10) = sum( Array.tabulate(t.length)(i=> t(i)*pow(base, t.length-i-1).toInt)) // ... cp.solveAll subjectTo { // ... // Usage: cp.add(x(i) == toNum(t, base)) // ... } // ...
For other constraints which don't return a single value, then one have to use a slightly different approach and add the CPSolver as a parameter to the function. Here is an example of defining the
inverse
, used in Marathon.scala and PhotoProblem.scala for presenting the result in a more appropriate manner:
def inverse(cp: CPSolver, x: Array[CPVarInt], y: Array[CPVarInt]) { val len = x.length for(i <- 0 until len; j <- 0 until len) { cp.add( (y(j) === i) == (x(i) === j) ) } } // ... cp.maximize(z) subjectTo { // Usage: Channeling positions <=> places inverse(cp, positions, places) } // ...Here are some other defined decompositions (many not at all advanced, but never the less nice to have):
-
contiguity
: ContiguityRegular.scala -
increasing
: GolombRuler.scala -
cumulative
: FurnitureMoving.scala. Note: there is a much more advanced Scheduling support in OscaR, though I have not used it much. -
circuit
, andcirctuit_path
: CircuitTest.scala. Please note that there is a more efficient implementation ofcircuit
built-in OscaR; this model should be seen as an etude...
Reification: Alldifferent except 0
Here is a simple decomposition of the global constraint alldifferent expcept 0 to show how reifications are done in OscaR.def alldifferent_except_0(cp: CPSolver, y: Array[CPVarInt]) = { for(i <- 0 until y.length; j <- 0 until i) { cp.add( ((y(i) !== 0) && (y(j) !== 0)) ==> (y(i) !== y(j)) ) } }I.e.: if both y(i) and y(j) are != 0, then they should be different.
OscaR use the following operators when using boolean operators and reifications:
A > B is reified with A >>= B A >= B is reified with A >== B A < B is reified with A <<= B A <= B is reified with A <== B A == B is reified with A === B(Note the trailing
=
for the reified variant.)
Also,
b1 ==> b2
represents the implication: b1
implies b2
(i.e. if b1
is true, then b2
is true). And !A
is the negation of A.
An example that use a lot of reifications is WhoKilledAgatha.scala. Here's an extract:
// ... // Charles hates noone that Agatha hates. NRANGE.foreach(i=> cp.add((hates(agatha)(i)) ==> (!hates(charles)(i)))) // The butler hates everyone not richer than Aunt Agatha. NRANGE.foreach(i=> cp.add(!richer(i)(agatha) ==> hates(butler)(i)))
Always put the CPVarInt at the left in mixed expressions
This is a gotcha when using OscaR, and I mentioned a similar problem regarding JaCoPInScala in my blog post A first look at scalaJaCoP (Scala wrapper for JaCoP).When an expression (constraint, reification) has both an Int and an CPVarInt variable, then OscaR/Scala is picky about where to put the terms. For example, the above equation SEND+MORE=MONEY must be written with the CPVarInt at the left, i.e. as
S * 1000 + ..
(rather than 1000 * S + ...
), and this is a consequence how Scala's overloading works: Int's don't know about constraints etc, but CPVarInt's knows everything...
If the equation would have been stated as
cp.add(1000*S...
then the following error is thrown which can be confusing; and I mention it here so you might remember it when starting to use OscaR on you own. (Perhaps more seasoned Scala users understands this immediately and feel offended by this friendly advice.)
SendMoreMoney.scala:57: error: overloaded method value * with alternatives: (x: Double)Double(x: Float)Float (x: Long)Long (x: Int)Int (x: Char)Int (x: Short)Int (x: Byte)Int cannot be applied to (oscar.cp.core.CPVarInt) cp.add( 1000*S + E*100 + N*10 + D + ^
Optimization
OscaR supports - of course - optimization. Here's a simple diet problem (Diet2.scala), where the object is to minimize the total cost with the restrictions that the nutritions have minimum limits. Note the nice feature that a decision variable (herecost
) can be defined directly using a constraint (val cost = weightedSum(price, x)
).
The way to indicate optimization of a variable is
cp.minimize(cost)
(and cp.minimize(z)
):
val cp = CPSolver() val n = 4 val price = Array( 50, 20, 30, 80) // minumum requirements for each nutrition val limits = Array(500, 6, 10, 8) // nutritions for each product val calories = Array(400, 200, 150, 500) val chocolate = Array(3,2,0,0) val sugar = Array(2,2,4,4) val fat = Array(2,4,1,5) val all = Array(calories, chocolate, sugar, fat) // variables val x = Array.fill(n)(CPVarInt(cp, 0 to 10)) val cost = weightedSum(price, x) cp.minimize(cost) subjectTo { // handle the nutrition requirements for(i <- 0 until n) ( cp.add(weightedSum(all(i), x) >= limits(i)) ) } exploration { cp.binaryFirstFail(x) println(x.mkString(" ")) }The result shows that the minimum cost is 90 using 3 units of item 2.
0 3 0 1 objective tighten to 140 0 3 1 0 objective tighten to 90 (time(ms),31) (#bkts,5) (time in fix point(ms),6) (time in trail restore(ms),0) (max trail size,446)
Element: Langford
Since I'm a little obsessed with how theelement
constraint is written in a CP system, it must be mentioned here as well. Example: element
constraints in used in Langford.scala:
for(i <- 1 to k) { cp.add(position(i+k-1) == (position(i-1) + i+1)) cp.add(element(solution, position(i-1)) == i) cp.add(element(solution, position(k+i-1)) == i) }And this is similar to most CP systems which are hosted in a programming language.
In a very high level CP system, such as MiniZinc (langford2.mzn), it is stated a little more simple (and - in my book - more natural) as:
forall(i in 1..to) ( position[i+k-1] = position[i-1] + i+1 /\ solution[position[i-1]] == i /\ solution[position[k+i-1]] == i )
Search (labeling/branching)
This principle of search (labeling, branching) in OscaR is inspired by Comet which means that you have building blocks that can be put together to do quite advanced things. The basic approach is defined in OscaR's Wiki search. In OscaR's main CP example directory, there are many example how to do advanced search.Here I will just mention some of the basic search methods (which I tend to use first). Let us here assume that
x
is an Array of CPVarInt, then OscaR support these utility functions:
-
cp.binary(x)
-
cp.binaryFirstFail(x)
-
cp.binaryMaxDegree(x)
I [Pierre Schaus] have adapted binary to let you add custom variable/value heuristics.The available attributes (of a
binary(vars: Array[CPVarInt], varHeuris: (CPVarInt => Int) = minVar, valHeuris: (CPVarInt => Int) = minVal)
So if you want a min dom heuristic, taking the max value you would do
cp.binary(x,_.size,_.max)
If you want a max degree one you can do
cp.binary(x,-_.constraintDegree,_.max)
CPVarInt
) that can be used directly in
this manner is:
- size
- min
- max
- constraintDegree
- randomValue (for value selection)
- an combinations such as
(v => v.max-v.min)
, etc.
// ... cp.binary(x_flat,-_.constraintDegree,_.randomValue) // ...
Scheduling
Very recently support for scheduling was added to OscaR by Renaud Hartert. See the Wiki entry Scheduling with CP for more information. I haven't used it very much so I just point to the examples.Some end notes
It's fun modeling in OscaR since it's so high level, and extra fun since Scala is a very nice programming language.As indicated in the introduction, OscaR has more features besides support for Constraint Programming, such as Constraint Based Local Search (former Asteroid), Linear Programming, and Discrete Event Simulation, as well as Scheduling in CP (mentioned above). Also there is support for visualization. These features has not been tested but I hope to test some of them in the future.
Most of my own OscaR models where converted from either my JaCoP/Scala models or my or-tools/C# models; and this probably shows in some of them.
My OscaR models
Here are some of my OscaR models.All these models (or at least the majority) is also be available from the OsacaR repo: https://bitbucket.org/oscarlib/oscar/src/tip/src/main/scala/oscar/examples/cp/hakank. Thanks to Pierre Schaus for improving several of these models, either directly or indirectly by adding functions (or syntactic sugar).
- ABCEndview.scala: ABC End View grid puzzle (a.k.a. "Easy as ABC", "Last Man Standing")
- AddedCorner.scala: Added corner problem
- AllDifferentExcept0.scala: Decomposition of
alldifferent_except_0
(with a decomposition of theincreasing
constraint) - AllIntervals.scala: All intervals (CSPLib #7)
- Alphametic.scala: Fairly general (simple) alphametic solver
- AlphameticGenerate.scala: A simple alphametic problem generator (generate words from a word list and test...)
- AppointmentSchedulingSet.scala: Appointment Scheduling, set approach (from the Stack Overflow question: Appointment scheduling algorithm (N people with N free-busy slots, constraint-satisfaction))
- APuzzle.scala: The "8809=6" puzzle (See God plays dice A puzzle)
- ARoundOFGolf.scala: A Round of Golf (Dell Logic Puzzles)
- Assignment.scala: Assignment problem (Winston)
- BalesOfHay.scala: Bales of hay problem (from The Math Less Traveled The haybaler)
- BowlsAndOranges.scala: Bowls and oranges problem
- BrokenWeights.scala: Broken weights problem
- BusSchedule.scala: Bus scheduling problem (Taha)
- CalculsDEnfer.scala: Calculs d'Enfer problem (from the NCL manual)
- CircuitTest.scala: Test of a decomposition of
circuit
as well as an "path extractor" for the circuit. - ClockTriplets.scala: Clock triplets problem (Dean Clark via Martin Gardner)
- Coins3.scala: Minimum mumber of coins that allows one to pay exactly any amount smaller than one Euro (from the ECLiPSe book)
- CoinsGrid.scala: Coins grid problem
- CombinatorialAuction.scala: Combinatorial auction
- ContiguityRegular.scala: Decomposition of
contiguity
constraint (usingregular
andAutomaton
) - CostasArray.scala: Costas Array
- CoveringOpl.scala: Set covering problem (OPL) (using a decomposition of
scalarProduct
constraint) - Crew.scala: Crew allocation problem
- CrossWord.scala: Simple crossword problem
- Crypta.scala: Crypta, alphametic problem (standard Prolog benchmark)
- Crypto.scala: Crypto, alphametic problem (standard Prolog benchmark)
- Crypto2.scala: Alternative approach to Crypto, alphametic problem (standard Prolog benchmark)
- CuriousSetOfIntegers.scala: Curious set of integers (Martin Gardner)
- DeBruijn.scala: de Bruijn sequence (using a decomposition of
toNum
, andmin
constraints) - Diet.scala: Simple diet opimization problem
- Diet2.scala: Simple diet opimization problem (alternative)
- DivisibleBy9Through1.scala: Divisible by 9 through 1
- Eq10.scala: Eq10 (standard benchmarking problem)
- FurnitureMoving.scala: Furniture moving, a simple scheduling problem (using
cumulative
) - EinavPuzzle.scala: Solving A programming puzzle from Einav
- EinsteinPuzzle.scala: Einstein logic puzzle (variant of Zebra, see below)
- FillAPix.scala: Fill-a-pix grid puzzle.
Problem instances: - FiveFloors.scala: Five Floors (logic puzzle)
- Futoshiki.scala: Futoshiki grid puzzle
- GolombRuler.scala: Golomb ruler (CSPLib #6)
- Grocery.scala: Grocery problem
- Hidato.scala: Hidato grid puzzle (slow)
- HidatoTable.scala: Hidato grid puzzle (faster version using table constraint.)
- JustForgotten.scala: Just forgotten puzzle (Enigma problem #1517)
- Kakuro.scala: Kakuro grid puzzle
- KenKen2.scala: KenKen grid puzzle
- KillerSudoku.scala: Killer Sudoku grid puzzle
- Langford.scala: Langford's number problem (CSPLib #24)
- LeastDiff.scala: Least difference problem (minimize the difference ABCDE-FGHIJ)
- Lectures.scala: Lectures, schedule problem (from Biggs "Discrete Mathematics")
- MagicSequence.scala: Magic sequence
- MagicSquare.scala: Magic square
- MagicSquareAndCards.scala: Magic square and cards (Martin Gardner)
- MapColoring.scala: Map coloring
- MapColoring2.scala: Map coloring, alternative approach
- MapColoring3.scala: Map coloring, alternative approach
- Marathon.scala: Marathon puzzle (from Xpress)
- MaxFlowTaha.scala: Maximum flow problem (Taha)
- MaxFlowWinston1.scala: Maximum flow problem (Winston)
- Minesweeper.scala: Minesweeper
Data files: - MrSmith.scala: Mr Smith problem (logic problem from IF Prolog)
- NontransitiveDice.scala: Nontransitive dice
- NurseRosteringRegular.scala: Nurse rostering using
regular
constraint - NurseRosteringRegular2.scala: Nurse rostering using
regular
constraint (with a little different requirements than NurseRosteringRegular.scala) - Olympic.scala: Olympic puzzle (alphametic, standard Prolog benchmark)
- OrganizeDay.scala: Organizing a day, simple scheduling problem
- PandigitalNumbers.scala: Pandigital numbers
- PMedian.scala: P-median problem (from the OPL manual)
- PhotoProblem.scala: Photo problem (From Mozart/Oz) (with a decomposition of the
inverse
constraint) - PlaceNumberPuzzle.scala: Place number puzzle
- PostOfficeProblem.scala: Post office problem (Winston)
- QuasigroupCompletion.scala: Quasigroup completion
Data files: - Rogo2.scala: Rogo grid puzzle solver
Data files: - SafeCracking.scala: Safe cracking puzzle (Oz primer)
- SchedulingSpeakers.scala: Scheduling speakers (Rina Dechter)
- SecretSanta.scala: Secret Santa problem
- SecretSanta2.scala: Secret Santa problem II (optimizing "Santa distance")
- SendMoreMoneyAnyBase.scala: SEND+MORE=MONEY alphametic problem in "any" base
- SendMoreMoney.scala: SEND+MORE=MONEY alphametic problem
- SendMostMoney.scala: SEND+MOST=MONEY alphametic optimization problem
- SequenceSum.scala: Sequence sum: sum each subsequence of length s to m. (Decomposition of
sequence_sum
constraint) - Seseman.scala: Seseman convent problem
- SetCovering.scala: Set covering problem, placing of firestations (Winston)
- SetCovering2.scala: Set covering problem, number of security telephons on a campus (Taha)
- SetCovering3.scala: Set covering problem, senators making a committe (Murty)
- SetCovering4.scala: Set covering problem (and set partition), work scheduling (Lundgren, Roennqvist, Vaerbrand "Optimeringslaera")
- SetCoveringDeployment.scala: Set covering deployment problem
- SetCoveringSkiena.scala: Set covering problem (Steven Skiena)
- SetPartition.scala: Set partition problem
- SichermanDice.scala: Sicherman dice
- SkiAssignment.scala: Ski assignment problem
- Strimko.scala: Strimko puzzle (Latin squares with "streams")
- StableMarriage.scala: Stable marriage problem (van Hentenryck, with a couple of different problem instances)
- StableMarriageRandom.scala: Stable marriage problem, randomized problem instance
- Sudoku.scala: Sudoku solver
- Sudoku2.scala: Sudoku solver, with the added functionality to read a problem instance file such as those in the page sudoku_problems. (A zip file with these files are sudoku_data.zip)
- SurvoPuzzle.scala: Survo puzzle
- ToNum.scala: channeling between a number and an array
- Tomography.scala: Discrete Tomography
- TrafficLights.scala: Traffic lights problem (CSPLib #16)
- WhoKilledAgatha.scala: Who killed agatha? (The Dreadsbury Mansion Murder Mystery, a automated reasoning problem).
- WordSquare.scala: Word square puzzle
- YoungTableaux.scala: Young Tableaux and partition
- Xkcd.scala: Xkcd knapsack problem
- Zebra.scala: Zebra problem ("Who own the zebra"?) (Lewis Caroll)
- Zebra2.scala: Zebra problem ("Who own the zebra"?) (Lewis Caroll), alternative version