Skip to content

Commit

Permalink
add JaCoP example
Browse files Browse the repository at this point in the history
  • Loading branch information
paulk-asert committed Nov 3, 2011
1 parent eb01516 commit 602097c
Show file tree
Hide file tree
Showing 9 changed files with 467 additions and 3 deletions.
2 changes: 2 additions & 0 deletions .gitignore
@@ -1 +1,3 @@
out
.idea
.gradle
11 changes: 11 additions & 0 deletions EinsteinsRiddle/EinsteinsRiddle.iml
Expand Up @@ -125,6 +125,17 @@
<SOURCES />
</library>
</orderEntry>
<orderEntry type="module-library">
<library>
<CLASSES>
<root url="jar://$MODULE_DIR$/lib/JaCoP-3.1.2.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$MODULE_DIR$/lib/JaCoP-3.1.2.jar!/" />
</SOURCES>
</library>
</orderEntry>
</component>
</module>

16 changes: 13 additions & 3 deletions EinsteinsRiddle/README.md
Expand Up @@ -3,11 +3,21 @@ Einstein's Riddle

Solves a logic puzzle using the following approaches:

* prolog directly
* prolog directly (for comparative purposes)
* prolog underneath a Groovy DSL
* a constraint solving library beneath a Groovy DSL
* the __choco__ constraint solving library beneath a Groovy DSL
* the __jacop__ constraint solving library beneath a Groovy DSL

The interesting thing to note is that the "business rules" are the same in all cases. The "DSL helper code" would typically be hidden from the user.

The [prolog4j](https://github.com/espakm/prolog4j) generic prolog interface api is used along with the [tuprolog](http://tuprolog.alice.unibo.it/) prolog engine but
you can try some of the other engines supported by prolog4j if you wish.

The [choco](http://www.emn.fr/z-info/choco-solver/) constraint solving library is used.
The [choco](http://www.emn.fr/z-info/choco-solver/) and [JaCoP](http://jacop.osolpro.com/) constraint solving libraries are used.
These libraries offer similar features as far as this problem is concerned. Given that the JaCoP package isn't available in a public
Maven repository and has a restrictive GPL license, we have a preference for Choco for this example; but see the respective
documentation of the two packages to see which better suits your needs.

To install the library needed for JaCoP you will need to run the following command (windows command shown):

..\gradlew downloadJaCoP
4 changes: 4 additions & 0 deletions EinsteinsRiddle/build.gradle
@@ -0,0 +1,4 @@
task downloadJaCoP() {
def name = 'JaCoP-3.1.2'
ant.get(src: "http://downloads.sourceforge.net/project/jacop-solver/$name/${name}.jar", dest: "lib")
}
181 changes: 181 additions & 0 deletions EinsteinsRiddle/src/jacop/JacopSolverDSL.groovy
@@ -0,0 +1,181 @@
package jacop

import JaCoP.core.*
import JaCoP.constraints.*
import JaCoP.search.*
import groovy.transform.Field

enum Pet { dog, cat, bird, fish, horse }
enum Color { green, white, red, blue, yellow }
enum Sport { baseball, volleyball, football, hockey, tennis }
enum Drink { water, tea, milk, coffee, beer }
enum Nationality { Norwegian, Dane, Briton, German, Swede }

import static Pet.*
import static Color.*
import static Sport.*
import static Drink.*
import static Nationality.*

// define logic solver data structures
num = 5
center = 2
first = 0
println "Solving Einstein's Riddle:"

@Field s = new Store()

def makeEnumVar(String st, Object[] arr) { new IntVar(s, st, 0, arr.size()-1) }
pets = new IntVar[num]
colors = new IntVar[num]
plays = new IntVar[num]
drinks = new IntVar[num]
nations = new IntVar[num]

(0..<num).each { i ->
pets[i] = makeEnumVar("pet$i", pets)
colors[i] = makeEnumVar("color$i", colors)
plays[i] = makeEnumVar("plays$i", plays)
drinks[i] = makeEnumVar("drink$i", drinks)
nations[i] = makeEnumVar("nation$i", nations)
}

def pretty(enumClass, selected) { enumClass.values().find{ it.ordinal().toString() == selected.toString() } }

// define DSL (simplistic non-refactored version)
def neighbours(var1, val1, var2, val2) {
s.impose and(
ifOnlyIf(eq(var1[0], val1), eq(var2[1], val2)),
implies(eq(var1[1], val1), or(eq(var2[0], val2), eq(var2[2], val2))),
implies(eq(var1[2], val1), or(eq(var2[1], val2), eq(var2[3], val2))),
implies(eq(var1[3], val1), or(eq(var2[2], val2), eq(var2[4], val2))),
ifOnlyIf(eq(var1[4], val1), eq(var2[3], val2))
)
}
iff = { e1, c1, e2, c2 -> s.impose and(*(0..<num).collect{ ifOnlyIf(eq(e1[it], c1), eq(e2[it], c2)) }) }
isEq = { a, b -> s.impose eq(a, b) }

dogs = dog; birds = bird; cats = cat; horses = horse
a = owner = house = the = abode = person = man = to = is = side = next = who = different = 'ignored'

// define the DSL in terms of DSL implementation
def the(Nationality n) {
def ctx = [nations, n]
[
drinks:iff.curry(*ctx, drinks),
plays:iff.curry(*ctx, plays),
keeps:iff.curry(*ctx, pets),
rears:iff.curry(*ctx, pets),
owns:{ _the -> [first:{ house -> isEq(nations[first], n)}] },
has:{ _a ->
[pet:iff.curry(*ctx, pets)] + Color.values().collectEntries{ c ->
[c.toString(), { _dummy -> iff(*ctx, colors, c) } ]
}
},
lives: { _next -> [to: { _the ->
Color.values().collectEntries{ c ->
[c.toString(), { _dummy -> neighbours(*ctx, colors, c) } ]
}
}]}
]
}

def the(Color c1) {[
house: { _is -> [on: { _the -> [left: { _side -> [of: { __the ->
Color.values().collectEntries{ c2 -> [c2.toString(), { _dummy ->
s.impose and(*(1..<num).collect{ ifOnlyIf(eq(colors[it-1], c1), eq(colors[it], c2)) })
}]}
}]}]}]}
]}

def the(String _dummy) {[
of:{ _the ->
Color.values().collectEntries{ c -> [c.toString(), { _house -> [
drinks:iff.curry(colors, c, drinks),
plays:iff.curry(colors, c, plays)
] } ] }
},
known: { _to -> [
play: { sport ->
def ctx = [plays, sport]
[
rears: iff.curry(*ctx, pets),
keeps: iff.curry(*ctx, pets),
drinks: iff.curry(*ctx, drinks),
lives: { _next -> [to: { _the -> [one: { _who -> [
keeps: { pet -> neighbours(pets, pet, *ctx) },
drinks: { beverage -> neighbours(drinks, beverage, *ctx) }
]}]}]}
]
},
keep : { pet -> [
lives: { _next -> [to: { _the -> [man: { _who -> [
plays: { sport -> neighbours(pets, pet, plays, sport) }
]}]}]}
]}
]},
from: { _the -> [center: { house ->
[drinks: { d -> isEq(drinks[center], d)}]
}]}
]}

def all(IntVar[] var) {
[are: { _different -> s.impose new Alldifferent(var) } ]
}

// define rules
all pets are different
all colors are different
all plays are different
all drinks are different
all nations are different
the man from the center house drinks milk
the Norwegian owns the first house
the Dane drinks tea
the German plays hockey
the Swede keeps dogs // alternate ending: has a pet dog
the Briton has a red house // alternate ending: red abode
the owner of the green house drinks coffee
the owner of the yellow house plays baseball
the person known to play football rears birds // alternate ending: keeps birds
the man known to play tennis drinks beer
the green house is on the left side of the white house
the man known to play volleyball lives next to the one who keeps cats
the man known to keep horses lives next to the man who plays baseball
the man known to play volleyball lives next to the one who drinks water
the Norwegian lives next to the blue house

// invoke logic solver
def vars = [pets, plays, drinks, colors, nations]
def search = new DepthFirstSearch()
def select = new SimpleMatrixSelect(vars as Var[][], new IndomainMin())
search.solutionListener.searchAll(true)
def result = search.labeling(s, select)
println "Solutions found: " + result
search.solutionListener.solutionsNo().times { i ->
println "Solution ${i + 1}:"
def sol = search.getSolution(i + 1)
def ps = sol.take(5); sol = sol.drop(5)
def ss = sol.take(5); sol = sol.drop(5)
def ds = sol.take(5); sol = sol.drop(5)
def cs = sol.take(5); sol = sol.drop(5)
def ns = sol.take(5)
5.times {
printSol(ns[it], ps[it], ss[it], ds[it], cs[it])
}
}

def printSol(n, p, s, d, c) {
print 'The ' + pretty(Nationality, n)
print ' has a pet ' + pretty(Pet, p)
print ' plays ' + pretty(Sport, s)
print ' drinks ' + pretty(Drink, d)
println ' and lives in a ' + pretty(Color, c) + ' house'
}

def eq(IntVar x, IntVar y) { new XeqY(x, y) }
def eq(IntVar x, enumVar) { new XeqC(x, enumVar.ordinal()) }
def or(PrimitiveConstraint c1, PrimitiveConstraint c2) { new Or(c1, c2) }
def and(PrimitiveConstraint[] cs) { new And(cs) }
def ifOnlyIf(PrimitiveConstraint c1, PrimitiveConstraint c2) { new Eq(c1, c2) }
def implies(PrimitiveConstraint c1, PrimitiveConstraint c2) { new IfThen(c1, c2) }
Binary file added gradle/wrapper/gradle-wrapper.jar
Binary file not shown.
6 changes: 6 additions & 0 deletions gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
#Mon May 02 20:57:32 EST 2011
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=http\://gradle.artifactoryonline.com/gradle/distributions/gradle-0.9.2-bin.zip

0 comments on commit 602097c

Please sign in to comment.