Documentation

DescriptiveComplexity.LogTime.Machine

Alternating machines with a logarithmic clock, at the level of bits #

The machine side of the bottom of the ladder: an alternating random-access machine whose registers hold addresses of the instance, whose clock is the number of bit positions of those addresses – logarithmic in the size of the universe – and whose deterministic steps are bit operations, never a numeric predicate.

The three parts of a machine #

Why the base has to be this weak, and what it can still do #

Letting the base evaluate an atom of ≤, + or × in one step would make the model a prenex FO(≤, +, ×) sentence in disguise, and the bridge to the logic vacuous. Sweeps and reads are the honest opposite: they see nothing but bits, and the arithmetic has to be built – DescriptiveComplexity.leSweep and DescriptiveComplexity.plusSweep (in DescriptiveComplexity.LogTime.Arith) are the comparison and the ripple-carry addition, exactly as DescriptiveComplexity.HeadArith builds them one resource bound higher. A read is an addressing operation, not an arithmetic one: it computes nothing, and the model still has no product.

Both restrictions are real, and their boundary is worth naming: a sweep carries σ bits of state past each position, so a constant number of sweeps carries a constant number of bits per position, that is, O(log n) bits of trace in total – which is what a first-order formula can guess, one element per bit vector (DescriptiveComplexity.LogTime.Simulate). A machine that could count its positions, or revisit them unboundedly often, would leave that budget, and with it the reach of the simulation.

Without the reads the model would be strictly weaker than AC⁰, and provably so: with a base of sweeps alone every atom is a regular relation of the bit tracks, alternating quantifiers over registers are projections, and the whole model collapses to what a finite automaton reading Nat.card A in binary can decide. That is the reason a read is a primitive here and not a convenience; see DescriptiveComplexity.LogTime.

Boolean expressions over bits #

A Boolean expression over bit variables: what a sweep computes in one step, and what its acceptance condition is. Expressions rather than functions, so that the translation into a formula is a recursion rather than an enumeration of a truth table.

Instances For
    Dependency graph
    Dependency graph
    Dependency graph
    Dependency graph

    Sweeps #

    A sweep: one pass over the bit positions of the universe, from the lowest to the highest, by a finite automaton with σ state bits reading, at each position, one bit of each of the ρ registers.

    • σ : ℕ

      The number of state bits.

    • init : Fin self.σ → Bool

      The state before the lowest position.

    • step : Fin self.σ → BitExpr (Fin self.σ ⊕ Fin ρ)

      The next state: one Boolean expression per state bit, over the old state and the register bits at the current position.

    • acc : BitExpr (Fin self.σ)

      The acceptance condition, read from the state after the last position.

    Instances For
      Dependency graph
      noncomputable def DescriptiveComplexity.Sweep.state {ρ : ℕ} {A : Type} [LinearOrder A] (S : Sweep ρ) (x : Fin ρ → A) :
      ℕ → Fin S.σ → Bool

      The state of a sweep before position i: the automaton starts in DescriptiveComplexity.Sweep.init and takes one step per position, reading the bits of the registers there.

      Equations
      Instances For
        Dependency graph
        @[simp]
        theorem DescriptiveComplexity.Sweep.state_zero {ρ : ℕ} {A : Type} [LinearOrder A] (S : Sweep ρ) (x : Fin ρ → A) :
        S.state x 0 = S.init
        Dependency graph
        theorem DescriptiveComplexity.Sweep.state_succ {ρ : ℕ} {A : Type} [LinearOrder A] (S : Sweep ρ) (x : Fin ρ → A) (i : ℕ) (j : Fin S.σ) :
        S.state x (i + 1) j = BitExpr.eval (Sum.elim (S.state x i) fun (k : Fin ρ) => (orank (x k)).testBit i) (S.step j)
        Dependency graph
        def DescriptiveComplexity.Sweep.Accepts {ρ : ℕ} {A : Type} [LinearOrder A] [Finite A] (S : Sweep ρ) (x : Fin ρ → A) :

        A sweep accepts when its acceptance condition holds of the state left after the last bit position.

        Equations
        Instances For
          Dependency graph

          The deterministic base #

          The deterministic base of a machine: a Boolean combination of sweeps and of queries to the instance. Its cost is a constant number of passes over the bit positions, hence O(log n) steps; nothing here evaluates a numeric predicate.

          Instances For
            Dependency graph
            def DescriptiveComplexity.BaseTest.Holds {L : FirstOrder.Language} {ρ : ℕ} {A : Type} [L.Structure A] [LinearOrder A] [Finite A] :
            BaseTest L ρ → (Fin ρ → A) → Prop

            What a base test says of a tuple of register values.

            Equations
            Instances For
              Dependency graph

              Machines, and the class they decide #

              An alternating machine with a logarithmic clock: a list of registers, each filled by one of the two players, and a deterministic bit-level test of the tuple they leave behind.

              Registers are filled in order, DescriptiveComplexity.LTMachine.pol i telling which player fills the i-th; a block of the classical normal form is a maximal run of equal polarities, and the number of alternations is the number of changes along the list.

              • regs : ℕ

                The number of registers, that is, of guessed addresses.

              • pol : Fin self.regs → Bool

                Who fills each register: true existentially, false universally.

              • base : BaseTest L self.regs

                The deterministic base test.

              Instances For
                Dependency graph
                def DescriptiveComplexity.prefixHolds {A : Type} (m : ℕ) :
                (Fin m → Bool) → ((Fin m → A) → Prop) → Prop

                The quantifier prefix of a machine, peeled from the innermost register outwards.

                Equations
                Instances For
                  Dependency graph

                  Prenexing: what a quantifier prefix does to the connectives #

                  The three lemmas a bit-level definability API needs, so that a construction built from the connectives and the quantifiers can be read off as a single prefix over a quantifier-free kernel. They are about prefixHolds alone, and they are what replaces a normal-form theorem: negation dualizes a prefix, a prefix absorbs a side condition it does not mention, and two prefixes concatenate.

                  theorem DescriptiveComplexity.prefixHolds_not {A : Type} (m : ℕ) (pol : Fin m → Bool) (P : (Fin m → A) → Prop) :
                  ¬prefixHolds m pol P ↔ prefixHolds m (fun (j : Fin m) => !pol j) fun (v : Fin m → A) => ¬P v

                  Negating a prefix dualizes it: every quantifier flips, and the body is negated. Classical, as prenexing is.

                  Dependency graph
                  theorem DescriptiveComplexity.prefixHolds_and_const {A : Type} [Nonempty A] (m : ℕ) (pol : Fin m → Bool) (P : (Fin m → A) → Prop) (Q : Prop) :
                  (prefixHolds m pol fun (v : Fin m → A) => P v ∧ Q) ↔ prefixHolds m pol P ∧ Q

                  A prefix absorbs a side condition it does not mention. The universal steps are what needs the universe to be nonempty.

                  Dependency graph
                  theorem DescriptiveComplexity.prefixHolds_add {A : Type} (k₁ k₂ : ℕ) (pol : Fin (k₁ + k₂) → Bool) (P : (Fin k₁ → A) → (Fin k₂ → A) → Prop) :
                  (prefixHolds (k₁ + k₂) pol fun (u : Fin (k₁ + k₂) → A) => P (fun (i : Fin k₁) => u (Fin.castAdd k₂ i)) fun (j : Fin k₂) => u (Fin.natAdd k₁ j)) ↔ prefixHolds k₁ (fun (i : Fin k₁) => pol (Fin.castAdd k₂ i)) fun (v : Fin k₁ → A) => prefixHolds k₂ (fun (j : Fin k₂) => pol (Fin.natAdd k₁ j)) (P v)

                  Two prefixes concatenate: a prefix of k₁ + k₂ variables, read on a body that splits its valuation into the first k₁ and the last k₂, is the first prefix wrapped around the second. This is the only place the index arithmetic of Fin appears; everything downstream uses it as a black box.

                  Dependency graph

                  The machine accepts the instance when the two players, filling the registers in order, leave a tuple passing the base test.

                  Equations
                  Instances For
                    Dependency graph

                    The class decided by the machines of this section: the analogue, at the bottom of the ladder, of DescriptiveComplexity.LOGSPACE's automata. A problem is decidable in constant-alternation logarithmic time – the level Σₖ-TIME(log n) of the logarithmic-time hierarchy fixed by the machine's own polarities – when one machine decides it on every nonempty finite ordered structure.

                    Equations
                    • One or more equations did not get rendered due to their size.
                    Instances For
                      Dependency graph