f(z) = 
How Does Symbolic Math Toolbox Define Differentiation of a Complex Function?
Show older comments
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
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
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
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)
That result is sensible when z is real. Given that the toobox defines @doc:sign as
f(z) = z/abs(z) % 4
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)
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
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".
Sam Chak
on 13 Sep 2026 at 15:14
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!

Paul
on 13 Sep 2026 at 17:37
Torsten
on 13 Sep 2026 at 19:52
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)
Answers (2)
Hi @Paul
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:
to see what a valid complex derivative looks like:syms z complex
diff(z^2, z)
% 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 = abs(z)^2 % If we look at |z|^2, the math becomes instantly clear to human intuition
% Expand the expression
f_split = expand(f)
% Group the real and imaginary parts, f = u + i*v
u = real(f_split)
v = imag(f_split)
% differentiate both components with respect to both x and y
du_dx = diff(u, x)
du_dy = diff(u, y)
dv_dx = diff(v, x)
dv_dy = diff(v, y)
% Test the Cauchy-Riemann Conditions for logical truth: returns 1 (True) or 0 (False)
condition1 = isAlways(du_dx == dv_dy)
condition2 = isAlways(du_dy == -dv_dx)
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
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.
Sam Chak
on 14 Sep 2026 at 7:04
Hi @Paul
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!
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.
. 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)
Df = diff(f, 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 = conj(z)
% Expand the expression
f_split = expand(f)
% Group the real and imaginary parts, f = u + i*v
u = real(f_split)
v = imag(f_split)
% differentiate both components with respect to both x and y
du_dx = diff(u, x)
du_dy = diff(u, y)
dv_dx = diff(v, x)
dv_dy = diff(v, y)
% Test the Cauchy-Riemann Conditions for logical truth: returns 1 (True) or 0 (False)
condition1 = isAlways(du_dx == dv_dy)
condition2 = isAlways(du_dy == -dv_dx)
% 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);
Paul
on 14 Sep 2026 at 11:31
Paul
on 14 Sep 2026 at 11:55
Stephen23
4 minutes ago
"Does Octave have any documentation that explains..."
Check the source code, I have found that it sometimes has useful comments.
Paul
1 minute ago
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)
Df = diff(f, z)
David Goodmanson
on 13 Sep 2026 at 21:22
Edited: David Goodmanson
on 14 Sep 2026 at 2:04
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
David Goodmanson
on 14 Sep 2026 at 1:59
Edited: David Goodmanson
on 14 Sep 2026 at 2:13
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.
Categories
Find more on Calculus in Help Center and File Exchange
Community Treasure Hunt
Find the treasures in MATLAB Central and discover how the community can help you!
Start Hunting!

