Factor of two in Zygote complex gradient

Ok, I think I got the connection between Zygote.gradient and the Wirtinger derivative now. It seems Iike I can in fact use a single call to Zygote.gradient, because my function J is \in \mathbb{R} (whereas the above wirtinger function also handles the more general case of J \in \mathbb{C}). Specifically:

  1. Zygote.gradient(func, Ψ) is equivalent to y, back = Zygote.pullback(func, Ψ); back(1.0)[1], according to the Zygote documentation.

  2. Zygote.pullback falls back to ChainRules.rrule (or, at least, is compatible with ChainRules.rrule).

  3. Given a function J: \mathbb{C}^N \rightarrow \mathbb{R}, and z⃗, back = Zygote.pullback(J, z⃗₀), the pullback for the imaginary unity is zero, that is, dv = back(1im)[1] = 0 in the wirtinger function:

    • According to the ChainRules documentation, for a function \mathbb{C} \rightarrow \mathbb{C} defined as f(x+iy) = u(x,y) + iv(x, y), the rrule returns \Delta u \, \tfrac{\partial u}{\partial x} + \Delta v \, \tfrac{\partial v}{\partial x} + i \, \Bigl(\Delta u \, \tfrac{\partial u }{\partial y} + \Delta v \, \tfrac{\partial v}{\partial y} \Bigr), where (\Delta u, \Delta v) is the adjoint that we feed into rrule. In our case, \Delta u = 0, \Delta v = 1 (because we’re feeding in 1im), and u=J, v=0 (because J \in \mathbb{R}). Thus the entire pullback is zero.
    • The pullback for a vector function is the sum of the scalar pullbacks for the individual components, so the argument that back(1im)[1]=0 still holds.

Thus, in the wirtinger function, du = Zygote.gradient(func, Ψ) and dv = 0, and the two outputs are simply du'/2 and du/2. That is, the gradient up to a factor of two, as I was observing.