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 builtDescriptiveComplexity.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
    theorem DescriptiveComplexity.BitExpr.holds_iff_eval {V : Type} (val : VBool) (e : BitExpr V) :
    Holds (fun (v : V) => val v = true) e eval val e = true

    The two readings agree.

    Dependency graph
    Dependency graph
    theorem DescriptiveComplexity.BitExpr.eval_mapVar {V W : Type} (f : VW) (val : WBool) (e : BitExpr V) :
    eval val (mapVar f e) = eval (val f) e

    Renaming bit variables is composition on the assignment.

    Dependency graph
    theorem DescriptiveComplexity.BitExpr.holds_congr {V : Type} {val val' : VProp} (h : ∀ (v : V), val v val' v) (e : BitExpr V) :
    Holds val e Holds val' e

    Holding only depends on the assignment pointwise.

    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
          def DescriptiveComplexity.Sweep.relabel {ρ ρ' : } (S : Sweep ρ) (f : Fin ρFin ρ') :
          Sweep ρ'

          Renaming the registers of a sweep: what lets a sweep built once – the comparison, the addition – be run on any registers of a larger machine.

          Equations
          Instances For
            Dependency graph
            theorem DescriptiveComplexity.Sweep.state_relabel {ρ : } {A : Type} [LinearOrder A] {ρ' : } (S : Sweep ρ) (f : Fin ρFin ρ') (x : Fin ρ'A) (i : ) (j : Fin S.σ) :
            (S.relabel f).state x i j = S.state (fun (k : Fin ρ) => x (f k)) i j

            A renamed sweep runs the original one on the renamed registers.

            Dependency graph
            theorem DescriptiveComplexity.Sweep.accepts_relabel {ρ : } {A : Type} [LinearOrder A] [Finite A] {ρ' : } (S : Sweep ρ) (f : Fin ρFin ρ') (x : Fin ρ'A) :
            (S.relabel f).Accepts x S.Accepts fun (k : Fin ρ) => x (f k)

            Acceptance of a renamed sweep.

            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.regsBool

                  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 mBool)((Fin mA)Prop)Prop

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

                  Equations
                  Instances For
                    Dependency graph
                    theorem DescriptiveComplexity.prefixHolds_congr {A : Type} (m : ) (pol : Fin mBool) {P Q : (Fin mA)Prop} :
                    (∀ (v : Fin mA), P v Q v) → (prefixHolds m pol P prefixHolds m pol Q)

                    Prefixes only depend on their body pointwise.

                    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 mBool) (P : (Fin mA)Prop) :
                    ¬prefixHolds m pol P prefixHolds m (fun (j : Fin m) => !pol j) fun (v : Fin mA) => ¬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 mBool) (P : (Fin mA)Prop) (Q : Prop) :
                    (prefixHolds m pol fun (v : Fin mA) => 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
                    theorem DescriptiveComplexity.prefixHolds_const_true {A : Type} (m : ) (P : (Fin mA)Prop) :
                    prefixHolds m (fun (x : Fin m) => true) P ∃ (w : Fin mA), P w

                    A block of equal polarity is one quantifier over the tuple it fills: the existential case.

                    Dependency graph
                    theorem DescriptiveComplexity.prefixHolds_const_false {A : Type} (m : ) (P : (Fin mA)Prop) :
                    prefixHolds m (fun (x : Fin m) => false) P ∀ (w : Fin mA), P w

                    A block of equal polarity is one quantifier: the universal case.

                    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