CMPUT-272 · Lesson 4 of 12 · 32 min

Predicate logic and quantifiers

Propositional logic cannot express a statement like every list has a length, or some user has administrator rights. Those statements talk about all or some members of a collection. Predicate logic, also called first-order logic, adds variables, predicates and quantifiers so that we can say such things precisely. It is the language of mathematical definitions, of database queries and of program specifications.

Predicates and domains

A predicate is a statement containing variables that becomes a proposition when the variables are given values. Write \(P(x)\) for x is prime. Then \(P(7)\) is true and \(P(8)\) is false. A predicate can have several variables: \(L(x, y)\) for x is less than y gives \(L(2, 5)\) true and \(L(5, 2)\) false. In programming terms, a predicate is a function that returns a Boolean.

Every variable ranges over a domain (also called the universe of discourse): the set of values it may take. The domain must be stated, because the truth of a quantified statement depends on it. The statement every number has a square root that is also a number is false if the domain is the integers (2 has no integer square root) and true if the domain is the non-negative real numbers.

The two quantifiers

The universal quantifier \(\forall\) means for all. The statement \(\forall x\, P(x)\) is true when \(P(x)\) is true for every \(x\) in the domain. The existential quantifier \(\exists\) means there exists. The statement \(\exists x\, P(x)\) is true when \(P(x)\) is true for at least one \(x\) in the domain. Over a finite domain \(\{a_1, \ldots, a_n\}\), the universal is a big conjunction and the existential is a big disjunction.

\[\forall x\, P(x) \equiv P(a_1) \wedge P(a_2) \wedge \cdots \wedge P(a_n) \qquad \exists x\, P(x) \equiv P(a_1) \vee P(a_2) \vee \cdots \vee P(a_n)\]

Python's built-in functions all and any implement exactly these two quantifiers over a finite collection. all returns True for an empty collection and any returns False, which matches the logic: a universal statement about nothing is vacuously true, and an existential statement about nothing is false.

all and any are the universal and existential quantifiers
nums = [4, 8, 15, 16, 23, 42]
is_even = lambda n: n % 2 == 0
print("for all n, n even?   ", all(is_even(n) for n in nums))
print("exists n, n even?    ", any(is_even(n) for n in nums))
print("for all n, n < 50?   ", all(n < 50 for n in nums))
print("exists n, n > 100?   ", any(n > 100 for n in nums))
print("empty domain: all ->", all(is_even(n) for n in []), " any ->", any(is_even(n) for n in []))
Output
for all n, n even?    False
exists n, n even?     True
for all n, n < 50?    True
exists n, n > 100?    False
empty domain: all -> True  any -> False
The domain changes the truth value
StatementDomainTruth valueReason
∀x (x² ≥ 0)Real numbersTrueSquares are never negative
∃x (x² = 2)IntegersFalseNo integer squares to 2
∃x (x² = 2)Real numbersTruex = √2
∀x (x + 1 > x)IntegersTrueAdding 1 increases any integer
∀x (x + 1 > x)Python int8 values with wraparoundFalse127 + 1 wraps to −128
∃x (x < 0)Natural numbersFalseNatural numbers are non-negative

The fourth and fifth rows make a point that matters in practice. A property that holds for mathematical integers can fail for the fixed-width integers inside a computer, as you saw with two's complement overflow in CMPUT-174 and as C programmers meet in CMPUT-201. Stating the domain is not pedantry; it is part of a correct specification.

Restricted quantifiers

We often want to quantify over part of a domain: every student in the class passed, or some prime is even. There are standard patterns. A universal restricted to elements with property \(S\) uses a conditional: \(\forall x\, (S(x) \to P(x))\). An existential restricted to \(S\) uses a conjunction: \(\exists x\, (S(x) \wedge P(x))\). Getting these the wrong way round is a classic error.

Worked problem

Choosing the right connective

Domain: all people. S(x) means x is a student; P(x) means x passed. Translate (a) every student passed and (b) some student passed.

Show solution
  1. (a) For each person, if they are a student then they passed: ∀x (S(x) → P(x)).
  2. The tempting ∀x (S(x) ∧ P(x)) says everyone is a student and everyone passed, which is far too strong.
  3. (b) Some person is a student and passed: ∃x (S(x) ∧ P(x)).
  4. The tempting ∃x (S(x) → P(x)) is true if anyone at all is not a student, because the conditional is vacuously true for that person.

Answer: (a) ∀x (S(x) → P(x)); (b) ∃x (S(x) ∧ P(x)). Universal goes with →, existential goes with ∧.

Negating quantified statements

Negation interacts with quantifiers through De Morgan's laws for quantifiers. It is not the case that every \(x\) has property \(P\) means some \(x\) lacks it. It is not the case that some \(x\) has property \(P\) means every \(x\) lacks it.

\[\neg \forall x\, P(x) \equiv \exists x\, \neg P(x) \qquad \neg \exists x\, P(x) \equiv \forall x\, \neg P(x)\]

To negate a statement with several quantifiers, move the negation inward one quantifier at a time, flipping each \(\forall\) to \(\exists\) and each \(\exists\) to \(\forall\), and finally negate the predicate using the propositional laws from lesson 3. This mechanical procedure is how you find what a counterexample must look like.

Worked problem

Negating a restricted universal

Negate: every function in the module has a test. Use F(x) for x is a function in the module and T(x) for x has a test.

Show solution
  1. Original: ∀x (F(x) → T(x)).
  2. Negate: ¬∀x (F(x) → T(x)) ≡ ∃x ¬(F(x) → T(x)).
  3. ¬(F(x) → T(x)) ≡ ¬(¬F(x) ∨ T(x)) ≡ F(x) ∧ ¬T(x).
  4. Result: ∃x (F(x) ∧ ¬T(x)).

Answer: Some function in the module has no test. The negation tells you exactly what a counterexample is: one untested function.

Nested quantifiers

Statements with more than one quantifier are where predicate logic becomes powerful and where most errors happen. The order of quantifiers of different kinds matters. Consider the domain of people and \(L(x, y)\) meaning x loves y.

The order of quantifiers changes meaning
FormulaEnglish reading
∀x ∃y L(x, y)Everyone loves someone (possibly different people)
∃y ∀x L(x, y)There is one person whom everyone loves
∃x ∀y L(x, y)Someone loves everyone
∀y ∃x L(x, y)Everyone is loved by someone
∀x ∀y L(x, y)Everyone loves everyone
∃x ∃y L(x, y)Someone loves someone

The difference between \(\forall x\, \exists y\) and \(\exists y\, \forall x\) is the difference between a choice that depends on \(x\) and a single choice that works for all \(x\). For every integer there is a larger integer, \(\forall x\, \exists y\, (y > x)\), is true: take \(y = x + 1\). There is an integer larger than every integer, \(\exists y\, \forall x\, (y > x)\), is false: no largest integer exists. Quantifiers of the same kind can be swapped freely: \(\forall x\, \forall y\) and \(\forall y\, \forall x\) mean the same.

Two panels of arrows from a left column x1, x2, x3 to a right column y1, y2, y3. In the first panel, for all x there exists y, each x has an arrow to some y, with different targets. In the second panel, there exists y for all x, all three x arrows point to the same y2.
For all x there exists y, versus there exists y for all x
Checking nested quantifiers on a finite domain: the witness y depends on x
D = range(5)                      # a small finite domain {0,...,4}
L = lambda x, y: (x + y) % 5 == 0  # "y is an additive inverse of x mod 5"

forall_exists = all(any(L(x, y) for y in D) for x in D)
exists_forall = any(all(L(x, y) for x in D) for y in D)
print("forall x exists y L(x,y):", forall_exists)
print("exists y forall x L(x,y):", exists_forall)
for x in D:
    print(f"  x = {x}: witness y = {[y for y in D if L(x, y)][0]}")
Output
forall x exists y L(x,y): True
exists y forall x L(x,y): False
  x = 0: witness y = 0
  x = 1: witness y = 4
  x = 2: witness y = 3
  x = 3: witness y = 2
  x = 4: witness y = 1

Nested quantifiers are how mathematics defines its central concepts. In calculus, a sequence converges to \(L\) if for every tolerance \(\varepsilon > 0\) there exists an index \(N\) such that for every \(n \ge N\), \(|a_n - L| < \varepsilon\). In algorithm analysis, which you will study in CMPUT-204, a function \(f(n)\) is \(O(g(n))\) if there exist constants \(c > 0\) and \(n_0\) such that for all \(n \ge n_0\), \(f(n) \le c\, g(n)\). Reading such definitions requires exactly the skills of this lesson.

Worked problem

Negating a nested statement

Negate the definition of f(n) = O(g(n)): ∃c ∃n₀ ∀n (n ≥ n₀ → f(n) ≤ c·g(n)), with c > 0 assumed.

Show solution
  1. Move the negation past the quantifiers, flipping each: ∀c ∀n₀ ∃n ¬(n ≥ n₀ → f(n) ≤ c·g(n)).
  2. Negate the conditional: ¬(A → B) ≡ A ∧ ¬B.
  3. Result: ∀c ∀n₀ ∃n (n ≥ n₀ ∧ f(n) > c·g(n)).
  4. In words: no matter which constant and starting point you choose, there is some n past the start where f(n) exceeds c times g(n).

Answer: To prove f is not O(g), show that for every c and n₀ you can find such an n. This is how one proves n² is not O(n).

Translating English and specifications

Translating English into predicate logic forces you to resolve ambiguity. Words such as every, each, all and any usually signal \(\forall\); some, there is, at least one and a usually signal \(\exists\). But English is slippery: a dog is a mammal is universal (every dog), while a dog is barking is existential (some dog). Any can mean either: if any test fails, stop is existential, while any integer squared is non-negative is universal.

Worked problem

Formalizing a uniqueness claim

Formalize: every user has exactly one primary email address. Use U(x) for x is a user and E(x, y) for y is a primary email of x.

Show solution
  1. At least one: ∃y E(x, y).
  2. At most one: ∀y ∀z ((E(x, y) ∧ E(x, z)) → y = z).
  3. Combine for each user: ∀x (U(x) → (∃y E(x, y) ∧ ∀y ∀z ((E(x, y) ∧ E(x, z)) → y = z))).
  4. This is often abbreviated with the uniqueness quantifier ∃!y E(x, y).

Answer: ∀x (U(x) → ∃!y E(x, y)), where ∃! expands to "at least one and at most one".

Predicate logic and databases

SQL is applied predicate logic. A query such as SELECT name FROM users WHERE age >= 18 AND country = 'CA' evaluates a predicate on each row. EXISTS in SQL is the existential quantifier. SQL has no direct FOR ALL, so universal queries are written with double negation: find the students for whom there does not exist a required course they have not taken. That is \(\neg \exists c\, (R(c) \wedge \neg T(s, c))\), which is equivalent to \(\forall c\, (R(c) \to T(s, c))\). You will write such queries in CMPUT-291.

A universal query written directly and in the double-negation form SQL uses
required = {"CMPUT-174", "CMPUT-272", "MATH-125"}
taken = {
    "Ana":   {"CMPUT-174", "CMPUT-272", "MATH-125", "STAT-151"},
    "Ben":   {"CMPUT-174", "MATH-125"},
    "Chloe": {"CMPUT-174", "CMPUT-272", "MATH-125"},
}
for s, courses in taken.items():
    universal = all(c in courses for c in required)                 # for all c: R(c) -> T(s,c)
    double_neg = not any(c not in courses for c in required)        # not exists c: R(c) and not T(s,c)
    print(f"{s:6s} completed all required: {universal}  (double-negation form: {double_neg})")
Output
Ana    completed all required: True  (double-negation form: True)
Ben    completed all required: False  (double-negation form: False)
Chloe  completed all required: True  (double-negation form: True)

Free and bound variables

In \(\forall x\, P(x, y)\) the variable \(x\) is bound by the quantifier, and \(y\) is free. A formula with free variables is a predicate, not a proposition; its truth depends on the free variables. This is the same idea as local and global variables in programming: inside the quantifier, \(x\) is a local name, and renaming it consistently, as in \(\forall z\, P(z, y)\), does not change the meaning. Using the same letter for two different bound variables in one formula is legal but confusing; good style gives each quantifier its own variable.

Common mistakes

  • MistakeTranslating 'every S is P' as ∀x (S(x) ∧ P(x)).

    InsteadUse ∀x (S(x) → P(x)); the conjunction claims everything is an S.

  • MistakeTranslating 'some S is P' as ∃x (S(x) → P(x)).

    InsteadUse ∃x (S(x) ∧ P(x)); the conditional is made true by any non-S.

  • MistakeNegating ∀x P(x) as ∀x ¬P(x).

    InsteadThe negation is ∃x ¬P(x): it takes only one counterexample.

  • MistakeSwapping ∀x ∃y to ∃y ∀x as if nothing changed.

    InsteadMixed quantifiers cannot be swapped; ∃y ∀x is a much stronger claim.

  • MistakeIgnoring the domain.

    InsteadState the domain; the same formula can be true over the reals and false over the integers.

  • MistakeExpecting all([]) to be False.

    InsteadA universal over an empty domain is vacuously true, so all([]) is True.

Summary and key terms

  • A predicate becomes a proposition when its variables get values from a stated domain.
  • ∀x P(x) means P holds for every x; ∃x P(x) means P holds for at least one x.
  • Python's all and any are the universal and existential quantifiers over finite collections.
  • Restricted universals use →; restricted existentials use ∧.
  • ¬∀x P(x) ≡ ∃x ¬P(x) and ¬∃x P(x) ≡ ∀x ¬P(x).
  • The order of mixed quantifiers matters: ∀x ∃y allows y to depend on x; ∃y ∀x does not.
  • SQL queries, Big-O definitions and specifications are written in predicate logic.
Predicate
A statement with variables that becomes a proposition when values are supplied.
Domain
The set of values a variable may take.
Universal quantifier
The symbol ∀, meaning for all.
Existential quantifier
The symbol ∃, meaning there exists at least one.
Uniqueness quantifier
The symbol ∃!, meaning there exists exactly one.
Bound variable
A variable governed by a quantifier.
Free variable
A variable not bound by any quantifier.
Nested quantifiers
Two or more quantifiers in sequence, whose order can change meaning.
Witness
A specific value showing that an existential statement is true.
Counterexample
A specific value showing that a universal statement is false.

Practice problems

Work each problem on paper first, then reveal the answer to check.

  1. Domain: integers. Is ∀x ∃y (x + y = 0) true?

    Show answer

    Yes; for each x take y = −x.

  2. Domain: natural numbers. Is ∃y ∀x (x + y = 0) true?

    Show answer

    No; only x = 0 works with y = 0, so no single y works for every x.

  3. Negate ∃x (x > 0 ∧ x² < 0).

    Show answer

    ∀x (x ≤ 0 ∨ x² ≥ 0).

  4. Translate 'no bug is harmless' with B(x) and H(x).

    Show answer

    ∀x (B(x) → ¬H(x)), equivalently ¬∃x (B(x) ∧ H(x)).

  5. Write Python to test 'every word in words has length at most 10'.

    Show answer

    all(len(w) <= 10 for w in words)

  6. What does any(x < 0 for x in []) return, and why?

    Show answer

    False; an existential over an empty domain has no witness.

  7. Translate 'every node has a parent except the root' using N(x), P(x, y) for y is the parent of x, and r for the root.

    Show answer

    ∀x ((N(x) ∧ x ≠ r) → ∃y P(x, y)).

  8. Negate 'for every ε > 0 there is a δ > 0 that works'.

    Show answer

    There is an ε > 0 such that no δ > 0 works.

  9. Which variable is free in ∃y (x < y)?

    Show answer

    x.

  10. Explain why ∀x ∀y P(x, y) ≡ ∀y ∀x P(x, y).

    Show answer

    Both say P holds for every pair; quantifiers of the same kind commute.

Lesson quiz · 5 questions

Score 60% or more to complete this lesson.

Take the quiz