Documentation

EconCSLib.Foundation.CostM

EconCSLib.Foundation.CostM #

CostM C A is the writer monad over an arbitrary additive monoid C: a value of type A together with an accumulated cost in C. Sequential composition (>>=) adds the cost components via the monoid operation.

Design #

The cost type C is deliberately abstract. Picking C is what selects which resource is being measured; the monad itself is the same for all of them.

The structure field is cost, not time: time is one cost among many and has no architectural privilege.

This is the standard writer-monad-over-a-monoid pattern; see Danielsson, Lightweight Semiformal Time Complexity Analysis for Purely Functional Data Structures (POPL 2008).

Discipline #

Cost annotations are trusted: the elaborator does not check that tick calls correspond to actual work. Each algorithm file must document its cost model — what counts as a unit, what is free, whether recursive calls are charged — and the author must tick accordingly.

Proofs separate cleanly:

The two are independent: changing the cost model never disturbs correctness proofs, and refactoring the algorithm preserves cost annotations by construction.

Main definitions #

Notation #

Attribution #

The monad core of this file is adapted from TimeM in the leanprover/cslib project, file Cslib/Algorithms/Lean/TimeM.lean (Apache License 2.0).

Original authors of TimeM: Sorrachai Yingchareonthawornhcai and Eric Wieser. Original copyright © 2025 Sorrachai Yingchareonthawornhcai.

What is adapted from upstream #

What is added in this file (not upstream as of the pinned commit) #

structure CostM (C : Type u_1) (A : Type u_2) :
Type (max u_1 u_2)

Writer monad over an arbitrary additive monoid C.

A CostM C A is a return value of type A together with an accumulated cost in C. The cost field aggregates via + and 0 of C. See the file docstring for the design rationale and for the choices of C that recover specific complexity measures.

  • ret : A

    The result of the computation.

  • cost : C

    The accumulated cost in C.

Instances For
    theorem CostM.ext {C : Type u_1} {A : Type u_2} {x y : CostM C A} (ret : x.ret = y.ret) (cost : x.cost = y.cost) :
    x = y
    theorem CostM.ext_iff {C : Type u_1} {A : Type u_2} {x y : CostM C A} :
    x = y x.ret = y.ret x.cost = y.cost
    def CostM.pure {C : Type u_1} {A : Type u} [Zero C] (a : A) :
    CostM C A

    Lift a pure value at zero cost.

    Equations
    Instances For
      @[implicit_reducible]
      instance CostM.instPure {C : Type u_1} [Zero C] :
      Equations
      def CostM.bind {C : Type u_1} {A B : Type u} [Add C] (m : CostM C A) (f : ACostM C B) :
      CostM C B

      Sequential composition. The cost of m >>= f is m.cost + (f m.ret).cost.

      Equations
      Instances For
        @[implicit_reducible]
        instance CostM.instBind {C : Type u_1} [Add C] :
        Equations
        @[implicit_reducible]
        instance CostM.instFunctor {C : Type u_1} :
        Equations
        @[implicit_reducible]
        instance CostM.instSeq {C : Type u_1} [Add C] :
        Equations
        @[implicit_reducible]
        instance CostM.instSeqLeft {C : Type u_1} [Add C] :
        Equations
        @[implicit_reducible]
        instance CostM.instSeqRight {C : Type u_1} [Add C] :
        Equations
        @[implicit_reducible]
        instance CostM.instMonad {C : Type u_1} [Zero C] [Add C] :
        Equations
        • One or more equations did not get rendered due to their size.

        simp lemmas — return component #

        @[simp]
        theorem CostM.ret_pure {C : Type u_1} {A : Type u} [Zero C] (a : A) :
        (pure a).ret = a
        @[simp]
        theorem CostM.ret_bind {C : Type u_1} {A B : Type u} [Add C] (m : CostM C A) (f : ACostM C B) :
        (m >>= f).ret = (f m.ret).ret
        @[simp]
        theorem CostM.ret_map {C : Type u_1} {A B : Type u} (f : AB) (x : CostM C A) :
        (f <$> x).ret = f x.ret
        @[simp]
        theorem CostM.ret_seqLeft {C : Type u_1} {A B : Type u} [Add C] (x : CostM C A) (y : UnitCostM C B) :
        (x <* y ()).ret = x.ret
        @[simp]
        theorem CostM.ret_seqRight {C : Type u_1} {A B : Type u} [Add C] (x : CostM C A) (y : UnitCostM C B) :
        (x *> y ()).ret = (y ()).ret
        @[simp]
        theorem CostM.ret_seq {C : Type u_1} {A B : Type u} [Add C] (f : CostM C (AB)) (x : UnitCostM C A) :
        (f <*> x ()).ret = f.ret (x ()).ret

        simp lemmas — cost component #

        @[simp]
        theorem CostM.cost_pure {C : Type u_1} {A : Type u} [Zero C] (a : A) :
        (pure a).cost = 0
        @[simp]
        theorem CostM.cost_bind {C : Type u_1} {A B : Type u} [Add C] (m : CostM C A) (f : ACostM C B) :
        (m >>= f).cost = m.cost + (f m.ret).cost
        @[simp]
        theorem CostM.cost_map {C : Type u_1} {A B : Type u} (f : AB) (x : CostM C A) :
        (f <$> x).cost = x.cost
        @[simp]
        theorem CostM.cost_seqLeft {C : Type u_1} {A B : Type u} [Add C] (x : CostM C A) (y : UnitCostM C B) :
        (x <* y ()).cost = x.cost + (y ()).cost
        @[simp]
        theorem CostM.cost_seqRight {C : Type u_1} {A B : Type u} [Add C] (x : CostM C A) (y : UnitCostM C B) :
        (x *> y ()).cost = x.cost + (y ()).cost
        @[simp]
        theorem CostM.cost_seq {C : Type u_1} {A B : Type u} [Add C] (f : CostM C (AB)) (x : UnitCostM C A) :
        (f <*> x ()).cost = f.cost + (x ()).cost

        CostM C is a lawful monad whenever C is an additive monoid.

        tick and notation #

        def CostM.tick {C : Type u_1} (c : C) :

        Charge a cost of c, returning unit. Use inside a do-block.

        Equations
        Instances For
          @[simp]
          theorem CostM.ret_tick {C : Type u_1} (c : C) :
          (tick c).ret = ()
          @[simp]
          theorem CostM.cost_tick {C : Type u_1} (c : C) :
          (tick c).cost = c

          ✓[c] body adds a cost of c and then runs body.

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

            body is ✓[1] body. The use site must provide [OfNat C 1].

            Equations
            Instances For
              @[implicit_reducible]
              instance CostM.instCoeHead {C : Type u_1} {A : Type u} :
              CoeHead (CostM C A) A

              Coerce a CostM C A to its return value, dropping the cost. Use this in contexts where the expected type is A; pair with .cost for complexity proofs. The pattern mirrors Subtype.val-style projection.

              Equations

              Parallel composition #

              par m₁ m₂ runs two independent CostM computations side by side; the cost is combined via (sup). This is not a monadic operation — neither operand's value feeds into the other. Use this only when the two sides are data-independent.

              The choice of for the cost models the "depth" / branch-worst-case view: both branches run concurrently, so the joint cost is the worse of the two. For combined work-and-depth tracking (cost type × with + on work and on depth), a custom combining function per cost type is required; this file does not provide it.

              def CostM.par {C : Type u_1} {A B : Type u} [SemilatticeSup C] (m₁ : CostM C A) (m₂ : CostM C B) :
              CostM C (A × B)

              Independent (parallel) composition of two CostM computations. The return is the pair of return values; the cost is the sup of the two costs.

              Equations
              Instances For
                @[simp]
                theorem CostM.ret_par {C : Type u_1} {A B : Type u} [SemilatticeSup C] (m₁ : CostM C A) (m₂ : CostM C B) :
                (m₁.par m₂).ret = (m₁.ret, m₂.ret)
                @[simp]
                theorem CostM.cost_par {C : Type u_1} {A B : Type u} [SemilatticeSup C] (m₁ : CostM C A) (m₂ : CostM C B) :
                (m₁.par m₂).cost = m₁.costm₂.cost

                N-ary parallel composition #

                parList ms extends par from a binary combinator to an arbitrary List of operands. All operands run independently (no data dependency between them); returns are collected into a List; costs are combined by the sup-semilattice join (). The empty list has cost (= 0 for C = ).

                parList requires [OrderBot C] in addition to [SemilatticeSup C] because the empty list needs an identity element for .

                For n independent operations each charging c_i, the joint parallel cost is c_1c_2 ⊔ ⋯ ⊔ c_n (the max for C = ). This corresponds to a flat "fan-out / fan-in" depth-1 schedule with n workers.

                def CostM.parList {C : Type u_1} {A : Type u} [SemilatticeSup C] [OrderBot C] (ms : List (CostM C A)) :
                CostM C (List A)

                N-ary parallel composition over a List of independent CostM operands. Returns are paired into a list in the original order; the cost is the sup of all operand costs (or for the empty list).

                Equations
                Instances For
                  @[simp]
                  theorem CostM.ret_parList {C : Type u_1} {A : Type u} [SemilatticeSup C] [OrderBot C] (ms : List (CostM C A)) :
                  (parList ms).ret = List.map (fun (x : CostM C A) => x.ret) ms
                  @[simp]
                  theorem CostM.cost_parList {C : Type u_1} {A : Type u} [SemilatticeSup C] [OrderBot C] (ms : List (CostM C A)) :
                  (parList ms).cost = List.foldr (fun (m : CostM C A) (acc : C) => m.costacc) ms
                  @[simp]
                  theorem CostM.parList_nil {C : Type u_1} {A : Type u} [SemilatticeSup C] [OrderBot C] :
                  parList [] = { ret := [], cost := }
                  @[simp]
                  theorem CostM.cost_parList_cons {C : Type u_1} {A : Type u} [SemilatticeSup C] [OrderBot C] (m : CostM C A) (ms : List (CostM C A)) :
                  (parList (m :: ms)).cost = m.cost(parList ms).cost

                  Complexity bound predicates #

                  Bounded alg size bound says "for every input i, the cost of alg i is at most bound (size i)". The size function reduces the input to a natural number (the conventional complexity-theoretic notion of "input size"). The bound function maps that size to an expected cost in C.

                  IsPolyBounded specializes to C = and existentially quantifies over the polynomial coefficient and degree: c k, costc * size^k.

                  def CostM.Bounded {C : Type u_1} {Input : Type u_2} {Output : Type u_3} [LE C] (alg : InputCostM C Output) (size : Input) (bound : C) :

                  The cost of alg i is bounded by bound (size i) for every input i.

                  Equations
                  Instances For
                    def CostM.IsPolyBounded {Input : Type u_2} {Output : Type u_3} (alg : InputCostM Output) (size : Input) :

                    Specialization of Bounded for C = : cost is polynomial in input size.

                    Equations
                    Instances For