package kodkod.examples.alloy; import java.util.ArrayList; import java.util.List; import kodkod.ast.Expression; import kodkod.ast.Formula; import kodkod.ast.Relation; import kodkod.ast.Variable; import kodkod.engine.Solution; import kodkod.engine.Solver; import kodkod.engine.satlab.SATFactory; import kodkod.instance.Bounds; import kodkod.instance.TupleFactory; import kodkod.instance.TupleSet; import kodkod.instance.Universe; /** * KodKod encoding of the following leader election algorithm: * * <pre> * module internal/ringElection * open util/ordering[Time] as TO * open util/ordering[Process] as PO * * sig Time {} * sig Process { * succ: Process, * toSend: Process -> Time, * elected: set Time } * * fact Ring {all p: Process | Process in p.^succ} * * pred init (t: Time) {all p: Process | p.toSend.t = p} * * pred step (t, t': Time, p: Process) { * let from = p.toSend, to = p.succ.toSend | * some id: from.t { * from.t' = from.t - id * to.t' = to.t + (id - PO/prevs(p.succ)) } } * * pred skip (t, t': Time, p: Process) {p.toSend.t = p.toSend.t'} * * fact Traces { * init (TO/first ()) * all t: Time - TO/last() | let t' = TO/next (t) | * all p: Process | step (t, t', p) or step (t, t', succ.p) or skip (t, t', p) } * * fact DefineElected { * no elected.TO/first() * all t: Time - TO/first()| * elected.t = {p: Process | p in p.toSend.t - p.toSend.(TO/prev(t))} } * * * pred progress () { * all t: Time - TO/last() | let t' = TO/next (t) | * some Process.toSend.t => some p: Process | not skip (t, t', p) } * * assert AtLeastOneElected { progress () => some elected.Time } * * pred looplessPath () {no disj t, t': Time | toSend.t = toSend.t'} * * assert AtMostOneElected {lone elected.Time} * * check AtMostOneElected for 3 Process, 7 Time * run looplessPath for 13 Time, 3 Process * </pre> * @author Emina Torlak */ public final class RingElection { private final Relation Process, Time, succ, toSend, elected; private final Relation pfirst, plast, pord, tfirst, tlast, tord; /** * Constructs an instance of the RingElection example. */ public RingElection() { Process = Relation.unary("Process"); Time = Relation.unary("Time"); succ = Relation.binary("succ"); toSend = Relation.ternary("toSend"); elected = Relation.binary("elected"); pfirst = Relation.unary("pfirst"); plast = Relation.unary("plast"); pord = Relation.binary("pord"); tfirst = Relation.unary("tfirst"); tlast = Relation.unary("tlast"); tord = Relation.binary("tord"); } /** * Returns the declaration constraints. * @return <pre> * sig Time {} * sig Process { * succ: Process, * toSend: Process -> Time, * elected: set Time } * </pre> */ public Formula declarations() { final Formula ordTime = tord.totalOrder(Time, tfirst, tlast); final Formula ordProcess = pord.totalOrder(Process, pfirst, plast); final Formula succFunction = succ.function(Process, Process); final Formula electedDomRange = elected.in(Process.product(Time)); return Formula.and(ordTime, ordProcess, succFunction, electedDomRange); } /** * Returns the fact Ring. * @return <pre>fact Ring {all p: Process | Process in p.^succ}</pre> */ public Formula ring() { final Variable p = Variable.unary("p"); return Process.in(p.join(succ.closure())).forAll(p.oneOf(Process)); } /** * Returns the init predicate. * @return <pre> pred init (t: Time) {all p: Process | p.toSend.t = p} </pre> */ public Formula init(Expression t) { final Variable p = Variable.unary("p"); return p.join(toSend).join(t).eq(p).forAll(p.oneOf(Process)); } /** * Returns the step predicate. * @return * <pre> * pred step (t, t': Time, p: Process) { * let from = p.toSend, to = p.succ.toSend | * some id: from.t { * from.t' = from.t - id * to.t' = to.t + (id - PO/prevs(p.succ)) } } * </pre> */ public Formula step(Expression t1, Expression t2, Expression p) { final Expression from = p.join(toSend); final Expression to = p.join(succ).join(toSend); final Variable id = Variable.unary("id"); final Expression prevs = (p.join(succ)).join((pord.transpose()).closure()); final Formula f1 = from.join(t2).eq(from.join(t1).difference(id)); final Formula f2 = to.join(t2).eq(to.join(t1).union(id.difference(prevs))); return f1.and(f2).forSome(id.oneOf(from.join(t1))); } /** * Returns the skip predicate * @return <pre>pred skip (t, t': Time, p: Process) {p.toSend.t = p.toSend.t'}<pre> */ public Formula skip(Expression t1, Expression t2, Expression p) { return p.join(toSend).join(t1).eq(p.join(toSend).join(t2)); } /** * Returns the Traces fact. * @return <pre> * fact Traces { * init (TO/first ()) * all t: Time - TO/last() | let t' = TO/next (t) | * all p: Process | step (t, t', p) or step (t, t', succ.p) or skip (t, t', p) } * </pre> */ public Formula traces() { final Variable t1 = Variable.unary("t"); final Expression t2 = t1.join(tord); final Variable p = Variable.unary("p"); final Formula f = step(t1, t2, p).or(step(t1, t2, succ.join(p))).or(skip(t1, t2, p)); final Formula fAll = f.forAll(p.oneOf(Process)).forAll(t1.oneOf(Time.difference(tlast))); return init(tfirst).and(fAll); } /** * Return DefineElected fact. * @return <pre> * fact DefineElected { * no elected.TO/first() * all t: Time - TO/first()| * elected.t = {p: Process | p in p.toSend.t - p.toSend.(TO/prev(t))} } * </pre> */ public Formula defineElected() { final Variable t = Variable.unary("t"); final Formula f1 = elected.join(tfirst).no(); final Variable p = Variable.unary("p"); final Formula c = p.in(p.join(toSend).join(t).difference(p.join(toSend).join(t.join(tord.transpose())))); final Expression comprehension = c.comprehension(p.oneOf(Process)); final Formula f2 = elected.join(t).eq(comprehension).forAll(t.oneOf(Time.difference(tfirst))); return f1.and(f2); } /** * Returns the progress predicate. * @return <pre> * pred progress () { * all t: Time - TO/last() | let t' = TO/next (t) | * some Process.toSend.t => some p: Process | not skip (t, t', p) } * </pre> */ public Formula progress() { final Variable t1 = Variable.unary("t"); final Expression t2 = t1.join(tord); final Variable p = Variable.unary("p"); final Formula f1 = Process.join(toSend).join(t1).some().implies(skip(t1, t2, p).not().forSome(p.oneOf(Process))); return f1.forAll(t1.oneOf(Time.difference(tlast))); } /** * Returns the looplessPath predicate * @return <pre>pred looplessPath () {no disj t, t': Time | toSend.t = toSend.t'}</pre> */ public Formula looplessPath() { final Variable t1 = Variable.unary("t"); final Variable t2 = Variable.unary("t'"); // all t, t': Time | some t&t' || !(toSend.t = toSend.t') final Formula f1 = t1.intersection(t2).some().or(toSend.join(t1).eq(toSend.join(t2)).not()); return f1.forAll(t1.oneOf(Time).and(t2.oneOf(Time))); } /** * Returns the AtLeastOneElected assertion. * @return <pre>assert AtLeastOneElected { progress () => some elected.Time }</pre> */ public Formula atLeastOneElected() { return progress().implies(elected.join(Time).some()); } /** * Returns the atMostOneElected assertion * @return <pre>assert AtMostOneElected {lone elected.Time}</pre> */ public Formula atMostOneElected() { return elected.join(Time).lone(); } /** * Returns the declarations and facts of the model * @return the declarations and facts of the model */ public Formula invariants() { return declarations().and(ring()).and(traces()).and(defineElected()); } /** * Returns the conjunction of the invariants and the negation of atMostOneElected. * @return invariants() && !atMostOneElected() */ public Formula checkAtMostOneElected() { return invariants().and(atMostOneElected().not()); } /** * Returns a bounds object with scope processes and times. * @return bounds object with scope processes and times. */ public Bounds bounds(int scope) { return bounds(scope, scope); } /** * Returns a bounds object that scopes Process, Time, and their * fields according to given values. * @return bounds */ public Bounds bounds(int processes, int times) { final List<String> atoms = new ArrayList<String>(processes + times); for(int i = 0; i < processes; i++) { atoms.add("Process"+i); } for(int i = 0; i < times; i++) { atoms.add("Time"+i); } final Universe u = new Universe(atoms); final TupleFactory f = u.factory(); final Bounds b = new Bounds(u); final TupleSet pb = f.range(f.tuple("Process0"), f.tuple("Process"+ (processes-1))); final TupleSet tb = f.range(f.tuple("Time0"), f.tuple("Time"+(times-1))); b.bound(Process, pb); b.bound(succ, pb.product(pb)); b.bound(toSend, pb.product(pb).product(tb)); b.bound(elected, pb.product(tb)); b.bound(pord, pb.product(pb)); b.bound(pfirst, pb); b.bound(plast, pb); b.bound(Time, tb); b.bound(tord, tb.product(tb)); b.bound(tfirst, tb); b.bound(tlast, tb); return b; } private static void usage() { System.out.println("java examples.RingElection [# processes] [# times]"); System.exit(1); } /** * Usage: java examples.RingElection [# processes] [# times] */ public static void main(String[] args) { if (args.length < 2) usage(); try { final RingElection model = new RingElection(); final Solver solver = new Solver(); solver.options().setSolver(SATFactory.MiniSat); final int p = Integer.parseInt(args[0]); final int t = Integer.parseInt(args[1]); final Formula checkAtMostOneElected = model.checkAtMostOneElected(); final Bounds boundspt = model.bounds(p,t); System.out.println("*****check AtMostOneElected for " + p +" Process, "+ t + " Time*****"); Solution sol1 = solver.solve(checkAtMostOneElected, boundspt); System.out.println(sol1); // // run looplessPath for 13 Time, 3 Process // final Formula runLooplessPath = model.declsAndFacts();//.and(model.looplessPath()); // final Bounds bounds313 = model.bounds(p, t); // System.out.println("*****run looplessPath for 13 Time, 3 Process*****"); // System.out.println(runLooplessPath); // System.out.println(bounds313); // Solution sol2 = solver.solve(runLooplessPath, bounds313); // System.out.println(sol2); } catch (NumberFormatException nfe) { usage(); } } }