UVa CS2120-002 F23 Midterm Exam
The first section of this exam just repeats our definition of propositional logic syntax and semantics. Skip ahead to the second section to find the exam.
Propositional Logic: Syntax, Sematics, Satisfiability
This section of the exam simply includes our formal definition of the syntax and semantics of propositional logic and of functions that determine whether a given expression is valid, satisfiable, or unsatisfiable.
Syntax
-- variables
structure var : Type := (n: Nat)
-- connectives/operators
inductive unary_op : Type | not
inductive binary_op : Type
| and
| or
| imp
| iff
-- expressions (abstract syntax)
inductive Expr : Type
-- Extra credit answers here
| var_exp (v : var)
| un_exp (op : unary_op) (e : Expr)
| bin_exp (op : binary_op) (e1 e2 : Expr)
-- concrete syntax
notation "{"v"}" => Expr.var_exp v
prefix:max "¬" => Expr.un_exp unary_op.not
infixr:35 " ∧ " => Expr.bin_exp binary_op.and
infixr:30 " ∨ " => Expr.bin_exp binary_op.or
infixr:25 " ⇒ " => Expr.bin_exp binary_op.imp
infixr:20 " ⇔ " => Expr.bin_exp binary_op.iff
notation " ⊤ " => Expr.top_exp
notation " ⊥ " => Expr.bot_exp
Semantics
-- meanings of unary operators
def eval_un_op : unary_op → (Bool → Bool)
| unary_op.not => not
-- missing binary Boolean operators
def implies : Bool → Bool → Bool
| true, false => false
| _, _ => true
def iff : Bool → Bool → Bool
| true, true => true
| false, false => true
| _, _ => false
-- meanings of binary operators
def eval_bin_op : binary_op → (Bool → Bool → Bool)
| binary_op.and => and
| binary_op.or => or
| binary_op.imp => implies
| binary_op.iff => iff
-- The interpretation type
def Interp := var → Bool
-- The meanings of expressions "under" given interpretations
def eval_expr : Expr → Interp → Bool
-- Extra credit answers here
| (Expr.var_exp v), i => i v
| (Expr.un_exp op e), i => (eval_un_op op) (eval_expr e i)
| (Expr.bin_exp op e1 e2), i => (eval_bin_op op) (eval_expr e1 i) (eval_expr e2 i)
Satisfiability
We built a satisfiability checker for propositional logic, in several pieces. This subsection includes all definitions.
Truth Table Input Rows
-- Nat to Binary -- You don't need to worry about the "have" part def right_bit (n : Nat) := n%2 def shift_right (n : Nat) := n/2 def:Nat →Nat: TypeListList: Type → TypeNat |Nat: Type0 => [0: Nat0] |0: Nat1 => [1: Nat1] |1: Natn' + 2 => have : (n': Natshift_right (shift_right: Nat → Natn' +n': Nat2)) < (2: Natn' +n': Nat2) :=2: Natsorrysorry: shift_right (n' + 2) < n' + 2nat_to_bin (nat_to_bin: Nat → List Natshift_right (shift_right: Nat → Natn' +n': Nat2)) ++ [2: Natright_bit (right_bit: Nat → Natn' +n': Nat2)] -- Left pad with zeros def2: Natzero_pad :zero_pad: Nat → List Nat → List NatNat →Nat: TypeListList: Type → TypeNat →Nat: TypeListList: Type → TypeNat |Nat: Typev,v: Natl =>l: List Natzero_pad_recursive (zero_pad_recursive: Nat → List Nat → List Natv - (v: Natl.l: List Natlength))length: {α : Type} → List α → Natl wherel: List Natzero_pad_recursive :zero_pad_recursive: Nat → List Nat → List NatNat →Nat: TypeListList: Type → TypeNat →Nat: TypeListList: Type → TypeNat |Nat: Type0,0: Natl =>l: List Natl |l: List Natv'+1,v': Natl =>l: List Natzero_pad_recursivezero_pad_recursive: Nat → List Nat → List Natv' (v': Nat0::0: Natl) -- Make row of bits at index "row" padded out to "cols" wide defl: List Natmk_bit_row : (mk_bit_row: Nat → Nat → List Natrow:row: NatNat) → (Nat: Typecols :cols: NatNat) →Nat: TypeListList: Type → TypeNat |Nat: Typer,r: Natc =>c: Natzero_padzero_pad: Nat → List Nat → List Natc (c: Natnat_to_binnat_to_bin: Nat → List Natr) -- Convert list of bits to list of bools defr: Natbit_to_bool :bit_to_bool: Nat → BoolNat →Nat: TypeBool |Bool: Type0 =>0: Natfalse | _ =>false: Booltrue deftrue: Boolbit_list_to_bool_list :bit_list_to_bool_list: List Nat → List BoolListList: Type → TypeNat →Nat: TypeListList: Type → TypeBool | [] =>Bool: Type[] |[]: List Boolh::h: Natt => (t: List Natbit_to_boolbit_to_bool: Nat → Boolh) :: (h: Natbit_list_to_bool_listbit_list_to_bool_list: List Nat → List Boolt) -- Make row'th row of truth table with vars variables deft: List Natmk_row_bools : (mk_row_bools: Nat → Nat → List Boolrow :row: NatNat) → (Nat: Typevars :vars: NatNat) →Nat: TypeListList: Type → TypeBool |Bool: Typer,r: Natv =>v: Natbit_list_to_bool_list (bit_list_to_bool_list: List Nat → List Boolmk_bit_rowmk_bit_row: Nat → Nat → List Natrr: Natv)v: Nat
Interpretations
-- Convert list of bools to interpretation
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
def bools_to_interp: List Bool → Interp
bools_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: 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
-- Make an interpretation for given row with "vars" variables
def mk_interp_vars_row: Nat → Nat → Interp
mk_interp_vars_row : (vars: Nat
vars: Nat: Type
Nat) → (row: Nat
row: Nat: Type
Nat) → Interp: Type
Interp
| v: Nat
v, r: Nat
r => bools_to_interp: List Bool → Interp
bools_to_interp (mk_row_bools: Nat → Nat → List Bool
mk_row_bools r: Nat
r v: Nat
v)
-- Given number of variables, return list of interpretations
def mk_interps: Nat → List Interp
mk_interps (vars: Nat
vars : Nat: Type
Nat) : List: Type → Type
List Interp: Type
Interp :=
mk_interps_helper: Nat → Nat → List Interp
mk_interps_helper (2: Nat
2^vars: Nat
vars) vars: Nat
vars
where mk_interps_helper: Nat → Nat → List Interp
mk_interps_helper : (rows: Nat
rows : Nat: Type
Nat) → (vars: Nat
vars : Nat: Type
Nat) → List: Type → Type
List Interp: Type
Interp
| 0: Nat
0, _ => []: List Interp
[]
| (n': Nat
n' + 1), v: Nat
v => (mk_interp_vars_row: Nat → Nat → Interp
mk_interp_vars_row v: Nat
v n': Nat
n')::mk_interps_helper: Nat → Nat → List Interp
mk_interps_helper n': Nat
n' v: Nat
v
-- Count the number of variables in a given expression
def max_variable_index: Expr → Nat
max_variable_index : Expr: Type
Expr → Nat: Type
Nat
| -- Extra credit answers here
| Expr.var_exp (var.mk i) => i
| Expr.un_exp _ e => max_variable_index e
| Expr.bin_exp _ e1 e2 => max (max_variable_index e1) (max_variable_index e2)
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
Truth Table Output Column
def eval_expr_interps: List Interp → Expr → List Bool
eval_expr_interps : List: Type → Type
List Interp: Type
Interp → Expr: Type
Expr → List: Type → Type
List Bool: Type
Bool
| [], _ => []: List Bool
[]
| h: Interp
h::t: List Interp
t, e: Expr
e => eval_expr_interps: List Interp → Expr → List Bool
eval_expr_interps t: List Interp
t e: Expr
e ++ [eval_expr: Expr → Interp → Bool
eval_expr e: Expr
e h: Interp
h]
-- Given expression, return truth table outputs by ascending row index
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_interps: List Interp → Expr → List Bool
eval_expr_interps (mk_interps: Nat → List Interp
mk_interps (num_vars: Expr → Nat
num_vars e: Expr
e)) e: Expr
e
Reducers: Boolean List to Bool with And and Or
-- functions to check if bool list has any, resp. all, values true
def reduce_or: List Bool → Bool
reduce_or : List: Type → Type
List Bool: Type
Bool → Bool: Type
Bool
| [] => false: Bool
false
| h: Bool
h::t: List Bool
t => or: Bool → Bool → Bool
or h: Bool
h (reduce_or: List Bool → Bool
reduce_or t: List Bool
t)
def reduce_and: List Bool → Bool
reduce_and : List: Type → Type
List Bool: Type
Bool → Bool: Type
Bool
| [] => true: Bool
true
| h: Bool
h::t: List Bool
t => and: Bool → Bool → Bool
and h: Bool
h (reduce_and: List Bool → Bool
reduce_and t: List Bool
t)
Satisfiability Checkers
-- Three main functions: test given expression for satsfiability properties
def is_sat: Expr → Bool
is_sat : Expr: Type
Expr → Bool: Type
Bool := λ e: Expr
e : Expr: Type
Expr => 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 : Expr: Type
Expr → Bool: Type
Bool := λ e: Expr
e : Expr: Type
Expr => 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 : Expr: Type
Expr → Bool: Type
Bool := λ e: Expr
e : Expr: Type
Expr => not: Bool → Bool
not (is_sat: Expr → Bool
is_sat e: Expr
e)
EXAM STARTS HERE
#1 Proofs as Programs
a. And elimination [15 points]
Prove, by completing the following function definition, that from a value of type α × β one can always derive a value of type α.
-- Your answer here defand_elimination {and_elimination: {α β : Type} → α × β → ααα: Typeβ :β: TypeType} :Type: Type 1α ×α: Typeβ →β: Typeα | (_, _) =>α: Type
b. Funny transitivity [15 points]
Prove, by completing the following function definition, that (α → β) × (β → γ) → (α → γ). In other words, if you have a pair of functions, one converting α to β and one converting β to γ, then you can always construct a function from α to γ. Hint: Use type-guided top-down programming, and remember how to express a function value: that's what you have to return in this case.
-- Your answer here def funny_transitivity {α β γ : Type} : (α → β) × (β → γ) → (α → γ) | _ =>
c. Ex empty quodlibet [15 points]
Prove that if a type, α, is uninhabited then from an assumed value (a : α) one can always derive a value of any type, β.
-- Your answer here def ex_empty {α β : Type} : (α → Empty) → α → β | _, _ =>
#2 Data Types
a. Enumerated Types [10 points]
Define three enumerated types, called Bread, Spread, and Cheese, where the values of type Bread are white and wheat; the values of type Spread are jam and pesto; and the values of type Cheese are cheddar and brie.
-- Your answers here
b. An interesting inductive type [15 points]
Define a data type called Sandwich, with one constructor called mk, taking as its arguments a choice of bread and either (but not both) a choice of Cheese or a choice of Spread. Hint: Remember how to define a type that carries a value of either one type or another. Extra credit [2pts] for using structure instead of inductive to declare the Sandwich type.
-- Your answer here
c. Now make yourself a Sandwich [15 points]
Define jam_sandwhich to be a Sandwhich made with wheat bread and jam. You have to use Sandwich.mk to create a term representing a sandwich with wheat bread and jam as a spread.
-- Your answer here def jam_sandwich : Sandwich :=
#3 Recursive Data and Functions [15 points]
In our implementation of propositional logic, we defined a function, bit_list_to_bool_list, to convert a list of Nat to a corresponding list of Bool. Here's the definition (with a tick mark on the name).
defbit_list_to_bool_list' :bit_list_to_bool_list': List Nat → List BoolListList: Type → TypeNat →Nat: TypeListList: Type → TypeBool | [] =>Bool: Type[] |[]: List Boolh::h: Natt => (t: List Natbit_to_boolbit_to_bool: Nat → Boolh) :: (h: Natbit_list_to_bool_listbit_list_to_bool_list: List Nat → List Boolt) -- expect [true, false, true, false, true]t: List Natbit_list_to_bool_list [bit_list_to_bool_list: List Nat → List Bool1,1: Nat0,0: Nat3,3: Nat0,0: Nat1]1: Nat
Your job is to generalize this solution by defining a new function, called map. Generalize the types, Nat and Bool, to arbitrary types, α and β. Generalize bit_to_bool to be any function for converting an individual α value into a corresponding β value. Your map function will thus take as its arguments (1) type parameters (make them implicit), (2) a function for converting elements, and (3) a List of α values, and will return a correspond List of β values.
-- Your answer here -- test case: use map instead of bit_list_to_bool_list -- expect [true, false, true, false, true] #evalbit_to_bool [1, 0, 1, 0, 1]bit_to_bool [1, 0, 1, 0, 1]: ?m.44293
#4 Propositional Logic [10 pts Extra Credit, for A+]
Propositional logic as we've formulate it has variable expressions (atomic expressions), and larger expressions built by applying connectives (∧, ∨, ¬, ⇒, ⇔) to smaller expressions.
Some formalizations of propositional logic also include the constant expressions True and False. In concrete syntax, they are sometimes written as ⊤ (pronounced top) and ⊥ (bottom). Semantically ⊤ evaluates to Boolean true and ⊥ evaluates to Boolean false.
a. Extend Syntax and Semantics
Your job is to extend our syntax and semantics to include ⊤ and ⊥ as valid expressions. You will have to carry out the following tasks.
- add top_exp and bot_exp as constructors in Expr
- note that we've already added concrete notation definitions
- add rules for evaluating these expressions to eval_expr
- add rules for these expressions to max_variable_index
When you're done, the following logic should evaluate without error.
defX := {X: Exprvar.mkvar.mk: Nat → var0}0: Nat-- expect true
b. Give a model for (X ⇒ ⊥)
Recall that a model is a binding of values to variables that makes a proposition true. What value must X have to make (X ⇒ ⊥) true?
-- Answer: {X is _____ } is a model of (X ⇒ ⊥)