How Does Symbolic Math Toolbox Define Differentiation of a Complex Function?

The doc page diff - Differentiate symbolic expression or function - MATLAB is essentially silent on how diff handles differentiation of complex functions. All it says in the Tips section is: "For complex arguments of abs and sign, the diff function formally computes the derivative, but this result is not generally valid because abs and sign are not differentiable over complex numbers."
What does "formally" mean in this context?
More specifically, consider the following:
syms f(z) Df(z) % z is complex unless assumed otherwise
The conjugate function is nowhere complex differentiable, yet
f(z) = conj(z),Df(z) = diff(f(z),z) % 1
f(z) = 
Df(z) = 
1
The magnitude-squared function is complex differentiable only at z0 = 0, yet diff returns an expression that is defined everywhere
f(z) = z*conj(z),Df(z) = diff(f(z),z) % 2
f(z) = 
Df(z) = 
Taking the previous result as correct, this result does follow from the product rule.
The magnitude function is nowhere complex differentiable, yet
f(z) = abs(z),Df(z) = diff(f(z),z) % 3
f(z) = 
Df(z) = 
This result does follow from implicit differentiation of abs(z)^2 and the previous results, if we assume the previous results are correct.
The sign function is also interesting
f(z) = sign(z),Df(z) = diff(f(z),z)
f(z) = 
Df(z) = 
That result is sensible when z is real. Given that the toobox defines @doc:sign as
f(z) = z/abs(z) % 4
f(z) = 
It could very well define the derivative in accordance with the previous results as
Df(z) = simplify((abs(z)*diff(z,z) - z*diff(abs(z),z))/abs(z)^2)
Df(z) = 
but it doesn't.
As with differentiation of real functions, complex differentiation is defined by a limit, but it seems like the toolbox is only considering that limit from one direction (at least for cases 1-3)
Case 2 can be written in terms of abs and 4 is sign and the doc page has warned those results are "not generally valid." That sounds like an understatement in these instances. And the doc is silent on case 1 (conjugation). Insofar as abs and sign can both be expressed in terms of conj, maybe the doc should replace "abs" and "sign" with "conj" in that statement (and note that functions like abs and sign can both be rewritten in terms of conj).
What does the doc mean by "fomally computes the derivative" and how is one to interpret any of these results?

5 Comments

Torsten
Torsten on 13 Sep 2026 at 14:29
Edited: Torsten on 13 Sep 2026 at 14:41
I got this answer from AI for the "formal" differentiation of f(z) = conj(z), e.g.:
In symbolic math engines, functions are often evaluated using generalized power series or mapping rules. Formally, MATLAB's symbolic toolbox treats the derivative of the conjugate function as:
It treats the operator as something that can be "pulled out" of the derivative or bypassed mechanically. It matches the form of an identity function ( ), completely ignoring that conjugation changes the direction of the imaginary component.
Using the product rule, the "formal" results for z*conj(z) and abs(z) = sqrt(z*conj(z)) follow.
Of course, all this is pure nonsense and should better be removed from the symbolic toolbox.
Wolfram Alpha gives the same result for f(z) = conj(z), but explicitly states "assuming a function from reals to reals" and adds "nowhere differentiable in the complex plane".
Both MATLAB and WolframAlpha engines do the exact same mechanical math under the hood and arrive at . However, WolframAlpha proactively provides clear, context-aware notes to the user.
The fact that WolframAlpha, a major symbolic engine, chooses to output a result rather than a hard error confirms that suspicion that diff() is structurally tethered to legacy, mechanical calculus rules. A potential solution is probably to allow the mechanical calculation to proceed for the engineers who need the raw gradient, while throwing an explicit warning flag to protect the integrity of the math using the try/catch block.
Can someone execute the test for the functions on Maple? I suspect its symbolic engine might fall into the exact same mechanical calculus trap, but it would be fascinating to see how its output format compares to MATLAB and WolframAlpha!
Did the AI provide that entire answer? In particular, did the AI say "this is pure nonsense"?
I don't see how assuming that d(conj(z)/dz = 1 would be considered "formal." I'm still not sure what that means. If anything, that seems to be "informal."
Interestingly, "the formal derivative" Formal derivative - Wikipedia is defined, and I assume that "formal differentiation" is the act of taking the formal derivative in that context. Though I don't see a connection to the context being discussed here.
I have marked the AI's contribution in italics.
I think the word "formally" in this context has no general meaning. It refers to some internal MATLAB rules on how the symbolic toolbox treats algebraic expressions. I can't tell how this formalism looks like - I think we'll have to ask the developers.
AI says now
The Differentiation Rule for conj is "Formally" Hardcoded to 1
Because f(z) = conj(z) is completely non-differentiable anywhere in the complex plane, a strict mathematical derivative does not exist. However, MATLAB's engine is built around a philosophy of algebraic formalism. [1]
Internally, MATLAB treats conj(z) as a distinct symbolic function and maps its derivative with respect to its own argument directly to 1:
It does this because, algebraically, the engine treats conj as an operator that can be stripped away during formal, independent variable differentiation—essentially treating it with the same rules it uses for a real variable.
Here “formally” appears to mean that diff applies symbolic differentiation rules without checking that the complex derivative exists. For example, diff(conj(z),z) returns 1, which is the derivative obtained by varying z in the real direction. Varying z in the imaginary direction gives -1, so AFAICT the actual complex derivative does not exist. The results for z*conj(z) and abs(z) follow mechanically from that rule, but they should not generally be interpreted as complex derivatives. The documentation would be clearer if it stated explicitly what mathematical operation these outputs represent and that diff does not test for actual complex differentiability.
syms f(z)
diff(conj(z),z)
ans = 
1

Sign in to comment.

Answers (2)

In my opinion, "fomally computes the derivative" probably means MATLAB is applying the literal, mechanical rules of algebra/calculus while completely ignoring whether the meaning of the math is actually valid on the complex plane. In other words, the symbolic diff() function only calculates form, but it does not calculate meaning.
Think of it like a spell-checker. A spell-checker can tell us that "The square root of a banana is a purple window" is a perfectly spelled sentence with correct grammar. It is formally correct. But logically, it is nonsense!
From a pure programming perspective, MATLAB will produce successful analytical outputs for functions like abs(z), abs(z)^2 or z^2 because there are no error messages, no warnings, and no red text. Without a human operating with self-human intelligence to run a Cauchy-Riemann verification script, a programmer could easily plug a diff(abs(z), z) result into a larger simulation and introduce a massive mathematical bug without ever realizing it.
Here is a basic Cauchy-Riemann verification script for a single complex variable z, by splitting the complex variable into its real and imaginary parts. Let's first test it on the function to see what a valid complex derivative looks like:
syms z complex
diff(z^2, z)
ans = 
% diff(abs(z)^2, z)
% ---------------------------
% Cauchy-Riemann verification
% ---------------------------
% Create independent, strictly real variables for x and y
syms x y real
% Split the complex variable into its real and imaginary parts
z = x + 1i*y;
% 3. Define the complex function f(z)
f = z^2 % Similar to the example on Wikipedia
f = 
% f = abs(z)^2 % If we look at |z|^2, the math becomes instantly clear to human intuition
% Expand the expression
f_split = expand(f)
f_split = 
% Group the real and imaginary parts, f = u + i*v
u = real(f_split)
u = 
v = imag(f_split)
v = 
% differentiate both components with respect to both x and y
du_dx = diff(u, x)
du_dx = 
du_dy = diff(u, y)
du_dy = 
dv_dx = diff(v, x)
dv_dx = 
dv_dy = diff(v, y)
dv_dy = 
% Test the Cauchy-Riemann Conditions for logical truth: returns 1 (True) or 0 (False)
condition1 = isAlways(du_dx == dv_dy)
condition1 = logical
1
condition2 = isAlways(du_dy == -dv_dx)
condition2 = logical
1
In short, because MATLAB prints no warning banners, triggers no runtime errors, and displays no red text, it creates a dangerous illusion of "success." It relies entirely on the user proactively digging into the "Tips" documentation to find the disclaimer (4th bullet point).

11 Comments

Hi Sam,
Suppose we have
syms z
f(z) = z*conj(z)
f(z) = 
This f(z) is complex differentiable only at the origin. We can show this by either computing the derivative via the defining limit or via the CR conditions. At z0 = 0, we have f'(z0) = 0.
Matlab returns
Df(z) = diff(f(z),z)
Df(z) = 
which yields the correct result when evaluated at z0 = 0, but is incorrect when evaluated anywhere else. As you noted, it's a bit insidious insofar as the user could evaluate Df(z) at any point other than z = 0 and not know there is a problem.
Can that happen with real functions? For example, suppose we have
syms x real
g(x) = 1/(x-1);
Dg(x) = diff(g(x),x)
Dg(x) = 
Obviously Dg(x) doesn't exist at x =1 (and neither does g(x)!) but I guess it's ok that Dg(x) is returned that way (as opposed to a piecewise definition) because any improper use of Dg(1) will result in a divide by 0 error. I wonder if there is a g(x) where we have an x0 that is in the domain of g(x), yet Matlab returns Dg(x) where Dg(x0) incorrectly evaluates to a number.
But back to the complex case. Seems like we have at least four different situations.
1. f(z) is nowhere complex differentiable, e.g., f(z) = conj(z)
2. f(z) is complex differentiable everywhere over its domain, but not necessarily at all points in the complex plane, e.g., f(z) = 1/z (keeping in mind that Matlab allows us to define a symfun w/o also specifying its domain).
3. f(z) is complex differentiable at a subset of points in its domain, but the expression for the derivative does not fail to evaluate at points in the domaing where f(z) is not complex differentiable, e.g., f(z) = z*conj(z)
4. f(z) is complex differentiable everywhere, e.g., f(z) = z
5. Anything else?
I think case (3) is the most problematic. Maybe the best way to handle that case would be to return a piecwise result. I don't know how realistic that is for more complicated expressions.
If starting from scratch, I'm not sure how the toolbox would be designed to correctly handle complex differentation consistently across complex functions AND with differentiation of real functions. Maybe there would need to be some sort of sympref, like StrictComplexDifferentiation.
Torsten
Torsten on 13 Sep 2026 at 20:06
Edited: Torsten on 13 Sep 2026 at 20:20
I don't think that diff(z*conj(z)) = z + conj(z) is mathematically correct.
You have an induced map g: IR^2 -> IR, (x,y)-> x^2+y^2, for which the gradient is
grad(g) = (2x,2y) = (z+conj(z),(z-conj(z))/i).
Any function f from C -> C or from C -> IR can be written as f(z,conj(z)). df/d(conj(z)) = 0 means that the Cauchy-Riemann equations are valid. If further real and imaginary part of f are continuously differentiable in the neighbourhood of a point z, f is analytic in z.
That's a great result: Cauchy-Riemann + continuously differentiable already guarantee analyticity.
Paul
Paul on 13 Sep 2026 at 20:21
Edited: Paul on 13 Sep 2026 at 20:22
As stated above, diff(z*conj(z),z) "yields the correct [result] when evaluated at z0 = 0, but is incorrect when evaluated anywhere else." So we agree that Matlab's result is not mathematically correct in general. It only yields the correct result when evaluating it at the origin of the complex plane.
Another complication in trying to have the software do complex differentiation correctly is that the usual rules of differentiation don't always apply. For example
syms f(z) g(z) h(z)
g(z) = z;
h(z) = conj(z);
f(z) = g(z) + h(z)
g(z) is complex differentiable everywhere. h(z) is complex differentiable at the origin, but f(z) is nowhere complex differentiable, even at the origin. (Edit 14 Sep 2027: previous statement is incorrect; h(z) is nowhere complex differentiable).
OTOH, if g(z) = -conj(z), then f(z) is complex differentiable everywhere, even though g(z) and h(z) are complex differentiable only at the origin. (Edit 14 Sep 2027: previous statement is incorrect; g(z) and h(z) are nowhere complex differentiable, but the point is that f(z) is complex differentiable everywhere).
I love these scenarios as you’ve perfectly captured the core dilemma of this problem.
Your initial suggestion of handling Case 3 as an edge case through an explicit foundational definition like an axiomatic piecewise return (piecewise(z == 0, 0, z ~= 0, NaN)) whenever the parser detects abs(), sign(), or conj(), is mathematically flawless in theory. It acts exactly like how MATLAB explicitly defines to bypass calculus ambiguities.
But then, your next two scenarios throw a mind-boggling complex analysis into that definition-based approach! They are a true mathematical masterpiece of the "Form is emptiness, emptiness is form" paradox.
The 1st scenario: "Form is emptiness"
You look at the equation, and it has an elegant, structured form. It is made of two clear, distinct mathematical objects (z and its conjugate). But when you expand it, they combine into , creating a broken, rigid surface that completely fails the Cauchy-Riemann equations. Its complex differentiability is completely hollow as the form dissolves into mathematical emptiness (nowhere differentiable).
The 2nd scenario: "Emptiness is form"
Individually, both components are mathematically "empty" (neither is complex-differentiable anywhere outside the origin). But when you add them together, their flaws perfectly annihilate each other. Out of that emptiness, a perfectly smooth, flawless, and analytic form is born (), which is complex-differentiable across the entire domain.
However, if MATLAB were programmed to rigidly flag the whole expression as invalid the moment it spots a conj(z) via a simple axiomatic assignment, it would falsely throw an error on your second scenario, which is actually a perfectly valid, differentiable function!
Hi @Paul and everyone,
I run the test in Octave and look at the contrast!
Octave completely refuses to make the illegal assumption that . It applies the product rule mechanically, but stops right at the boundary of truth, leaving it unevaluated to let the user know the true complex derivative cannot be resolved.
Is outputting the unevaluated derivative token d/dz{conj(z)} (like GNU Octave does) actually the best possible outcome we can hope for from a symbolic math engine?
Though I don't know how MATLAB's core symbolic engine treats variables, we can run the Cauchy-Riemann script on to find out the truth.
syms f(z) complex
f(z) = conj(z)
f(z) = 
Df = diff(f, z)
Df(z) = 
1
% ---------------------------
% Cauchy-Riemann verification
% ---------------------------
% Create independent, strictly real variables for x and y
syms x y real
% Split the complex variable into its real and imaginary parts
z = x + 1i*y;
% 3. Define the complex function f(z)
f = conj(z)
f = 
% Expand the expression
f_split = expand(f)
f_split = 
% Group the real and imaginary parts, f = u + i*v
u = real(f_split)
u = 
x
v = imag(f_split)
v = 
% differentiate both components with respect to both x and y
du_dx = diff(u, x)
du_dx = 
1
du_dy = diff(u, y)
du_dy = 
0
dv_dx = diff(v, x)
dv_dx = 
0
dv_dy = diff(v, y)
dv_dy = 
% Test the Cauchy-Riemann Conditions for logical truth: returns 1 (True) or 0 (False)
condition1 = isAlways(du_dx == dv_dy)
condition1 = logical
0
condition2 = isAlways(du_dy == -dv_dx)
condition2 = logical
1
% The Ultimate Binary Truth Flag (Must be 1 to be fully valid)
differentiable_everywhere = condition1*condition2;
% Display the verdict
fprintf('Is this function complex-differentiable everywhere? %d\n', differentiable_everywhere);
Is this function complex-differentiable everywhere? 0
I was actually working on a comment on my thought experiment as to how things might work if diff were able to return an unevaluated call like fourier. I might come back to it. Also, I made a mistake in this comment and have edited it accordingly.
Does Octave have any documentation that explains what it does for complex differentiation? z*conj(z) is complex differentiable at z = 0, but the Octave result seems to ignore that (perhaps by design, hence my question about Octave doc).
"Does Octave have any documentation that explains..."
Check the source code, I have found that it sometimes has useful comments.
It's my understanding that satisfying the CR equations is a neccesary condition for complex differentiability, but it is not a sufficient condition (or so I've read).
Hi Paul,
I ran the diff() function on Octave Online. But the official Octave documentation for @sym/diff focuses entirely on basic syntax and examples, offering no deep math explanation for how it evaluates complex calculus. Under the hood, it is a lightweight wrapper that passes all symbolic text commands straight to the SymPy engine.
We started this journey because MATLAB’s diff() function prints invalid result when a function contains abs(), or conj(). The lightweight CR script is a simple tool against these basic traps. Think of it exactly like the necessary condition of stability for linear time-invariant systems, where all coefficients of a characteristic polynomial must have the same sign. It is an essential first gate, even though it is insufficient to guarantee global stability on its own.
According to Wolfram MathWorld, for a function to be complex differentiable at a point , its derivative must satisfy the CR equations and possess continuous first partial derivatives in the neighborhood of .
For complex rational functions such as and , things get tricky and it is necessary to run an explicit test for continuity in MATLAB after the CR equations are satisfied. The latter function is a math-optical illusion specifically engineered to confuse both human intuition and symbolic engines.
syms f(z) complex
f(z) = abs(z)^2/conj(z) % We know by definition that |z|² = z*conj(z)
f(z) = 
Df = diff(f, z)
Df(z) = 

Sign in to comment.

Hi Paul,
Seems it would be much clearer if the documentation of symbolic diff pointed out that it is intended for real values of the independent variable.
By 'formally' they may be referring to the fortuitous fact that if x is real and z is complex, and f(z) is a function of z only (no conj(z)), then formally
if g(x) = diff(f(x)) then replacing x with z --> g(z) = diff(f(z))
which is correct. Bringing in conj(z) throws that relationship out the window and the help function should probably just state it that way as you suggest.
In Torsten's quote, AI appears to say that Mathworks has effectively chosen to implement d/dz (z*) = 1 which if true results in a total fabrication.

2 Comments

Hi David,
I don't think there's any doubt that Matlab implements d/dz(z*) == 1 as shown in the originating question.
syms z
diff(conj(z),1)
ans = 
1
Taking this incorrect result as a fact, the other examples in the question directly follow assuming standard rules of differentiation (which might not always be correct), except d/dz(sign(z)).
I'm afraid I disagree with
d/dz (z*) = 0 % not 1
d/dz* (z) = 0
As I understand based on the definiton of the derivative of the complex function, d/dz(z*) does not exist anywhere in the complex plane, which is not the same as saying it's equal to zero.
Also, IF it's true that d/dz*(f(z) is defined as d/dw (f(w*)), then I believe that d/dz*(z) does not exist anywhere in the plane either.
Hi Paul,
The results I showed before do work for the restricted case of linear algebra, but derivatives coming in from an arbitrary angle in the complex plane are a different animal. I agree that d/dz (z*) = 0 does not work under the standard assumption of what d/dz repesents, so I went back and took out a bunch of the answer.

Sign in to comment.

Products

Release

R2026a

Asked:

on 11 Sep 2026 at 2:37

Commented:

about 8 hours ago

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!