The Imandra Automated Reasoning System (system description)

04/21/2020
by   Grant Olney Passmore, et al.
0

We describe Imandra, a modern computational logic theorem prover designed to bridge the gap between decision procedures such as SMT, semi-automatic inductive provers of the Boyer-Moore family like ACL2, and interactive proof assistants for typed higher-order logics. Imandra's logic is computational, based on a pure subset of OCaml in which all functions are terminating, with restrictions on types and higher-order functions that allow conjectures to be translated into multi-sorted first-order logic with theories, including arithmetic and datatypes. Imandra has novel features supporting large-scale industrial applications, including a seamless integration of bounded and unbounded verification, first-class computable counterexamples, efficiently executable models and a cloud-native architecture supporting live multiuser collaboration. The core reasoning mechanisms of Imandra are (i) a semi-complete procedure for finding models of formulas in the logic mentioned above, centered around the lazy expansion of recursive functions, and (ii) an inductive waterfall and simplifier which "lifts" many Boyer-Moore ideas to our typed higher-order setting. These mechanisms are tightly integrated and subject to many forms of user control. Imandra's user interfaces include an interactive toplevel, Jupyter notebooks and asynchronous document-based verification (in the spirit of Isabelle's Prover IDE) with VS Code.

READ FULL TEXT VIEW PDF

page 1

page 2

page 3

page 4

02/27/2019

HoCHC: a Refutationally-complete and Semantically-invariant System of Higher-order Logic Modulo Theories

We present a simple resolution proof system for higher-order constrained...
03/17/2018

Meta-F*: Metaprogramming and Tactics in an Effectful Program Verifier

Verification tools for effectful programming languages often rely on aut...
07/26/2019

Extensional Higher-Order Paramodulation in Leo-III

Leo-III is an automated theorem prover for extensional type theory with ...
09/10/2021

Reducing Higher-order Recursion Scheme Equivalence to Coinductive Higher-order Constrained Horn Clauses

Higher-order constrained Horn clauses (HoCHC) are a semantically-invaria...
09/21/2020

Synthesizing Lemmas for Inductive Reasoning

Recursively defined structures and properties about them are naturally e...
09/19/2020

Proceedings 36th International Conference on Logic Programming (Technical Communications)

Since the first conference held in Marseille in 1982, ICLP has been the ...
03/04/2019

Learning Ex Nihilo

This paper introduces, philosophically and to a degree formally, the nov...

1 Introduction

Imandra is a modern computational logic theorem prover built around a pure, higher-order subset of OCaml. Mathematical models and conjectures are written as executable OCaml programs, and Imandra may be used to reason about them, combining models, proofs and counterexamples in a unified computational environment. Imandra is designed to bridge the gap between decision procedures such as SMT [z3_prover], semi-automatic inductive provers of the Boyer-Moore family like ACL2 [boyer-moore-acl, kaufmann1996acl2], and interactive proof assistants for typed higher-order logics [gordon-melham-hol, nipkow-paulson-wenzel:2002, harrison-hollight, cade92-pvs]. Our goal is to build a friendly, easy to use system by leveraging strong automation in proof search that can also robustly provide counterexamples for false conjectures. Imandra has novel features supporting large-scale industrial applications, including a seamless integration of bounded and unbounded verification, first-class computable counterexamples, efficiently executable models and a cloud-native architecture supporting live multiuser collaboration. Imandra is already in use by major companies in the financial sector, including Goldman Sachs, Itiviti and OneChronos [passmore2017formal].

An online version may be found at https://try.imandra.ai.

Figure 1: An example Imandra session illustrating recursive definitions, computable counterexamples (CX), bounded verification (verify upto), unbounded verification with automated induction (@@auto), and higher-order instance synthesis.

2 Logic

Imandra’s logic is built on a mechanized formal semantics for a pure, higher-order subset of OCaml. Foundationally, the subset of OCaml Imandra supports (called the ‘Imandra Modelling Language’) corresponds to a (specializable) computational fragment of HOL equivalent to multi-sorted first-order logic with induction up to extended with theories of datatypes, integer and real arithmetic. Theorems are implicitly universally quantified and expressed as Boolean-valued functions. Proving a theorem establishes that the corresponding function always evaluates to true. As in PRA (Primitive Recursive Arithmetic) and Boyer-Moore logics, existential goals are expressed with explicit computable Skolem functions [goodstein-rnt, skolem-pra, boyer-moore-acl].

2.1 Definitional Principle

Users work with Imandra by incrementally extending its logical world through definitions of types, functions, modules and theorems. Each extension is governed by a definitional principle designed to maintain the consistency of Imandra’s current logical theory through a discipline of conservative extensions. Types must be proved well-founded. Functions must be proved terminating. These termination proofs play a dual role: Their structure is mined in order to instruct Imandra how to construct induction principles tailored to the recursive function being admitted when it later appears in conjectures.

Imandra’s definitional principle is built upon the ordinals up to . Ordinals are encoded as a datatype (Ordinal.t) in Imandra using a variant of Cantor normal form, and the well-foundedness of Ordinal.(<<) — the strict less-than relation on Ordinal.t values — is an axiom of Imandra’s logic.

To prove a function terminating, an ordinal-valued measure is required. Measures can often be inferred (e.g., for structural recursions) and may be specified by the user. To establish termination, all recursive calls of are collected together with their guards, and their arguments must be proved to conditionally map to strictly smaller ordinals via the measure. Imandra provides a shorthand annotation for specifying lexicographic orders (@@adm), and explicit measure functions may be given using the @@measure annotation.

Example 1 (Ackermann)

We can define the Ackermann function and prove it terminating with the attribute [@@adm m,n] which maps ack m n to the ordinal . Alternatively, we could use [@@measure Ordinal.(pair (of_int m) (of_int n))] to give an explicit measure via helper functions in Imandra’s Ordinal module.

let rec ack m n =
  if m <= 0 then n + 1 else if n <= 0 then ack (m-1) 1 else ack (m-1) (ack m (n-1))
[@@adm m,n]
Example 2

Here we have a naive version of the classic

left-pad

function [leftpad], where termination depends on both arguments in a non-lexicographic manner:

let rec left_pad c n xs =
  if List.length xs >= n then xs else left_pad c n (c :: xs)
[@@measure Ordinal.of_int (n - List.length xs)]

2.2 Lifting, Specialization and Monomorphization

Imandra definitions may be polymorphic and higher-order. However, once Imandra is tasked with determining the truth value of a conjecture, the goal and its transitive dependencies are transformed into a family of ground, monomorphic first-order (recursive) definitions. These transformations include lambda lifting, specialization and monomorphization. Imandra’s supported fragment of OCaml is designed so that all admitted definitions may be transformed in this way.

Example 3

To prove the following higher-order theorem

theorem same_len l =
  List.length (List.map (fun x -> x+1) l) = List.length l

we obtain a set of lower level definitions, where the anonymous function was lifted, the type list was monomorphised, and map and length were specialised:

type int_list = Nil_int | Cons_int of int * int_list
let rec length_int = function
  | Nil_int -> 0
  | Cons_int (_, tl) -> 1 + length_int tl
let map_lambda0 x = x+1
let rec map1 = function
  | Nil_int -> Nil_int
  | Cons_int (x, tl) -> Cons_int (map_lambda0 x, map1 tl)
theorem same_len (l:int_list) : bool =
  length_int (map1 l) = length_int l

3 Unrolling of Recursive Functions

A major feature of Imandra is its ability to automatically search for proofs and counterexamples in a logic with recursive functions. When a counterexample is found, it is reflected as a first-class value in Imandra’s runtime and can be directly computed with and run through the model being analysed. In fact, the statement verify (fun x -> …) does not try any inductive proving unless requested; the default strategy is recursive function unrolling for a fixed number of steps, a form of bounded symbolic model-checking.

Our core unrolling algorithm is similar in spirit to the work of Suter et al. [suter2011satisfiability] but with crucial strategic differences. In essence, Imandra uses the assumption mechanism of SMT to block all Boolean assignments that involve the evaluation of a (currently) uninterpreted ground instance of a recursive function. A refinement loop, based on extraction of unsat-cores from this set of assumptions, then expands (interprets) the function calls one by one until a model is found, an empty unsat-core is obtained, or a maximal number of steps is reached.

Definition 1 (Function template)

A function template for is a set of tuples such that the body of contains a call to under the path .

Example 4

  • has as template


  • has as template

We use what we call reachability literals to prevent the SMT solver from picking assignments that use function calls that are not expanded yet. A reachability literal is a Boolean atom that doesn’t appear in the original problem, and that we associate to a given function call regardless of where it occurs. This is to be contrasted with Suter et al.’s notion of control literals associated with individual occurrences of function calls within the expanded body of another function call. We denote by the unique reachability literal for .

1def calls_of_term(t: Term):
2  return 
3
4def subcalls_of_call(: Term, expanded: Set[Term]):
5  return 
6
7def unroll(goal: Formula) -> SAT|UNSAT:
8  q = calls_of_term(goal), expanded = 
9  F = goal 
10  while True:
11    is_sat, unsat_core = check_sat(F, assume=)  
12    if is_sat == SAT: return SAT
13    else if is_sat == UNSAT:
14      if unsat_core == : return UNSAT
15       = pick_from(unsat_core)  # next call to expand   
16      expanded = 
17       = subcalls_of_call(, expanded)
18      q = q 
19      F = F 
Figure 2: Unrolling algorithm

The main search loop is presented in Figure 2, where is the body of (i.e. ) and means is a proper subterm of . We start with initialized to the original goal, and the queue containing function calls in the goal (computed by calls_of_term). Each iteration of the loop starts by checking validity under the assumption that all reachability literals in are false (line 2). If no model is found, we pick an unexpanded function call from the unsat core (line 2). Selection must be fair: all function calls must eventually be picked.

To expand , the corresponding reachability literal becomes true, we instantiate the body of on , and use subcalls_of_call to compute the set of subcalls along with their control path within (using ’s template). For each occurring under path inside , we need to block models that would make valid until gets expanded. The assertions delegate to SMT the work of tracking which paths are forbidden. This way, expanding one function call might lead to many paths becoming “unlocked” at once.

4 Induction

Imandra has extensive support for automated induction built principally around Imandra’s inductive waterfall111More details about Imandra’s waterfall and rule classes may be found in our online documentation at https://docs.imandra.ai.. This combines techniques such as symbolic execution, lemma-based conditional rewriting, forward-chaining, generalization and the automatic synthesis of goal-specific induction principles. Induction principle synthesis depends upon data computed about a function’s termination obtained when it was admitted via our definitional principle. Imandra’s waterfall is deeply inspired by the pioneering work of Boyer-Moore [kaufmann1996acl2, boyer-moore-acl], and is in many ways a “lifting” of the Boyer-Moore waterfall to our typed, higher-order setting.

Figure 3: Imandra’s inductive waterfall

Imandra’s waterfall contains a simplifier which automatically makes use of previously proved lemmas. Once proved, lemmas may be installed as rewrite, forward-chaining, elimination or generalization rules. Imandra gives users feedback in order to help them design efficient collections of rules. With a good collection of rules (especially rewrite rules), it is hoped that “most” useful theorems over a given domain will be provable by simplification alone, and induction will only be applied as a last resort. In these cases, the subsequent waterfall moves are designed to prepare the simplified conjecture for induction (via, e.g., generalization) before goal-specific induction principles are synthesized.

Imandra’s inductive waterfall plays an important role in what we believe to be a robust verification strategy for applying Imandra to real-world systems. Recall that all Imandra goals may be subjected to bounded verification via unrolling (cf. Sec 3

). In practice, we almost always attack a goal by unrolling first, attempting to verify it up to a bound before we consider trying to prove it by induction. Typically, for real-world systems, models and conjectures will have flaws, and unrolling will uncover many counterexamples, confusions and mistakes. As all models are executable and all counterexamples are reflected in Imandra’s runtime, they can be directly run through models facilitating rapid investigation. It is typically only after iterating on models and conjectures until all (bounded) counterexamples have been eliminated that we consider trying to prove them by induction. Imandra’s support for counterexamples also plays another important role: as a filter on heuristic waterfall steps such as generalization.

5 Architecture and User Interfaces

Imandra is developed in OCaml and integrates with its compiler libraries. Arbitrary OCaml code may interact with Imandra models and counterexamples through the use of Imandra’s program mode and reflection machinery. Imandra integrates with Z3 [z3_prover] for checking satisfiability of various classes of ground formulas. Imandra has a client-server architecture: (i) the client parses and executes models with an integrated toplevel; (ii) the server, typically in the cloud, performs all reasoning. Imandra’s user interfaces include:

Command line

for power users, with tab-completion, hints, and colorful messages. This interface is similar in some ways to OCaml’s utop.

Jupyter notebooks

hosted online or via local installation through Docker [ipython-2007]. This presents Imandra through interactive notebooks in the browser.

VSCode plugin

where documents are checked on the fly and errors are underlined in the spirit of Isabelle’s Prover IDE [wenzel-prover-ide].

6 Conclusion

Imandra is an industrial-strength reasoning system combining ideas from SMT, Boyer-Moore inductive provers, and ITPs for typed higher-order logics. Imandra delivers an extremely high degree of automation and has novel techniques such as reflected computable counterexamples that we now believe are indispensible for the effective industrial application of automated reasoning. We are encouraged by Imandra’s success in mainstream finance [passmore2017formal], and share a deep conviction that further advances in automation and UI — driven in large part by meeting the demands of industrial users — will lead to a (near-term) future in which automated reasoning is a widely adopted foundational technology.

References