u/KaleidoscopeLate2505

Proving the deduction theorem by induction, inside a Hilbert system with custom axioms. In particular, where the induction recurses over those custom axioms.
▲ 3 r/logic

Proving the deduction theorem by induction, inside a Hilbert system with custom axioms. In particular, where the induction recurses over those custom axioms.

Introduction:
Note: This question is a more detailed elaboration to this older post on SO: https://stackoverflow.com/q/79947597/32395400.

Hello, I am reading Introduction to Mathematical Logic, 4th edition by Elliott Mendelson. Chapter 1, section 4.

I am striving to prove the deduction theorem by induction in my Hilbert system (written in python), but have been incapable of achieving this goal thus far.

I am having a few troubles in particular; specifically with representing sets where the number of elements is unknown (I will explain below), and representing proofs where the number of steps is unknown (again, this will make sense after I share the target proof).

The target proof:
"Let C1, ..., Cn be a proof of C from Γ ∪ B, where Cn is C. We will prove by induction on j that Γ ⊢ (B → Cj) for 1 ≤ j ≤ n. C1 must be either in Γ or an axiom of our system, or B itself. By axiom 1, C1 → (B → C1) is an axiom. Hence, in the first two cases, by MP, Γ ⊢ B → C1. For the third case, when C1 is B, we have ⊢ B → C1 by lemma 1.8, and therefore, Γ ⊢ B → C1. This takes care of the base case (j=1). Assume now that Γ ⊢ B → Ck for all k < j. Either Cj is an axiom, or Cj is in Γ, or Cj is B, or, Cj follows by modus ponens from some Cl and Cm, where l < j, m < j, and Cm has the form Cl → Cj. In the first three cases, Γ ⊢ B → Cj just as in the base case. In the last case, we have by inductive hypothesis that Γ ⊢ B → Cl and Γ ⊢ B → (Cl → Cj). But, by axiom 2, ⊢ (B → (Cl → Cj)) → ((B → Cl) → (B → Cj)). Hence, by MP, Γ ⊢ (B → Cl) → (B → Cj), and, again by MP, Γ ⊢ B → Cj. Thus, the proof by induction is complete." --- An almost verbatim copy of Mendelson.

Research:
I have seen the post https://proofassistants.stackexchange.com/a/4378/7947, but Coq does it in a way that is not consistent with the book I am reading. Just one example would be the use of quantifiers, which Mendelson does not introduce until later.

I would really much prefer a simpler-without sacrificing correctness!-solution that I could implement in my system without elaborate coding. Currently my code is less that 300 lines, and I would like to keep it short and powerful, and not have ad hoc code for everything.

The problems:
In a typical induction proof, we would prove the base case, then assume the inductive hypothesis and prove that the next step follows from that. I could do that in a CAS-like software, but I haven't been able to map those skills onto my Hilbert system. This is partly because they use different data structures.

My Hilbert system currently works for logical statements (a logical statement is a set of assumptions and a well formed statement which follows from them) with assumption sets that are finite and have known elements. The target proof however depends on a set whose elements are unknown, other than that they have the collective property of contributing to the proof's validity. It would therefore be wrong to have discrete elements in this set, and instead we must work with them abstractly.

Similarly, the Γ ∪ B → C proof has an unknown number of steps. It has at least one (the conclusion), but we don't know how many there are before that, so enummerating steps would feel wrong here. Instead we must work with them abstractly too. There might be one; there might be five; heck, there could be five hundred. That's why we should avoid using known elements, besides possibly the conclusion which at least we know exists. NOTE: The conclusion is an abstract conclusion here, not a typical (known) wfs. It could be substituted with any (known) wfs in this setup, so it would be bad to treatit as an individual wfs because it is meant to speak of them generally.

The final problem I see is how to account for the properties the step(s) of the "proof", without knowing what they are. This is especially complicated, because these properties would depend on axioms and theorems already present in the Hilbert system (which the user declares). This is worthy of more attention actually, so let me explain please.

What the solution would need to do:
It would need to prove the deduction theorem by induction, so that it can be used later on without repeating the logic. Deduction can be carried out recursively, and that is not the goal. We will prove it recursively, but when we actually apply it, it should just take an assumption and make it the antecedent of an implication.

The proof should be reproducible for other types of similar induction proofs too, even though I am not aware of any currently.

The current system:

from dataclasses import dataclass, field
from tabulate import tabulate
import textwrap

# see https://stackoverflow.com/a/79946254
@dataclass(frozen=True)
class Wfs: 
    def subs(self, mapping: dict["StatLett", "Wfs"]) -&gt; "Wfs":
        raise NotImplementedError

@dataclass(frozen=True)
class Symb(Wfs): pass 

@dataclass(frozen=True)
class PrimConn(Symb): pass

@dataclass(frozen=True)
class Not(PrimConn):
    arg: Wfs
    def __repr__(self):
        return f"¬({self.arg})"
    def subs(self, mapping: dict[StatLett, Wfs]) -&gt; Wfs:
        return Not(self.arg.subs(mapping))
@dataclass(frozen=True)
class To(PrimConn):
    arg1: Wfs
    arg2: Wfs
    def __repr__(self):
        return f"({self.arg1} → {self.arg2})"
    def subs(self, mapping: dict[StatLett, Wfs]) -&gt; Wfs:
        return To(self.arg1.subs(mapping), self.arg2.subs(mapping))

@dataclass(frozen=True)
class StatLett(Symb):
    name: str
    def __repr__(self):
        return self.name
    def subs(self, mapping: dict[StatLett, Wfs]) -&gt; Wfs:
        return mapping.get(self, self)

@dataclass(frozen=True)
class FiniteSet:
    # see page 5 explanation
    elements: list[Wfs]
    def __contains__(self, other):
        flag = False
        for element in self.elements:
            if element == other:
                flag = True
                break 
        return flag
    def issubset(self, other):
        assert isinstance(other, FiniteSet)
        for element in self.elements:
            if element not in other.elements:
                return False
        return True
    def __eq__(self, other):
        if not isinstance(other, FiniteSet):
            return False 
        return self.issubset(other) and other.issubset(self)
    def ispropersubset(self, other):
        return self.issubset(other) and not self == other
    def union(self, other):
        assert isinstance(other, FiniteSet)
        new_set = [element for element in self.elements]
        for element in other.elements:
            if element not in self:
                new_set.append(element)
        return FiniteSet(new_set)
    def intersect(self, other):
        assert isinstance(other, FiniteSet)
        new_set = []
        for element in self.elements:
            if element in other:
                new_set.append(element)
        return FiniteSet(new_set)
    def __sub__(self, other):
        # in self, but not in other
        assert isinstance(other, FiniteSet)
        new_set = []
        for element in self.elements:
            if element not in other.elements:
                new_set.append(element)
        return FiniteSet(new_set)
    def __repr__(self):
        return ", ".join(repr(i) for i in self.elements)
@dataclass(frozen=True)
class LogiStat:
    assu: FiniteSet
    conc: Wfs
    vdash: bool
    def __repr__(self):
        
        elems = self.assu.elements
        lengt = len(elems)

        if lengt == 0 and self.vdash == False:
            return f"{self.conc}"
        elif lengt == 0 and self.vdash == True:
            return f"⊢ {self.conc}"
        elif lengt &gt; 0 and self.vdash == True:
            return f"{self.assu} ⊢ {self.conc}"
    def subs(self, mapping: dict[StatLett, Wfs]) -&gt; LogiStat:
        return LogiStat(
            FiniteSet([i.subs(mapping) for i in self.assu.elements]),
            self.conc.subs(mapping),
            True
        )
    

@dataclass
class ProofTable:
    table: list[str] = field(
        default_factory=lambda: [
            [
                "Line",
                "Reasoning", 
                "Logic", 
                "Label"
            ]
        ]
    )
    def __repr__(self):
        width = 25  # max line width per cell

        wrapped_table = [
            [textwrap.fill(str(cell), width=width) for cell in row]
            for row in self.table
        ]

        return tabulate(
            wrapped_table,
            headers="firstrow",
            tablefmt="plain"
        )
    def axiom(self, stat: Wfs):
        self.table.append(
            [
                len(self.table),
                "Axiom", 
                LogiStat(
                    FiniteSet([]), 
                    stat, 
                    False
                ), 
                ""
            ]
        )
    def index(self, location: int | str):
        if isinstance(location, int):
            return len(self.table) - location 
        for i, row in enumerate(self.table):
                if row[3] == location:
                    return i
        raise ValueError(f"Label '{location}' not found")
    def label(self, label: str):
        self.table[len(self.table) - 1][3] = label
    def subs(self, prev: int | str, mapping: dict[StatLett, Wfs]) -&gt; Wfs:
        idx = self.index(prev)
        old_logic = self.table[idx][2]
        new_logic = old_logic.subs(mapping)
        self.table.append(
            [
                len(self.table),
                f"Subs({prev}, {mapping})",
                new_logic,
                ""
            ]
        )
    def MP(self, line1: int | str, line2: int | str):
        i1 = self.index(line1)
        i2 = self.index(line2)
        l1 = self.table[i1][2]
        l2 = self.table[i2][2]
        c1 = l1.conc
        c2 = l2.conc
        assert isinstance(c1, Wfs) and isinstance(c2, To)
        assert c1 == c2.arg1
        self.table.append(
            [
                len(self.table),
                f"MP({line1}, {line2})",
                LogiStat(
                    l1.assu.union(l2.assu),
                    c2.arg2,
                    True
                ),
                ""
            ]
        )
    def hypothesis(self, stat: Wfs):
        self.table.append(
            [
                len(self.table),
                "Hypothesis",
                LogiStat(FiniteSet([stat]), stat, True),
                ""
            ]
        )
    def cut(self, assu_line, proved_line):
        i1 = self.index(assu_line)
        i2 = self.index(proved_line)
        proved = self.table[i2][2].conc
        assus = self.table[i1][2].assu
        assert proved in assus
        new_assus = (assus - FiniteSet([proved])).union(self.table[i2][2].assu)
        self.table.append(
            [
                len(self.table),
                f"Cut({assu_line}, {proved_line})",
                LogiStat(new_assus, self.table[i1][2].conc, True),
                ""
            ]
        )
        

def main():
    p = ProofTable()

    b = StatLett("B") 
    c = StatLett("C")
    d = StatLett("D")

    p.axiom(To(b, To(c, b)))
    p.label("Axiom (A1)")

    p.axiom(To(
        To(b, To(c, d)),
        To(To(b, c), To(b, d))
    ))
    p.label("Axiom (A2)")

    p.axiom(To(
        To(Not(c), Not(b)),
        To(To(Not(c), b), c)
    ))
    p.label("Axiom (A3)")

    p.subs("Axiom (A2)", {c: To(b, b), d: b})
    p.subs("Axiom (A1)", {c: To(b, b)})
    p.MP(1, 2)
    p.subs("Axiom (A1)", {c: b})
    p.MP(1, 2)
    p.label("Lemma 1.8")

    p.subs("Lemma 1.8", {b: Not(b)})
    p.subs("Axiom (A3)", {c: b})
    p.MP(2, 1)
    p.label("Ex 1.47 (a)")

    p.subs("Axiom (A1)", {b: To(c, d), c: b})
    p.hypothesis(To(c, d))
    p.MP(1, 2)
    p.MP(1, "Axiom (A2)")
    p.hypothesis(To(b, c))
    p.MP(1, 2)
    p.label("Ex 1.47 (b)")

    p.hypothesis(To(b, To(c, d)))
    p.MP(1, "Axiom (A2)")
    p.subs("Axiom (A1)", {b: c, c: b})
    p.subs("Ex 1.47 (b)", {b: c, c: To(b, c), d: To(b, d)})
    p.cut(1, 2)
    p.cut(1, 4)
    p.label("Ex 1.47 (c)")

    p.subs("Ex 1.47 (c)", {b: To(Not(c), Not(b)), c: To(Not(c), b), d: c})
    p.cut(1, "Axiom (A3)")
    p.subs("Axiom (A1)", {c: Not(c)})
    p.subs("Ex 1.47 (b)", {c: To(Not(c), b), d: To(To(Not(c), Not(b)), c)})
    p.cut(1, 3)
    p.cut(1, 3)
    p.subs("Ex 1.47 (c)", {c: To(Not(c), Not(b)), d: c})
    p.cut(1, 2)
    p.label("Ex 1.47 (d)")

    print(p)    

if __name__ == "__main__":
    main()

The output

Line  Reasoning                  Logic                      Label
     1  Axiom                      (B → (C → B))              Axiom (A1)
     2  Axiom                      ((B → (C → D)) → ((B → C)  Axiom (A2)
                                   → (B → D)))
     3  Axiom                      ((¬(C) → ¬(B)) → ((¬(C) →  Axiom (A3)
                                   B) → C))
     4  Subs(Axiom (A2), {C: (B →  ⊢ ((B → ((B → B) → B)) →
        B), D: B})                 ((B → (B → B)) → (B →
                                   B)))
     5  Subs(Axiom (A1), {C: (B →  ⊢ (B → ((B → B) → B))
        B)})
     6  MP(1, 2)                   ⊢ ((B → (B → B)) → (B →
                                   B))
     7  Subs(Axiom (A1), {C: B})   ⊢ (B → (B → B))
     8  MP(1, 2)                   ⊢ (B → B)                  Lemma 1.8
     9  Subs(Lemma 1.8, {B:        ⊢ (¬(B) → ¬(B))
        ¬(B)})
    10  Subs(Axiom (A3), {C: B})   ⊢ ((¬(B) → ¬(B)) → ((¬(B)
                                   → B) → B))
    11  MP(2, 1)                   ⊢ ((¬(B) → B) → B)         Ex 1.47 (a)
    12  Subs(Axiom (A1), {B: (C →  ⊢ ((C → D) → (B → (C →
        D), C: B})                 D)))
    13  Hypothesis                 (C → D) ⊢ (C → D)
    14  MP(1, 2)                   (C → D) ⊢ (B → (C → D))
    15  MP(1, Axiom (A2))          (C → D) ⊢ ((B → C) → (B →
                                   D))
    16  Hypothesis                 (B → C) ⊢ (B → C)
    17  MP(1, 2)                   (B → C), (C → D) ⊢ (B →    Ex 1.47 (b)
                                   D)
    18  Hypothesis                 (B → (C → D)) ⊢ (B → (C →
                                   D))
    19  MP(1, Axiom (A2))          (B → (C → D)) ⊢ ((B → C)
                                   → (B → D))
    20  Subs(Axiom (A1), {B: C,    ⊢ (C → (B → C))
        C: B})
    21  Subs(Ex 1.47 (b), {B: C,   (C → (B → C)), ((B → C) →
        C: (B → C), D: (B → D)})   (B → D)) ⊢ (C → (B → D))
    22  Cut(1, 2)                  ((B → C) → (B → D)) ⊢ (C
                                   → (B → D))
    23  Cut(1, 4)                  (B → (C → D)) ⊢ (C → (B →  Ex 1.47 (c)
                                   D))
    24  Subs(Ex 1.47 (c), {B:      ((¬(C) → ¬(B)) → ((¬(C) →
        (¬(C) → ¬(B)), C: (¬(C) →  B) → C)) ⊢ ((¬(C) → B) →
        B), D: C})                 ((¬(C) → ¬(B)) → C))
    25  Cut(1, Axiom (A3))         ⊢ ((¬(C) → B) → ((¬(C) →
                                   ¬(B)) → C))
    26  Subs(Axiom (A1), {C:       ⊢ (B → (¬(C) → B))
        ¬(C)})
    27  Subs(Ex 1.47 (b), {C:      (B → (¬(C) → B)), ((¬(C)
        (¬(C) → B), D: ((¬(C) →    → B) → ((¬(C) → ¬(B)) →
        ¬(B)) → C)})               C)) ⊢ (B → ((¬(C) → ¬(B))
                                   → C))
    28  Cut(1, 3)                  (B → (¬(C) → B)) ⊢ (B →
                                   ((¬(C) → ¬(B)) → C))
    29  Cut(1, 3)                  ⊢ (B → ((¬(C) → ¬(B)) →
                                   C))
    30  Subs(Ex 1.47 (c), {C:      (B → ((¬(C) → ¬(B)) → C))
        (¬(C) → ¬(B)), D: C})      ⊢ ((¬(C) → ¬(B)) → (B →
                                   C))
    31  Cut(1, 2)                  ⊢ ((¬(C) → ¬(B)) → (B →    Ex 1.47 (d)
                                   C))

Why I didn't attempt a solution for you:
I have attempted solutions to this on my own, but they are all insufficient, and I believe that they would do more to distract from the goal than to help achieve it.

u/KaleidoscopeLate2505 — 10 hours ago

How do you systematically decide what mathematics is worth illustrating within a given field/topic/concept?

I'm interested in how people think about coverage when creating mathematical illustrations. Not "how do you draw everything?", but rather how do you survey a field and identify the kinds of diagrams that actually matter, instead of stopping with the first standard examples?

For context, I've spent years making mathematical figures and writing material with diagrams in subjects like linear algebra and calculus. Some of that work involved building diagram progressions. For example, starting from simple objects like points, line segments, and vectors, then moving into bases, transformations, parameterized surfaces, and more technical spatial figures.

Oddly, once I got reasonably good at making the visuals themselves, the harder question became deciding what to illustrate and how to approach that systematically.

So I'm asking about the method.

  • How do you break a subject down so you can see what is worth drawing?
  • How do you make the whole thing an actual process, instead of a bunch of disconnected pictures?

I'm almost looking for a way to tabulate the kinds of diagrams a topic naturally gives rise to.

I'm not necessarily asking for examples from one specific field unless they help explain the broader approach. I'm more interested in how people mentally organize a subject and turn that into a principled strategy for generating illustrations.

reddit.com
u/KaleidoscopeLate2505 — 1 month ago
▲ 5 r/logic+1 crossposts

I am trying to do an exercise in Elliot Mendelson's Intoduction to mathematical logic.

It is exercise 1.47 (d); the proof of ⊢ (¬(C) → ¬(B)) → (B → C).

It is the fourth in a series of exercises following a reflexivity lemma, which follows three axioms of a formal system. This is the formal system L:

1. The symbols of L are ¬, →, (, ), and the statement letters A_1,...,A_k.

2.  
    - All statement letters are wfs.
    - If B and C are wfs, then so are ¬B and B→C.

3. If B, C, and D are wfs of L, then the following are axioms of L: 
    - B→(C→B)
    - (B→(C→D))→((B→C)→(B→D))
    - ((¬C)→(¬B))→(((¬C)→B)→C)

4. The only rule of inference is modus ponens.

I have been fortunate to own a book whose previous owner jotted down the proofs of the first three exercises - in a different language too, as well as to have had help learning the syntax and methodology on math.sx. This has allowed me to complete the derivation of these first three proofs proofs.

However, having used this crutch, I have not developed the intuition which guides the justification synthesis. *How am I supposed to synthesize these types of derivations when I encounter future proofs on my own?

Even though I can follow the logic in (a) -- (c), I cannot synthesize my own pattern of it to solve (d).

This is what I have derived to far: (It is computer generated by a theorem proving algorithm I wrote). I don't know how to be creative/smart when combining previous theorems and axioms with modus ponens. My goal is not only to solve (d), but to be able to solve other theorems on my own without assistance.

Note: I use "IE" to mean modus ponens, because it also means implication elimination.

Note: Cut means that an assumption is removed when it is the proposition of a known assumptionless theorem.

Note: I have not labeled the steps as I reached them in the table itself, but they are line 8. (lemma, given), line 11. (a), line 17. (b), and line 23. (c).

Line.    Logic                                                  Reasoning
   1.    (B → (C → B))                                          axiom
   2.    ((B → (C → D)) → ((B → C) → (B → D)))                  axiom
   3.    ((¬(C) → ¬(B)) → ((¬(C) → B) → C))                     axiom
   4.    ⊢ ((B → ((B → B) → B)) → ((B → (B → B)) → (B → B)))    2.[C:=(B → B), D:=B]
   5.    ⊢ (B → ((B → B) → B))                                  1.[C:=(B → B)]
   6.    ⊢ ((B → (B → B)) → (B → B))                            IE[5, 4]
   7.    ⊢ (B → (B → B))                                        1.[C:=B]
   8.    ⊢ (B → B)                                              IE[7, 6]
   9.    ⊢ (¬(B) → ¬(B))                                        8.[B:=¬(B)]
  10.    ⊢ ((¬(B) → ¬(B)) → ((¬(B) → B) → B))                   3.[C:=B]
  11.    ⊢ ((¬(B) → B) → B)                                     IE[9, 10]
  12.    ⊢ ((C → D) → (B → (C → D)))                            1.[B:=(C → D), C:=B]
  13.    (C → D) ⊢ (C → D)                                      assumption
  14.    (C → D) ⊢ (B → (C → D))                                IE[13, 12]
  15.    (C → D) ⊢ ((B → C) → (B → D))                          IE[14, 2]
  16.    (B → C) ⊢ (B → C)                                      assumption
  17.    (B → C), (C → D) ⊢ (B → D)                             IE[16, 15]
  18.    (B → (C → D)) ⊢ (B → (C → D))                          assumption
  19.    (B → (C → D)) ⊢ ((B → C) → (B → D))                    IE[18, 2]
  20.    ⊢ (C → (B → C))                                        1.[B:=C, C:=B]
  21.    ((B → C) → (B → D)), (C → (B → C)) ⊢ (C → (B → D))     17.[B:=C, C:=(B → C), D:=(B → D)]
  22.    ((B → C) → (B → D)) ⊢ (C → (B → D))                    Cut[21, 20]
  23.    (B → (C → D)) ⊢ (C → (B → D))                          Cut[22, 19]
reddit.com
u/KaleidoscopeLate2505 — 1 month ago