/// /// Heavyweight expression equivalence checks. /// /// /// Unlike normal expression checks, these produce Equiv /// values, which must be passed to runEquiv to check. /// /// /// /// These are farmed out to Z3. As such, they are not likely to /// execute quickly. Use with caution. /// /// module Starling.Core.ExprEquiv open Microsoft open Starling.Core.Expr open Starling.Core.Var open Starling.Core.Z3 /// /// Type for equivalence checks. /// type Equiv<'var> = ('var -> string) -> Z3.Context -> Z3.BoolExpr /// /// Runs an equivalence check. /// /// /// A function converting variables in the check to Vars. /// The vars must be unique to their origin variables across the /// equivalence. /// /// /// The equivalence check to run. /// /// /// Meta-type of variables inside the equivalence-checked expressions. /// /// /// True if the equivalence check definitely succeeded. /// False otherwise (including if the check was undecideable). /// let equivHolds (toVar : 'var -> Var) (e : Equiv<'var>) = (* The tactic here is the same as the Starling one: negate the equivalence and try to falsify it. *) use ctx = new Z3.Context () let term = ctx.MkNot (e toVar ctx) match (Run.runTerm ctx term) with | Z3.Status.UNSATISFIABLE -> true | _ -> false /// /// Or-disjoins two equivalence checks. /// /// /// The first equivalence check to disjoin. /// /// /// The second equivalence check to disjoin. /// /// /// Meta-type of variables inside the equivalence-checked expressions. /// /// /// The or-disjunction of the two, which will return true only if /// (but not necessarily if!) at least one equivalence holds. /// let orEquiv (x : Equiv<'var>) (y : Equiv<'var>) : Equiv<'var> = fun toVar (ctx : Z3.Context) -> ctx.MkOr [| x toVar ctx ; y toVar ctx |] /// /// And-conjoins two equivalence checks. /// /// /// The first equivalence check to conjoin. /// /// /// The second equivalence check to conjoin. /// /// /// Meta-type of variables inside the equivalence-checked expressions. /// /// /// The and-conjunction of the two, which will return true only if /// (but not necessarily if!) both equivalences hold. /// let andEquiv (x : Equiv<'var>) (y : Equiv<'var>) : Equiv<'var> = fun toVar (ctx : Z3.Context) -> ctx.MkAnd [| x toVar ctx ; y toVar ctx |] /// /// Returns true if two expressions are definitely equivalent to each /// other. /// /// /// This is sound, but not complete. It should only be used for /// optimisations. /// /// /// /// The first expression to check. /// /// /// The second expression to check. /// /// /// Meta-type of variables inside the equivalence-checked expressions. /// /// /// An equivalence check returning true only if (but not if!) /// and are equivalent. /// /// /// This function calls into Z3, and is thus likely to be slow. /// Use with caution. /// let equiv (x : BoolExpr<'var>) (y : BoolExpr<'var>) : Equiv<'var> = fun toVar ctx -> let sx = Expr.boolToZ3 false toVar ctx (normalBool (simp x)) let sy = Expr.boolToZ3 false toVar ctx (normalBool (simp y)) ctx.MkIff (sx, sy) /// /// Returns true if two expressions are definitely negations of each /// other. /// /// /// This is sound, but not complete. It should only be used for /// optimisations. /// /// /// /// The first expression to check. /// /// /// The second expression to check. /// /// /// Meta-type of variables inside the equivalence-checked expressions. /// /// /// An equivalence check returning true only if (but not if!) /// and negate each other. /// /// /// This function calls into Z3, and is thus likely to be slow. /// Use with caution. /// let negates (x : BoolExpr<'var>) (y : BoolExpr<'var>) : Equiv<'var> = fun toVar ctx -> equiv x (BNot y) toVar ctx /// /// Tests for ExprEquiv. /// module Tests = open NUnit.Framework open Starling.Utils.Testing open Starling.Core.Pretty open Starling.Core.Var.Pretty /// /// NUnit tests for ExprEquiv. /// type NUnit () = /// Test cases for negation checking. static member ObviousNegations = [ (tcd [| (BTrue : VBoolExpr) (BFalse : VBoolExpr) |]) .Returns(true) (tcd [| (BTrue : VBoolExpr) (BTrue : VBoolExpr) |]) .Returns(false) (tcd [| (BFalse : VBoolExpr) (BFalse : VBoolExpr) |]) .Returns(false) (tcd [| (BTrue : VBoolExpr) (iEq (IInt 5L) (IInt 6L) : VBoolExpr) |]) .Returns(true) (tcd [| (iEq (IVar "x") (IInt 2L)) (BNot (iEq (IVar "x") (IInt 2L))) |]) .Returns(true) (tcd [| (iEq (IVar "x") (IInt 2L)) (BNot (iEq (IVar "y") (IInt 2L))) |]) .Returns(false) // De Morgan (tcd [| (BAnd [ BVar "x" ; BVar "y" ]) (BOr [ BNot (BVar "x") BNot (BVar "y") ] ) |] ) .Returns(true) (tcd [| (BAnd [ BVar "x" ; BVar "y" ]) (BOr [ BNot (BVar "y") BNot (BVar "x") ] ) |] ) .Returns(true) (tcd [| (BOr [ BVar "x" ; BVar "y" ]) (BAnd [ BNot (BVar "x") BNot (BVar "y") ] ) |] ) .Returns(true) (tcd [| (BOr [ BVar "x" ; BVar "y" ]) (BAnd [ BNot (BVar "y") BNot (BVar "x") ] ) |] ) .Returns(true) ] |> List.map ( fun d -> d.SetName(sprintf "%s and %s are %s negation" (((d.OriginalArguments.[1]) :?> VBoolExpr) |> printVBoolExpr |> print) (((d.OriginalArguments.[0]) :?> VBoolExpr) |> printVBoolExpr |> print) (if (d.ExpectedResult :?> bool) then "a" else "not a"))) /// Checks whether negation checking is sound and sufficiently complete. [] member x.``negates is sound and sufficiently complete`` a b = equivHolds id (negates a b)