Jo Devriendt
Online editor @ manyworlds.site
declare S,E,N,D,M,O,R,Y: -> {0..9}.
1000*S() + 100*E() + 10*N() + D()
+ 1000*M() + 100*O() + 10*R() + E()
//---------------------------------------------
= 10000*M() + 1000*O() + 100*N() + 10*E() + Y().
M() > 0.
distinct(S(),E(),N(),D(),M(),O(),R(),Y()).
declare some 0-arity functions
function that takes arbitrary number of arguments
My mother can understand this. Yours too! :p
write constraints using normal operators
functions
= != > < >= =< + - * / rem
not and or xor implies
abs min max count
distinct same if-then-else
Strongly typed and total along bool, int, string
arguments must be known at compile time for now
overloaded for multiple types
Builtin functions
declare <name>: <type>, <type>, ... -> <finite range>.
User functions
declare prime: int -> {true, false}.
declare distance: string, string -> {0..1000}.
declare cities: string -> bool.
declare state: int -> {"busy", "ready", "down"}.
declare edge_color: string, string -> {"r", "g", "b"}.
signature
all ranges have a clear type, but are finite no int or string
declare n: -> {0..9}.
declare color: string -> {"r", "g", "b", "y"}.
color("NL") != color("BE").
color("NL") != color("DE").
color("BE") != color("LU").
color("BE") != color("DE").
color("BE") != color("FR").
color("FR") != color("LU").
color("FR") != color("DE").
color("LU") != color("DE").
There must be a better way...
declare color: string -> {"r", "g", "b", "y"}.
declare border: string, string -> bool.
define border as {("NL","BE"), ("NL","DE"),
("BE","LU"), ("BE","DE"), ("BE","FR"),
("FR","LU"), ("FR","DE"), ("LU","DE")} default false.
all [ color(x)!=color(y) for x,y where border(x,y) ].
definition fixes meaning of function
FMF expression
total definition
compiling / flattening / unrolling / grounding / instantiating
yields previous disequalities
max(f(1), f(2), f(3))
all
any
none
count
sum
product
min
max
distinct
same
odd
even
and
or
not( or )
count
+
*
min
max
distinct
same
xor
not( xor )
max [ f(x) for x where x in {1..3} ]
all [ color(x)!=color(y) for x,y where border(x,y) ].
Filter: select all x,y
where border(x,y)
holds
Fold: reduce those color(x)!=color(y)
to true
iff all
are true
Map: map those x,y
to color(x)!=color(y)
all [ color(x)!=color(y) for x,y where border(x,y) ].
list comprehension
aggregate function converting list to one expression
Flexible!
sum [
weight(x)
for x where Item(x) and inKnapsack(x)
] <= capacity().
Captures global constraints and global functions.
distinct [
color(x)
for x where Country(x) and not color(x)="r"
].
"allDiff except 0"
decdef num as {1..9}.
declare cell: int,int -> num.
all [
distinct [ cell(r,c) for c where num(c) ]
for r where num(r) ].
all [
distinct [ cell(r,c) for r where num(r) ]
for c where num(c) ].
declare square: int,int,int -> bool.
define square as ...
all [
distinct [ cell(r,c) for r,c where square(r,c,s)]
for s where num(s) ].
define cell as ...
Circuit constraint in TSP
decdef City as {"city_" 1..n}.
declare next: string -> City.
declare order: string -> {1..n}.
order("city_1") = 1.
all [ order(next(x)) = order(x) + 1
for x where City(x) and next(x) != "city_1" ].
decdef City as {"city_" 1..n}.
declare next: string -> City.
declare order: string -> {1..n}.
order("city_1") = 1.
all [ order(next(x)) = order(x) + 1
for x where City(x) and next(x) != "city_1" ].
City limits instantations of x as it is a finite set of strings.
Online editor @ manyworlds.site
FOUND WORLD
color as {("DE","b"), ("BE","g"), ("FR","r"), ("LU","y"), ("NL","r")}.
declare color: string -> {"r", "g", "b", "y"}.
declare border: string, string -> bool.
define border as {("NL","BE"), ("NL","DE"),
("BE","LU"), ("BE","DE"), ("BE","FR"),
("FR","LU"), ("FR","DE"), ("LU","DE")} default false.
all [ color(x)!=color(y) for x,y where border(x,y) ].
FOUND BLOCKERS
Line 9: not color("BE")=color("DE")
Line 9: not color("BE")=color("FR")
Line 9: not color("BE")=color("LU")
Line 9: not color("DE")=color("FR")
Line 9: not color("DE")=color("LU")
Line 9: not color("FR")=color("LU")
declare color: string -> {"r", "g", "b", "y"}.
declare border: string, string -> bool.
define border as {("NL","BE"), ("NL","DE"),
("BE","LU"), ("BE","DE"), ("BE","FR"),
("FR","LU"), ("FR","DE"), ("LU","DE")} default false.
all [ color(x)!=color(y) for x,y where border(x,y) ].
NL is not involved, as it only borders two other countries
@minimize sum [ distance(x,next(x)) for x where City(x) ].
find a world now returns
declare man: string -> bool.
declare mortal: string -> bool.
all [ man(x) implies mortal(x)
for x where
x in {"Socrates","Athens","poison cup","Zeus"}
].
man("Socrates").
not mortal("Zeus").
FOUND INTERSECTION
man as {("Socrates",true), ("Zeus",false)} default unknown.
mortal as {("Socrates",true), ("Zeus",false)} default unknown.
// we have 5 dice
decdef die as {"d" 1..5}.
// with 1 to 6 dots
decdef dots as {1..6}.
// rolling assigns a number of dots to each die
declare roll: die -> dots.
// the sum of the dice rolls must be 14
sum[ roll(x) for x where die(x) ] = 14.
// at most two dice can have rolled the same dots
all [ 2 >= count [ roll(x) = y for x where die(x) ]
for y where dots(y) ].
7776 candidate(s) exist.
COUNTING WORLDS...
450 world(s) exist.
// we have 5 dice ...
// show the most common value ('mode') of the highest die
// this also yields statistics on the highest die
@mode max[ roll(x) for x where die(x) ].
7776 candidate(s) exist.
CALCULATING DISTRIBUTION OF WORLDS...
450 world(s) exist.
5: 210 (mode objective fixed to this)
6: 150
4: 90
mean: 77/15
median: 5
declare drinksAlcohol: -> bool.
declare age: -> {0..150}.
age() >= 18 implies drinksAlcohol().
A common beginner mistake is to invert the implication. Here it means that everyone greater than 18 must drink alcohol...
define drinksAlcohol() as true.
define age() as 0.
The system returns an
"incorrect world":
· · age() [0]
· >= [false]
· · 18
implies [true]
· drinksAlcohol() [true].
Evaluating the constraint that should invalidate this world may reveal the mistake:
Tools
Bad: in each step, full expression tree is rebuilt...
Good: minimal set of compiled constraints
Combinatorial programming is simpler than imperative programming. Why are the languages so hard?
Successful when a 12-year old can do their math homework with ManyWorlds and when a lawyer can recognize ManyWorld-encoded laws.
Caveat: no silver bullet!
But simple problems should have simple solutions.
Problem domain description should match code.
sum [ distance(x,next(x)) for x where City(x) ]
declare fib: int -> {0..1e21}.
define fib(x) where x in {0..100} as
0 if x=0 else
1 if x=1 else
fib(x-1) + fib(x-2)
default 0.
declare fib: int -> {0..1e21}.
declare a,b: -> {0..10}.
define fib(x) where x in {0..100} as
a() if x=0 else
b() if x=1 else
fib(x-1) + fib(x-2)
default 0.
fib(10)=144.