Calculemus


Getting started

Introduction

Calculemus contains functions to perform formal deductive inference in propositional or first order logic.

This document demonstrates how to use this library.

First, we reference and open Calculemus

#r "nuget: Calculemus, 1.0.6"
open Calculemus

Custom printers

Then, it is necessary to load the module for the type of logical objects we are going to work with

open Fol

and also convenient to setup the corresponding custom printers

fsi.AddPrinter sprint_fol_formula

Entering formulas

Now, to create a formula, let's say first order, it is necessary to apply the parser to the desired string expression.

let fm = !!"exists x. forall y z. (P(y) ==> Q(z)) ==> P(x) ==> Q(x)"

Automated proving

The library contains various procedures to prove propositional tautologies or first order valid formulas. Note that, depending on the complexity of the formulas involved, these procedures may or may not be able to complete the task.

To try an automatic proof, it is enough to load the module containing it and apply the function to the desired formula.

Propositional logic

The functions for tautology checking of propositional formulas return true or false, depending if the formula is a tautology or not:

open Prop
open DP

dplbtaut !>"(p ==> q) <=> (~q ==> ~p)"
val it: bool = true

First order logic

Unlike propositional logic, for first order logic automated validity checking is only semidecidable. There are methods that, given a valid formula, can generate a sequence of inferences such that after a finite (but not definable in advance) number of steps prove the validity of the formula. However, if the formula is not valid, the same procedures will continue ad infinitum without returning any result. This is not simply a question of how well designed such procedures are but the best that can be hoped also from a theoretical point of view.

Thus, the automated validity checking functions for first order logic, in general, don't return a simple true or false but rather the information that the procedure ended successfully or, in some cases, that it was interrupted and therefore did not produce any significant results.

For example, Herbrand.davisputnam

open Herbrand

let test = davisputnam fm
0 ground instances tried; 0 items in list.
0 ground instances tried; 0 items in list.
1 ground instances tried; 3 items in list.
1 ground instances tried; 3 items in list.
2 ground instances tried; 6 items in list.
val test: int = 3

as the api reference reports, returns

The number of ground tuples generated and prints to the stdout how many ground instances were tried, if the procedure terminates (thus indicating that the formula is valid); otherwise, loops indefinitely till the memory is full.

Interactive theorem proving

The library contains also various interactive theorem proving features in the LCF styles, both with simple forward rules (starting from axioms and deriving new theorems by applying inference rules); as with goal oriented proofs based on tactics.

open Lcf
open Lcfprop
open Folderived
open Tactics

Like before is convenient to install the custom printers:

fsi.AddPrinter sprint_thm
fsi.AddPrinter sprint_goal

Forward rules

axiom_addimp !!"P(x)" !!"Q(x)"  // |- P(x) ==> Q(x) ==> P(x)
|> add_assum !!"R(x,y)"         // |- R(x,y) ==> P(x) ==> Q(x) ==> P(x)

Goals proving

let g0 = 
    !! @"
        (forall x. x <= x) /\
        (forall x y z. x <= y /\ y <= z ==> x <= z) /\
        (forall x y. f(x) <= y <=> x <= g(y))
        ==> (forall x y. x <= y ==> f(x) <= f(y)) /\
            (forall x y. x <= y ==> g(x) <= g(y))"
    |> set_goal

g0 |> print_goal
1 subgoal:
---> (forall x. x <= x) /\ (forall x y z. x <= y /\ y <= z ==> x <= z) /\ (forall x y. f(x) <= y <=> x <= g(y)) ==> (forall x y. x <= y ==> f(x) <= f(y)) /\ (forall x y. x <= y ==> g(x) <= g(y))
let g1 = imp_intro_tac "ant" g0

g1 |> print_goal
1 subgoal:
ant: (forall x. x <= x) /\ (forall x y z. x <= y /\ y <= z ==> x <= z) /\ (forall x y. f(x) <= y <=> x <= g(y))
---> (forall x y. x <= y ==> f(x) <= f(y)) /\ (forall x y. x <= y ==> g(x) <= g(y))
let g2 = conj_intro_tac g1

g2 |> print_goal
2 subgoals starting with
ant: (forall x. x <= x) /\ (forall x y z. x <= y /\ y <= z ==> x <= z) /\ (forall x y. f(x) <= y <=> x <= g(y))
---> forall x y. x <= y ==> f(x) <= f(y)
let g3 = Lib.Function.funpow 2 (auto_tac by ["ant"]) g2

g3 |> print_goal
Searching with depth limit 0
Searching with depth limit 1
Searching with depth limit 2
Searching with depth limit 3
Searching with depth limit 4
Searching with depth limit 5
Searching with depth limit 6
Searching with depth limit 0
Searching with depth limit 1
Searching with depth limit 2
Searching with depth limit 3
Searching with depth limit 4
Searching with depth limit 5
Searching with depth limit 6
No subgoals
extract_thm g3
|> print_thm
|- (forall x. x <= x) /\ (forall x y z. x <= y /\ y <= z ==> x <= z) /\ (forall x y. f(x) <= y <=> x <= g(y)) ==> (forall x y. x <= y ==> f(x) <= f(y)) /\ (forall x y. x <= y ==> g(x) <= g(y))

Declarative proof

[note("eq_sym",(!!"forall x y. x = y ==> y = x"))
    using [eq_sym (!!!"x") (!!!"y")];
note("eq_trans",(!!"forall x y z. x = y /\ y = z ==> x = z"))
    using [eq_trans (!!!"x") (!!!"y") (!!!"z")];
note("eq_cong",(!!"forall x y. x = y ==> f(x) = f(y)"))
    using [axiom_funcong "f" [(!!!"x")] [(!!!"y")]];
assume ["le",(!!"forall x y. x <= y <=> x * y = x");
        "hom",(!!"forall x y. f(x * y) = f(x) * f(y)")];
fix "x"; fix "y";
assume ["xy",(!!"x <= y")];
so have (!!"x * y = x") by ["le"];
so have (!!"f(x * y) = f(x)") by ["eq_cong"];
so have (!!"f(x) = f(x * y)") by ["eq_sym"];
so have (!!"f(x) = f(x) * f(y)") by ["eq_trans"; "hom"];
so have (!!"f(x) * f(y) = f(x)") by ["eq_sym"];
so conclude (!!"f(x) <= f(y)") by ["le"];
qed]
|> prove !! @"
        (forall x y. x <= y <=> x * y = x) /\
        (forall x y. f(x * y) = f(x) * f(y))
        ==> forall x y. x <= y ==> f(x) <= f(y)"
|> print_thm
Searching with depth limit 0
Searching with depth limit 1
Searching with depth limit 2
Searching with depth limit 3
Searching with depth limit 0
Searching with depth limit 1
Searching with depth limit 2
Searching with depth limit 0
Searching with depth limit 1
Searching with depth limit 2
Searching with depth limit 0
Searching with depth limit 1
Searching with depth limit 2
Searching with depth limit 0
Searching with depth limit 1
Searching with depth limit 2
Searching with depth limit 3
Searching with depth limit 4
Searching with depth limit 5
Searching with depth limit 0
Searching with depth limit 1
Searching with depth limit 2
Searching with depth limit 0
Searching with depth limit 1
Searching with depth limit 2
|- (forall x y. x <= y <=> x * y = x) /\ (forall x y. f(x * y) = f(x) * f(y)) ==> (forall x y. x <= y ==> f(x) <= f(y))
namespace Calculemus
module Fol from Calculemus
<summary> Basic stuff for first order logic: datatype, parsing and printing, semantics, syntax operations and substitution. </summary>
<category index="4">First order logic</category>
val fm: Formulas.formula<fol>
module Prop from Calculemus
<summary> Basic stuff for propositional logic: datatype, parsing and prettyprinting, syntax and semantics, normal forms. </summary>
<note> Although this module defines a specific type <see cref="T:Calculemus.Prop.prop" /> for primitive propositions, most of the functions defined here for propositional logic are applicable (and intended to be applied) in general to any kind of <see cref="T:Calculemus.Formulas.formula`1" /> and in particular to our specific type of first order logic formulas <see cref="T:Calculemus.FolModule.fol" />. These functions handle symbolic computation at the propositional level for any kind of <see cref="T:Calculemus.Formulas.formula`1" /> unless the signature restricts them to the <see cref="T:Calculemus.Prop.prop" /> type. <p></p> As remarked in the handbook, the defined type <see cref="T:Calculemus.Prop.prop" /> for propositional variables is fixed here just to make experimentation with some of the operations easier. </note>
<category index="3">Propositional logic</category>
module DP from Calculemus
<summary> The Davis-Putnam and the Davis-Putnam-Loveland-Logemann procedures. </summary>
<category index="3">Propositional logic</category>
val dplbtaut: fm: Formulas.formula<prop> -> bool
<summary> Tests <c>fm</c> (propositional) validity with the Davis-Putnam-Loveland-Logemann procedure with iterative implementation and backjumping and learning optimizations. </summary>
<param name="fm">The input formula.</param>
<returns> true, if the input formula is a tautology: otherwise false. </returns>
<example id="dplbtaut-1"><code lang="fsharp"> dplbtaut !&gt; "p" </code> Evaluates to <c>false</c>. </example>
<example id="dplbtaut-2"><code lang="fsharp"> dplbtaut (prime 11) </code> Evaluates to <c>true</c>. </example>
<example id="dplbtaut-3"> dplbtaut is 4X more faster than dplitaut: <code lang="fsharp"> time dplbtaut (prime 101) // Evaluates to: // CPU time (user): 36.981689 // val it: bool = true time dplitaut (prime 101) // Evaluates to: // CPU time (user): 130.045742 // val it: bool = true </code></example>
<category index="8">DPLL with backjumping and learning</category>
module Herbrand from Calculemus
<summary> Relation between first order and propositional logic; Herbrand theorem. </summary>
<category index="4">First order logic</category>
val test: int
val davisputnam: fm: Formulas.formula<fol> -> int
<summary> Tests the validity of a formula with the Davis-Putnam procedure. </summary>
<param name="fm">The input formula.</param>
<returns> The number of ground tuples generated and prints to the <c>stdout</c> how many ground instances were tried, if the procedure terminates (thus indicating that the formula is valid); otherwise, loops indefinitely till the memory is full. </returns>
<example id="davisputnam-1"><code lang="fsharp"> davisputnam !! "exists x. forall y. P(x) ==&gt; P(y)" </code> Evaluates to <c>2</c> and print to the <c>stdout</c>: <code lang="fsharp"> 0 ground instances tried; 0 items in list. 0 ground instances tried; 0 items in list. 1 ground instances tried; 2 items in list. 1 ground instances tried; 2 items in list. </code></example>
<note> The procedure loops infinitely if the initial formula is not valid and even if it is, it is not guaranteed to end. </note>
<category index="5">The Davis-Putnam procedure for first order logic</category>
module Lcf from Calculemus
<summary> LCF-style system for first order logic. </summary>
<remarks> Basic first order deductive system. <p></p> This is based on Tarski's trick for avoiding use of a substitution primitive. It seems about the simplest possible system we could use. </remarks>
<category index="7">Interactive theorem proving</category>
module Lcfprop from Calculemus
<summary> Propositional logic by inference. </summary>
<category index="7">Interactive theorem proving</category>
module Folderived from Calculemus
<summary> First-order reasoning by inference. </summary>
<category index="7">Interactive theorem proving</category>
module Tactics from Calculemus
<summary> Tactics and Mizar-style proofs. </summary>
<category index="7">Interactive theorem proving</category>
val axiom_addimp: p: Formulas.formula<fol> -> q: Formulas.formula<fol> -> thm
<summary> |- p -&gt; (q -&gt; p) </summary>
val add_assum: p: Formulas.formula<fol> -> th: thm -> thm
val g0: goals
val set_goal: p: Formulas.formula<fol> -> goals
val print_goal: g: goals -> unit
val g1: goals
val imp_intro_tac: s: string -> gls: goals -> goals
val g2: goals
val conj_intro_tac: gls: goals -> goals
val g3: goals
namespace Calculemus.Lib
module Function from Calculemus.Lib
<namespacedoc><summary> Basic library for the global implementation. </summary></namespacedoc>
<summary>Functions over predicates and functions.</summary>
<category index="1">Functions over predicates and functions</category>
val funpow: n: int -> f: ('a -> 'a) -> x: 'a -> 'a
<summary> Iterates the application of a function <c>f</c> to an argument <c>x</c> a fixed number <c>n</c> of times. </summary>
<remarks><c>funpow n f x</c> applies <c>f</c> to <c>x</c> for <c>n</c> times, giving the result <c>f (f ... (f x)...)</c> where the number of <c>f</c>'s is <c>n</c>. <c>funpow 0 f x</c> returns <c>x</c>. If <c>n</c> is negative, it is treated as <c>0</c>. It fails, if any of the <c>n</c> applications of <c>f</c> fail. </remarks>
<param name="n">The number of times to apply the function.</param>
<param name="f">The function to apply.</param>
<param name="x">The element to apply the function to.</param>
<returns> The result of applying <c>f</c> to <c>x</c> for <c>n</c> times, if <c>n</c> is &gt;= 0. Otherwise, the input argument <c>x</c> unchanged. </returns>
<example id="funpow-1"><code lang="fsharp"> 2. |&gt; funpow 2 (fun x -&gt; x ** 2.) </code> Evaluates to <c>16.</c></example>
<example id="funpow-2"><code lang="fsharp"> 2. |&gt; funpow 0 (fun x -&gt; x ** 2.) </code> Evaluates to <c>2.</c></example>
<example id="funpow-3"><code lang="fsharp"> 2. |&gt; funpow -1 (fun x -&gt; x ** 2.) </code> Evaluates to <c>2.</c></example>
<example id="funpow-4"><code lang="fsharp"> 2. |&gt; funpow 2 ((/) 0) </code> Throws <c>System.DivideByZeroException: Attempted to divide by zero.</c></example>
val auto_tac: byfn: ('a -> Formulas.formula<fol> -> goals -> thm list) -> hyps: 'a -> g: goals -> goals
val by: hyps: string list -> p: 'a -> gls: goals -> thm list
val extract_thm: gls: goals -> thm
val print_thm: th: thm -> unit
<summary> A printer for theorems </summary>
val note: l: string * p: Formulas.formula<fol> -> (('a -> Formulas.formula<fol> -> goals -> thm list) -> 'a -> goals -> goals)
val using: ths: thm list -> p: 'a -> g: goals -> thm list
val eq_sym: s: term -> t: term -> thm
val eq_trans: s: term -> t: term -> u: term -> thm
val axiom_funcong: f: string -> lefts: term list -> rights: term list -> thm
<summary> |- s1 = t1 -&gt; ... -&gt; sn = tn -&gt; f(s1, ..., sn) = f(t1, ..., tn) </summary>
val assume: lps: (string * Formulas.formula<fol>) list -> gls: goals -> goals
val fix: (string -> goals -> goals)
val so: tac: ('a -> ('b -> 'c -> goals -> thm list) -> 'd) -> arg: 'a -> byfn: ('b -> 'c -> goals -> thm list) -> 'd
val have: p: Formulas.formula<fol> -> (('a -> Formulas.formula<fol> -> goals -> thm list) -> 'a -> goals -> goals)
val conclude: p: Formulas.formula<fol> -> byfn: ('a -> Formulas.formula<fol> -> goals -> thm list) -> hyps: 'a -> gl: goals -> goals
val qed: gl: goals -> goals
val prove: p: Formulas.formula<fol> -> prf: (goals -> goals) list -> thm