; lib/artdag/cost.sx — cost model for the scheduler: per-node weights, critical
; path (min makespan with unlimited parallelism), plan makespan under batching/cap,
; total serial work, and the resulting speedup. Costs come from an injected
; cost-fn (op params) -> number so media-op costs stay opaque. Depends on dag.sx.

(define artdag/const-cost (fn (op params) 1))

(define
  artdag/op-cost
  (fn
    (table)
    (fn (op params) (if (has-key? table op) (get table op) 1))))

(define
  artdag/-node-cost
  (fn
    (dag cost-fn id)
    (let
      ((n (artdag/dag-get dag id)))
      (cost-fn (artdag/node-op n) (artdag/node-params n)))))

(define
  artdag/-max
  (fn (xs) (reduce (fn (mx x) (if (> x mx) x mx)) 0 xs)))

; longest weighted path through the dag = makespan with unlimited workers.
(define
  artdag/critical-path
  (fn
    (dag cost-fn)
    (let
      ((ft (reduce (fn (m id) (let ((maxdep (artdag/-max (map (fn (d) (get m d)) (artdag/node-inputs (artdag/dag-get dag id)))))) (assoc m id (+ (artdag/-node-cost dag cost-fn id) maxdep)))) {} (artdag/dag-order dag))))
      (artdag/-max (map (fn (id) (get ft id)) (keys ft))))))

; estimated wall-clock for a plan: each batch runs in parallel (costs its
; slowest node), batches run in sequence.
(define
  artdag/makespan
  (fn
    (dag plan cost-fn)
    (reduce
      (fn
        (total batch)
        (+
          total
          (artdag/-max
            (map (fn (id) (artdag/-node-cost dag cost-fn id)) batch))))
      0
      plan)))

; total serial work = sum of all node costs.
(define
  artdag/total-work
  (fn
    (dag cost-fn)
    (reduce
      (fn (s id) (+ s (artdag/-node-cost dag cost-fn id)))
      0
      (keys (artdag/dag-nodes dag)))))

; speedup of a plan vs running everything serially.
(define
  artdag/speedup
  (fn
    (dag plan cost-fn)
    (/ (artdag/total-work dag cost-fn) (artdag/makespan dag plan cost-fn))))
