Logic & Propositions
Logic is the grammar of precise thought — the formal language that turns vague reasoning into proofs, programs, and verifiable truth.
Overview
Every time you write an if statement, you are applying propositional logic. Every time a database executes a query with WHERE age > 18 AND country = 'US', it is evaluating predicates over a domain. Every time a compiler proves that a program is type-safe, it is constructing a formal proof. Logic is not an abstract curiosity — it is the operating system of rigorous thought.
This module introduces the two foundational layers of mathematical logic: propositional logic, which handles the truth or falsity of atomic statements and their combinations, and predicate (first-order) logic, which lets us make statements about objects, properties, and relationships across entire domains. Together, these form the bedrock on which all of computer science — algorithms, data structures, programming languages, databases, AI, and formal verification — is built.
The journey follows the natural arc of the subject: we begin with the simple (a statement is either true or false), formalize the connectives that combine statements (AND, OR, NOT, IF-THEN, IFF), develop truth tables as a mechanical verification tool, identify the profound equivalences that let us simplify and transform logical formulas, and then graduate to the expressive power of predicate logic with its quantifiers (∀ and ∃). Along the way, we build the intuition for why these structures matter and how they appear constantly in real code and real proofs.
By the end of this module, you will have the vocabulary and the tools to read mathematical proofs, write airtight logical arguments, understand how SAT solvers work, see the connection between logic and Boolean circuits, and appreciate why Gödel's incompleteness theorems shook the foundations of mathematics. These ideas have a beauty to them — a compression of the mechanisms of reason into a handful of symbols — and they reward careful study many times over.
Logic is load-bearing infrastructure for every other field in computer science. Type systems in programming languages are proof systems in disguise — the Curry-Howard correspondence shows that every type is a proposition and every well-typed program is a proof of that proposition. Formal verification tools like TLA+, Coq, and Lean use logic to prove that distributed systems and cryptographic protocols are correct. SAT solvers — which decide whether a propositional formula can be satisfied — underpin modern chip design, software model checking, and automated theorem proving. Every SQL WHERE clause is a predicate; query optimizers rewrite these predicates using logical equivalences to execute faster. In machine learning, Bayesian inference is grounded in probabilistic logic. Security proofs for cryptographic protocols are logical arguments about the capabilities of adversaries. You cannot go far in any direction in computer science without running into logic as the lingua franca.
Beyond its technical uses, logic trains a mode of thinking that is invaluable across all of mathematics and engineering: the discipline of making every assumption explicit, every step of an argument accountable, and every claim falsifiable by a counterexample. Students who internalize propositional and predicate logic find that proofs become less mysterious, bugs become easier to isolate (a bug is a mismatch between what your code's logic asserts and what you intended), and specifications become clearer. This module is your initiation into the formal side of computer science — the side where "I think it works" is replaced by "I can prove it works."
Propositional Logic: From Variables to SAT
The pipeline from atomic propositions to SAT solving: variables are combined with connectives into formulas, truth tables classify formulas as tautologies/contradictions/contingent, contingent formulas are converted to CNF, and SAT solvers determine satisfiability.
The concepts
01Propositions and Truth Values
A proposition is a declarative statement that is either true (T or 1) or false (F or 0) — never both, never neither. This is the law of excluded middle (every proposition is T or F) paired with the law of non-contradiction (no proposition is both T and F). Together, these two laws define the classical two-valued logic that underlies almost all of computer science.
Examples of valid propositions:
- "7 is a prime number" (T)
- "The number 4 is odd" (F)
- "Every Python list is mutable" (T)
- "This program terminates in under 1 second on all inputs" (T or F — we just might not know which)
Non-examples (not propositions):
- "What is the time?" — a question, not a declarative statement
- "Close the door." — a command
- "This statement is false." — the liar's paradox, neither T nor F in classical logic
- "x > 5" — not a proposition by itself because the truth value depends on x; this is a predicate, covered later
In logic, we use propositional variables — typically lowercase letters p, q, r, s — to stand for arbitrary propositions. This lets us study the structure of logical arguments independently of their content. The statement "p AND q" is valid regardless of whether p is about primes or planets.
# In Python, bool is literally a proposition type
p = True # "7 is prime"
q = False # "4 is odd"
# Every Boolean expression in a program IS a proposition
x = 10
proposition = (x > 5) # evaluates to True
print(type(proposition)) # <class 'bool'>
02Logical Connectives
Logical connectives are the "words" of logic — they combine simpler propositions into compound ones. There are five fundamental connectives:
1. Negation (NOT, ¬p) — flips the truth value. If p is T, ¬p is F, and vice versa. In code: !p or not p.
2. Conjunction (AND, p ∧ q) — true only when both p and q are true. This is the logical AND gate. In code: p && q or p and q. Think of it as a series circuit: both switches must be closed for current to flow.
3. Disjunction (OR, p ∨ q) — true when at least one of p or q is true (inclusive OR). In code: p || q or p or q. Think of it as a parallel circuit.
4. Implication (IF-THEN, p → q) — "if p then q". This is the most subtle connective. It is only false when p is true and q is false. Crucially, when p is false (the hypothesis is false), the implication is vacuously true regardless of q. Example: "If it rains, I carry an umbrella" is not violated on a sunny day when I don't carry an umbrella. The implication p → q is logically equivalent to ¬p ∨ q.
5. Biconditional (IFF, p ↔ q) — "p if and only if q". True exactly when p and q have the same truth value (both T or both F). This is logical equality. In code: p == q for booleans.
The precedence (binding strength, highest to lowest): ¬, ∧, ∨, →, ↔. So ¬p ∧ q → r means ((¬p) ∧ q) → r.
p, q = True, False
print(not p) # ¬p → False
print(p and q) # p∧q → False
print(p or q) # p∨q → True
print(not p or q) # p→q (equivalent form) → False
print(p == q) # p↔q → False
# Implication: vacuously true when antecedent is false
rain = False
umbrella = False
# "if rain then umbrella" — not violated!
implication = (not rain) or umbrella
print(implication) # True — vacuous truth
03Truth Tables
A truth table exhaustively enumerates all possible truth-value assignments to the propositional variables in a formula and computes the formula's truth value under each assignment. It is a mechanical, algorithmic procedure — essentially brute-force verification.
For n propositional variables, there are 2ⁿ rows (all combinations of T/F assignments).
Constructing a truth table:
- List all propositional variables as column headers.
- Fill in all 2ⁿ combinations of T/F systematically (like binary counting).
- Add columns for each sub-formula, building up to the full formula.
Example: p → q
| p | q | p → q |
|---|---|---|
| T | T | T |
| T | F | F |
| F | T | T |
| F | F | T |
The key row is (T, F) → F: this is the only way an implication fails.
Tautology, Contradiction, Satisfiability:
- A formula is a tautology if every row is T (e.g., p ∨ ¬p, called the law of excluded middle).
- A formula is a contradiction (or unsatisfiable) if every row is F (e.g., p ∧ ¬p).
- A formula is satisfiable if at least one row is T.
Satisfiability (SAT) is one of the most important problems in CS — the first problem proven NP-complete (Cook-Levin theorem, 1971). Modern SAT solvers check satisfiability for formulas with millions of variables and are used in chip verification.
from itertools import product
def truth_table(formula_fn, var_names):
n = len(var_names)
print(' | '.join(var_names) + ' | result')
print('-' * (6 * n + 10))
for values in product([True, False], repeat=n):
result = formula_fn(*values)
row = ' | '.join('T' if v else 'F' for v in values)
print(f"{row} | {'T' if result else 'F'}")
# p → q (implication)
truth_table(lambda p, q: (not p) or q, ['p', 'q'])
# Tautology: p ∨ ¬p
truth_table(lambda p: p or not p, ['p'])
04Logical Equivalence and the Laws of Logic
Two formulas φ and ψ are logically equivalent (written φ ≡ ψ) if they have identical truth values for every possible assignment of their variables. Equivalence is verified by showing their truth tables are identical column-by-column, or by algebraic derivation using known laws.
Logical equivalences form an algebra — you can substitute equals for equals and simplify complex formulas, exactly as in high-school algebra. The key laws are:
Double Negation: ¬¬p ≡ p
De Morgan's Laws (among the most useful):
- ¬(p ∧ q) ≡ ¬p ∨ ¬q
- ¬(p ∨ q) ≡ ¬p ∧ ¬q
Intuition: "Not (raining AND cold)" = "Not raining OR not cold" — at least one condition fails.
Implication Elimination: p → q ≡ ¬p ∨ q (removes the → connective)
Contrapositive: p → q ≡ ¬q → ¬p (same truth table, different form)
Commutativity: p ∧ q ≡ q ∧ p; p ∨ q ≡ q ∨ p
Associativity: (p ∧ q) ∧ r ≡ p ∧ (q ∧ r)
Distributivity:
- p ∧ (q ∨ r) ≡ (p ∧ q) ∨ (p ∧ r)
- p ∨ (q ∧ r) ≡ (p ∨ q) ∧ (p ∨ r)
Absorption: p ∧ (p ∨ q) ≡ p
Identity: p ∧ T ≡ p; p ∨ F ≡ p
Domination: p ∨ T ≡ T; p ∧ F ≡ F
These laws underpin Boolean algebra, which is isomorphic to propositional logic and directly describes how digital circuits work. Circuit simplification IS logical simplification — fewer gates, lower power consumption.
# Verify De Morgan's law computationally
from itertools import product
for p, q in product([True, False], repeat=2):
lhs = not (p and q) # ¬(p ∧ q)
rhs = (not p) or (not q) # ¬p ∨ ¬q
assert lhs == rhs, f"De Morgan failed at p={p}, q={q}"
print("De Morgan's law verified for all inputs.")
# Contrapositive: p→q ≡ ¬q→¬p
for p, q in product([True, False], repeat=2):
forward = (not p) or q # p → q
contra = q or (not p) # ¬q → ¬p (same!)
assert forward == contra
print("Contrapositive equivalence verified.")
05Normal Forms: CNF and DNF
Any propositional formula can be converted to a canonical form — a standardized structure that makes automated reasoning easier. The two standard forms are:
Conjunctive Normal Form (CNF): A conjunction (AND) of clauses, where each clause is a disjunction (OR) of literals (a variable or its negation).
Example: (p ∨ ¬q) ∧ (¬p ∨ r) ∧ (q ∨ r)
Disjunctive Normal Form (DNF): A disjunction (OR) of terms, where each term is a conjunction (AND) of literals.
Example: (p ∧ ¬q) ∨ (¬p ∧ r) ∨ (q ∧ ¬r)
Why normal forms matter:
- SAT solvers accept input in CNF (DIMACS format is the universal standard).
- A formula in CNF is satisfiable if and only if every clause can be simultaneously satisfied — this structural insight powers DPLL and CDCL algorithms.
- Resolution, the core proof rule in automated theorem proving, works on clauses in CNF.
- Hardware synthesis tools convert Boolean expressions to CNF/DNF as an intermediate step.
Converting to CNF:
- Eliminate ↔ and → using equivalences.
- Push ¬ inward using De Morgan's laws until negation only touches variables (NNF = Negation Normal Form).
- Distribute ∧ over ∨.
A useful insight: the truth table directly gives you CNF and DNF. Each row where the formula is F contributes a clause to the CNF (using the falsifying assignment). Each row where the formula is T contributes a minterm to the DNF.
# Convert a formula to CNF via truth table (minterm/maxterm method)
# Formula: p → q i.e. ¬p ∨ q
# Truth table:
# p=T,q=T: T — satisfying
# p=T,q=F: F — falsifying → clause: (¬p ∨ q)
# p=F,q=T: T
# p=F,q=F: T
# CNF: just one clause (¬p ∨ q) — matches ¬p∨q, as expected!
# In the DIMACS CNF format used by SAT solvers:
# Variables are integers 1..n; negation is negative
# p→q becomes -1 2 0 (one clause: -p ∨ q)
cnf_clause = [-1, 2, 0] # 0 terminates each clause
print("DIMACS clause for p→q:", cnf_clause)
06Predicate Logic: Predicates and Quantifiers
Propositional logic is powerful but limited — it cannot express statements about objects or relationships. "Every natural number has a successor" cannot be stated with propositions alone. Predicate logic (also called first-order logic, FOL) extends propositional logic with:
Predicates — functions that return T or F for given objects. P(x) means "x has property P". Example: Prime(7) is T; Even(3) is F. Predicates can have multiple arguments: Greater(x, y) means x > y.
Domain of discourse — the set of objects the variables range over. Always state this explicitly! "∀x, P(x)" means different things if x ranges over integers vs. real numbers.
Universal Quantifier (∀): ∀x P(x) means "for ALL x in the domain, P(x) is true". To disprove it, you only need ONE counterexample.
Existential Quantifier (∃): ∃x P(x) means "there EXISTS at least one x in the domain such that P(x) is true". To prove it, you only need to exhibit ONE example.
Translating English:
- "All students passed" → ∀x (Student(x) → Passed(x))
- "Some student failed" → ∃x (Student(x) ∧ Failed(x))
Note the asymmetry: universal statements use →, existential statements use ∧. This is a common source of errors. "∀x (Student(x) ∧ Passed(x))" would wrongly claim everything in the domain is a student.
Negating Quantified Statements (De Morgan's for quantifiers):
- ¬(∀x P(x)) ≡ ∃x ¬P(x) — "Not all x satisfy P" ≡ "Some x does not satisfy P"
- ¬(∃x P(x)) ≡ ∀x ¬P(x) — "No x satisfies P" ≡ "All x fail to satisfy P"
These negation rules are essential for proof by contradiction and are used constantly in algorithm analysis ("there is no faster algorithm" ↔ "all algorithms are at least this slow").
# Python mirrors predicate logic naturally
domain = range(1, 11) # {1, 2, ..., 10}
def prime(n):
return n > 1 and all(n % i != 0 for i in range(2, n))
def even(n):
return n % 2 == 0
# ∀x in domain: even(x) → False (not all numbers 1-10 are even)
universal = all(even(x) for x in domain)
print(f"∀x Even(x): {universal}") # False
# ∃x in domain: prime(x) ∧ even(x) (is there an even prime?)
existential = any(prime(x) and even(x) for x in domain)
print(f"∃x (Prime(x) ∧ Even(x)): {existential}") # True (x=2)
# Negation: ¬(∀x prime(x)) ≡ ∃x ¬prime(x)
not_all_prime = any(not prime(x) for x in domain)
print(f"∃x ¬Prime(x): {not_all_prime}") # True
07Nested Quantifiers
Quantifiers can be nested, and the order matters fundamentally when quantifiers of different types are mixed.
Same-type quantifiers are commutative:
- ∀x ∀y P(x,y) ≡ ∀y ∀x P(x,y)
- ∃x ∃y P(x,y) ≡ ∃y ∃x P(x,y)
Mixed-type quantifiers are NOT commutative — this is one of the most important and counterintuitive facts in logic:
- ∀x ∃y (y > x) — "For every number, there exists a larger one" — TRUE over the integers (just take y = x + 1)
- ∃y ∀x (y > x) — "There exists a number larger than all numbers" — FALSE over the integers
The difference: in ∀x ∃y, the witness y can depend on x. In ∃y ∀x, one fixed y must work for all x simultaneously.
Reading nested quantifiers systematically: scan left to right. Each ∀ means "for any choice an adversary makes"; each ∃ means "we get to choose (possibly depending on prior choices)".
Formal definition of a limit (from calculus, the classic example):
- lim_{x→a} f(x) = L is: ∀ε > 0, ∃δ > 0, ∀x (0 < |x - a| < δ → |f(x) - L| < ε)
- Reading this aloud: "for any (adversarial) ε, we can find δ, such that for any (adversarial) x close to a, f(x) is close to L."
This is why calculus students struggle with the ε-δ definition — it hides three nested quantifiers with two alternations. Once you see it as a quantifier expression, the game-theoretic reading (adversary picks ε, you respond with δ, adversary picks x) makes it tractable.
# Demonstrate quantifier non-commutativity computationally
domain = range(-5, 6) # integers -5..5
# ∀x ∃y: y > x (for every x, some y is larger)
all_have_larger = all(
any(y > x for y in domain)
for x in domain
)
print(f"∀x ∃y (y>x) over -5..5: {all_have_larger}") # False! -5 works, but 5 fails
big_domain = range(-100, 101)
all_have_larger2 = all(
any(y > x for y in big_domain)
for x in big_domain
)
print(f"∀x ∃y (y>x) over -100..100: {all_have_larger2}") # still False at x=100
# Over all integers (unbounded), it's True — but we can't iterate ℤ in code
# ∃y ∀x: y > x (one y larger than everything)
one_larger_than_all = any(
all(y > x for x in domain)
for y in domain
)
print(f"∃y ∀x (y>x) over -5..5: {one_larger_than_all}") # False
08Logical Inference and Proof Rules
Logic gives us not just a language but also a proof system — rules for deriving new truths from existing ones. These are rules of inference: patterns of the form "given these premises, you may conclude this."
Modus Ponens (the most fundamental rule):
- Premises: p → q, and p
- Conclusion: q
- Reading: "If the rule holds and the condition is met, the conclusion follows."
- In code: every
ifstatement uses this!
Modus Tollens (contrapositive reasoning):
- Premises: p → q, and ¬q
- Conclusion: ¬p
- Reading: "If the rule holds and the conclusion failed, the hypothesis must have failed too."
Hypothetical Syllogism (transitivity of implication):
- Premises: p → q, and q → r
- Conclusion: p → r
Disjunctive Syllogism:
- Premises: p ∨ q, and ¬p
- Conclusion: q
Resolution (the workhorse of automated theorem proving):
- Premises: p ∨ q, and ¬p ∨ r
- Conclusion: q ∨ r
- This rule, applied repeatedly to CNF clauses, is the basis for DPLL/CDCL SAT solvers and Prolog's execution model.
Soundness vs. Completeness:
- A proof system is sound if every derivable conclusion is actually true (no false conclusions from true premises).
- A proof system is complete if every true statement is derivable.
- First-order logic with standard rules is both sound and complete (Gödel's completeness theorem, 1929). But Gödel's incompleteness theorems (1931) show that any consistent system powerful enough to express arithmetic has true statements that are unprovable within the system — a profound limitation.
# Modus ponens as a Python function
def modus_ponens(p: bool, implication_p_q: bool) -> bool:
"""Given p and (p→q), return q.
Note: implication is passed as a precomputed bool here.
"""
# If p is True and p→q is True, q must be True
# (because p→q is False only when p=T, q=F)
if p and implication_p_q:
return True # q is True
elif not p:
return None # q indeterminate from these premises alone
else: # p=T, implication=F means inconsistent premises
raise ValueError("Inconsistent: p is True but p→q is False")
# Example: "If it is raining (p), the ground is wet (q)."
# It is raining. Therefore: the ground is wet.
p = True
p_implies_q = True # rule holds
q = modus_ponens(p, p_implies_q)
print(f"Ground is wet: {q}") # True
09Logic in Computer Science: Types, Circuits, and Databases
The connection between logic and CS is not metaphorical — it is mathematically precise.
Boolean Circuits: Every logical formula over propositions corresponds directly to a combinational logic circuit. AND gates implement ∧, OR gates implement ∨, NOT gates implement ¬. The problem of minimizing circuit complexity (fewest gates) is exactly the problem of finding the simplest logically equivalent formula. The Karnaugh map is a visual tool for this simplification. The AND, OR, NOT basis is universal — every Boolean function can be computed. So is {NAND} alone — a single gate type suffices.
Type Systems (Curry-Howard Correspondence): In typed functional programming languages like Haskell or Coq, there is a startling isomorphism:
- A type corresponds to a proposition
- A program of that type corresponds to a proof of that proposition
A -> B(function type) corresponds toA → B(implication)(A, B)(pair/tuple) corresponds toA ∧ B(conjunction)Either A B(sum type) corresponds toA ∨ B(disjunction)- The empty type
Voidcorresponds to ⊥ (contradiction)
Writing a program IS constructing a proof. A type error IS a logical inconsistency.
Databases: A SQL query SELECT * FROM users WHERE age >= 18 AND country = 'US' evaluates a predicate P(x) = Age(x) ≥ 18 ∧ Country(x) = 'US' over the domain of users. This is first-order logic directly. Relational algebra — the theory behind SQL — is a subset of first-order logic. Query optimization uses logical equivalences to rewrite predicates into more efficient forms.
Formal Verification: Tools like TLA+ (Leslie Lamport) and Alloy specify systems as logical formulas and use model checkers (essentially exhaustive truth-table evaluation over a bounded state space) to verify correctness. The Amazon and Microsoft distributed systems teams use TLA+ to prove properties of their protocols.
-- Curry-Howard: types are propositions, programs are proofs
-- In Haskell:
-- The type (a, b) is the proposition A ∧ B
-- A value of type (a, b) IS a proof of A ∧ B
conjunction_proof :: (Bool, Int)
conjunction_proof = (True, 42) -- witness: both A and B hold
-- The type Either a b is the proposition A ∨ B
disjunction_proof :: Either String Int
disjunction_proof = Left "hello" -- witness: A holds
-- The type a -> b is the proposition A → B
-- A function IS a proof of implication
implication_proof :: Bool -> Bool
implication_proof p = p || True -- always True (proves p → T)
-- There is NO value of type Void (the empty type)
-- This corresponds to: contradiction has no proof
-- absurd :: Void -> a
10Satisfiability (SAT) and Computational Complexity
The Boolean Satisfiability Problem (SAT) asks: given a propositional formula, does there exist an assignment of truth values to its variables that makes it true?
This is one of the most important problems in all of computer science. The Cook-Levin theorem (1971) proved that SAT is NP-complete — it was the first problem proven NP-complete, anchoring the entire NP-completeness theory. This means:
- SAT is in NP: given a satisfying assignment, we can verify it in polynomial time.
- SAT is NP-hard: every problem in NP can be reduced to SAT in polynomial time.
Since SAT is NP-complete, if we found a polynomial-time algorithm for SAT, we would prove P = NP and solve one of the Millennium Prize Problems (worth $1 million). Almost all complexity theorists believe P ≠ NP.
3-SAT is the special case where every clause has exactly 3 literals. It is still NP-complete (and often the canonical NP-complete problem used in reductions).
2-SAT, however, is solvable in polynomial time (O(n + m) via strongly connected components on an implication graph). This is a beautiful example of how a tiny structural restriction changes the complexity class drastically.
Modern SAT Solvers: Despite NP-completeness in the worst case, modern solvers (MiniSat, Glucose, Z3) handle formulas with millions of variables in practice using:
- DPLL algorithm (Davis-Putnam-Logemann-Loveland): backtracking search with unit propagation
- CDCL (Conflict-Driven Clause Learning): learn new clauses from conflicts to prune the search space
Intel and AMD use SAT solvers to verify that chip designs are bug-free before fabrication. The cost of a bug post-fabrication (Intel's 1994 FDIV bug: $475M recall) makes formal verification economically essential.
# Simple DPLL SAT solver for 3-CNF
# Formula: list of clauses; each clause: list of literals (int; neg = negation)
# Variable i represented as i, negation as -i
def dpll(clauses, assignment={}):
# Unit propagation: if a clause has one unassigned literal, force it
changed = True
while changed:
changed = False
for clause in clauses:
unassigned = [l for l in clause
if abs(l) not in assignment]
satisfied = any(
(l > 0 and assignment.get(l) == True) or
(l < 0 and assignment.get(-l) == False)
for l in clause
)
if satisfied:
continue
if len(unassigned) == 0:
return None # conflict: empty clause
if len(unassigned) == 1:
lit = unassigned[0]
assignment[abs(lit)] = (lit > 0)
changed = True
# Check: all clauses satisfied?
if all(any((l > 0 and assignment.get(l)) or
(l < 0 and not assignment.get(-l, True))
for l in clause) for clause in clauses):
return assignment
# Pick unassigned variable and branch
all_vars = {abs(l) for clause in clauses for l in clause}
unassigned_vars = all_vars - set(assignment.keys())
if not unassigned_vars:
return None
var = next(iter(unassigned_vars))
for val in [True, False]:
new_assign = dict(assignment)
new_assign[var] = val
result = dpll(clauses, new_assign)
if result is not None:
return result
return None # unsatisfiable
# (p ∨ q) ∧ (¬p ∨ r) ∧ (¬q ∨ ¬r)
clauses = [[1, 2], [-1, 3], [-2, -3]]
print(dpll(clauses, {})) # Some satisfying assignment
Key insights
- ✦Implication p → q is only false when the hypothesis p is true and the conclusion q is false — vacuous truth (false hypothesis) makes any implication true, which surprises almost every student the first time.
- ✦De Morgan's laws are the single most-used equivalences in practice: negation distributes over AND/OR by flipping the connective. Every time you simplify a complex conditional in code, you use De Morgan's.
- ✦The order of mixed quantifiers (∀∃ vs ∃∀) fundamentally changes meaning — 'for every x there exists y' is categorically different from 'there exists y for every x'. The ε-δ definition of limits hides exactly this alternation.
- ✦SAT (Boolean Satisfiability) was the first NP-complete problem — yet modern SAT solvers handle millions of variables in practice using conflict-driven clause learning, powering chip verification, AI planning, and cryptographic analysis.
- ✦The Curry-Howard correspondence is a deep and beautiful fact: writing a well-typed program and constructing a logical proof are literally the same activity. A type error is a logical inconsistency; a proof gap is a missing function implementation.
- ✦Normal forms (CNF/DNF) are not just theoretical conveniences — DIMACS CNF is the universal input format for SAT solvers, and every formula has a CNF equivalent readable directly from its truth table.
- ✦Predicate logic gives logic its expressive power for CS: every SQL query is a predicate formula, every type-system rule is a logical inference rule, and every formal specification is a first-order (or higher-order) logical statement.
- ✦Gödel's incompleteness theorem (1931) proves that any consistent formal system strong enough to express arithmetic contains true statements that are unprovable within the system — logic has fundamental, inescapable limits.
Common pitfalls
- ⚠Confusing implication with causation or time: p → q is a static truth-value relationship, not a statement about cause or sequence. 'If 2+2=5 then the moon is made of cheese' is vacuously true (the hypothesis is false), even though there is no causal link.
- ⚠Using ∧ instead of → in universal statements: '∀x (Student(x) ∧ Passed(x))' claims everything in the domain is a passed student. The correct form is '∀x (Student(x) → Passed(x))' — being a student is a precondition, not a universal fact.
- ⚠Forgetting to negate both the quantifier and the predicate when negating: ¬(∀x P(x)) is ∃x ¬P(x), NOT ∃x P(x) or ∀x ¬P(x). Students often drop the negation on P or forget to flip ∀ to ∃.
- ⚠Assuming that a converse (q → p) follows from an implication (p → q): these are logically independent. 'If it rains, the ground is wet' does NOT imply 'if the ground is wet, it rained' (a sprinkler could be on).
- ⚠Misreading nested quantifiers: ∀x ∃y P(x,y) and ∃y ∀x P(x,y) are entirely different statements. The inner witness in the first can depend on x; in the second it cannot. Reversing them is a very common error in mathematical writing.
- ⚠Conflating satisfiability and tautology: a formula can be satisfiable (true for some assignment) without being a tautology (true for all assignments). SAT solvers check satisfiability; tautology-checking requires that the negation is unsatisfiable.
- ⚠Misapplying logical equivalence in proofs: two statements being 'equivalent in spirit' or 'equivalent when all variables are positive' is not logical equivalence. Equivalence means identical truth tables under ALL assignments.
- ⚠Thinking 2-SAT and 3-SAT have the same complexity: 2-SAT is polynomial (O(n+m) via SCCs); 3-SAT is NP-complete. One extra literal per clause makes an enormous difference.
Practice
- 01easy
Build a complete truth table generator in Python for arbitrary propositional formulas passed as Python lambda functions. Extend it to automatically classify each formula as a tautology, contradiction, or contingent. Test it on: (a) p ∨ ¬p, (b) p ∧ ¬p, (c) (p → q) ↔ (¬q → ¬p), (d) (p → q) → ((q → r) → (p → r)).
- 02medium
Prove using a chain of logical equivalences (NOT a truth table) that (p → q) ∧ (p → ¬q) ≡ ¬p. Write out each step and name the law used. Then verify your proof computationally with a truth table.
- 03medium
Implement the resolution proof rule for CNF clauses. Given a list of CNF clauses, repeatedly apply resolution to derive new clauses. If you derive the empty clause, the formula is unsatisfiable. Test your implementation on: (1) {p, ¬p} — unsatisfiable, (2) {p ∨ q, ¬p ∨ r, ¬q ∨ r, ¬r} — show the refutation derivation.
- 04medium
Formalize the following English statements in predicate logic with domain = all humans. Then negate each one and simplify until no quantifier has a negation in front of it: (a) Everyone loves someone. (b) Someone is loved by everyone. (c) No one loves themselves. (d) There exist two different people who love each other.
- 05hard
Implement a 2-SAT solver using Kosaraju's or Tarjan's algorithm for finding strongly connected components (SCCs) on the implication graph. An instance is satisfiable if and only if no variable and its negation appear in the same SCC. Test on: (x₁ ∨ x₂) ∧ (¬x₁ ∨ x₃) ∧ (¬x₂ ∨ ¬x₃) ∧ (x₁ ∨ ¬x₃).
Resources
The canonical MIT course covering propositional and predicate logic in depth. Lecture notes, problem sets, and exams freely available. Chapters 1-3 directly cover this module.
Stanford's Logic course (offered through Coursera); covers propositional and predicate logic with interactive exercises and automated grading.
Excellent free textbook from CMU covering propositional and predicate logic through the lens of the Lean proof assistant. Rigorous and readable.
Thorough and well-maintained reference with formal syntax, semantics, proof systems, and complexity sections.
Accessible and competition-oriented treatment of logic; good for building fast intuition and drilling edge cases.
A survey paper on modern SAT solving — connects the theoretical NP-completeness results to the practical algorithms powering today's solvers.
Checkpoints — you can now…
- ✓You can construct a complete truth table for any formula with up to 4 variables, correctly computing each connective including implication and biconditional.
- ✓You can prove or disprove logical equivalence between two formulas using a combination of truth tables and chains of named equivalence laws (De Morgan, contrapositive, implication elimination, distributivity).
- ✓You can convert an arbitrary propositional formula to Conjunctive Normal Form (CNF) via the three-step procedure: eliminate ↔ and →, push negations inward (NNF), distribute ∧ over ∨.
- ✓You can translate English statements about objects and properties into precise predicate logic formulas, correctly choosing ∀/∃ and →/∧ for universal and existential statements respectively.
- ✓You can negate any quantified formula by applying De Morgan's quantifier laws repeatedly, ending with all negations only in front of atomic predicates.
- ✓You can correctly identify the key difference between ∀x ∃y P(x,y) and ∃y ∀x P(x,y), construct examples that distinguish them, and explain the game-theoretic reading of nested quantifiers.
- ✓You can explain why SAT is NP-complete, what this means for worst-case complexity, and why modern SAT solvers are nonetheless practically powerful for industrial-scale problems.
- ✓You can articulate the Curry-Howard correspondence at a high level — that types correspond to propositions and programs to proofs — and give examples in a typed language.