packages feed

egison-5.1.0: lib/math/analysis/derivative.egi

--
--
-- Differentiation
--
--

-- Differentiable type class. Each CAS shape's instance owns its own
-- structural decomposition rule. Recursive sub-calls go through
-- `partialDiff` (the typeclass method) so runtime-type dispatch picks the
-- right instance again — and the dispatch is O(1) thanks to
-- IRuntimeDispatch reusing the already-evaluated argument (see
-- `evalExprShallow env (IRuntimeDispatch ...)` in Core.hs).
--
-- We don't write an explicit `instance Differentiable MathValue`: the
-- compiler emits `IRuntimeDispatch` for that case, picking the right
-- concrete instance below from the runtime CAS shape.
class Differentiable a where
  partialDiff (f: a) (x: MathValue) : MathValue

instance Differentiable Factor where
  -- Atomic shapes: a single symbol, apply1-4, quote, or func.
  -- Reachable via static dispatch from Term inst's `$fx` (typed Factor through
  -- the `^` pattern's `as (factor, integer)` signature). Symbols short-circuit
  -- here; apply / quote / func delegate to the chain dispatcher.
  partialDiff f x := match f as factor with
    | #x -> 1
    | symbol _ _ -> 0
    | _ -> chainPartialDiff f x

instance Differentiable (Term MathValue [..]) where
  -- The `^` constructor in `inductive pattern MathValue` is `(^) Factor Integer`,
  -- so `$fx` is statically Factor and `partialDiff fx x` dispatches at compile
  -- time to the Factor instance. No need for in-body atomic short-circuits.
  partialDiff f x := match f as mathValue with
    | #0 -> 0
    | _ * #1 -> 0
    | #1 * $fx ^ $n -> n * fx ^ (n - 1) * partialDiff fx x
    | $a * $fx ^ $n * $r -> a * partialDiff (fx ^' n) x * r + a * fx ^' n * partialDiff r x
    | _ -> 0

instance Differentiable (Poly MathValue [..]) where
  -- Polynomial: sum of per-term derivatives. The `poly` constructor in
  -- `inductive pattern MathValue` is `poly [Term MathValue [..]]`, so each
  -- `$t` has static type `Term MathValue [..]` and `partialDiff t x` dispatches
  -- to the Term instance at compile time — no runtime dispatch needed.
  partialDiff f x := match f as mathValue with
    | poly $ts -> sum (map (\t -> partialDiff t x) ts)
    | _ -> chainPartialDiff f x

instance Differentiable (Frac MathValue) where
  -- Quotient rule.
  partialDiff f x := match f as mathValue with
    | $p1 / $p2 ->
        let p1' := partialDiff p1 x
            p2' := partialDiff p2 x
         in (p1' * p2 - p2' * p1) / p2 ^ 2
    | _ -> chainPartialDiff f x

-- ---------------------------------------------------------------------------
-- Top-level differentiation operator (∂/∂) and the chain dispatcher.
-- ---------------------------------------------------------------------------

-- partialDiffMV is a top-level def whose static type (MathValue ->
-- MathValue -> MathValue) is concrete, so the `partialDiff` inside gets
-- expanded by the typeclass machinery (IRuntimeDispatch) at this
-- definition site. Inlining it as an anonymous lambda inside `∂/∂` would
-- leave the partialDiff call in a context whose static type is still
-- polymorphic by the time TypeClassExpand sees it.
-- Validate the complete CAS value before differentiation so an application
-- without a registered analytic derivative cannot silently become zero.
def partialDiffMV (f : MathValue) (x : MathValue) : MathValue :=
  partialDiff (requireAnalyticDerivative f x) x

def ∂/∂ (f : Tensor MathValue) (x : Tensor MathValue) : Tensor MathValue :=
  tensorMap2 partialDiffMV f (flipIndices x)

-- chainPartialDiff is the user-facing dispatcher for "non-decomposable"
-- shapes — atomic symbols, built-in apply1 derivatives, function
-- expressions and quotes. It does NOT handle term / poly / frac shapes
-- (those have their own typeclass instances above). Each
-- `declare derivative <name>` redefines this var to add new apply1
-- branches before falling back to `chainPartialDiffBuiltin` (which is
-- never redefined), so user-declared derivatives win over the built-in
-- cases without infinite recursion.
def chainPartialDiff (v : MathValue) (dx : MathValue) : MathValue :=
  chainPartialDiffBuiltin v dx

-- chainPartialDiffBuiltin handles the shapes that don't have their own
-- type-class instance: function expressions and quote nodes. apply1
-- cases are dispatched first by the redefined `chainPartialDiff` via
-- `declare derivative` rules; symbols are filtered upstream by Term
-- inst's `#dx`/`?isSymbol` short-circuits. Everything else returns 0
-- (treated as a constant, which is the correct conservative answer for
-- truly opaque values).
def chainPartialDiffBuiltin (v : MathValue) (dx : MathValue) : MathValue :=
  match v as mathValue with
    -- function expression
    | func _ $args ->
       sum (map2 (\s r -> (userRefs v [s]) * partialDiff r dx) (between 1 (length args)) args)
    -- general power u^w with a symbolic exponent: an integer exponent is
    -- a monomial and is decomposed by the Term instance, and e^w never
    -- arrives either ((^) rewrites it to exp w), so what reaches here is
    -- the opaque apply2 form.  (u^w)' = u^w * (w' * log u + w * u' / u);
    -- previously this fell to the final 0 (silently treated as constant).
    | apply2 #(^) $u $w ->
        let u' := partialDiff u dx
            w' := partialDiff w dx
         in v * (w' * log u + w * u' / u)
    -- quote
    | quote $g ->
      let g' := partialDiff g dx
       in if isMonomial g'
            then g'
            else let d := foldl1 (\a b -> (gcdForMathValue a b)) (fromPoly g')
                  in d *' (mapPoly (/' d) g')
    | _ -> 0

-- ---------------------------------------------------------------------------
-- Built-in derivatives. Each `declare derivative <name> = <expr>` rebuilds
-- `chainPartialDiff` to dispatch `apply1 #<name>` through the chain rule
-- using `<expr>` as f'(g) for `f(g(x))`. Adding new derivatives here is
-- equivalent to adding new `apply1 #<name>` cases to a hand-written matcher,
-- but cleaner and uniform with user-declared derivatives.
declare derivative sin = cos
declare derivative cos = \z -> - sin z
declare derivative exp = exp
declare derivative log = \z -> 1 / z
declare derivative sqrt = \z -> 1 / (2 * sqrt z)

-- ---------------------------------------------------------------------------
-- Aliases.
-- ---------------------------------------------------------------------------

def d/d : MathValue -> MathValue -> MathValue := ∂/∂

def pd/pd : MathValue -> MathValue -> MathValue := ∂/∂

def ∇ : Tensor MathValue -> Vector MathValue -> Tensor MathValue := ∂/∂

def nabla : Tensor MathValue -> Vector MathValue -> Tensor MathValue := ∇

def grad : Tensor MathValue -> Vector MathValue -> Tensor MathValue := ∇

def taylorExpansion (f: MathValue) (x: MathValue) (a: MathValue) : [MathValue] :=
  multivariateTaylorExpansion f [|x|] [|a|]

def maclaurinExpansion (f: MathValue) (x: MathValue) : [MathValue] := taylorExpansion f x 0

def multivariateTaylorExpansion (f: MathValue) (xs: Vector MathValue) (ys: Vector MathValue)
  : [MathValue] :=
  withSymbols [h]
    let hs := generateTensor (\[x] -> h_x) (tensorShape xs)
     in map2
          (*)
          (map 1#(1 / fact $1) nats0)
          (map
             (compose
                1#(V.substitute xs ys $1)
                1#(V.substitute hs (withSymbols [i] xs_i - ys_i) $1))
             (iterate (compose 1#(∇ $1 xs) 1#(V.* hs $1)) f))

def multivariateMaclaurinExpansion (f: MathValue) (xs: Vector MathValue) : [MathValue] :=
  multivariateTaylorExpansion f xs (tensorMap 1#0 xs)