Complex Variables

from Essential Mathematica for Students of Science
© James J. Kelly, 1998
Last revised: January, 2006

In this notebook we explore some of the basic techniques for manipulating complex variables using Mathematica.

Initialization

ClearAll["Global`*"];
Off[General::spell, General::spell1];

Introduction

Mathematica does not automatically assume that variables are real or that real variables are positive; its symbolic manipulations are intended to be as general as possible unless given specific information about the properties of particular variables.  This, as we've seen before, is the reason that it often does not perform some of the simplifications you want.  For example, although at first glance it might seem that the following expression can be simplified, Mathematica declines to produce the obvious result because the replacement (Sqrt[(a^2) (b^2)]) -> a b may not be correct if either a or b is negative or if either is complex.

expr1  =  Sqrt[ (a^2) (b^2) ];
expr1  //  Simplify

However, if you are certain that all variables in an expression are real and positive, PowerExpand will perform the desired simplification.  Of course, you are then responsible for any problems that might arise if the variables involved violate these assumptions!

expr1  // PowerExpand

Similarly, many other functions do not respond to Simplify without making assumptions about their arguments which determine the appropriate branch for functions of complex variables.

Log[expr1]  // Simplify

Log[expr1]  // PowerExpand  // Simplify

Similarly, ComplexExpand separates an expression into its real and imaginary parts assuming that all variables within that expression are real.

z = x  +  I y;
{Sin[z], Cosh[(z^2)], Log[z]} // ComplexExpand

We can still use ComplexExpand to simplify expressions involving complex variables by supplying a list of complex arguments as an optional argument, such that ComplexExpand[expr,{z1,z2...}] can recognize z1 and z2 as complex quantities.

Clear[k, z];
ComplexExpand[ Exp[I k z],  k]

Another useful option is to specify a list of TargetFunctions we would like to appear in the result.  Thus, TargetFunctions->{Abs,Arg} can be used to obtain polar notation.

Clear[k, z];
ComplexExpand[ Exp[I k z],  k,  
      TargetFunctions -> {Abs, Arg}]

Clear[k, z];
ComplexExpand[ Exp[I k z],  k,  
        TargetFunctions -> {Abs, Arg}]  /. {Abs[k] -> κ, 
        Arg[k] -> φ}

Built-in Functions: Re, Im, Abs, Conjugate

The built-in functions Re, Im, Abs, and Conjugate return the real part, imaginary part, absolute value, and complex conjugate of complex numbers, but make no a priori assumptions about their arguments.  Hence, the evaluation of many expressions appears incomplete.

Clear[a, b, z];
z  =  a  +  2 I b;
{Re[z],  Im[z],  Abs[z],  Conjugate[z]}

Now suppose that we declare a and b to be real by associating rules with those symbols that nullify their imaginary parts.  These rules are known as upvalues because they are associated with the argument of a function, here Im, instead of the function itself.

Clear[a, b, z];
a /:  Im[a]  =  0; 
b /:  Im[b]  =  0;
z  =  a  +  2 I b;
{Re[z],  Im[z],  Abs[z],  Conjugate[z]}

Notice that we could have assigned the upvalue using the more compact syntax shown below instead.

?a

Better results are obtained, but notice that setting Im[a]->0 does not automatically ensure that Re[a]->a.  The following declarations are more specific and give somewhat better results.

Clear[a, b, z];
a /:  Im[a]  =  0; 
a /:  Re[a]  =  a;
b /:  Im[b]  =  0;
b /:  Re[b]  =  b;
z  =  a  +  2  I b;

{Re[z], Im[z],  Abs[z],  Conjugate[z]}

Nevertheless, Abs and Conjugate still do not behave as we would like.  These deficiencies arise because this family of functions is defined for numerical arguments and do not have rules attached to guide the evaluation of expressions.  Although we have supplemented these rules by attaching declarations to some of the symbols, it is a rather incomplete solution.  For example, even if we declare a to be real, Re does not recognize that Cos[a] is then real.

Re[Cos[a]]

For expressions involving real symbols, we can use ComplexExpand to obtain the real or imaginary parts of functions.

{Re[Cos[a]], Re[Log[x  +  I y]]}  // 
      ComplexExpand

Similarly, Conjugate does not utilize associate or distributive properties.

Conjugate[z]

The obvious way to circumvent this problem is to manually replace i by -i, but it fails!

z  /. I -> -I

Why doesn't this work (other than Murphy's Law prohibiting success of the obvious)?  If we use FullForm to examine our expression

FullForm[z]

FullForm[I]

we discover that neither the symbol i nor its internal representation Complex[0,1] is present in the expression for z.  Thus, the replacement rule has no effect.  Fortunately, this problem is easily circumvented by defining our own complex conjugation function as suggested by Zimmerman and Olness:

\(\(\(conjugate::usage\  = "A simple method for computing the conjugate of an object which is explicitly complex.";\)\(
\)
    \)\)
conjugateRule  =  
        Complex[re_, im_] :> Complex[re, -im];
conjugate[exp__]  :=  exp /. conjugateRule;

conjugate[z]

conjugate[x  -  I y]

However, this function will not work for arbitrary expressions because it assumes that all symbols are real and conjugates only the numerical coefficients expressed in terms of Complex.

Explain why Re[ ComplexExpand[a + i b] and ComplexExpand[ Re[a + i b] ] give different results.  Which should be preferred?

Determine the real and imaginary parts of Cosh[k z] for real z and complex k.

Plot the real and imaginary parts of BesselJ[3,z] for complex z.

Write a function which properly simplifies Abs[expr] assuming that the variables in expr are real.  Thus, your function should yield Abs[x + i y] -> Sqrt[(x^2) + (y^2)] and similar results for related expressions.  Use your function to obtain a simple expression for Abs[Cos[x + i y]]  involving only real quantities.

Write a function which declares a variable to be real.  Your function should specify values for Re, Im, and Conjugate.

Using the package ReIm

The package

Needs["Algebra`ReIm`"]

The package ReIm can also be used to extend the functionality of the built-in functions that manipulate complex variables.  Hence, if we declare certain variables to be real, expressions involving those variables can be simplified in the expected manner.

Clear[a, b, z];
a /:  Im[a]  =  0;
b /:  Im[b]  =  0;
z  =  a  +  I b;

{Re[z], Im[z], Abs[z], Conjugate[z],  z Conjugate[z]}  // 
      Simplify

Notice that now it is not necessary to give two rules to get a real number, simply nullifying the imaginary part is sufficient when ReIm is active.  However, the absolute value function still does not operate as we would like, but we can work around that with a replacement rule.  Furthermore, expressions involving variables of unspecified type are handled as if they are complex.

Conjugate[x  -  I y]

Log[z]  // ComplexExpand

The ReIm package provides a function RealValued[f,g,⋯] that declares that the functions f,g,⋯ produce real-valued output for real-valued arguments.  Thus, if we declare s and t to be real

RealValued[f, g];
Im[s] ^= 0;
Im[t] ^= 0;

the imaginary part of the logarithm of a product of real-valued functions

Im[Log[f[s] g[t]]]

reduces to the sum of their arguments, as expected.  Similarly, if we also declare s and t to be negative

Negative[s] ^= True;
Negative[t] ^= True;

then

Im[Log[s t]]

is interpreted as the sum Arg[s] + Arg[t] = π + π = 2  π.

Efficiency issues for ReIm

Although ReIm provides a set of rules which performs many simplifications of expressions involving complex variables in a manner that is transparent to the user, this package is quite inefficient and can slow down calculations enormously.  To illustrate this problem, we borrow the following example from Bahder.  Before evaluating the following expression, quit the kernel in order to unload the package.

expr  =  
      Integrate[ ((a (x^2) + b x  +  c)/(c x  +  a)), x]

The time needed to evaluate the real part of this result, assuming that the coefficients a, b, c are complex while the integration variable x is real can now be obtained using Timing.

ComplexExpand[ Re[expr],  {a, b, c}];  // Timing

Now we reload the package, declare x to be real, and evaluate Re[expr] using the rules from the package.  Note that ComplexExpand is not needed when using the package.

Needs["Algebra`ReIm`"]

x  /: Im[x] = 0;
Re[expr];  // Timing

Thus, similar evaluations take much longer using ReIm compared with ComplexExpand.  Bahder ascribes this inefficiency to the need to test all of the rules associated with Re that are contained in the package, whether or not they are actually relevant to the expression being evaluated.  Therefore, for most purposes it is better to simplify expressions manually, with appropriate replacement rules, or to supply a set of user-defined rules pertinent to the expressions encountered in one's applications.  Some guidance in constructing those rules can be obtained from the package itself because the package is a text file which can be edited by the user.  Of course, the user is advised to work on a copy rather than the original!