/* * Kodkod -- Copyright (c) 2005-present, Emina Torlak * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package kodkod.examples.alloy; import java.util.ArrayList; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import kodkod.ast.Expression; import kodkod.ast.Formula; import kodkod.ast.Relation; import kodkod.ast.Variable; import kodkod.engine.Proof; import kodkod.engine.Solution; import kodkod.engine.Solver; import kodkod.engine.satlab.SATFactory; import kodkod.engine.ucore.RCEStrategy; import kodkod.instance.Bounds; import kodkod.instance.TupleFactory; import kodkod.instance.Universe; import kodkod.util.nodes.Nodes; import kodkod.util.nodes.PrettyPrinter; /** * Encodes the hotel example. * * @author Emina Torlak */ public final class Hotel { private final Relation Time, Event, first, last; private final Relation pre, post, next; private final Relation Key, Card, Room, Guest, HotelEvent, RoomCardEvent, Checkin, Enter, NormalEnter, RecodeEnter, Checkout; private final Relation guest, room, card, k1, k2; private final Relation key, prev, occ, holds; /** * Constructs a new instance of the hotel problem. */ public Hotel() { this.Time = Relation.unary("Time"); this.Event = Relation.unary("Event"); this.first = Relation.unary("first"); this.last = Relation.unary("last"); this.pre = Relation.binary("pre"); this.post = Relation.binary("post"); this.next = Relation.binary("next"); this.Key = Relation.unary("Key"); this.Card = Relation.unary("Card"); this.Guest = Relation.unary("Guest"); this.Room = Relation.unary("Room"); this.HotelEvent = Relation.unary("HotelEvent"); this.RoomCardEvent = Relation.unary("RoomCardEvent"); this.Checkin = Relation.unary("Checkin"); this.Enter = Relation.unary("Enter"); this.NormalEnter = Relation.unary("NormalEnter"); this.RecodeEnter = Relation.unary("RecodeEnter"); this.Checkout = Relation.unary("Checkout"); this.key = Relation.ternary("key"); this.prev = Relation.ternary("prev"); this.occ = Relation.ternary("occ"); this.holds = Relation.ternary("holds"); this.guest = Relation.binary("guest"); this.room = Relation.binary("room"); this.card = Relation.binary("card"); this.k1 = Relation.binary("k1"); this.k2 = Relation.binary("k2"); } /** * Returns the invariants for the Time and Event signatures and their fields. * @return invariants for the Time and Event signatures and their fields. */ public Formula timeEventInvariants() { final List<Formula> invs = new ArrayList<Formula>(); invs.add(next.totalOrder(Time, first, last)); invs.add(pre.function(Event, Time)); invs.add(post.function(Event, Time)); final Variable t = Variable.unary("t"); final Variable e = Variable.unary("e"); // all t: Time - last | one e: Event | e.pre = t and e.post = t.next final Formula f0 = e.join(pre).eq(t).and(e.join(post).eq(t.join(next))); final Formula f1 = f0.comprehension(e.oneOf(Event)).one(); invs.add( f1.forAll(t.oneOf(Time.difference(last))) ); return Formula.and(invs); } /** * Returns the "unchanged" predicate for the given event expression and field. * @return "unchanged" predicate for the given event expression and field. */ public Formula unchanged(Expression e, Expression field) { // field.(this.pre) = field.(this.post) return field.join(e.join(pre)).eq(field.join(e.join(post))); } /** * Returns the "modifes" predicate for the given event expression and field. * @return "modifies" predicate for the given event expression and field. */ public Formula modifies(Expression es, Expression field) { // all e: Event -es | field.(e.pre) = field.(e.post) final Variable e = Variable.unary("e"); return field.join(e.join(pre)).eq(field.join(e.join(post))).forAll(e.oneOf(Event.difference(es))); } /** * Returns the invariants for Card and its fields. * @return invariants for Card and its fields. */ public Formula cardInvariants() { return k1.function(Card, Key).and(k2.function(Card, Key)); } /** * Returns the invariants for Room and its fields. * @return invariants for Room and its fields. */ public Formula roomInvariants() { //key: Key one -> Time, //prev: Key lone -> Time, //occ: Guest -> Time final List<Formula> invs = new ArrayList<Formula>(); invs.add(key.in(Room.product(Key).product(Time))); invs.add(prev.in(Room.product(Key).product(Time))); invs.add(occ.in(Room.product(Guest).product(Time))); final Variable r = Variable.unary("r"); final Variable t = Variable.unary("t"); // all r: Room, t: Time | one r.key.t invs.add( r.join(key).join(t).one().forAll(r.oneOf(Room).and(t.oneOf(Time)))); // all r: Room, t: Time | lone r.prev.t invs.add( r.join(prev).join(t).lone().forAll(r.oneOf(Room).and(t.oneOf(Time)))); return Formula.and(invs); } /** * Returns the invariants for Guest and its fields. * @return invariants for Guest and its fields. */ public Formula guestInvariants() { //holds: Card -> Time return holds.in(Guest.product(Card).product(Time)); } /** * Returns the invariants for HotelEvent and its fields. * @return invariants for HotelEvent and its fields. */ public Formula hotelEventInvariants() { // abstract sig HotelEvent extends Event { // guest: Guest // } final List<Formula> invs = new ArrayList<Formula>(); invs.add( HotelEvent.eq(Event)) ; invs.add( HotelEvent.eq(RoomCardEvent.union(Checkout))); invs.add( RoomCardEvent.intersection(Checkout).no() ); invs.add( guest.function(HotelEvent, Guest)); return Formula.and(invs); } /** * Returns the invariants for RoomCardEvent and its fields. * @return invariants for RoomCardEvent and its fields. */ public Formula roomCardInvariants() { // abstract sig RoomCardEvent extends HotelEvent { // room: Room, // card: Card // } final List<Formula> invs = new ArrayList<Formula>(); invs.add( RoomCardEvent.in(HotelEvent) ); invs.add( RoomCardEvent.eq(Checkin.union(Enter))); invs.add( Checkin.intersection(Enter).no()); invs.add( room.function(RoomCardEvent, Room)); invs.add( card.function(RoomCardEvent, Card)); return Formula.and(invs); } /** @return e.room */ Expression room(Expression e) { return e.join(room); } /** @return e.card */ Expression card(Expression e) { return e.join(card); } /** @return e.pre */ Expression pre(Expression e) { return e.join(pre); } /** @return e.post */ Expression post(Expression e) { return e.join(post); } /** @return e.guest */ Expression guest(Expression e) { return e.join(guest); } /** * Returns the invariants for Checkin and its fields. * @return invariants for Checkin and its fields. */ public Formula invsForCheckin() { // sig Checkin extends RoomCardEvent { } // { // no room.occ.pre // card.k1 = room.prev.pre // holds.post = holds.pre + guest -> card // prev.post = prev.pre ++ room -> card.k2 // occ.post = occ.pre + room -> guest // // key.unchanged // } final List<Formula> invs = new ArrayList<Formula>(); invs.add(Checkin.in(RoomCardEvent)); final Variable c = Variable.unary("c"); // no room.occ.pre invs.add( room(c).join(occ).join(pre(c)).no().forAll(c.oneOf(Checkin)) ); // card.k1 = room.prev.pre invs.add( card(c).join(k1).eq(room(c).join(prev).join(pre(c))).forAll(c.oneOf(Checkin)) ); // holds.post = holds.pre + guest -> card invs.add( holds.join(post(c)).eq(holds.join(pre(c)).union(guest(c).product(card(c)))).forAll(c.oneOf(Checkin)) ); // prev.post = prev.pre ++ room -> card.k2 invs.add( prev.join(post(c)).eq(prev.join(pre(c)).override(room(c).product(card(c).join(k2)))).forAll(c.oneOf(Checkin)) ); // occ.post = occ.pre + room -> guest invs.add( occ.join(post(c)).eq(occ.join(pre(c)).union(room(c).product(guest(c)))).forAll(c.oneOf(Checkin)) ); invs.add( unchanged(c, key).forAll(c.oneOf(Checkin))); return Formula.and(invs); } /** * Returns the invariants for Enter and its fields. * @return invariants for Enter and its fields. */ public Formula enterInvariants() { // abstract sig Enter extends RoomCardEvent { } // { // card in guest.holds.pre // } final List<Formula> invs = new ArrayList<Formula>(); invs.add( Enter.in(RoomCardEvent)); invs.add( Enter.eq(NormalEnter.union(RecodeEnter)) ); invs.add( NormalEnter.intersection(RecodeEnter).no() ); final Variable e = Variable.unary("e"); invs.add( card(e).in(guest(e).join(holds).join(pre(e))).forAll(e.oneOf(Enter))); return Formula.and(invs); } /** * Returns the invariants for NormalEnter and its fields. * @return invariants for NormalEnter and its fields. */ public Formula normalEnterInvariants() { // sig NormalEnter extends Enter { } // { // card.k2 = room.key.pre // // prev.unchanged // holds.unchanged // occ.unchanged // key.unchanged // } final List<Formula> invs = new ArrayList<Formula>(); invs.add( NormalEnter.in(Enter)); final Variable n = Variable.unary("n"); invs.add( card(n).join(k2).eq(room(n).join(key).join(pre(n))).forAll(n.oneOf(NormalEnter))); invs.add( unchanged(n, prev).forAll(n.oneOf(NormalEnter))); invs.add( unchanged(n, holds).forAll(n.oneOf(NormalEnter))); invs.add( unchanged(n, occ).forAll(n.oneOf(NormalEnter))); invs.add( unchanged(n, key).forAll(n.oneOf(NormalEnter))); return Formula.and(invs); } /** * Returns the invariants for RecodeEnter and its fields. * @return invariants for RecodeEnter and its fields. */ public Formula recodeEnterInvariants() { // sig RecodeEnter extends Enter { } // { // card.k1 = room.key.pre // key.post = key.pre ++ room -> card.k2 // // prev.unchanged // holds.unchanged // occ.unchanged // } final List<Formula> invs = new ArrayList<Formula>(); invs.add( RecodeEnter.in(Enter)); final Variable r = Variable.unary("n"); invs.add( card(r).join(k1).eq(room(r).join(key).join(pre(r))).forAll(r.oneOf(RecodeEnter))); invs.add( key.join(post(r)).eq(key.join(pre(r)).override(room(r).product(card(r).join(k2)))).forAll(r.oneOf(RecodeEnter))); invs.add( unchanged(r, prev).forAll(r.oneOf(RecodeEnter))); invs.add( unchanged(r, holds).forAll(r.oneOf(RecodeEnter))); invs.add( unchanged(r, occ).forAll(r.oneOf(RecodeEnter))); return Formula.and(invs); } /** * Returns the invariants for Checkout and its fields. * @return invariants for Checkout and its fields. */ public Formula invsForCheckout() { // sig Checkout extends HotelEvent { } // { // some occ.pre.guest // // -- DNJ: can comment these out and still unsat // occ.post = occ.pre - Room -> guest // prev.unchanged // holds.unchanged // key.unchanged // } final List<Formula> invs = new ArrayList<Formula>(); invs.add( Checkout.in(HotelEvent)); final Variable c = Variable.unary("n"); invs.add( occ.join(pre(c)).join(guest(c)).some().forAll(c.oneOf(Checkout))); invs.add( occ.join(post(c)).eq(occ.join(pre(c)).difference(Room.product(guest(c)))).forAll(c.oneOf(Checkout))); invs.add( unchanged(c, prev).forAll(c.oneOf(Checkout))); invs.add( unchanged(c, holds).forAll(c.oneOf(Checkout))); invs.add( unchanged(c, key).forAll(c.oneOf(Checkout))); return Formula.and(invs); } /** * Returns FreshIssue fact. * @return FressIssue fact. */ public Formula freshIssue(){ // -- don't issue same key twice // all disj e1, e2: Checkin | e1.card.k2 != e2.card.k2 // -- don't issue key initially installed in lock // all e: Checkin | e.card.k2 !in Room.key.first final List<Formula> invs = new ArrayList<Formula>(); final Variable e1 = Variable.unary("e1"); final Variable e2 = Variable.unary("e2"); final Variable e = Variable.unary("e"); invs.add(e1.eq(e2).not().implies(e1.join(card).join(k2).eq(e2.join(card).join(k2)).not()).forAll(e1.oneOf(Checkin).and(e2.oneOf(Checkin)))); invs.add(e.join(card).join(k2).in(Room.join(key).join(first)).not().forAll(e.oneOf(Checkin))); return Formula.and(invs); } /** * Returns init fact. * @return init fact. */ public Formula initInvariant() { // pred init (t: Time) { // prev.t = key.t // key.t in Room lone -> Key // no holds.t and no occ.t // } // fact {first.init} final List<Formula> invs = new ArrayList<Formula>(); invs.add(prev.join(first).eq(key.join(first))); invs.add(key.join(first).in(Room.product(Key))); final Variable k = Variable.unary("k"); invs.add(key.join(first).join(k).lone().forAll(k.oneOf(Key))); invs.add(holds.join(first).no()); invs.add(occ.join(first).no()); return Formula.and(invs); } /** * Returns the noIntervening fact. * @return noIntervening fact. */ public Formula noIntervening() { // fact NoIntervening { // all c: Checkin - pre.last | // some e: Enter | e.pre = c.post and e.room = c.room and e.guest = c.guest // } final Variable c = Variable.unary("c"); final Variable e = Variable.unary("e"); final Formula f = e.join(pre).eq(c.join(post)). and(e.join(room).eq(c.join(room))). and(e.join(guest).eq(c.join(guest))); return f.forSome(e.oneOf(Enter)).forAll(c.oneOf(Checkin.difference(pre.join(last)))); } /** * Returns NoBadEntry formula. * @return NoBadEntry formula. */ public Formula noBadEntry() { // all e: Enter | let occs = occ.(e.pre) [e.room] | // some occs => e.guest in occs final Variable e = Variable.unary("e"); final Expression occs = e.join(room).join(occ.join(e.join(pre))); return occs.some().implies(e.join(guest).in(occs)).forAll(e.oneOf(Enter)); } /** * Returns the conjunction of all invariants. * @return conjunction of all invariants. */ public Formula invariants() { final List<Formula> invs = new ArrayList<Formula>(); invs.add( timeEventInvariants() ); invs.add( cardInvariants() ); invs.add( roomInvariants() ); invs.add( guestInvariants() ); invs.add( initInvariant() ); invs.add( hotelEventInvariants() ); invs.add( roomCardInvariants() ); invs.add( invsForCheckin() ); invs.add( enterInvariants() ); invs.add( normalEnterInvariants() ); invs.add( recodeEnterInvariants() ); invs.add( invsForCheckout() ); invs.add( freshIssue() ); invs.add( noIntervening() ); return Formula.and(invs); } /** * Returns the assertion "check noBadEntry" * @return invariants() && !oBadEntry() */ public Formula checkNoBadEntry() { return invariants().and(noBadEntry().not()); } /** * Returns bounds for the given number of times, events, rooms, cards, keys and guests. * @return bounds for the given scopes. */ public Bounds bounds(int t, int e, int r, int c, int k, int g) { final Relation[] tops = { Time, Event, Room, Card, Key, Guest }; final int[] scopes = { t, e, r, c, k, g}; final List<String> atoms = new ArrayList<String>(); for(int i = 0; i < tops.length; i++) { Relation top = tops[i]; for(int j = 0, scope = scopes[i]; j < scope; j++) atoms.add(top.name()+j); } final Universe u = new Universe(atoms); final TupleFactory f = u.factory(); final Bounds b = new Bounds(u); for(int i = 0 ; i < tops.length; i++) { Relation top = tops[i]; b.bound(top, f.range(f.tuple(top.name()+0), f.tuple(top.name()+(scopes[i]-1)))); } b.bound(first, b.upperBound(Time)); b.bound(last, b.upperBound(Time)); b.bound(next, b.upperBound(Time).product(b.upperBound(Time))); b.bound(pre, b.upperBound(Event).product(b.upperBound(Time))); b.bound(post, b.upperBound(Event).product(b.upperBound(Time))); b.bound(HotelEvent, b.upperBound(Event)); b.bound(RoomCardEvent, b.upperBound(Event)); b.bound(Enter, b.upperBound(Event)); b.bound(NormalEnter, b.upperBound(Event)); b.bound(RecodeEnter, b.upperBound(Event)); b.bound(Checkin, b.upperBound(Event)); b.bound(Checkout, b.upperBound(Event)); b.bound(k1, b.upperBound(Card).product(b.upperBound(Key))); b.bound(k2, b.upperBound(Card).product(b.upperBound(Key))); b.bound(key, b.upperBound(Room).product(b.upperBound(Key)).product(b.upperBound(Time))); b.bound(prev, b.upperBound(Room).product(b.upperBound(Key)).product(b.upperBound(Time))); b.bound(occ, b.upperBound(Room).product(b.upperBound(Guest)).product(b.upperBound(Time))); b.bound(holds, b.upperBound(Guest).product(b.upperBound(Card)).product(b.upperBound(Time))); b.bound(guest, b.upperBound(HotelEvent).product(b.upperBound(Guest))); b.bound(room, b.upperBound(RoomCardEvent).product(b.upperBound(Room))); b.bound(card, b.upperBound(RoomCardEvent).product(b.upperBound(Card))); return b; } /** * Returns bounds for the given scope. * @return bounds for the given scope. */ public Bounds bounds(int n) { return bounds(n,n,n,n,n,n); } private static void usage() { System.out.println("java examples.Hotel [scope]"); System.exit(1); } private static void checkMinimal(Set<Formula> core, Bounds bounds) { System.out.print("checking minimality ... "); final long start = System.currentTimeMillis(); final Set<Formula> minCore = new LinkedHashSet<Formula>(core); Solver solver = new Solver(); solver.options().setSolver(SATFactory.MiniSat); for(Iterator<Formula> itr = minCore.iterator(); itr.hasNext();) { Formula f = itr.next(); Formula noF = Formula.TRUE; for( Formula f1 : minCore ) { if (f!=f1) noF = noF.and(f1); } if (solver.solve(noF, bounds).instance()==null) { itr.remove(); } } final long end = System.currentTimeMillis(); if (minCore.size()==core.size()) { System.out.println("minimal (" + (end-start) + " ms)."); } else { System.out.println("not minimal (" + (end-start) + " ms). The minimal core has these " + minCore.size() + " formulas:"); for(Formula f : minCore) { System.out.println(" " + f); } // Solution sol = problem.solver.solve(Formula.and(minCore), problem.bounds); // System.out.println(sol); // sol.proof().highLevelCore(); } } /** * Usage: java examples.Hotel [scope] */ public static void main(String[] args) { if (args.length < 1) usage(); try { final int n = Integer.parseInt(args[0]); if (n < 1) usage(); final Hotel model = new Hotel(); final Solver solver = new Solver(); solver.options().setSolver(SATFactory.MiniSatProver); solver.options().setLogTranslation(1); final Formula f = model.checkNoBadEntry(); final Bounds b = model.bounds(n); // System.out.println(PrettyPrinter.print(f, 2, 100)); final Solution sol = solver.solve(f, b); System.out.println(sol); if (sol.instance()==null) { final Proof proof = sol.proof(); System.out.println("top-level formulas: " + proof.log().roots().size()); System.out.println("initial core: " + proof.highLevelCore().size()); System.out.print("\nminimizing core ... "); final long start = System.currentTimeMillis(); proof.minimize(new RCEStrategy(proof.log())); final Set<Formula> core = Nodes.minRoots(f, proof.highLevelCore().values()); final long end = System.currentTimeMillis(); System.out.println("done (" + (end-start) + " ms)."); System.out.println("minimal core: " + core.size()); for(Formula u : core) { System.out.println(PrettyPrinter.print(u, 2, 100)); } checkMinimal(core, b); } else { System.out.println(sol); } } catch (NumberFormatException nfe) { usage(); } } }