mentat-collective / emmy

The Emmy Computer Algebra System.
https://emmy.mentat.org
GNU General Public License v3.0
406 stars 24 forks source link

`g/partial-derivative` for structures shouldn't use fn impl for NON-structural inputs #84

Open sritchie opened 3 years ago

sritchie commented 3 years ago

The current implementation of g/partial-derivative for ::s/structure instances is the same implementation used by ::function.

The reason for this, I think (and the reason it works!) Is that all of the machinery that derivative.cljc develops to allow for structured inputs (Jacobian support) gets to work a SINGLE time and call the structure as a function for each perturbed input in the incoming structure.

The alternative would be to implement partial-derivative on structures like this:

(defmethod g/partial-derivative [::s/structure v/seqtype]
  [structure selectors]
  (s/mapr #(g/partial-derivative % selectors) structure))

This would fix two problems I've noticed:

Compare these two cases for an example:

sicmutils.calculus.derivative> ((D [g/sin g/cos]) 5)
Execution error (IllegalArgumentException) at sicmutils.calculus.derivative/derivative$fn (derivative.cljc:191).
Key must be integer

sicmutils.calculus.derivative> ((D (s/up g/sin g/cos)) 5)
(up 0.28366218546322625 0.9589242746631385)
sritchie commented 3 years ago

I've thought about this more, and I see now that this will only work if you call D on a function that takes a single numerical (or symbolic) argument.

The reason is that we interpret a structure of functions (up f1, f2, f3) from a => b as a single function of a => (up b1, b2, b3).

If "a" is a structure, say, (up x y z), then D needs to add a NEW structure (with orientations flipped) around the outputs.

The correct result is

(down (up (((partial 0) f1) (up x y z))
          (((partial 0) f2) (up x y z))
          (((partial 0) f3) (up x y z)))
      (up (((partial 1) f1) (up x y z))
          (((partial 1) f2) (up x y z))
          (((partial 1) f3) (up x y z)))
      (up (((partial 2) f1) (up x y z))
          (((partial 2) f2) (up x y z))
          (((partial 2) f3) (up x y z))))

my suggestion would give:

(up (down (((partial 0) f1) (up x y z))
          (((partial 1) f1) (up x y z))
          (((partial 2) f1) (up x y z)))
    (down (((partial 0) f2) (up x y z))
          (((partial 1) f2) (up x y z))
          (((partial 2) f2) (up x y z)))
    (down (((partial 0) f3) (up x y z))
          (((partial 1) f3) (up x y z))
          (((partial 2) f3) (up x y z))))

They would match for a single numerical (or symbolic) argument.

Preserving a power series through a derivative is still something we want, I think! Maybe this hints that "partial-derivative" and "jacobian" should be two distinct generic functions.

sritchie commented 3 years ago

This implies that maybe we should treat these cases separately, EVEN if they're both covered by D...