Model Finders and Counterexample Generators
The main topic of this chapter is model and counterexample generation: given a proposition in propositional logic, find models if there are any, and similarly find counterexamples if there are any.
We'll begin by generalizing some patterns we've been seeing in functions that handle lists. The first section introduces and illustrates the use of List map, foldr, and filter functions.
Second, we'll see that with these functions in hand and a better understanding of recursion, we can improve our propositional logic satisfiability checking functions.
Finally, we will introduce the concept of a model finder for expressions in propositional logic, also known as a SAT solver, and see how that idea can also provide a way to generate counterexamples to propositions that are not always true.
Higher-Order Functions On Lists
List.map
The List.map function, converts a list of α terms, into a list of corresponding β values by applying a given function, f : α → β, to each α in turn. E.g., map (λ (s : String) => s.length) ["Hello", "Lean"] returns [5, 4].
Here's the type of List.map in the Lean libraries.
@List.mapList.map: {α : Type u_1} → {β : Type u_2} → (α → β) → List α → List βList.map (λList.map: {α β : Type} → (α → β) → List α → List βn =>n: Natn +n: Nat1) [1: Nat0,0: Nat1,1: Nat2,2: Nat3,3: Nat4]4: NatList.mapList.map: {α β : Type} → (α → β) → List α → List βString.length [String.length: String → Nat"I","I": String"Love","Love": String"Logic!"]"Logic!": String
List.foldr
The foldr function converts a binary operation along with its identity element into a generalized n-ary operation that takes any number of arguments, in a list. As an example, our reduce_or function, taking a list of Bools and reducing it to just one, indicating whether the list has at least one true value, is simply an n-ary extension of or. Applying such an n-ary operation on no arguments (an empty list) simply returns the identity element (base case value).
@List.foldrList.foldr: {α : Type u_1} → {β : Type u_2} → (α → β → β) → β → List α → βList.foldrList.foldr: {α β : Type} → (α → β → β) → β → List α → βNat.addNat.add: Nat → Nat → Nat0 [0: Nat1,1: Nat2,2: Nat3,3: Nat4,4: Nat5] -- expect 155: NatList.foldrList.foldr: {α β : Type} → (α → β → β) → β → List α → βNat.mulNat.mul: Nat → Nat → Nat0 [0: Nat1,1: Nat2,2: Nat3,3: Nat4,4: Nat5] -- expect 120, oops!5: NatList.foldrList.foldr: {α β : Type} → (α → β → β) → β → List α → βNat.mulNat.mul: Nat → Nat → Nat1 [1: Nat1,1: Nat2,2: Nat3,3: Nat4,4: Nat5] -- expect 120, ah!5: Nat
List.filter
The List.filter function takes a list, l of α values, and an α → Bool predicate function that indicates whether a given α value has a particular property, and returns the sublist of α values in l that have property, p.
@List.filterList.filter: {α : Type u_1} → (α → Bool) → List α → List αList.filter (λ (List.filter: {α : Type} → (α → Bool) → List α → List αn :n: NatNat) =>Nat: Typen%n: Nat2 ==2: Nat0) [0: Nat0,0: Nat1,1: Nat2,2: Nat3,3: Nat4,4: Nat5,5: Nat6,6: Nat7]7: Nat
Propositional Logic: The Next Generation
Here again is our definition of the syntax and semantics of propositional logic, now supporting all the connectives, including ⇔. There's little additional information here to review, so you may skim this section quickly.
Syntax
structure var: Type
var : Type: Type 1
Type := (n: var → Nat
n: Nat: Type
Nat)
inductive unary_op: Type
unary_op : Type: Type 1
Type | not: unary_op
not
inductive binary_op: Type
binary_op : Type: Type 1
Type
| and: binary_op
and
| or: binary_op
or
| imp: binary_op
imp
| iff: binary_op
iff
inductive Expr: Type
Expr : Type: Type 1
Type
| true_exp: Expr
true_exp
| false_exp: Expr
false_exp
| var_exp: var → Expr
var_exp (v: var
v : var: Type
var)
| un_exp: unary_op → Expr → Expr
un_exp (op: unary_op
op : unary_op: Type
unary_op) (e: Expr
e : Expr: Type
Expr)
| bin_exp: binary_op → Expr → Expr → Expr
bin_exp (op: binary_op
op : binary_op: Type
binary_op) (e1: Expr
e1 e2: Expr
e2 : Expr: Type
Expr)
notation "{"v: Lean.TSyntax `term
v"}" => Expr.var_exp: var → Expr
Expr.var_exp v: Lean.TSyntax `term
v
prefix:max "¬" => Expr.un_exp: unary_op → Expr → Expr
Expr.un_exp unary_op.not: unary_op
unary_op.not
infixr:35 " ∧ " => Expr.bin_exp: binary_op → Expr → Expr → Expr
Expr.bin_exp binary_op.and: binary_op
binary_op.and
infixr:30 " ∨ " => Expr.bin_exp: binary_op → Expr → Expr → Expr
Expr.bin_exp binary_op.or: binary_op
binary_op.or
infixr:25 " ⇒ " => Expr.bin_exp: binary_op → Expr → Expr → Expr
Expr.bin_exp binary_op.imp: binary_op
binary_op.imp
infixr:20 " ⇔ " => Expr.bin_exp: binary_op → Expr → Expr → Expr
Expr.bin_exp binary_op.iff: binary_op
binary_op.iff
notation " ⊤ " => Expr.top_exp: Type
Expr.top_exp
notation " ⊥ " => Expr.bot_exp: Type
Expr.bot_exp
Semantics
def eval_un_op: unary_op → Bool → Bool
eval_un_op : unary_op: Type
unary_op → (Bool: Type
Bool → Bool: Type
Bool)
| unary_op.not: unary_op
unary_op.not => not: Bool → Bool
not
def implies: Bool → Bool → Bool
implies : Bool: Type
Bool → Bool: Type
Bool → Bool: Type
Bool
| true: Bool
true, false: Bool
false => false: Bool
false
| _, _ => true: Bool
true
def iff: Bool → Bool → Bool
iff : Bool: Type
Bool → Bool: Type
Bool → Bool: Type
Bool
| true: Bool
true, true: Bool
true => true: Bool
true
| false: Bool
false, false: Bool
false => true: Bool
true
| _, _ => false: Bool
false
def eval_bin_op: binary_op → Bool → Bool → Bool
eval_bin_op : binary_op: Type
binary_op → (Bool: Type
Bool → Bool: Type
Bool → Bool: Type
Bool)
| binary_op.and: binary_op
binary_op.and => and: Bool → Bool → Bool
and
| binary_op.or: binary_op
binary_op.or => or: Bool → Bool → Bool
or
| binary_op.imp: binary_op
binary_op.imp => implies: Bool → Bool → Bool
implies
| binary_op.iff: binary_op
binary_op.iff => iff: Bool → Bool → Bool
iff
def Interp: Type
Interp := var: Type
var → Bool: Type
Bool
-- main semantic evaluation function
def eval_expr: Expr → Interp → Bool
eval_expr : Expr: Type
Expr → Interp: Type
Interp → Bool: Type
Bool
| Expr.true_exp: Expr
Expr.true_exp, _ => true: Bool
true
| Expr.false_exp: Expr
Expr.false_exp, _ => false: Bool
false
| (Expr.var_exp: var → Expr
Expr.var_exp v: var
v), i: Interp
i => i: Interp
i v: var
v
| (Expr.un_exp: unary_op → Expr → Expr
Expr.un_exp op: unary_op
op e: Expr
e), i: Interp
i => (eval_un_op: unary_op → Bool → Bool
eval_un_op op: unary_op
op) (eval_expr: Expr → Interp → Bool
eval_expr e: Expr
e i: Interp
i)
| (Expr.bin_exp: binary_op → Expr → Expr → Expr
Expr.bin_exp op: binary_op
op e1: Expr
e1 e2: Expr
e2), i: Interp
i => (eval_bin_op: binary_op → Bool → Bool → Bool
eval_bin_op op: binary_op
op) (eval_expr: Expr → Interp → Bool
eval_expr e1: Expr
e1 i: Interp
i) (eval_expr: Expr → Interp → Bool
eval_expr e2: Expr
e2 i: Interp
i)
Satisfiability Properties
Next we present an improved version of or code for checking of expressions for validity, satisfiability, and unsatisfiability.
One significant enhancement, suggested by Mikhail, is replacement of our rather ponderous approach to generating the input sides of truth tables with a single recursive function. We also use our new map, filter, and reduce functions to replace numerous specialized instances.
Truth Table Input Rows
We had previousl developed an explanatory but ponderous approach to generating a list of of all lists of boolean input rows. The idea was to treat the each (input) row as a binary expansion of the row index (a lit of bit), convert bits to bools, and add padding on the left. Mikhail noticed that we could replace it all with a single recursive function.
Exercise: Study this function definition until you understand fully how it works. Along the way, use it to generate a few outputs then inspect them to be sure you know what the function does. Figure out the recursion works to the point you're confident you could write the code yourself. To test yourself, erase the implementation then write it again.
-- Mikhail defmake_bool_lists:make_bool_lists: Nat → List (List Bool)Nat →Nat: TypeList (List: Type → TypeListList: Type → TypeBool) |Bool: Type0 => [0: Nat[]] |[]: List Booln' + 1 => (n': NatList.map (funList.map: {α β : Type} → (α → β) → List α → List βL =>L: List Boolfalse::false: BoolL) (L: List Boolmake_bool_listsmake_bool_lists: Nat → List (List Bool)n')) ++ (n': NatList.map (funList.map: {α β : Type} → (α → β) → List α → List βL =>L: List Booltrue::true: BoolL) (L: List Boolmake_bool_listsmake_bool_lists: Nat → List (List Bool)n')) -- REVIEWn': Natmake_bool_listsmake_bool_lists: Nat → List (List Bool)00: Natmake_bool_listsmake_bool_lists: Nat → List (List Bool)11: Natmake_bool_listsmake_bool_lists: Nat → List (List Bool)22: Natmake_bool_listsmake_bool_lists: Nat → List (List Bool)33: Nat
Bool List to/from Interpretation Function
Given a list of n Boolean values, [b₀, ..., bₙ₋₁], we have to be able to turn it into an interpretation function, so that we can evaluate expressions with that interpretation using eval_expr. The resulting function will be { v₀ ↦ b₀, ..., vₙ₋₁ ↦ bₙ₋₁}, where each vᵢ means (var.mk i).
Our approach will be to start with a given interpretation (such as the all false interpretation) and then for each bᵢ in the list of Booleans, we will iteratively override the function so that when it's used to evaluate the value of vᵢ it will return bᵢ.
-- Function override
def override: Interp → var → Bool → Interp
override : Interp: Type
Interp → var: Type
var → Bool: Type
Bool → Interp: Type
Interp
| old_interp: Interp
old_interp, var: _root_.var
var, new_val: Bool
new_val =>
(λ v: _root_.var
v => if (v: _root_.var
v.n: _root_.var → Nat
n == var: _root_.var
var.n: _root_.var → Nat
n) -- when applied to var
then new_val: Bool
new_val -- return new value
else old_interp: Interp
old_interp v: _root_.var
v) -- else retur old value
-- Bool list to interpretation function
-- Uses list length as number of variables to associate with bools
def bool_list_to_interp: List Bool → Interp
bool_list_to_interp : List: Type → Type
List Bool: Type
Bool → Interp: Type
Interp
| l: List Bool
l => bools_to_interp_helper: Nat → List Bool → Interp
bools_to_interp_helper l: List Bool
l.length: {α : Type} → List α → Nat
length l: List Bool
l
where bools_to_interp_helper: Nat → List Bool → Interp
bools_to_interp_helper : (vars: Nat
vars : Nat: Type
Nat) → (vals: List Bool
vals : List: Type → Type
List Bool: Type
Bool) → Interp: Type
Interp
| _, [] => (λ _: var
_ => false: Bool
false)
| vars: Nat
vars, h: Bool
h::t: List Bool
t =>
let len: Nat
len := (h: Bool
h::t: List Bool
t).length: {α : Type} → List α → Nat
length
-- override recursively computed interp mapping variable to head bool
override: Interp → var → Bool → Interp
override (bools_to_interp_helper: Nat → List Bool → Interp
bools_to_interp_helper vars: Nat
vars t: List Bool
t) (var.mk: Nat → var
var.mk (vars: Nat
vars - len: Nat
len)) h: Bool
h
To think about: smells like some kind of fold. Iteratively combine bool at head of list with given interpretation by overriding at with the h ead value for the which? variable
In addition to converting Boolean lists to interpretations it will also be useful to turn interpretations back into Boolean lists, where the length of each list is typically fixed at a specified number of variables (all variables beyond a certain point being irrelevant to a given expression).
-- From number of variables, interpretation, to list of Bools
def interp_to_list_bool: Nat → Interp → List Bool
interp_to_list_bool : (num_vars: Nat
num_vars : Nat: Type
Nat) → Interp: Type
Interp → List: Type → Type
List Bool: Type
Bool
| 0: Nat
0, _ => []: List Bool
[]
| (n': Nat
n' + 1) , i: Interp
i => interp_to_list_bool: Nat → Interp → List Bool
interp_to_list_bool n': Nat
n' i: Interp
i ++ [(i: Interp
i (var.mk: Nat → var
var.mk n': Nat
n'))]
-- From number of variables, list of interpretations, to list of Bool lists
def interps_to_list_bool_lists: Nat → List Interp → List (List Bool)
interps_to_list_bool_lists : Nat: Type
Nat → List: Type → Type
List Interp: Type
Interp → List: Type → Type
List (List: Type → Type
List Bool: Type
Bool)
| vars: Nat
vars, is: List Interp
is => List.map: {α β : Type} → (α → β) → List α → List β
List.map (interp_to_list_bool: Nat → Interp → List Bool
interp_to_list_bool vars: Nat
vars) is: List Interp
is
Maximum Variable Index in Expression
We will consider the number of variables to include in a truth table for a given expression to be the one plus the zero-based index of the highest-indexed variable in any given expression. For example, if an expression uses only v₉ explicitly we will consider it to use all ten variables, v₀ to v₉ inclusive.
def max_variable_index: Expr → Nat
max_variable_index : Expr: Type
Expr → Nat: Type
Nat
| Expr.true_exp: Expr
Expr.true_exp => 0: Nat
0
| Expr.false_exp: Expr
Expr.false_exp => 0: Nat
0
| Expr.var_exp: var → Expr
Expr.var_exp (var.mk: Nat → var
var.mk i: Nat
i) => i: Nat
i
| Expr.un_exp: unary_op → Expr → Expr
Expr.un_exp _ e: Expr
e => max_variable_index: Expr → Nat
max_variable_index e: Expr
e
| Expr.bin_exp: binary_op → Expr → Expr → Expr
Expr.bin_exp _ e1: Expr
e1 e2: Expr
e2 => max: {α : Type} → [self : Max α] → α → α → α
max (max_variable_index: Expr → Nat
max_variable_index e1: Expr
e1) (max_variable_index: Expr → Nat
max_variable_index e2: Expr
e2)
Number of Variables in Expression
We take the number of variables in an expression to be the index of the highest-indexed variable in the expression, plus one to account for the usual zero-based indexing.
def num_vars: Expr → Nat
num_vars : Expr: Type
Expr → Nat: Type
Nat := λ e: Expr
e => max_variable_index: Expr → Nat
max_variable_index e: Expr
e + 1: Nat
1
From Expression to List of Interpretations
Given an expression, we compute the number, n, of variables it uses then we generate a list of all 2^n interpretation functions for it. Note that we just eliminate a whole raft of ponderous code with a single clever recursive function, thanks to Mikhail.
-- Number of variables to interpretations list using Mikhail's code
def mk_interps_vars: Nat → List Interp
mk_interps_vars : Nat: Type
Nat → List: Type → Type
List Interp: Type
Interp
| n: Nat
n => List.map: {α β : Type} → (α → β) → List α → List β
List.map bool_list_to_interp: List Bool → Interp
bool_list_to_interp (make_bool_lists: Nat → List (List Bool)
make_bool_lists n: Nat
n)
-- From expression to a list of interpretations for it
def mk_interps_expr: Expr → List Interp
mk_interps_expr : Expr: Type
Expr → List: Type → Type
List Interp: Type
Interp
| e: Expr
e => mk_interps_vars: Nat → List Interp
mk_interps_vars (num_vars: Expr → Nat
num_vars e: Expr
e)
Truth Table Outputs
Exercise: Replace the following definition of truth_table_outputs with a single line of code using List.map. The resulting list of Boolean values should reflect the values of the given expression under each interpretation in the list of interpretations. You will use map to convert a list of interpretations (for e) into a list of Boolean values.
-- The column of truth table outputs for e
def truth_table_outputs': Expr → List Bool
truth_table_outputs' : Expr: Type
Expr → List: Type → Type
List Bool: Type
Bool
| e: Expr
e => eval_expr_over_interps: Expr → List Interp → List Bool
eval_expr_over_interps e: Expr
e (mk_interps_vars: Nat → List Interp
mk_interps_vars (num_vars: Expr → Nat
num_vars e: Expr
e))
where eval_expr_over_interps: Expr → List Interp → List Bool
eval_expr_over_interps : Expr: Type
Expr → List: Type → Type
List Interp: Type
Interp → List: Type → Type
List Bool: Type
Bool
| _, [] => []: List Bool
[]
| e: Expr
e, h: Interp
h::t: List Interp
t => eval_expr_over_interps: Expr → List Interp → List Bool
eval_expr_over_interps e: Expr
e t: List Interp
t ++ [eval_expr: Expr → Interp → Bool
eval_expr e: Expr
e h: Interp
h]
-- REVIEW
def truth_table_outputs: Expr → List Bool
truth_table_outputs : Expr: Type
Expr → List: Type → Type
List Bool: Type
Bool
| e: Expr
e => List.map: {α β : Type} → (α → β) → List α → List β
List.map (eval_expr: Expr → Interp → Bool
eval_expr e: Expr
e) (mk_interps_vars: Nat → List Interp
mk_interps_vars (num_vars: Expr → Nat
num_vars e: Expr
e))
-- | e => eval_expr_over_interps e (mk_interps_vars (num_vars e))
-- where eval_expr_over_interps : Expr → List Interp → List Bool
-- | _, [] => []
-- | e, h::t => eval_expr_over_interps e t ++ [eval_expr e h]
n-ary And and Or functions
def reduce_or: List Bool → Bool
reduce_or := List.foldr: {α β : Type} → (α → β → β) → β → List α → β
List.foldr or: Bool → Bool → Bool
or false: Bool
false
def reduce_and: List Bool → Bool
reduce_and := List.foldr: {α β : Type} → (α → β → β) → β → List α → β
List.foldr and: Bool → Bool → Bool
and true: Bool
true
Satisfiability-Related Properties of Expressions
Finally we can define the API we want to provide for checking arbitrary propositional logic expressions for their satisfiability properties: for being satisfiable, valid, or unsatisfiable.
def is_sat: Expr → Bool
is_sat (e: Expr
e : Expr: Type
Expr) : Bool: Type
Bool := reduce_or: List Bool → Bool
reduce_or (truth_table_outputs: Expr → List Bool
truth_table_outputs e: Expr
e)
def is_valid: Expr → Bool
is_valid (e: Expr
e : Expr: Type
Expr) : Bool: Type
Bool := reduce_and: List Bool → Bool
reduce_and (truth_table_outputs: Expr → List Bool
truth_table_outputs e: Expr
e)
def is_unsat: Expr → Bool
is_unsat (e: Expr
e : Expr: Type
Expr) : Bool: Type
Bool := not: Bool → Bool
not (is_sat: Expr → Bool
is_sat e: Expr
e)
Models and Counterexamples
We now turn to the third and last major topic in this chapter. Given a propositional logic expression, e, a model finder finds a model of e if there is one. It returns either a model of e if there is one or a signal that there isn't one.
To return either a model if there is one or a signal that there isn't one, we could use a sum type: either a model on the left or Unit.unit on the right to signal that there is no model.
def SomeInterpOrNone: Type
SomeInterpOrNone := Interp: Type
Interp ⊕ Unit: Type
Unit -- NB: this is a *type*
A better solution is to use the standard polymorphic Option type. Its two constructors are some α and none. The first is used to construct an option carrying a value, (a : α). The second is used (in lieu of Sum.inr Unit.unit) to indicate that there's no value to provide.
def o1: Option Bool
o1 := Option.some: {α : Type} → α → Option α
Option.some true: Bool
true
def o2: Option Bool
o2 := @Option.none: {α : Type} → Option α
Option.none Bool: Type
Bool -- need to make type argument explicit
Model Finder
Here's the main API for our model finder. Given an expression, e, return some m, m a model of e if there is one, or none if not.
@Option defOption: Type u_1 → Type u_1find_model :find_model: Expr → Option InterpExpr →Expr: TypeOptionOption: Type → TypeInterp |Interp: Typee => lete: Exprinterps :=interps: List Interpmk_interps_exprmk_interps_expr: Expr → List Interpee: Exprfind_model_helperfind_model_helper: List Interp → Expr → Option Interpinterpsinterps: List Interpe wheree: Exprfind_model_helper :find_model_helper: List Interp → Expr → Option InterpListList: Type → TypeInterp →Interp: TypeExpr →Expr: TypeOptionOption: Type → TypeInterp | [], _ =>Interp: Typenone |none: {α : Type} → Option αh::h: Interpt,t: List Interpe => if (e: Expreval_expreval_expr: Expr → Interp → Boolee: Exprh) thenh: Interpsomesome: {α : Type} → α → Option αh elseh: Interpfind_model_helperfind_model_helper: List Interp → Expr → Option Interptt: List Interpe -- REVIEW -- Utility: convert a "Option model" into a list of Bools, empty for none defe: Exprsome_model_or_none_to_bools :some_model_or_none_to_bools: SomeInterpOrNone → Nat → List BoolSomeInterpOrNone → (SomeInterpOrNone: Typenum_vars :num_vars: NatNat) →Nat: TypeListList: Type → TypeBool |Bool: TypeSum.inlSum.inl: {α : Type ?u.57773} → {β : Type ?u.57772} → α → α ⊕ βi,i: Interpn =>n: Natinterp_to_list_boolinterp_to_list_bool: Nat → Interp → List Boolnn: Nati |i: InterpSum.inr _, _ =>Sum.inr: {α : Type ?u.57803} → {β : Type ?u.57802} → β → α ⊕ β[][]: List Bool
Model Enumerator
The main API of our model enumeration section is the function, find_models, that takes an expression, e,, and returns a list of all models of e. It does so by generating an exhaustive list of all interpretations then filtering them to save those that make e true.
def find_models: Expr → List Interp
find_models (e: Expr
e : Expr: Type
Expr) :=
List.filter: {α : Type} → (α → Bool) → List α → List α
List.filter -- filter on
(λ i: Interp
i => eval_expr: Expr → Interp → Bool
eval_expr e: Expr
e i: Interp
i) -- i makes e true
(mk_interps_expr: Expr → List Interp
mk_interps_expr e: Expr
e) -- over all interps
-- Render models of e : Expr as List of Bool Lists (num_vars e long)
def find_models_bool: Expr → List (List Bool)
find_models_bool : Expr: Type
Expr → List: Type → Type
List (List: Type → Type
List Bool: Type
Bool)
| e: Expr
e => interps_to_list_bool_lists: Nat → List Interp → List (List Bool)
interps_to_list_bool_lists (num_vars: Expr → Nat
num_vars e: Expr
e) (find_models: Expr → List Interp
find_models e: Expr
e)
Model Counter
A model counter takes an expression and tells you how many models it has. From a list of all models, it's obviously easy to derive the number of models: it's just the length of the list. Note that we use function composition to define our model counting function.
def count_models: Expr → Nat
count_models := List.length: {α : Type} → List α → Nat
List.length ∘ find_models: Expr → List Interp
find_models
Counter-Example Generator
More interesting, and oft used, is counter-example finding. When we say we want to disprove a proposition, mean is that we want to show that it's not valid: that there's at least one interpretation that makes the proposition is false. If that is so, then it makes the negation of the proposition true. Counterexamples, if there are any, are models of the negation of a propositio; and we now know how to find such models using our model finder. Defining a counter-example finder is thus trivial.
def find_counterexamples: Expr → List Interp
find_counterexamples (e: Expr
e : Expr: Type
Expr) := find_models: Expr → List Interp
find_models (¬e: Expr
e)
def find_counterexamples_bool: Expr → List (List Bool)
find_counterexamples_bool : Expr: Type
Expr → List: Type → Type
List (List: Type → Type
List Bool: Type
Bool)
| e: Expr
e => interps_to_list_bool_lists: Nat → List Interp → List (List Bool)
interps_to_list_bool_lists (num_vars: Expr → Nat
num_vars e: Expr
e) (find_counterexamples: Expr → List Interp
find_counterexamples e: Expr
e)
Tests and Demonstrations
defX := {X: Exprvar.mkvar.mk: Nat → var0} def0: NatY := {Y: Exprvar.mkvar.mk: Nat → var1} def1: NatZ := {Z: Exprvar.mkvar.mk: Nat → var2}2: Nattruth_table_outputs (truth_table_outputs: Expr → List BoolX ∧X: ExprY)Y: ExprList.foldrList.foldr: {α β : Type} → (α → β → β) → β → List α → βoror: Bool → Bool → Boolfalse (false: Booltruth_table_outputs (truth_table_outputs: Expr → List BoolX ∧X: ExprY))Y: ExprList.foldrList.foldr: {α β : Type} → (α → β → β) → β → List α → βandand: Bool → Bool → Booltrue (true: Booltruth_table_outputs (truth_table_outputs: Expr → List BoolX ∧X: ExprY))Y: Expr
Is it true that if X being true makes Y true, then does X being false make Y false?
((X ⇒X: ExprY) ⇒ (¬Y: ExprX ⇒ ¬X: ExprY))Y: Expris_valid ((is_valid: Expr → BoolX ⇒X: ExprY) ⇒ (¬Y: ExprX ⇒ ¬X: ExprY))Y: Exprfind_counterexamples_bool ((find_counterexamples_bool: Expr → List (List Bool)X ⇒X: ExprY) ⇒ (¬Y: ExprX ⇒ ¬X: ExprY))Y: Expr(implies (implies: Bool → Bool → Boolimpliesimplies: Bool → Bool → Boolfalsefalse: Booltrue) (true: Boolimpliesimplies: Bool → Bool → Booltruetrue: Boolfalse))false: Bool
Is it true that if X being true means that Y must be true, then does Y being false imply X is false?
((X ⇒X: ExprY) ⇒ (¬Y: ExprY ⇒ ¬Y: ExprX))X: Expris_valid ((is_valid: Expr → BoolX ⇒X: ExprY) ⇒ (¬Y: ExprY ⇒ ¬Y: ExprX))X: Exprfind_counterexamples_bool ((find_counterexamples_bool: Expr → List (List Bool)X ⇒X: ExprY) ⇒ (¬Y: ExprY ⇒ ¬Y: ExprX))X: Expr
We can find all the models of an expression.
find_models_bool ((find_models_bool: Expr → List (List Bool)X ⇒X: ExprY) ⇒ (¬Y: ExprX ⇒ ¬X: ExprY))Y: Expr
Simple model counting.
count_models (count_models: Expr → NatX ∨X: ExprY)Y: Exprcount_models (count_models: Expr → NatX ∧X: ExprY)Y: Expr
Search for models (returns list of functions)
find_models (find_models: Expr → List InterpX ∧ ¬X: ExprX) -- expect []X: Expr(find_models (find_models: Expr → List InterpX ∨X: ExprY)).Y: Exprlength -- expect 3length: {α : Type} → List α → Nat(find_models (find_models: Expr → List InterpX ∧X: ExprY)).Y: Exprlength -- expect 1length: {α : Type} → List α → Nat
Search for models (returns list of list of bools)
find_models_bool (find_models_bool: Expr → List (List Bool)X ∧ ¬X: ExprX) -- []X: Exprfind_models_bool (find_models_bool: Expr → List (List Bool)X ∨ ¬X: ExprX) -- [[false], [true]X: Exprfind_models_bool (find_models_bool: Expr → List (List Bool)X ∧X: ExprY) -- [[true, true]]Y: Exprfind_models_bool (¬(find_models_bool: Expr → List (List Bool)X ∧X: ExprY) ⇒ ¬Y: ExprX ∨ ¬X: ExprY) -- all four interpsY: Exprfind_models_bool ((find_models_bool: Expr → List (List Bool)X ⇒X: ExprY) ⇒ (Y: ExprY ⇒Y: ExprZ) ⇒ (Z: ExprX ⇒X: ExprZ)) -- all eight interpsZ: Expr
Homework
Forthcoming:
- Expand make_bool_lists applied to values 0-3.
- Validate a list of standard inference rules.
- Find the Fallacies, Explain Counterexamples.
- Replace ponderous function definition using map.