Simplification

At the heart of Imandra is a powerful symbolic simplifier and partial evaluator. The simplifier is integrated with the inductive waterfall (e.g., [@@auto]), and is the main way in which previously proved lemmas are used during proofs, through the automatic application of rules. The simplifier can also be used as a pre-processing step before unrolling, via the [@@simp] attribute.

As the name suggests, simplification is a process that attempts to transform a formula into a "simpler" form, bringing the salient features of a formula or conjecture to the surface. Simplification can also prove goals by reducing them to true, and refute them by reducing them to false.

Notably, because the symbolic evaluation semantics of the simplifier operate on a compact digraph representation of formulas and function definitions, simplification can be thought as having memoized semantics for free.

We can see an example of this by using the following naive recursive version of the fibonacci function:

In [1]:
let rec fib n =
  if n <= 1 then
    1
  else
    fib (n-1) + fib (n-2)
Out[1]:
val fib : Z.t -> Z.t = <fun>
termination proof

Termination proof

call `fib (n - 1)` from `fib n`
original:fib n
sub:fib (n - 1)
original ordinal:Ordinal.Int (_cnt n)
sub ordinal:Ordinal.Int (_cnt (n - 1))
path:[not (n <= 1)]
proof:
detailed proof
ground_instances:1
definitions:0
inductions:0
search_time:
0.008s
details:
Expand
smt_stats:
num checks:3
arith assert lower:6
arith tableau max rows:4
arith tableau max columns:9
arith pivots:2
rlimit count:2198
mk clause:5
datatype occurs check:2
mk bool var:17
arith assert upper:3
decisions:2
arith row summations:3
propagations:2
conflicts:2
arith fixed eqs:2
datatype accessor ax:2
arith conflicts:1
arith num rows:4
num allocs:2383056
final checks:1
added eqs:4
del clause:5
arith eq adapter:2
memory:5.140000
max memory:5.140000
Expand
  • start[0.008s]
      let (_x_0 : int) = if n >= 0 then n else 0 in
      let (_x_1 : int) = n - 1 in
      let (_x_2 : int) = if _x_1 >= 0 then _x_1 else 0 in
      let (_x_3 : bool) = _x_1 <= 1 in
      not (n <= 1) && _x_0 >= 0 && _x_2 >= 0
      ==> _x_3 && _x_3 || Ordinal.( << ) (Ordinal.Int _x_2) (Ordinal.Int _x_0)
  • simplify
    into:
    (n <= 1 || n <= 2)
    || Ordinal.( << ) (Ordinal.Int (if n >= 1 then (-1) + n else 0))
       (Ordinal.Int (if n >= 0 then n else 0))
    expansions:
    []
    rewrite_steps:
      forward_chaining:
      • unroll
        expr:
        (|Ordinal.<<_129/client|
          (|Ordinal.Int_114/client|
            (ite (>= n_1622/server 1) (+ (- 1) n_1622/s…
        expansions:
        • Unsat
        call `fib (n - 2)` from `fib n`
        original:fib n
        sub:fib (n - 2)
        original ordinal:Ordinal.Int (_cnt n)
        sub ordinal:Ordinal.Int (_cnt (n - 2))
        path:[not (n <= 1)]
        proof:
        detailed proof
        ground_instances:1
        definitions:0
        inductions:0
        search_time:
        0.012s
        details:
        Expand
        smt_stats:
        num checks:3
        arith assert lower:6
        arith tableau max rows:4
        arith tableau max columns:10
        arith pivots:2
        rlimit count:1099
        mk clause:5
        datatype occurs check:2
        mk bool var:17
        arith assert upper:3
        decisions:2
        arith row summations:3
        propagations:2
        conflicts:2
        arith fixed eqs:2
        datatype accessor ax:2
        arith conflicts:1
        arith num rows:4
        num allocs:657700
        final checks:1
        added eqs:4
        del clause:5
        arith eq adapter:2
        memory:5.100000
        max memory:5.100000
        Expand
        • start[0.012s]
            let (_x_0 : int) = if n >= 0 then n else 0 in
            let (_x_1 : int) = n - 2 in
            let (_x_2 : int) = if _x_1 >= 0 then _x_1 else 0 in
            let (_x_3 : bool) = _x_1 <= 1 in
            not (n <= 1) && _x_0 >= 0 && _x_2 >= 0
            ==> _x_3 && _x_3 || Ordinal.( << ) (Ordinal.Int _x_2) (Ordinal.Int _x_0)
        • simplify
          into:
          (n <= 1 || n <= 3)
          || Ordinal.( << ) (Ordinal.Int (if n >= 2 then (-2) + n else 0))
             (Ordinal.Int (if n >= 0 then n else 0))
          expansions:
          []
          rewrite_steps:
            forward_chaining:
            • unroll
              expr:
              (|Ordinal.<<_129/client|
                (|Ordinal.Int_114/client|
                  (ite (>= n_1622/server 2) (+ (- 2) n_1622/s…
              expansions:
              • Unsat

              If we try to use Imandra's simplification to search for a solution for fib 200, Imandra comes back to us with a solution immediately:

              In [2]:
              #check_models false;;
              instance (fun x -> x = fib 200) [@@simp];;
              #check_models true;;
              
              Out[2]:
              - : Z.t -> bool = <fun>
              module CX : sig val x : Z.t end
              
              Instance (after 0 steps, 0.041s):
              let x : int = 453973694165307953197296969697410619233826
              
              Instance
              proof attempt
              ground_instances:0
              definitions:201
              inductions:0
              search_time:
              0.041s
              details:
              Expand
              smt_stats:
              eliminated vars:1
              rlimit count:7909
              num allocs:11614005
              time:0.006000
              memory:5.620000
              max memory:6.070000
              Expand
              • start[0.041s] ( :var_0: ) = fib 200
              • simplify

                into:
                x = 453973694165307953197296969697410619233826
                expansions:
                [fib, fib, fib, fib, fib, fib, fib, fib, fib, fib, fib, fib, fib, fib, fib,
                 fib, fib, fib, fib, fib, fib, fib, fib, fib, fib, fib, fib, fib, fib, fib,
                 fib, fib, fib, fib, fib, fib, fib, fib, fib, fib, fib, fib, fib, fib, fib,
                 fib, fib, fib, fib, fib, fib, fib, fib, fib, fib, fib, fib, fib, fib, fib,
                 fib, fib, fib, fib, fib, fib, fib, fib, fib, fib, fib, fib, fib, fib, fib,
                 fib, fib, fib, fib, fib, fib, fib, fib, fib, fib, fib, fib, fib, fib, fib,
                 fib, fib, fib, fib, fib, fib, fib, fib, fib, fib, fib, fib, fib, fib, fib,
                 fib, fib, fib, fib, fib, fib, fib, fib, fib, fib, fib, fib, fib, fib, fib,
                 fib, fib, fib, fib, fib, fib, fib, fib, fib, fib, fib, fib, fib, fib, fib,
                 fib, fib, fib, fib, fib, fib, fib, fib, fib, fib, fib, fib, fib, fib, fib,
                 fib, fib, fib, fib, fib, fib, fib, fib, fib, fib, fib, fib, fib, fib, fib,
                 fib, fib, fib, fib, fib, fib, fib, fib, fib, fib, fib, fib, fib, fib, fib,
                 fib, fib, fib, fib, fib, fib, fib, fib, fib, fib, fib, fib, fib, fib, fib,
                 fib, fib, fib, fib, fib, fib]
                rewrite_steps:
                  forward_chaining:
                  • Sat (Some let x : int = (Z.of_string "453973694165307953197296969697410619233826") )

                  If, however, we tried to use normal OCaml evaluation to compute fib 200, OCaml would take over 10 minutes in order to come back with a response (the #check_models false command here is used to tell Imandra not to check the instance Imandra computed using OCaml evaluation, as that requires the expensive computation of fib 200 via standard OCaml evaluation, which is not memoized).

                  Now that we know what simplification does, let's learn about how to influence it using rewrite and forward chaining rules.

                  Rewrite Rules

                  A rewrite rule is a theorem of the form

                  h_1 && ... && h_k ==> lhs = rhs

                  which Imandra can use in further proofs to replace terms that match with lhs (the "left hand side") with the appropriate instantiation of rhs (the "right hand side"), provided that the instantiations of the hypotheses can be established. Observe that rewrite rules are both conditional (requiring in general the establishment of hypotheses) and directed (replacing lhs with rhs). The lhs is also called the "pattern" of the rule.

                  An enabled rewrite rule causes Imandra to look for matches of the lhs, replacing the matched term with the (suitably instantiated) rhs, provided that Imandra can establish ("relieve") the (suitably instantiated) hypotheses of the rule.

                  For example, consider the lemma rev_len below. This lemma expresses the fact that the length of a list x is equal to the length of List.rev x, i.e., if we reverse a list, we end up with a list of the same length. This rule is unconditional: it has no hypotheses. Thus, it will always be able to fire on terms that match its left-hand side. Notice that Imandra uses a previously defined rewrite rule in order to prove this lemma! The lemma rev_len would make an excellent rewrite rule, so we use the [@@rw] annotation to install it:

                  In [3]:
                  lemma rev_len x =
                    List.length (List.rev x) = List.length x
                  [@@auto] [@@rw]
                  
                  Out[3]:
                  val rev_len : 'a list -> bool = <fun>
                  Goal:
                  
                  List.length (List.rev x) = List.length x.
                  
                  1 nontautological subgoal.
                  
                  Subgoal 1:
                  
                  |---------------------------------------------------------------------------
                   List.length (List.rev x) = List.length x
                  
                  
                  Must try induction.
                  
                  The recursive terms in the conjecture suggest 2 inductions.
                  Subsumption and merging reduces this to 1.
                  
                  We shall induct according to a scheme derived from List.rev.
                  
                  Induction scheme:
                  
                   (not (x <> []) ==> φ x) && (x <> [] && φ (List.tl x) ==> φ x).
                  
                  2 nontautological subgoals.
                  
                  Subgoal 1.2:
                  
                  |---------------------------------------------------------------------------
                   C0. x <> []
                   C1. List.length (List.rev x) = List.length x
                  
                  But simplification reduces this to true, using the definitions of List.length
                  and List.rev.
                  
                  Subgoal 1.1:
                  
                   H0. x <> []
                   H1. List.length (List.rev (List.tl x)) = List.length (List.tl x)
                  |---------------------------------------------------------------------------
                   List.length (List.rev x) = List.length x
                  
                  This simplifies, using the definitions of List.length and List.rev to:
                  
                  Subgoal 1.1':
                  
                   H0. x <> []
                   H1. List.length (List.rev (List.tl x)) = List.length (List.tl x)
                  |---------------------------------------------------------------------------
                   List.length (List.append (List.rev (List.tl x)) [List.hd x]) =
                   1 + List.length (List.tl x)
                  
                  
                  We can eliminate destructors by the following substitution:
                   x -> x1 :: x2
                  
                  This produces the modified subgoal:
                  
                  Subgoal 1.1'':
                  
                   H0. List.length (List.rev x2) = List.length x2
                  |---------------------------------------------------------------------------
                   List.length (List.append (List.rev x2) [x1]) = 1 + List.length x2
                  
                  
                  Cross-fertilizing with:
                  
                   List.length (List.rev x2) = List.length x2
                  
                  This produces the modified subgoal:
                  
                  Subgoal 1.1''':
                  
                  |---------------------------------------------------------------------------
                   List.length (List.append (List.rev x2) [x1]) = 1 + List.length (List.rev x2)
                  
                  
                  Candidates for generalization:
                  
                   List.rev x2
                  
                  This produces the modified subgoal:
                  
                  Subgoal 1.1'''':
                  
                  |---------------------------------------------------------------------------
                   List.length (List.append gen_1 [x1]) = 1 + List.length gen_1
                  
                  
                  Must try induction.
                  
                  The recursive terms in the conjecture suggest 2 inductions.
                  Subsumption and merging reduces this to 1.
                  
                  We shall induct according to a scheme derived from List.append.
                  
                  Induction scheme:
                  
                   (not (gen_1 <> []) ==> φ gen_1 x1)
                   && (gen_1 <> [] && φ (List.tl gen_1) x1 ==> φ gen_1 x1).
                  
                  2 nontautological subgoals.
                  
                  Subgoal 1.1''''.2:
                  
                  |---------------------------------------------------------------------------
                   C0. gen_1 <> []
                   C1. List.length (List.append gen_1 [x1]) = 1 + List.length gen_1
                  
                  But simplification reduces this to true, using the definitions of List.append
                  and List.length.
                  
                  Subgoal 1.1''''.1:
                  
                   H0. gen_1 <> []
                   H1. List.length (List.append (List.tl gen_1) [x1]) =
                       1 + List.length (List.tl gen_1)
                  |---------------------------------------------------------------------------
                   List.length (List.append gen_1 [x1]) = 1 + List.length gen_1
                  
                  But simplification reduces this to true, using the definitions of List.append
                  and List.length.
                  
                   Rules:
                      (:def List.append)
                      (:def List.length)
                      (:def List.rev)
                      (:fc List.len_nonnegative)
                      (:induct List.append)
                      (:induct List.rev)
                  
                  
                  Proved
                  proof
                  ground_instances:0
                  definitions:12
                  inductions:2
                  search_time:
                  0.282s
                  Expand
                  • start[0.282s, "Goal"]
                      List.length (List.rev ( :var_0: )) = List.length ( :var_0: )
                  • subproof

                    List.length (List.rev x) = List.length x
                    • start[0.282s, "1"] List.length (List.rev x) = List.length x
                    • induction on (functional ?)
                      :scheme (not (x <> []) ==> φ x) && (x <> [] && φ (List.tl x) ==> φ x)
                    • Split (let (_x_0 : bool) = x <> [] in
                             let (_x_1 : bool) = List.length (List.rev x) = List.length x in
                             let (_x_2 : sko_ty_0 list) = List.tl x in
                             (_x_0 || _x_1)
                             && (not (_x_0 && List.length (List.rev _x_2) = List.length _x_2)
                                 || _x_1)
                             :cases [x <> [] || List.length (List.rev x) = List.length x;
                                     let (_x_0 : sko_ty_0 list) = List.tl x in
                                     (not (x <> [])
                                      || not (List.length (List.rev _x_0) = List.length _x_0))
                                     || List.length (List.rev x) = List.length x])
                      • subproof
                        let (_x_0 : sko_ty_0 list) = List.tl x in (not (x <> []) || not (List.length (List.rev _x_0) = List.length _x_0)) || List.length (List.rev x) = List.length x
                        • start[0.250s, "1.1"]
                            let (_x_0 : sko_ty_0 list) = List.tl x in
                            (not (x <> []) || not (List.length (List.rev _x_0) = List.length _x_0))
                            || List.length (List.rev x) = List.length x
                        • simplify
                          into:
                          let (_x_0 : sko_ty_0 list) = List.tl x in
                          let (_x_1 : sko_ty_0 list) = List.rev _x_0 in
                          let (_x_2 : int) = List.length _x_0 in
                          (not (x <> []) || not (List.length _x_1 = _x_2))
                          || List.length (List.append _x_1 [List.hd x]) = 1 + _x_2
                          expansions:
                          [List.length, List.rev]
                          rewrite_steps:
                            forward_chaining:
                            • List.len_nonnegative
                            • List.len_nonnegative
                            • List.len_nonnegative
                            • List.len_nonnegative
                            • List.len_nonnegative
                            • List.len_nonnegative
                            • List.len_nonnegative
                            • List.len_nonnegative
                            • List.len_nonnegative
                            • List.len_nonnegative
                            • List.len_nonnegative
                          • Elim_destructor (:cstor ( :: ) :replace x1 :: x2 :context [])
                          • Generalize (let (_x_0 : sko_ty_0 list) = List.rev x2 in
                                        List.length (List.append _x_0 [x1]) = 1 + List.length _x_0
                                        :as (gen_1 : sko_ty_0 list))
                          • induction on (functional ?)
                            :scheme (not (gen_1 <> []) ==> φ gen_1 x1)
                                    && (gen_1 <> [] && φ (List.tl gen_1) x1 ==> φ gen_1 x1)
                          • Split (let (_x_0 : bool) = gen_1 <> [] in
                                   let (_x_1 : sko_ty_0 list) = [x1] in
                                   let (_x_2 : bool)
                                       = List.length (List.append gen_1 _x_1) = 1 + List.length gen_1
                                   in
                                   let (_x_3 : sko_ty_0 list) = List.tl gen_1 in
                                   (_x_0 || _x_2)
                                   && (not
                                       (_x_0
                                        && List.length (List.append _x_3 _x_1) = 1 + List.length _x_3)
                                       || _x_2)
                                   :cases [gen_1 <> []
                                           || List.length (List.append gen_1 [x1]) =
                                              1 + List.length gen_1;
                                           let (_x_0 : sko_ty_0 list) = List.tl gen_1 in
                                           let (_x_1 : sko_ty_0 list) = [x1] in
                                           (not (gen_1 <> [])
                                            || not
                                               (List.length (List.append _x_0 _x_1) =
                                                1 + List.length _x_0))
                                           || List.length (List.append gen_1 _x_1) =
                                              1 + List.length gen_1])
                            • Subproof
                            • Subproof
                        • subproof
                          x <> [] || List.length (List.rev x) = List.length x
                          • start[0.250s, "1.2"] x <> [] || List.length (List.rev x) = List.length x
                          • simplify
                            into:
                            true
                            expansions:
                            [List.length, List.length, List.rev]
                            rewrite_steps:
                              forward_chaining:
                              • List.len_nonnegative
                              • List.len_nonnegative
                              • List.len_nonnegative
                              • List.len_nonnegative

                      With this rule installed and enabled, if Imandra's simplifier encounters a term of the form List.length (List.rev <term>), it will replace it with the simpler form List.length <term>.

                      Both the hypotheses and rhs can be omitted, in which case Imandra will default them to true. That is, h_1 && ... && h_k ==> lhs is equivalent to h_1 && h_k ==> lhs = true and lhs = rhs is equal to true ==> lhs = rhs.

                      Imandra's rewriting is:

                      • conditional: rewrite rules may contain conditions (hypotheses), and eligible rules are only applied when their hypotheses are established. Once the pattern of a rule has been matched, Imandra uses backward-chaining ("backchaining") to relieve the rule's hypotheses, recursively attempting to simplify them to true modulo the current simplification context. This is important to keep in mind when developing your theories: rewrite rules will not fire unless Imandra can simplify their instantiated hypotheses to true.

                      • oriented: given a rule whose conclusion is of the form lhs = rhs, rewriting happens by replacing the (instantiated) lhs with the (instantiated) rhs.

                      When adding a new rewrite rule, users should take care to orient the equality so that rhs is simpler or more canonical than lhs. If it's not clear what it means for an rhs to be "better" than the lhs, e.g., in the case of the proof for the associativity of append x @ (y @ z) = (x @ y) @ z, a canonical form should typically be chosen (e.g., associating to the left in this case) and kept in mind for further rules.

                      By default, the lhs must contain all the top-level variables of the theorem (i.e. the arguments to the lambda term representing the goal). There is an exception to this rule: if the lhs does not contain all the variables of the theorem but the rule hypotheses have subterms containing the remaining free variables, these terms can be annotated with [@trigger rw], signaling Imandra that the annotated terms should be used to complete the matching.

                      It's helpful to see an example where the use of [@trigger rw] is necessary. Let's first define a subset function on lists:

                      In [4]:
                      let rec subset x y =
                        match x with
                        | [] -> true
                        | x :: xs ->
                          if List.mem x y then subset xs y
                          else false
                      
                      Out[4]:
                      val subset : 'a list -> 'a list -> bool = <fun>
                      
                      termination proof

                      Termination proof

                      call `subset (List.tl x) y` from `subset x y`
                      original:subset x y
                      sub:subset (List.tl x) y
                      original ordinal:Ordinal.Int (_cnt x)
                      sub ordinal:Ordinal.Int (_cnt (List.tl x))
                      path:[List.mem (List.hd x) y && x <> []]
                      proof:
                      detailed proof
                      ground_instances:4
                      definitions:0
                      inductions:0
                      search_time:
                      0.012s
                      details:
                      Expand
                      smt_stats:
                      num checks:10
                      arith assert lower:12
                      arith tableau max rows:5
                      arith tableau max columns:12
                      arith pivots:11
                      rlimit count:86036
                      mk clause:20
                      datatype occurs check:36
                      mk bool var:96
                      arith assert upper:11
                      datatype splits:7
                      decisions:29
                      arith row summations:24
                      propagations:28
                      conflicts:12
                      arith fixed eqs:8
                      datatype accessor ax:12
                      minimized lits:3
                      arith conflicts:1
                      arith num rows:5
                      arith assert diseq:1
                      datatype constructor ax:15
                      num allocs:440954273
                      final checks:8
                      added eqs:86
                      del clause:11
                      arith eq adapter:13
                      memory:6.600000
                      max memory:7.130000
                      Expand
                      • start[0.012s]
                          let (_x_0 : int) = count.list (const 0) x in
                          let (_x_1 : ty_0 list) = List.tl x in
                          let (_x_2 : int) = count.list (const 0) _x_1 in
                          List.mem (List.hd x) y && x <> [] && _x_0 >= 0 && _x_2 >= 0
                          ==> not (List.mem (List.hd _x_1) y && _x_1 <> [])
                              || Ordinal.( << ) (Ordinal.Int _x_2) (Ordinal.Int _x_0)
                      • simplify
                        into:
                        let (_x_0 : ty_0 list) = List.tl x in
                        let (_x_1 : int) = count.list (const 0) x in
                        let (_x_2 : int) = count.list (const 0) _x_0 in
                        (not (List.mem (List.hd _x_0) y && _x_0 <> [])
                         || not (((List.mem (List.hd x) y && x <> []) && _x_1 >= 0) && _x_2 >= 0))
                        || Ordinal.( << ) (Ordinal.Int _x_2) (Ordinal.Int _x_1)
                        expansions:
                        []
                        rewrite_steps:
                          forward_chaining:
                          • unroll
                            expr:
                            (|List.mem_1928/server| (|get.::.0_1921/server| x_1933/server) y_1934/server)
                            expansions:
                            • unroll
                              expr:
                              (|count.list_1946/server| (|get.::.1_1922/server| x_1933/server))
                              expansions:
                              • unroll
                                expr:
                                (|count.list_1946/server| x_1933/server)
                                expansions:
                                • unroll
                                  expr:
                                  (|Ordinal.<<_129/client|
                                    (|Ordinal.Int_114/client|
                                      (|count.list_1946/server| (|get.::.1_1922/s…
                                  expansions:
                                  • Unsat

                                  Let's now suppose that we want to verify the transitivity of subset:

                                  In [5]:
                                  #max_induct 1;;
                                  verify (fun x y z -> subset x y && subset y z ==> subset x z) [@@auto]
                                  #max_induct 3;;
                                  
                                  Out[5]:
                                  - : 'a list -> 'a list -> 'a list -> bool = <fun>
                                  Goal:
                                  
                                  subset x y && subset y z ==> subset x z.
                                  
                                  1 nontautological subgoal.
                                  
                                  Subgoal 1:
                                  
                                   H0. subset x y
                                   H1. subset y z
                                  |---------------------------------------------------------------------------
                                   subset x z
                                  
                                  
                                  Must try induction.
                                  
                                  The recursive terms in the conjecture suggest 3 inductions.
                                  Subsumption and merging reduces this to 2.
                                  
                                  However, scheme scoring gives us a clear winner.
                                  We shall induct according to a scheme derived from subset.
                                  
                                  Induction scheme:
                                  
                                   (not (List.mem (List.hd x) z && x <> []) ==> φ x y z)
                                   && (x <> [] && List.mem (List.hd x) z && φ (List.tl x) y z ==> φ x y z).
                                  
                                  2 nontautological subgoals.
                                  
                                  Subgoal 1.2:
                                  
                                   H0. subset y z
                                   H1. subset x y
                                  |---------------------------------------------------------------------------
                                   C0. subset x z
                                   C1. List.mem (List.hd x) z && x <> []
                                  
                                  This simplifies, using the definition of subset to:
                                  
                                  Subgoal 1.2':
                                  
                                   H0. subset y z
                                   H1. subset x y
                                   H2. x <> []
                                  |---------------------------------------------------------------------------
                                   List.mem (List.hd x) z
                                  
                                  
                                  We can eliminate destructors by the following substitution:
                                   x -> x1 :: x2
                                  
                                  This produces the modified subgoal:
                                  
                                  Subgoal 1.2'':
                                  
                                   H0. subset y z
                                   H1. subset (x1 :: x2) y
                                  |---------------------------------------------------------------------------
                                   List.mem x1 z
                                  
                                  This simplifies, using the definition of subset to:
                                  
                                  Subgoal 1.2''':
                                  
                                   H0. subset y z
                                   H1. List.mem x1 y
                                   H2. subset x2 y
                                  |---------------------------------------------------------------------------
                                   List.mem x1 z
                                  
                                  
                                  Must try induction.
                                  
                                   Aborting proof attempt for _verify_target.
                                  
                                   Rules:
                                      (:def subset)
                                      (:induct subset)
                                  
                                  Checkpoints:
                                  
                                   H0. subset y z
                                   H1. List.mem x1 y
                                   H2. subset x2 y
                                  |---------------------------------------------------------------------------
                                   List.mem x1 z
                                  
                                  
                                  At <none>:1
                                  
                                  Error[/server]:
                                    Maximum induction depth reached (1). You can set this with #max_induct.
                                  

                                  It looks like Imandra needs an additional lemma in order to prove this. By inspecting the checkpoint, it looks like all we need is a rule relating subset and List.mem. Let's attempt to prove it:

                                  In [6]:
                                  lemma mem_subset x y z =
                                    List.mem x y && subset y z ==> List.mem x z
                                  [@@auto] [@@rewrite]
                                  
                                  Out[6]:
                                  val mem_subset : 'a -> 'a list -> 'a list -> bool = <fun>
                                  Goal:
                                  
                                  List.mem x y && subset y z ==> List.mem x z.
                                  
                                  1 nontautological subgoal.
                                  
                                  Subgoal 1:
                                  
                                   H0. List.mem x y
                                   H1. subset y z
                                  |---------------------------------------------------------------------------
                                   List.mem x z
                                  
                                  
                                  Must try induction.
                                  
                                  The recursive terms in the conjecture suggest 3 inductions.
                                  Subsumption and merging reduces this to 2.
                                  
                                  Only 1 of those schemes are unflawed.
                                  We shall induct according to a scheme derived from subset.
                                  
                                  Induction scheme:
                                  
                                   (not (List.mem (List.hd y) z && y <> []) ==> φ x y z)
                                   && (y <> [] && List.mem (List.hd y) z && φ x (List.tl y) z ==> φ x y z).
                                  
                                  2 nontautological subgoals.
                                  
                                  Subgoal 1.2:
                                  
                                   H0. subset y z
                                   H1. List.mem x y
                                  |---------------------------------------------------------------------------
                                   C0. List.mem x z
                                   C1. List.mem (List.hd y) z && y <> []
                                  
                                  This simplifies, using the definitions of List.mem and subset to:
                                  
                                  Subgoal 1.2':
                                  
                                   H0. List.mem x y
                                  |---------------------------------------------------------------------------
                                   C0. List.mem x z
                                   C1. List.mem (List.hd y) z
                                   C2. y <> []
                                  
                                  But simplification reduces this to true, using the definition of List.mem.
                                  
                                  Subgoal 1.1:
                                  
                                   H0. y <> []
                                   H1. List.mem (List.hd y) z
                                   H2. List.mem x (List.tl y) && subset (List.tl y) z ==> List.mem x z
                                   H3. subset y z
                                   H4. List.mem x y
                                  |---------------------------------------------------------------------------
                                   List.mem x z
                                  
                                  But simplification reduces this to true, using the definitions of List.mem
                                  and subset.
                                  
                                   Rules:
                                      (:def List.mem)
                                      (:def subset)
                                      (:induct subset)
                                  
                                  
                                  At jupyter cell 6:1,0--91
                                  1 | lemma mem_subset x y z =
                                  2 |   List.mem x y && subset y z ==> List.mem x z
                                  3 | [@@auto] [@@rewrite]
                                  
                                  Error[/server]:
                                    unsupported: in rewrite rule,
                                    variable (y : 'a list) does not occur in left-hand side of the rule
                                    or in `[@trigger rw]` terms
                                    See https://docs.imandra.ai/imandra-docs/notebooks/verification-simplification/#Rewrite-Rules
                                  
                                  Proved
                                  proof
                                  ground_instances:0
                                  definitions:5
                                  inductions:1
                                  search_time:
                                  0.187s
                                  Expand
                                  • start[0.187s, "Goal"]
                                      List.mem ( :var_0: ) ( :var_1: ) && subset ( :var_1: ) ( :var_2: )
                                      ==> List.mem ( :var_0: ) ( :var_2: )
                                  • subproof

                                    (not (List.mem x y) || not (subset y z)) || List.mem x z
                                    • start[0.186s, "1"] (not (List.mem x y) || not (subset y z)) || List.mem x z
                                    • induction on (functional ?)
                                      :scheme (not (List.mem (List.hd y) z && y <> []) ==> φ x y z)
                                              && (y <> [] && List.mem (List.hd y) z && φ x (List.tl y) z
                                                  ==> φ x y z)
                                    • Split (let (_x_0 : bool) = List.mem x z in
                                             let (_x_1 : bool) = not (subset y z) in
                                             let (_x_2 : bool) = List.mem (List.hd y) z in
                                             let (_x_3 : bool) = y <> [] in
                                             let (_x_4 : bool) = not (List.mem x y) in
                                             let (_x_5 : sko_ty_0 list) = List.tl y in
                                             (((_x_0 || _x_1) || _x_2 && _x_3) || _x_4)
                                             && (((not
                                                   ((_x_3 && _x_2)
                                                    && ((not (List.mem x _x_5) || _x_0) || not (subset _x_5 z)))
                                                   || _x_0)
                                                  || _x_1)
                                                 || _x_4)
                                             :cases [((not (subset y z) || not (List.mem x y)) || List.mem x z)
                                                     || List.mem (List.hd y) z && y <> [];
                                                     let (_x_0 : sko_ty_0 list) = List.tl y in
                                                     let (_x_1 : bool) = List.mem x z in
                                                     ((((not (y <> []) || not (List.mem (List.hd y) z))
                                                        || not (List.mem x _x_0 && subset _x_0 z ==> _x_1))
                                                       || not (subset y z))
                                                      || not (List.mem x y))
                                                     || _x_1])
                                      • subproof
                                        let (_x_0 : sko_ty_0 list) = List.tl y in let (_x_1 : bool) = List.mem x z in ((((not (y <> []) || not (List.mem (List.hd y) z)) || not (List.mem x _x_0 && subset _x_0 z ==> _x_1)) || not (subset y z)) || not (List.mem x y)) || _x_1
                                        • start[0.126s, "1.1"]
                                            let (_x_0 : sko_ty_0 list) = List.tl y in
                                            let (_x_1 : bool) = List.mem x z in
                                            ((((not (y <> []) || not (List.mem (List.hd y) z))
                                               || not (List.mem x _x_0 && subset _x_0 z ==> _x_1))
                                              || not (subset y z))
                                             || not (List.mem x y))
                                            || _x_1
                                        • simplify
                                          into:
                                          true
                                          expansions:
                                          [subset, List.mem]
                                          rewrite_steps:
                                            forward_chaining:
                                          • subproof
                                            ((not (subset y z) || not (List.mem x y)) || List.mem x z) || List.mem (List.hd y) z && y <> []
                                            • start[0.126s, "1.2"]
                                                ((not (subset y z) || not (List.mem x y)) || List.mem x z)
                                                || List.mem (List.hd y) z && y <> []
                                            • simplify
                                              into:
                                              ((List.mem x z || List.mem (List.hd y) z) || y <> []) || not (List.mem x y)
                                              expansions:
                                              [List.mem, subset]
                                              rewrite_steps:
                                                forward_chaining:
                                                • simplify
                                                  into:
                                                  true
                                                  expansions:
                                                  List.mem
                                                  rewrite_steps:
                                                    forward_chaining:

                                              While Imandra was successful in proving this lemma, it raised an error while trying to turn this lemma into a rewrite rule. As Imandra tells us, this is because the free variable y does not appear in the lhs term List.mem x z. If we, however, annotate the subset y z term with the appropriate [@trigger rw] attribute, Imandra can then successfully turn this term into a valid rewrite rule:

                                              In [7]:
                                              lemma mem_subset x y z =
                                                List.mem x y && (subset y z [@trigger rw]) ==> List.mem x z
                                              [@@auto] [@@rewrite]
                                              
                                              Out[7]:
                                              val mem_subset : 'a -> 'a list -> 'a list -> bool = <fun>
                                              Goal:
                                              
                                              List.mem x y && subset y z ==> List.mem x z.
                                              
                                              1 nontautological subgoal.
                                              
                                              Subgoal 1:
                                              
                                               H0. List.mem x y
                                               H1. subset y z
                                              |---------------------------------------------------------------------------
                                               List.mem x z
                                              
                                              
                                              Must try induction.
                                              
                                              The recursive terms in the conjecture suggest 3 inductions.
                                              Subsumption and merging reduces this to 2.
                                              
                                              Only 1 of those schemes are unflawed.
                                              We shall induct according to a scheme derived from subset.
                                              
                                              Induction scheme:
                                              
                                               (not (List.mem (List.hd y) z && y <> []) ==> φ x y z)
                                               && (y <> [] && List.mem (List.hd y) z && φ x (List.tl y) z ==> φ x y z).
                                              
                                              2 nontautological subgoals.
                                              
                                              Subgoal 1.2:
                                              
                                               H0. subset y z
                                               H1. List.mem x y
                                              |---------------------------------------------------------------------------
                                               C0. List.mem (List.hd y) z && y <> []
                                               C1. List.mem x z
                                              
                                              This simplifies, using the definitions of List.mem and subset to:
                                              
                                              Subgoal 1.2':
                                              
                                               H0. List.mem x y
                                              |---------------------------------------------------------------------------
                                               C0. List.mem (List.hd y) z
                                               C1. y <> []
                                               C2. List.mem x z
                                              
                                              But simplification reduces this to true, using the definition of List.mem.
                                              
                                              Subgoal 1.1:
                                              
                                               H0. subset y z
                                               H1. List.mem x y
                                               H2. y <> []
                                               H3. List.mem (List.hd y) z
                                               H4. subset (List.tl y) z && List.mem x (List.tl y) ==> List.mem x z
                                              |---------------------------------------------------------------------------
                                               List.mem x z
                                              
                                              But simplification reduces this to true, using the definitions of List.mem
                                              and subset.
                                              
                                               Rules:
                                                  (:def List.mem)
                                                  (:def subset)
                                                  (:induct subset)
                                              
                                              
                                              Proved
                                              proof
                                              ground_instances:0
                                              definitions:5
                                              inductions:1
                                              search_time:
                                              0.241s
                                              Expand
                                              • start[0.241s, "Goal"]
                                                  List.mem ( :var_0: ) ( :var_1: ) && subset ( :var_1: ) ( :var_2: )
                                                  ==> List.mem ( :var_0: ) ( :var_2: )
                                              • subproof

                                                (not (List.mem x y) || not (subset y z)) || List.mem x z
                                                • start[0.241s, "1"] (not (List.mem x y) || not (subset y z)) || List.mem x z
                                                • induction on (functional ?)
                                                  :scheme (not (List.mem (List.hd y) z && y <> []) ==> φ x y z)
                                                          && (y <> [] && List.mem (List.hd y) z && φ x (List.tl y) z
                                                              ==> φ x y z)
                                                • Split (let (_x_0 : bool) = not (subset y z) || not (List.mem x y) in
                                                         let (_x_1 : bool) = List.mem (List.hd y) z in
                                                         let (_x_2 : bool) = y <> [] in
                                                         let (_x_3 : bool) = List.mem x z in
                                                         let (_x_4 : sko_ty_0 list) = List.tl y in
                                                         ((_x_0 || _x_1 && _x_2) || _x_3)
                                                         && ((_x_0
                                                              || not
                                                                 ((_x_2 && _x_1)
                                                                  && ((not (subset _x_4 z) || not (List.mem x _x_4)) || _x_3)))
                                                             || _x_3)
                                                         :cases [((not (subset y z) || not (List.mem x y))
                                                                  || List.mem (List.hd y) z && y <> [])
                                                                 || List.mem x z;
                                                                 let (_x_0 : sko_ty_0 list) = List.tl y in
                                                                 let (_x_1 : bool) = List.mem x z in
                                                                 ((((not (subset y z) || not (List.mem x y)) || not (y <> []))
                                                                   || not (List.mem (List.hd y) z))
                                                                  || not (subset _x_0 z && List.mem x _x_0 ==> _x_1))
                                                                 || _x_1])
                                                  • subproof
                                                    let (_x_0 : sko_ty_0 list) = List.tl y in let (_x_1 : bool) = List.mem x z in ((((not (subset y z) || not (List.mem x y)) || not (y <> [])) || not (List.mem (List.hd y) z)) || not (subset _x_0 z && List.mem x _x_0 ==> _x_1)) || _x_1
                                                    • start[0.184s, "1.1"]
                                                        let (_x_0 : sko_ty_0 list) = List.tl y in
                                                        let (_x_1 : bool) = List.mem x z in
                                                        ((((not (subset y z) || not (List.mem x y)) || not (y <> []))
                                                          || not (List.mem (List.hd y) z))
                                                         || not (subset _x_0 z && List.mem x _x_0 ==> _x_1))
                                                        || _x_1
                                                    • simplify
                                                      into:
                                                      true
                                                      expansions:
                                                      [subset, List.mem]
                                                      rewrite_steps:
                                                        forward_chaining:
                                                      • subproof
                                                        ((not (subset y z) || not (List.mem x y)) || List.mem (List.hd y) z && y <> []) || List.mem x z
                                                        • start[0.184s, "1.2"]
                                                            ((not (subset y z) || not (List.mem x y))
                                                             || List.mem (List.hd y) z && y <> [])
                                                            || List.mem x z
                                                        • simplify
                                                          into:
                                                          ((not (List.mem x y) || List.mem (List.hd y) z) || y <> []) || List.mem x z
                                                          expansions:
                                                          [List.mem, subset]
                                                          rewrite_steps:
                                                            forward_chaining:
                                                            • simplify
                                                              into:
                                                              true
                                                              expansions:
                                                              List.mem
                                                              rewrite_steps:
                                                                forward_chaining:

                                                          And finally, let's verify that the rewrite rule can indeed match as we expect:

                                                          In [8]:
                                                          verify (fun x y z -> subset x y && subset y z ==> subset x z) [@@auto]
                                                          
                                                          Out[8]:
                                                          - : 'a list -> 'a list -> 'a list -> bool = <fun>
                                                          Goal:
                                                          
                                                          subset x y && subset y z ==> subset x z.
                                                          
                                                          1 nontautological subgoal.
                                                          
                                                          Subgoal 1:
                                                          
                                                           H0. subset x y
                                                           H1. subset y z
                                                          |---------------------------------------------------------------------------
                                                           subset x z
                                                          
                                                          
                                                          Must try induction.
                                                          
                                                          The recursive terms in the conjecture suggest 3 inductions.
                                                          Subsumption and merging reduces this to 2.
                                                          
                                                          However, scheme scoring gives us a clear winner.
                                                          We shall induct according to a scheme derived from subset.
                                                          
                                                          Induction scheme:
                                                          
                                                           (not (List.mem (List.hd x) z && x <> []) ==> φ x y z)
                                                           && (x <> [] && List.mem (List.hd x) z && φ (List.tl x) y z ==> φ x y z).
                                                          
                                                          2 nontautological subgoals.
                                                          
                                                          Subgoal 1.2:
                                                          
                                                           H0. subset y z
                                                           H1. subset x y
                                                          |---------------------------------------------------------------------------
                                                           C0. subset x z
                                                           C1. List.mem (List.hd x) z && x <> []
                                                          
                                                          This simplifies, using the definition of subset to:
                                                          
                                                          Subgoal 1.2':
                                                          
                                                           H0. subset y z
                                                           H1. subset x y
                                                           H2. x <> []
                                                          |---------------------------------------------------------------------------
                                                           List.mem (List.hd x) z
                                                          
                                                          But simplification reduces this to true, using the definition of List.mem,
                                                          and the rewrite rule mem_subset.
                                                          
                                                          Subgoal 1.1:
                                                          
                                                           H0. x <> []
                                                           H1. List.mem (List.hd x) z
                                                           H2. subset (List.tl x) y && subset y z ==> subset (List.tl x) z
                                                           H3. subset y z
                                                           H4. subset x y
                                                          |---------------------------------------------------------------------------
                                                           subset x z
                                                          
                                                          But simplification reduces this to true, using the definitions of List.mem
                                                          and subset, and the rewrite rule mem_subset.
                                                          
                                                           Rules:
                                                              (:def List.mem)
                                                              (:def subset)
                                                              (:rw mem_subset)
                                                              (:induct subset)
                                                          
                                                          
                                                          Proved
                                                          proof
                                                          ground_instances:0
                                                          definitions:7
                                                          inductions:1
                                                          search_time:
                                                          0.285s
                                                          Expand
                                                          • start[0.285s, "Goal"]
                                                              subset ( :var_0: ) ( :var_1: ) && subset ( :var_1: ) ( :var_2: )
                                                              ==> subset ( :var_0: ) ( :var_2: )
                                                          • subproof

                                                            (not (subset x y) || not (subset y z)) || subset x z
                                                            • start[0.285s, "1"] (not (subset x y) || not (subset y z)) || subset x z
                                                            • induction on (functional ?)
                                                              :scheme (not (List.mem (List.hd x) z && x <> []) ==> φ x y z)
                                                                      && (x <> [] && List.mem (List.hd x) z && φ (List.tl x) y z
                                                                          ==> φ x y z)
                                                            • Split (let (_x_0 : bool) = subset x z in
                                                                     let (_x_1 : bool) = not (subset y z) in
                                                                     let (_x_2 : bool) = not (subset x y) in
                                                                     let (_x_3 : bool) = List.mem (List.hd x) z in
                                                                     let (_x_4 : bool) = x <> [] in
                                                                     let (_x_5 : sko_ty_0 list) = List.tl x in
                                                                     (((_x_0 || _x_1) || _x_2) || _x_3 && _x_4)
                                                                     && (((_x_0
                                                                           || not
                                                                              ((_x_4 && _x_3)
                                                                               && ((not (subset _x_5 y) || subset _x_5 z) || _x_1)))
                                                                          || _x_1)
                                                                         || _x_2)
                                                                     :cases [((not (subset y z) || not (subset x y)) || subset x z)
                                                                             || List.mem (List.hd x) z && x <> [];
                                                                             let (_x_0 : sko_ty_0 list) = List.tl x in
                                                                             let (_x_1 : bool) = subset y z in
                                                                             ((((not (x <> []) || not (List.mem (List.hd x) z))
                                                                                || not (subset _x_0 y && _x_1 ==> subset _x_0 z))
                                                                               || not _x_1)
                                                                              || not (subset x y))
                                                                             || subset x z])
                                                              • subproof
                                                                let (_x_0 : sko_ty_0 list) = List.tl x in let (_x_1 : bool) = subset y z in ((((not (x <> []) || not (List.mem (List.hd x) z)) || not (subset _x_0 y && _x_1 ==> subset _x_0 z)) || not _x_1) || not (subset x y)) || subset x z
                                                                • start[0.218s, "1.1"]
                                                                    let (_x_0 : sko_ty_0 list) = List.tl x in
                                                                    let (_x_1 : bool) = subset y z in
                                                                    ((((not (x <> []) || not (List.mem (List.hd x) z))
                                                                       || not (subset _x_0 y && _x_1 ==> subset _x_0 z))
                                                                      || not _x_1)
                                                                     || not (subset x y))
                                                                    || subset x z
                                                                • simplify
                                                                  into:
                                                                  true
                                                                  expansions:
                                                                  [subset, subset, List.mem]
                                                                  rewrite_steps:
                                                                  • mem_subset
                                                                  • mem_subset
                                                                  • mem_subset
                                                                  forward_chaining:
                                                                • subproof
                                                                  ((not (subset y z) || not (subset x y)) || subset x z) || List.mem (List.hd x) z && x <> []
                                                                  • start[0.218s, "1.2"]
                                                                      ((not (subset y z) || not (subset x y)) || subset x z)
                                                                      || List.mem (List.hd x) z && x <> []
                                                                  • simplify
                                                                    into:
                                                                    ((not (subset y z) || not (subset x y)) || List.mem (List.hd x) z)
                                                                    || not (x <> [])
                                                                    expansions:
                                                                    [subset, subset, subset]
                                                                    rewrite_steps:
                                                                      forward_chaining:
                                                                      • simplify
                                                                        into:
                                                                        true
                                                                        expansions:
                                                                        List.mem
                                                                        rewrite_steps:
                                                                        • mem_subset
                                                                        • mem_subset
                                                                        forward_chaining:

                                                                  Permutative Restriction

                                                                  The @@permutative annotation applies only to rewrite rules and is used to restrict the rule so that it will only apply if the instantiated rhs is lexicographically smaller than the matched lhs. This restriction can be particularly useful in order to break out of infinite rewrite loops while trying to "canonicalize" a form, for example distributing the "simplest" terms to the left.

                                                                  Let's say we want to prove the commutativity of Peano_nat.plus and install it as a rewrite rule:

                                                                  In [9]:
                                                                  lemma comm_plus x y =
                                                                    Peano_nat.(plus x y = plus y x)
                                                                   [@@auto] [@@rw] [@@permutative]
                                                                  
                                                                  Out[9]:
                                                                  val comm_plus : Peano_nat.t -> Peano_nat.t -> bool = <fun>
                                                                  Goal:
                                                                  
                                                                  Peano_nat.( = ) (Peano_nat.plus x y) (Peano_nat.plus y x).
                                                                  
                                                                  1 nontautological subgoal.
                                                                  
                                                                  Subgoal 1:
                                                                  
                                                                  |---------------------------------------------------------------------------
                                                                   Peano_nat.plus x y = Peano_nat.plus y x
                                                                  
                                                                  
                                                                  Must try induction.
                                                                  
                                                                  The recursive terms in the conjecture suggest 2 inductions.
                                                                  
                                                                  As we have backtracked, we break ties by purity.
                                                                  We shall induct according to a scheme derived from Peano_nat.plus.
                                                                  
                                                                  Induction scheme:
                                                                  
                                                                   (Is_a(Peano_nat.Z, x) ==> φ x y)
                                                                   && (not Is_a(Peano_nat.Z, x) && φ (Destruct(Peano_nat.S, 0, x)) y
                                                                       ==> φ x y).
                                                                  
                                                                  2 nontautological subgoals.
                                                                  
                                                                  Subgoal 1.2:
                                                                  
                                                                   H0. Is_a(Peano_nat.Z, x)
                                                                  |---------------------------------------------------------------------------
                                                                   Peano_nat.plus x y = Peano_nat.plus y x
                                                                  
                                                                  This simplifies, using the definition of Peano_nat.plus to:
                                                                  
                                                                  Subgoal 1.2':
                                                                  
                                                                   H0. Is_a(Peano_nat.Z, x)
                                                                  |---------------------------------------------------------------------------
                                                                   y = Peano_nat.plus y x
                                                                  
                                                                  
                                                                  Must try induction.
                                                                  
                                                                  We shall induct according to a scheme derived from Peano_nat.plus.
                                                                  
                                                                  Induction scheme:
                                                                  
                                                                   (Is_a(Peano_nat.Z, y) ==> φ x y)
                                                                   && (not Is_a(Peano_nat.Z, y) && φ x (Destruct(Peano_nat.S, 0, y))
                                                                       ==> φ x y).
                                                                  
                                                                  2 nontautological subgoals.
                                                                  
                                                                  Subgoal 1.2'.2:
                                                                  
                                                                   H0. Is_a(Peano_nat.Z, x)
                                                                   H1. Is_a(Peano_nat.Z, y)
                                                                  |---------------------------------------------------------------------------
                                                                   y = Peano_nat.plus y x
                                                                  
                                                                  But simplification reduces this to true, using the definition of
                                                                  Peano_nat.plus.
                                                                  
                                                                  Subgoal 1.2'.1:
                                                                  
                                                                   H0. not Is_a(Peano_nat.Z, y)
                                                                   H1. Is_a(Peano_nat.Z, x)
                                                                       ==> Destruct(Peano_nat.S, 0, y) =
                                                                           Peano_nat.plus (Destruct(Peano_nat.S, 0, y)) x
                                                                   H2. Is_a(Peano_nat.Z, x)
                                                                  |---------------------------------------------------------------------------
                                                                   y = Peano_nat.plus y x
                                                                  
                                                                  But simplification reduces this to true, using the definition of
                                                                  Peano_nat.plus.
                                                                  
                                                                  Subgoal 1.1:
                                                                  
                                                                   H0. not Is_a(Peano_nat.Z, x)
                                                                   H1. Peano_nat.plus (Destruct(Peano_nat.S, 0, x)) y =
                                                                       Peano_nat.plus y (Destruct(Peano_nat.S, 0, x))
                                                                  |---------------------------------------------------------------------------
                                                                   Peano_nat.plus x y = Peano_nat.plus y x
                                                                  
                                                                  This simplifies, using the definition of Peano_nat.plus to:
                                                                  
                                                                  Subgoal 1.1':
                                                                  
                                                                   H0. Peano_nat.plus (Destruct(Peano_nat.S, 0, x)) y =
                                                                       Peano_nat.plus y (Destruct(Peano_nat.S, 0, x))
                                                                  |---------------------------------------------------------------------------
                                                                   C0. Is_a(Peano_nat.Z, x)
                                                                   C1. Peano_nat.S (Peano_nat.plus (Destruct(Peano_nat.S, 0, x)) y) =
                                                                       Peano_nat.plus y x
                                                                  
                                                                  
                                                                  We can eliminate destructors by the following
                                                                  substitution:
                                                                   x -> Peano_nat.S x1
                                                                  
                                                                  This produces the modified subgoal:
                                                                  
                                                                  Subgoal 1.1'':
                                                                  
                                                                   H0. Peano_nat.plus x1 y = Peano_nat.plus y x1
                                                                  |---------------------------------------------------------------------------
                                                                   Peano_nat.S (Peano_nat.plus x1 y) = Peano_nat.plus y (Peano_nat.S x1)
                                                                  
                                                                  
                                                                  Cross-fertilizing with:
                                                                  
                                                                   Peano_nat.plus x1 y = Peano_nat.plus y x1
                                                                  
                                                                  This produces the modified subgoal:
                                                                  
                                                                  Subgoal 1.1''':
                                                                  
                                                                  |---------------------------------------------------------------------------
                                                                   Peano_nat.S (Peano_nat.plus y x1) = Peano_nat.plus y (Peano_nat.S x1)
                                                                  
                                                                  
                                                                  Must try induction.
                                                                  
                                                                  The recursive terms in the conjecture suggest 2 inductions.
                                                                  Subsumption and merging reduces this to 1.
                                                                  
                                                                  We shall induct according to a scheme derived from Peano_nat.plus.
                                                                  
                                                                  Induction scheme:
                                                                  
                                                                   (Is_a(Peano_nat.Z, y) ==> φ x1 y)
                                                                   && (not Is_a(Peano_nat.Z, y) && φ x1 (Destruct(Peano_nat.S, 0, y))
                                                                       ==> φ x1 y).
                                                                  
                                                                  2 nontautological subgoals.
                                                                  
                                                                  Subgoal 1.1'''.2:
                                                                  
                                                                   H0. Is_a(Peano_nat.Z, y)
                                                                  |---------------------------------------------------------------------------
                                                                   Peano_nat.S (Peano_nat.plus y x1) = Peano_nat.plus y (Peano_nat.S x1)
                                                                  
                                                                  But simplification reduces this to true, using the definition of
                                                                  Peano_nat.plus.
                                                                  
                                                                  Subgoal 1.1'''.1:
                                                                  
                                                                   H0. not Is_a(Peano_nat.Z, y)
                                                                   H1. Peano_nat.S (Peano_nat.plus (Destruct(Peano_nat.S, 0, y)) x1) =
                                                                       Peano_nat.plus (Destruct(Peano_nat.S, 0, y)) (Peano_nat.S x1)
                                                                  |---------------------------------------------------------------------------
                                                                   Peano_nat.S (Peano_nat.plus y x1) = Peano_nat.plus y (Peano_nat.S x1)
                                                                  
                                                                  But simplification reduces this to true, using the definition of
                                                                  Peano_nat.plus.
                                                                  
                                                                   Rules:
                                                                      (:def Peano_nat.plus)
                                                                      (:induct Peano_nat.plus)
                                                                  
                                                                  
                                                                  Proved
                                                                  proof
                                                                  ground_instances:0
                                                                  definitions:8
                                                                  inductions:3
                                                                  search_time:
                                                                  0.351s
                                                                  Expand
                                                                  • start[0.351s, "Goal"]
                                                                      Peano_nat.plus ( :var_0: ) ( :var_1: ) =
                                                                      Peano_nat.plus ( :var_1: ) ( :var_0: )
                                                                  • subproof

                                                                    Peano_nat.plus x y = Peano_nat.plus y x
                                                                    • start[0.351s, "1"] Peano_nat.plus x y = Peano_nat.plus y x
                                                                    • induction on (functional ?)
                                                                      :scheme (Is_a(Peano_nat.Z, x) ==> φ x y)
                                                                              && (not Is_a(Peano_nat.Z, x) && φ (Destruct(Peano_nat.S, 0, x)) y
                                                                                  ==> φ x y)
                                                                    • Split (let (_x_0 : bool) = not Is_a(Peano_nat.Z, x) in
                                                                             let (_x_1 : bool) = Peano_nat.plus x y = Peano_nat.plus y x in
                                                                             let (_x_2 : Peano_nat.t) = Destruct(Peano_nat.S, 0, x) in
                                                                             (_x_0 || _x_1)
                                                                             && (not (_x_0 && Peano_nat.plus _x_2 y = Peano_nat.plus y _x_2)
                                                                                 || _x_1)
                                                                             :cases [not Is_a(Peano_nat.Z, x)
                                                                                     || Peano_nat.plus x y = Peano_nat.plus y x;
                                                                                     let (_x_0 : Peano_nat.t) = Destruct(Peano_nat.S, 0, x) in
                                                                                     (Is_a(Peano_nat.Z, x)
                                                                                      || not (Peano_nat.plus _x_0 y = Peano_nat.plus y _x_0))
                                                                                     || Peano_nat.plus x y = Peano_nat.plus y x])
                                                                      • subproof
                                                                        let (_x_0 : Peano_nat.t) = Destruct(Peano_nat.S, 0, x) in (Is_a(Peano_nat.Z, x) || not (Peano_nat.plus _x_0 y = Peano_nat.plus y _x_0)) || Peano_nat.plus x y = Peano_nat.plus y x
                                                                        • start[0.317s, "1.1"]
                                                                            let (_x_0 : Peano_nat.t) = Destruct(Peano_nat.S, 0, x) in
                                                                            (Is_a(Peano_nat.Z, x)
                                                                             || not (Peano_nat.plus _x_0 y = Peano_nat.plus y _x_0))
                                                                            || Peano_nat.plus x y = Peano_nat.plus y x
                                                                        • simplify
                                                                          into:
                                                                          let (_x_0 : Peano_nat.t) = Destruct(Peano_nat.S, 0, x) in
                                                                          let (_x_1 : Peano_nat.t) = Peano_nat.plus _x_0 y in
                                                                          (Is_a(Peano_nat.Z, x) || not (_x_1 = Peano_nat.plus y _x_0))
                                                                          || Peano_nat.S _x_1 = Peano_nat.plus y x
                                                                          expansions:
                                                                          Peano_nat.plus
                                                                          rewrite_steps:
                                                                            forward_chaining:
                                                                            • Elim_destructor (:cstor Peano_nat.S :replace Peano_nat.S x1 :context [])
                                                                            • induction on (functional ?)
                                                                              :scheme (Is_a(Peano_nat.Z, y) ==> φ x1 y)
                                                                                      && (not Is_a(Peano_nat.Z, y) && φ x1 (Destruct(Peano_nat.S, 0, y))
                                                                                          ==> φ x1 y)
                                                                            • Split (let (_x_0 : bool) = not Is_a(Peano_nat.Z, y) in
                                                                                     let (_x_1 : Peano_nat.t) = Peano_nat.S x1 in
                                                                                     let (_x_2 : bool)
                                                                                         = Peano_nat.S (Peano_nat.plus y x1) = Peano_nat.plus y _x_1
                                                                                     in
                                                                                     let (_x_3 : Peano_nat.t) = Destruct(Peano_nat.S, 0, y) in
                                                                                     (_x_0 || _x_2)
                                                                                     && (not
                                                                                         (_x_0
                                                                                          && Peano_nat.S (Peano_nat.plus _x_3 x1) =
                                                                                             Peano_nat.plus _x_3 _x_1)
                                                                                         || _x_2)
                                                                                     :cases [not Is_a(Peano_nat.Z, y)
                                                                                             || Peano_nat.S (Peano_nat.plus y x1) =
                                                                                                Peano_nat.plus y (Peano_nat.S x1);
                                                                                             let (_x_0 : Peano_nat.t) = Destruct(Peano_nat.S, 0, y) in
                                                                                             let (_x_1 : Peano_nat.t) = Peano_nat.S x1 in
                                                                                             (Is_a(Peano_nat.Z, y)
                                                                                              || not
                                                                                                 (Peano_nat.S (Peano_nat.plus _x_0 x1) =
                                                                                                  Peano_nat.plus _x_0 _x_1))
                                                                                             || Peano_nat.S (Peano_nat.plus y x1) = Peano_nat.plus y _x_1])
                                                                              • Subproof
                                                                              • Subproof
                                                                          • subproof
                                                                            not Is_a(Peano_nat.Z, x) || Peano_nat.plus x y = Peano_nat.plus y x
                                                                            • start[0.317s, "1.2"]
                                                                                not Is_a(Peano_nat.Z, x) || Peano_nat.plus x y = Peano_nat.plus y x
                                                                            • simplify
                                                                              into:
                                                                              not Is_a(Peano_nat.Z, x) || y = Peano_nat.plus y x
                                                                              expansions:
                                                                              Peano_nat.plus
                                                                              rewrite_steps:
                                                                                forward_chaining:
                                                                                • induction on (functional ?)
                                                                                  :scheme (Is_a(Peano_nat.Z, y) ==> φ x y)
                                                                                          && (not Is_a(Peano_nat.Z, y) && φ x (Destruct(Peano_nat.S, 0, y))
                                                                                              ==> φ x y)
                                                                                • Split (let (_x_0 : bool) = not Is_a(Peano_nat.Z, x) in
                                                                                         let (_x_1 : bool) = not Is_a(Peano_nat.Z, y) in
                                                                                         let (_x_2 : bool) = y = Peano_nat.plus y x in
                                                                                         let (_x_3 : Peano_nat.t) = Destruct(Peano_nat.S, 0, y) in
                                                                                         ((_x_0 || _x_1) || _x_2)
                                                                                         && ((not (_x_1 && (_x_0 || _x_3 = Peano_nat.plus _x_3 x)) || _x_0)
                                                                                             || _x_2)
                                                                                         :cases [(not Is_a(Peano_nat.Z, x) || not Is_a(Peano_nat.Z, y))
                                                                                                 || y = Peano_nat.plus y x;
                                                                                                 let (_x_0 : bool) = Is_a(Peano_nat.Z, x) in
                                                                                                 let (_x_1 : Peano_nat.t) = Destruct(Peano_nat.S, 0, y) in
                                                                                                 ((Is_a(Peano_nat.Z, y)
                                                                                                   || not (_x_0 ==> _x_1 = Peano_nat.plus _x_1 x))
                                                                                                  || not _x_0)
                                                                                                 || y = Peano_nat.plus y x])
                                                                                  • Subproof
                                                                                  • Subproof

                                                                          Had we not restricted comm_plus as a permutative rule, the simplifier would have entered a rewrite loop every time it encountered a term matching plus <x> <y>, while with the permutative restriction in place, this has the effect of directing all the "simplest" terms to the left of plus, which will help with making further rewrite rules applicable and with simplification in general.

                                                                          Forward-chaining Rules

                                                                          Forward chaining is the second type of rule that Imandra allows us to register and participate automatically in proofs.

                                                                          A forward chaining rule is a theorem containing a collection of trigger terms which must include all free variables of the theorem. If Imandra can appropriately match the triggers with terms in the goal, then an instantiation of the rule is added to the context. The context of a goal is not displayed in the goal itself (i.e., when the goal is printed), but is rather used in the background to aid the simplifier in closing branches, relieving hypotheses of rewrite rules during backchaining, and so on.

                                                                          For example, let us prove the following theorem and install it as a forward-chaining rule:

                                                                          In [10]:
                                                                          lemma len_nonnegative x =
                                                                            List.length x [@trigger] >= 0
                                                                          [@@simp] [@@fc]
                                                                          
                                                                          Out[10]:
                                                                          val len_nonnegative : 'a list -> bool = <fun>
                                                                          
                                                                          Proved
                                                                          proof
                                                                          ground_instances:0
                                                                          definitions:0
                                                                          inductions:0
                                                                          search_time:
                                                                          0.027s
                                                                          details:
                                                                          Expand
                                                                          smt_stats:
                                                                          rlimit count:514
                                                                          time:0.006000
                                                                          memory:9.730000
                                                                          max memory:10.310000
                                                                          num allocs:10518351191.000000
                                                                          Expand
                                                                          • start[0.027s] List.length ( :var_0: ) >= 0
                                                                          • simplify

                                                                            into:
                                                                            true
                                                                            expansions:
                                                                            []
                                                                            rewrite_steps:
                                                                              forward_chaining:
                                                                              List.len_nonnegative
                                                                            • Unsat

                                                                            Now when Imandra encounters a term of the form List.length <term>, the formula List.length <term> >= 0 will be added to the context of the goal under focus. In other words, a forward chaining rule allows Imandra to extend the database of background logical facts it knows about a goal. These facts are made available to the simplifier, and can thus be used to enhance simplification by closing branches and relieving hypotheses of conditional rewrite rules.

                                                                            A forward chaining rule can contain multiple disjoint triggers. In this case, if either of the triggers matches, the forward chaining rule fires. For example, the following forward chaining version of the rev_len rewrite rule we added above will fire if either List.length x or List.length (List.rev x) matches.

                                                                            In [11]:
                                                                            lemma rev_len_fc x =
                                                                               List.length x [@trigger] = List.length (List.rev x) [@trigger]
                                                                            [@@auto] [@@fc]
                                                                            
                                                                            Out[11]:
                                                                            val rev_len_fc : 'a list -> bool = <fun>
                                                                            Goal:
                                                                            
                                                                            List.length x = List.length (List.rev x).
                                                                            
                                                                            1 nontautological subgoal.
                                                                            
                                                                            Subgoal 1:
                                                                            
                                                                            |---------------------------------------------------------------------------
                                                                             List.length x = List.length (List.rev x)
                                                                            
                                                                            But simplification reduces this to true, using the rewrite rule rev_len.
                                                                            
                                                                             Rules:
                                                                                (:rw rev_len)
                                                                                (:fc List.len_nonnegative)
                                                                                (:fc len_nonnegative)
                                                                            
                                                                            
                                                                            Proved
                                                                            proof
                                                                            ground_instances:0
                                                                            definitions:0
                                                                            inductions:0
                                                                            search_time:
                                                                            0.015s
                                                                            Expand
                                                                            • start[0.015s, "Goal"]
                                                                                List.length ( :var_0: ) = List.length (List.rev ( :var_0: ))
                                                                            • subproof

                                                                              List.length x = List.length (List.rev x)
                                                                              • start[0.014s, "1"] List.length x = List.length (List.rev x)
                                                                              • simplify
                                                                                into:
                                                                                true
                                                                                expansions:
                                                                                []
                                                                                rewrite_steps:
                                                                                rev_len
                                                                                forward_chaining:
                                                                                • List.len_nonnegative
                                                                                • len_nonnegative
                                                                                • List.len_nonnegative
                                                                                • len_nonnegative

                                                                            Additionally, a forward chaining rule can contain multiple conjoined triggers, forming a trigger cluster. In this case, all the triggers must match in order for the forward chaining rule to apply.

                                                                            To create a trigger cluster multiple terms must be annotated with [@trigger <x>i], where <x> is a numeric identifier common to all the triggers in the cluster. For example:

                                                                            In [12]:
                                                                            theorem subset_trans l1 l2 l3 =
                                                                              (subset l1 l2 [@trigger 0i]) && (subset l2 l3 [@trigger 0i])
                                                                              ==>
                                                                              subset l1 l3
                                                                             [@@auto] [@@forward_chaining]
                                                                            
                                                                            Out[12]:
                                                                            val subset_trans : 'a list -> 'a list -> 'a list -> bool = <fun>
                                                                            Goal:
                                                                            
                                                                            subset l1 l2 && subset l2 l3 ==> subset l1 l3.
                                                                            
                                                                            1 nontautological subgoal.
                                                                            
                                                                            Subgoal 1:
                                                                            
                                                                             H0. subset l1 l2
                                                                             H1. subset l2 l3
                                                                            |---------------------------------------------------------------------------
                                                                             subset l1 l3
                                                                            
                                                                            
                                                                            Must try induction.
                                                                            
                                                                            The recursive terms in the conjecture suggest 3 inductions.
                                                                            Subsumption and merging reduces this to 2.
                                                                            
                                                                            However, scheme scoring gives us a clear winner.
                                                                            We shall induct according to a scheme derived from subset.
                                                                            
                                                                            Induction scheme:
                                                                            
                                                                             (not (List.mem (List.hd l1) l3 && l1 <> []) ==> φ l1 l2 l3)
                                                                             && (l1 <> [] && List.mem (List.hd l1) l3 && φ (List.tl l1) l2 l3
                                                                                 ==> φ l1 l2 l3).
                                                                            
                                                                            2 nontautological subgoals.
                                                                            
                                                                            Subgoal 1.2:
                                                                            
                                                                             H0. subset l2 l3
                                                                             H1. subset l1 l2
                                                                            |---------------------------------------------------------------------------
                                                                             C0. subset l1 l3
                                                                             C1. List.mem (List.hd l1) l3 && l1 <> []
                                                                            
                                                                            This simplifies, using the definition of subset to:
                                                                            
                                                                            Subgoal 1.2':
                                                                            
                                                                             H0. subset l2 l3
                                                                             H1. subset l1 l2
                                                                             H2. l1 <> []
                                                                            |---------------------------------------------------------------------------
                                                                             List.mem (List.hd l1) l3
                                                                            
                                                                            But simplification reduces this to true, using the definition of List.mem,
                                                                            and the rewrite rule mem_subset.
                                                                            
                                                                            Subgoal 1.1:
                                                                            
                                                                             H0. l1 <> []
                                                                             H1. List.mem (List.hd l1) l3
                                                                             H2. subset (List.tl l1) l2 && subset l2 l3 ==> subset (List.tl l1) l3
                                                                             H3. subset l2 l3
                                                                             H4. subset l1 l2
                                                                            |---------------------------------------------------------------------------
                                                                             subset l1 l3
                                                                            
                                                                            But simplification reduces this to true, using the definitions of List.mem
                                                                            and subset, and the rewrite rule mem_subset.
                                                                            
                                                                             Rules:
                                                                                (:def List.mem)
                                                                                (:def subset)
                                                                                (:rw mem_subset)
                                                                                (:induct subset)
                                                                            
                                                                            
                                                                            Proved
                                                                            proof
                                                                            ground_instances:0
                                                                            definitions:9
                                                                            inductions:1
                                                                            search_time:
                                                                            0.314s
                                                                            Expand
                                                                            • start[0.314s, "Goal"]
                                                                                subset ( :var_0: ) ( :var_1: ) && subset ( :var_1: ) ( :var_2: )
                                                                                ==> subset ( :var_0: ) ( :var_2: )
                                                                            • subproof

                                                                              (not (subset l1 l2) || not (subset l2 l3)) || subset l1 l3
                                                                              • start[0.314s, "1"] (not (subset l1 l2) || not (subset l2 l3)) || subset l1 l3
                                                                              • induction on (functional ?)
                                                                                :scheme (not (List.mem (List.hd l1) l3 && l1 <> []) ==> φ l1 l2 l3)
                                                                                        && (l1 <> [] && List.mem (List.hd l1) l3 && φ (List.tl l1) l2 l3
                                                                                            ==> φ l1 l2 l3)
                                                                              • Split (let (_x_0 : bool) = subset l1 l3 in
                                                                                       let (_x_1 : bool) = not (subset l2 l3) in
                                                                                       let (_x_2 : bool) = not (subset l1 l2) in
                                                                                       let (_x_3 : bool) = List.mem (List.hd l1) l3 in
                                                                                       let (_x_4 : bool) = l1 <> [] in
                                                                                       let (_x_5 : sko_ty_0 list) = List.tl l1 in
                                                                                       (((_x_0 || _x_1) || _x_2) || _x_3 && _x_4)
                                                                                       && (((_x_0
                                                                                             || not
                                                                                                ((_x_4 && _x_3)
                                                                                                 && ((subset _x_5 l3 || not (subset _x_5 l2)) || _x_1)))
                                                                                            || _x_1)
                                                                                           || _x_2)
                                                                                       :cases [((not (subset l2 l3) || not (subset l1 l2)) || subset l1 l3)
                                                                                               || List.mem (List.hd l1) l3 && l1 <> [];
                                                                                               let (_x_0 : sko_ty_0 list) = List.tl l1 in
                                                                                               let (_x_1 : bool) = subset l2 l3 in
                                                                                               ((((not (l1 <> []) || not (List.mem (List.hd l1) l3))
                                                                                                  || not (subset _x_0 l2 && _x_1 ==> subset _x_0 l3))
                                                                                                 || not _x_1)
                                                                                                || not (subset l1 l2))
                                                                                               || subset l1 l3])
                                                                                • subproof
                                                                                  let (_x_0 : sko_ty_0 list) = List.tl l1 in let (_x_1 : bool) = subset l2 l3 in ((((not (l1 <> []) || not (List.mem (List.hd l1) l3)) || not (subset _x_0 l2 && _x_1 ==> subset _x_0 l3)) || not _x_1) || not (subset l1 l2)) || subset l1 l3
                                                                                  • start[0.246s, "1.1"]
                                                                                      let (_x_0 : sko_ty_0 list) = List.tl l1 in
                                                                                      let (_x_1 : bool) = subset l2 l3 in
                                                                                      ((((not (l1 <> []) || not (List.mem (List.hd l1) l3))
                                                                                         || not (subset _x_0 l2 && _x_1 ==> subset _x_0 l3))
                                                                                        || not _x_1)
                                                                                       || not (subset l1 l2))
                                                                                      || subset l1 l3
                                                                                  • simplify
                                                                                    into:
                                                                                    true
                                                                                    expansions:
                                                                                    [subset, List.mem, subset, subset, List.mem]
                                                                                    rewrite_steps:
                                                                                    • mem_subset
                                                                                    • mem_subset
                                                                                    • mem_subset
                                                                                    • mem_subset
                                                                                    • mem_subset
                                                                                    forward_chaining:
                                                                                  • subproof
                                                                                    ((not (subset l2 l3) || not (subset l1 l2)) || subset l1 l3) || List.mem (List.hd l1) l3 && l1 <> []
                                                                                    • start[0.246s, "1.2"]
                                                                                        ((not (subset l2 l3) || not (subset l1 l2)) || subset l1 l3)
                                                                                        || List.mem (List.hd l1) l3 && l1 <> []
                                                                                    • simplify
                                                                                      into:
                                                                                      ((not (subset l2 l3) || not (subset l1 l2)) || List.mem (List.hd l1) l3)
                                                                                      || not (l1 <> [])
                                                                                      expansions:
                                                                                      [subset, subset, subset]
                                                                                      rewrite_steps:
                                                                                        forward_chaining:
                                                                                        • simplify
                                                                                          into:
                                                                                          true
                                                                                          expansions:
                                                                                          List.mem
                                                                                          rewrite_steps:
                                                                                          • mem_subset
                                                                                          • mem_subset
                                                                                          forward_chaining:

                                                                                    This forward chaining rule will match only if a goal contains terms that match both subset l1 l2 and subset l2 l3.

                                                                                    It should be noted that Imandra supports automatic trigger selection, meaning it's often not necessary to annotate the trigger terms manually. Imandra can typically infer for us both simple triggers and trigger clusters. In fact for both the single trigger examples above, we could have omitted the trigger annotations altogether, and Imandra would have found some for us automatically.