12.5 Irrational and Transcendental Functions

Common Lisp provides no data type that can accurately represent irrational numerical values. The functions in this section are described as if the results were mathematically accurate, but actually they all produce floating-point approximations to the true mathematical result in the general case. In some places mathematical identities are set forth that are intended to elucidate the meanings of the functions; however, two mathematically identical expressions may be computationally different because of errors inherent in the floating-point approximation process.

When the arguments to a function in this section are all rational and the true mathematical result is also (mathematically) rational, then unless otherwise noted an implementation is free to return either an accurate result of type rational or a single-precision floating-point approximation. If the arguments are all rational but the result cannot be expressed as a rational number, then a single-precision floating-point approximation is always returned.

If the arguments to a function are all of type (or rational (complex rational)) and the true mathematical result is (mathematically) a complex number with rational real and imaginary parts, then unless otherwise noted an implementation is free to return either an accurate result of type (or rational (complex rational)) or a single-precision floating-point approximation of type single-float (permissible only if the imaginary part of the true mathematical result is zero) or (complex single-float). If the arguments are all of type (or rational (complex rational)) but the result cannot be expressed as a rational or complex rational number, then the returned value will be of type single-float (permissible only if the imaginary part of the true mathematical result is zero) or (complex single-float).

The rules of floating-point contagion and complex contagion are effectively obeyed by all the functions in this section except expt, which treats some cases of rational exponents specially. When, possibly after contagious conversion, all of the arguments are of the same floating-point or complex floating-point type, then the result will be of that same type unless otherwise noted. ____________________________________________________________________

Implementation note: There is a “floating-point cookbook” by Cody and Waite [14] that may be a useful aid in implementing the functions defined in this section.

___________________________________________________________________________________________________________

12.5.1 Exponential and Logarithmic Functions

Along with the usual one-argument and two-argument exponential and logarithm functions, sqrt is considered to be an exponential function, because it raises a number to the power 1/2.

[Function] exp number

Returns e raised to the power number, where e is the base of the natural logarithms.


[Function] expt base-number power-number

Returns base-number raised to the power power-number. If the base-number is of type rational and the power-number is an integer, the calculation will be exact and the result will be of type rational; otherwise a floating-point approximation may result.

X3J13 voted in March 1989 to clarify that provisions similar to those of the previous paragraph apply to complex numbers. If the base-number is of type (complex rational) and the power-number is an integer, the calculation will also be exact and the result will be of type (or rational (complex rational)); otherwise a floating-point or complex floating-point approximation may result.

When power-number is 0 (a zero of type integer), then the result is always the value 1 in the type of base-number, even if the base-number is zero (of any type). That is:

(expt x 0)  (coerce 1 (type-of x))

If the power-number is a zero of any other data type, then the result is also the value 1, in the type of the arguments after the application of the contagion rules, with one exception: it is an error if base-number is zero when the power-number is a zero not of type integer.

Implementations of expt are permitted to use different algorithms for the cases of a rational power-number and a floating-point power-number; the motivation is that in many cases greater accuracy can be achieved for the case of a rational power-number. For example, (expt pi 16) and (expt pi 16.0) may yield slightly different results if the first case is computed by repeated squaring and the second by the use of logarithms. Similarly, an implementation might choose to compute (expt x 3/2) as if it had been written (sqrt (expt x 3)), perhaps producing a more accurate result than would (expt x 1.5). It is left to the implementor to determine the best strategies.

The result of expt can be a complex number, even when neither argument is complex, if base-number is negative and power-number is not an integer. The result is always the principal complex value. Note that (expt -8 1/3) is not permitted to return -2; while -2 is indeed one of the cube roots of -8, it is not the principal cube root, which is a complex number approximately equal to #C(1.0 1.73205).


[Function] log number &optional base

Returns the logarithm of number in the base base, which defaults to e, the base of the natural logarithms. For example:

(log 8.0 2)  3.0
(log 100.0 10)  2.0

The result of (log 8 2) may be either 3 or 3.0, depending on the implementation.

Note that log may return a complex result when given a non-complex argument if the argument is negative. For example:

(log -1.0)  (complex 0.0 (float pi 0.0))

X3J13 voted in January 1989 to specify certain floating-point behavior when minus zero is supported. As a part of that vote it approved a mathematical definition of complex logarithm in terms of real logarithm, absolute value, arc tangent of two real arguments, and the phase function as

Logarithm log |z| + i phase z

This specifies the branch cuts precisely whether minus zero is supported or not; see phase and atan.

[Function] sqrt number

Returns the principal square root of number. If the number is not complex but is negative, then the result will be a complex number. For example:

(sqrt 9.0)  3.0
(sqrt -9.0)  #c(0.0 3.0)

The result of (sqrt 9) may be either 3 or 3.0, depending on the implementation. The result of (sqrt -9) may be either #c(0 3) or #c(0.0 3.0).

X3J13 voted in January 1989 to specify certain floating-point behavior when minus zero is supported. As a part of that vote it approved a mathematical definition of complex square root in terms of complex logarithm and exponential functions as

Square root e(log z)∕2

This specifies the branch cuts precisely whether minus zero is supported or not; see phase and atan.

[Function] isqrt integer

Integer square root: the argument must be a non-negative integer, and the result is the greatest integer less than or equal to the exact positive square root of the argument. For example:

(isqrt 9)  3
(isqrt 12)  3
(isqrt 300)  17
(isqrt 325)  18


12.5.2 Trigonometric and Related Functions

Some of the functions in this section, such as abs and signum, are apparently unrelated to trigonometric functions when considered as functions of real numbers only. The way in which they are extended to operate on complex numbers makes the trigonometric connection clear.

[Function] abs number

Returns the absolute value of the argument. For a non-complex number x,

(abs x)  (if (minusp x) (- x) x)

and the result is always of the same type as the argument.

For a complex number z, the absolute value may be computed as

(sqrt (+ (expt (realpart z) 2) (expt (imagpart z) 2)))

__________________________________________________________________________

Implementation note: The careful implementor will not use this formula directly for all complex numbers but will instead handle very large or very small components specially to avoid intermediate overflow or underflow.

___________________________________________________________________________________________________________

For example:

(abs #c(3.0 -4.0))  5.0

The result of (abs #c(3 4)) may be either 5 or 5.0, depending on the implementation.


[Function] phase number

The phase of a number is the angle part of its polar representation as a complex number. That is,

(phase z)  (atan (imagpart z) (realpart z))

X3J13 voted in January 1989 to specify certain floating-point behavior when minus zero is supported; phase is still defined in terms of atan as above, but thanks to a change in atan the range of phase becomes − π inclusive to π inclusive. The value − π results from an argument whose real part is negative and whose imaginary part is minus zero. The phase function therefore has a branch cut along the negative real axis. The phase of + 0 + 0i is + 0, of + 0 − 0i is − 0, of − 0 + 0i is + π, and of − 0 − 0i is − π.

If the argument is a complex floating-point number, the result is a floating-point number of the same type as the components of the argument. If the argument is a floating-point number, the result is a floating-point number of the same type. If the argument is a rational number or complex rational number, the result is a single-format floating-point number.


[Function] signum number

By definition,

(signum x)  (if (zerop x) x (/ x (abs x)))

For a rational number, signum will return one of -1, 0, or 1 according to whether the number is negative, zero, or positive. For a floating-point number, the result will be a floating-point number of the same format whose value is − 1, 0, or 1. For a complex number z, (signum z) is a complex number of the same phase but with unit magnitude, unless z is a complex zero, in which case the result is z. For example:

(signum 0)  0
(signum -3.7L5)  -1.0L0
(signum 4/5)  1
(signum #C(7.5 10.0))  #C(0.6 0.8)
(signum #C(0.0 -14.7))  #C(0.0 -1.0)

For non-complex rational numbers, signum is a rational function, but it may be irrational for complex arguments.


[Function] sin radians
[Function] cos radians
[Function] tan radians

sin returns the sine of the argument, cos the cosine, and tan the tangent. The argument is in radians. The argument may be complex.


[Function] cis radians

This computes ei⋅radians. The name cis means “cos + i sin,” because ei𝜃 = cos 𝜃 + i sin 𝜃. The argument is in radians and may be any non-complex number. The result is a complex number whose real part is the cosine of the argument and whose imaginary part is the sine. Put another way, the result is a complex number whose phase is the equal to the argument (mod ) and whose magnitude is unity. _______________________________________________________

Implementation note: Often it is cheaper to calculate the sine and cosine of a single angle together than to perform two disjoint calculations.

___________________________________________________________________________________________________________

[Function] asin number
[Function] acos number

asin returns the arc sine of the argument, and acos the arc cosine. The result is in radians. The argument may be complex.

The arc sine and arc cosine functions may be defined mathematically for an argument z as follows:

Arc sine i log (    √ ------)
 iz +   1 − z2
Arc cosine i log (    √ ------)
 z + i 1 − z2

Note that the result of asin or acos may be complex even if the argument is not complex; this occurs when the absolute value of the argument is greater than 1.

Kahan [25] suggests for acos the defining formula

Arc cosine 2 log ( ∘ 1+z-  ∘ -1−-z)
     2 +  i   2 i

or even the much simpler (π∕2) − arcsin z. Both equations are mathematically equivalent to the formula shown above.
__________________________________________________________________________

Implementation note: These formulae are mathematically correct, assuming completely accurate computation. They may be terrible methods for floating-point computation. Implementors should consult a good text on numerical analysis. The formulae given above are not necessarily the simplest ones for real-valued computations, either; they are chosen to define the branch cuts in desirable ways for the complex case.

___________________________________________________________________________________________________________

[Function] atan y &optional x

An arc tangent is calculated and the result is returned in radians.

With two arguments y and x, neither argument may be complex. The result is the arc tangent of the quantity y/x. The signs of y and x are used to derive quadrant information; moreover, x may be zero provided y is not zero. The value of atan is always between − π (exclusive) and π (inclusive). The following table details various special cases.

Condition
Cartesian Locus Range of Result
y = +0 x > 0 Just above positive x-axis + 0
y > 0  x > 0 Quadrant I + 0 < result < π∕2
y > 0  x = ±0Positive y-axis π∕2
y > 0  x < 0 Quadrant II π∕2 < result < π
y = +0 x < 0 Just below negative x-axis π
y = −0 x < 0 Just above negative x-axis π
y < 0  x < 0 Quadrant III − π < result < −π∕2
y < 0  x = ±0Negative y-axis − π∕2
y < 0  x > 0 Quadrant IV − π∕2 < result < −0
y = −0 x > 0 Just below positive x-axis − 0
y = +0 x = +0Near origin + 0
y = −0 x = +0Near origin − 0
y = +0 x = −0Near origin π
y = −0 x = −0Near origin − π
 

Note that the case y = 0,x = 0 is an error in the absence of minus zero, but the four cases y = ±0,x = ±0 are defined in the presence of minus zero.

With only one argument y, the argument may be complex. The result is the arc tangent of y, which may be defined by the following formula:

Arc tangent log(1+iy)−log(1−iy) 2i

__________________________________________________________________________

Implementation note: This formula is mathematically correct, assuming completely accurate computation. It may be a terrible method for floating-point computation. Implementors should consult a good text on numerical analysis. The formula given above is not necessarily the simplest one for real-valued computations, either; it is chosen to define the branch cuts in desirable ways for the complex case.

___________________________________________________________________________________________________________

For a non-complex argument y, the result is non-complex and lies between − π∕2 and π∕2 (both exclusive).


[Constant] pi

This global variable has as its value the best possible approximation to π in long floating-point format. For example:

(defun sind (x)     ;The argument is in degrees
  (sin (* x (/ (float pi x) 180))))

An approximation to π in some other precision can be obtained by writing (float pi x), where x is a floating-point number of the desired precision, or by writing (coerce pi type), where type is the name of the desired type, such as short-float.


[Function] sinh number
[Function] cosh number
[Function] tanh number
[Function] asinh number
[Function] acosh number
[Function] atanh number

These functions compute the hyperbolic sine, cosine, tangent, arc sine, arc cosine, and arc tangent functions, which are mathematically defined for an argument z as follows:

Hyperbolic sine (eze−z)∕2
Hyperbolic cosine (ez + e−z)∕2
Hyperbolic tangent (eze−z)∕(ez + e−z)
Hyperbolic arc sine log (    √ -----2)
 z +   1 + z
Hyperbolic arc cosine log (          ∘ ---------------)
 z + (z + 1 ) (z − 1)∕(z + 1)
Hyperbolic arc tangent log (       ∘ ---------)
 (1 + z)  1∕(1 − z2)

Note that the result of acosh may be complex even if the argument is not complex; this occurs when the argument is less than 1. Also, the result of atanh may be complex even if the argument is not complex; this occurs when the absolute value of the argument is greater than 1. __________________________

Implementation note: These formulae are mathematically correct, assuming completely accurate computation. They may be terrible methods for floating-point computation. Implementors should consult a good text on numerical analysis. The formulae given above are not necessarily the simplest ones for real-valued computations, either; they are chosen to define the branch cuts in desirable ways for the complex case.

__________________________________________________________________________

12.5.3 Branch Cuts, Principal Values, and Boundary Conditions in the Complex Plane

Many of the irrational and transcendental functions are multiply defined in the complex domain; for example, there are in general an infinite number of complex values for the logarithm function. In each such case, a principal value must be chosen for the function to return. In general, such values cannot be chosen so as to make the range continuous; lines in the domain called branch cuts must be defined, which in turn define the discontinuities in the range.

Common Lisp defines the branch cuts, principal values, and boundary conditions for the complex functions following a proposal for complex functions in APL [36]. The contents of this section are borrowed largely from that proposal.

Indeed, X3J13 voted in January 1989 to alter the direction of continuity for the branch cuts of atan, and also to address the treatment of branch cuts in implementations that have a distinct floating-point minus zero.

The treatment of minus zero centers in two-argument atan. If there is no minus zero, then the branch cut runs just below the negative real axis as before, and the range of two-argument atan is (−π,π]. If there is a minus zero, however, then the branch cut runs precisely on the negative real axis, skittering between pairs of numbers of the form x ± 0i, and the range of two-argument atan is [−π,π].

The treatment of minus zero by all other irrational and transcendental functions is then specified by defining those functions in terms of two-argument atan. First, phase is defined in terms of two-argument atan, and complex abs in terms of real sqrt; then complex log is defined in terms of phase, abs, and real log; then complex sqrt in terms of complex log; and finally all others are defined in terms of these.

Kahan [25] treats these matters in some detail and also suggests specific algorithms for implementing irrational and transcendental functions in IEEE standard floating-point arithmetic [23].

Remarks in the first edition about the direction of the continuity of branch cuts continue to hold in the absence of minus zero and may be ignored if minus zero is supported; since all branch cuts happen to run along the principal axes, they run between plus zero and minus zero, and so each sort of zero is associated with the obvious quadrant.

With these definitions, the following useful identities are obeyed throughout the applicable portion of the complex domain, even on the branch cuts:

sin iz = i sinh z   sinh iz = i sin z arctan iz = i arctanh z
cos i z = cosh z   cosh i z = cos z arcsinh i z = i arcsin z
tan iz = i tanh z  arcsin iz = i arcsinh z arctanh iz = i arctan z

I thought it would be useful to provide some graphs illustrating the behavior of the irrational and transcendental functions in the complex plane. It also provides an opportunity to show off the Common Lisp code that was used to generate them.

Imagine the complex plane to be decorated as follows. The real and imaginary axes are painted with thick lines. Parallels from the axes on both sides at distances of 1, 2, and 3 are painted with thin lines; these parallels are doubly infinite lines, as are the axes. Four annuli (rings) are painted in gradated shades of gray. Ring 1, the inner ring, consists of points whose radial distances from the origin lie in the range [1∕4, 1∕2]; ring 2 is in the radial range [3∕4, 1]; ring 3, in the range [π∕2, 2]; and ring 4, in the range [3,π]. Ring j is divided into 2j+1 equal sectors, with each sector painted a different shade of gray, darkening as one proceeds counterclockwise from the positive real axis.

We can illustrate the behavior of a numerical function f by considering how it maps the complex plane to itself. More specifically, consider each point z of the decorated plane. We decorate a new plane by coloring the point f(z) with the same color that point z had in the original decorated plane. In other words, the newly decorated plane illustrates how the f maps the axes, other horizontal and vertical lines, and annuli.

In each figure we will show only a fragment of the complex plane, with the real axis horizontal in the usual manner ( −∞ to the left, + ∞ to the right) and the imaginary axis vertical ( −∞i below, + ∞i above). Each fragment shows a region containing points whose real and imaginary parts are in the range [−4.1, 4.1]. The axes of the new plane are shown as very thin lines, with large tick marks at integer coordinates and somewhat smaller tick marks at multiples of π∕2.

Figure 12.1 shows the result of plotting the identity function (quite literally); the graph exhibits the decoration of the original plane.

Figures 12.2 through 12.20 show the graphs for the functions sqrt, exp, log, sin, asin, cos, acos, tan, atan, sinh, asinh, cosh, acosh, tanh, and atanh, and as a bonus, the graphs for the functions   ------
√ 1 − z2,   ------
√ 1 + z2, (z − 1)∕(z + 1), and (1 + z)∕(1 −z). All of these are related to the trigonometric functions in various ways. For example, if f(z) = (z − 1)∕(z + 1), then tanh z = f(e2z), and if g(z) = √ ------
  1 − z2, then cos z = g(sin z). It is instructive to examine the graph for √ ------
  1 − z2 and try to visualize how it transforms the graph for sin into the graph for cos.

Each figure is accompanied by a commentary on what maps to what and other interesting features. None of this material is terribly new; much of it may be found in any good textbook on complex analysis. I believe that the particular form in which the graphs are presented is novel, as well as the fact that the graphs have been generated as PostScript [1] code by Common Lisp code. This PostScript code was then fed directly to the typesetting equipment that set the pages for this book. Samples of this PostScript code follow the figures themselves, after which the code for the entire program is presented.

In the commentaries that accompany the figures I sometimes speak of mapping the points ±∞ or ±∞i. When I say that function f maps + ∞ to a certain point z, I mean that

z = lim x→+∞f(x + 0 i)

Similarly, when I say that f maps −∞i to z, I mean that
z = lim y→−∞f(0 + yi)

In other words, I am considering a limit as one travels out along one of the main axes. I also speak in a similar manner of mapping to one of these infinities.


Figure 12.1: Initial Decoration of the Complex Plane (Identity Function)

PIC
This figure was produced in exactly the same manner as succeeding figures, simply by plotting the function identity instead of a numerical function. Thus the first of these figures was produced by the last function of the first edition. I knew it would come in handy someday!




Figure 12.2: Illustration of the Range of the Square Root Function

PIC
The sqrt function maps the complex plane into the right half of the plane by slitting it along the negative real axis and then sweeping it around as if half-closing a folding fan. The fan also shrinks, as if it were made of cotton and had gotten wetter at the periphery than at the center. The positive real axis is mapped onto itself. The negative real axis is mapped onto the positive imaginary axis (but if minus zero is supported, then x + 0i is mapped onto the positive imaginary axis and x − 0i onto the negative imaginary axis, assuming x > 0). The positive imaginary axis is mapped onto the northeast diagonal, and the negative imaginary axis onto the southeast diagonal. More generally, lines are mapped to rectangular hyperbolas (or fragments thereof) centered on the origin; lines through the origin are mapped to degenerate hyperbolas (perpendicular lines through the origin).




Figure 12.3: Illustration of the Range of the Exponential Function

PIC
The exp function maps horizontal lines to radii and maps vertical lines to circles centered at the origin. The origin is mapped to 1. (It is instructive to compare this graph with those of other functions that map the origin to 1, for example (1 + z)∕(1 −z), cosz, and √1--−-z2-.) The entire real axis is mapped to the positive real axis, with −∞ mapping to the origin and + ∞ to itself. The imaginary axis is mapped to the unit circle with infinite multiplicity (period ); therefore the mapping of the imaginary infinities ±∞i is indeterminate. It follows that the entire left half-plane is mapped to the interior of the unit circle, and the right half-plane is mapped to the exterior of the unit circle. A line at any angle other than horizontal or vertical is mapped to a logarithmic spiral (but this is not illustrated here).




Figure 12.4: Illustration of the Range of the Natural Logarithm Function

PIC
The log function, which is the inverse of exp, naturally maps radial lines to horizontal lines and circles centered at the origin to vertical lines. The interior of the unit circle is thus mapped to the entire left half-plane, and the exterior of the unit circle is mapped to the right half-plane. The positive real axis is mapped to the entire real axis, and the negative real axis to a horizontal line of height π. The positive and negative imaginary axes are mapped to horizontal lines of height ± π∕2. The origin is mapped to −∞.




Figure 12.5: Illustration of the Range of the Function (z − 1)∕(z + 1)

PIC
A line is a degenerate circle with infinite radius; when I say “circles” here I also mean lines. Then (z − 1)∕(z + 1) maps circles into circles. All circles through − 1 become lines; all lines become circles through 1. The real axis is mapped onto itself: 1 to the origin, the origin to − 1, − 1 to infinity, and infinity to 1. The imaginary axis becomes the unit circle; i is mapped to itself, as is i. Thus the entire right half-plane is mapped to the interior of the unit circle, the unit circle interior to the left half-plane, the left half-plane to the unit circle exterior, and the unit circle exterior to the right half-plane. Imagine the complex plane to be a vast sea. The Colossus of Rhodes straddles the origin, its left foot on i and its right foot on i. It bends down and briefly paddles water between its legs so furiously that the water directly beneath is pushed out into the entire area behind it; much that was behind swirls forward to either side; and all that was before is sucked in to lie between its feet.




Figure 12.6: Illustration of the Range of the Function (1 + z)∕(1 −z)

PIC
The function h(z) = (1 + z)∕(1 −z) is the inverse of f(z) = (z − 1)∕(z + 1); that is, h(f(z)) = f(h(z)) = z. At first glance, the graph of h appears to be that of f flipped left-to-right, or perhaps reflected in the origin, but careful consideration of the shaded annuli reveals that this is not so; something more subtle is going on. Note that f(f(z)) = h(h(z)) = g(z) = −1∕z. The functions f, g, h, and the identity function thus form a group under composition, isomorphic to the group of the cyclic permutations of the points − 1, 0, 1, and , as indeed these functions accomplish the four possible cyclic permutations on those points. This function group is a subset of the group of bilinear transformations (az + b)∕(cz + d), all of which are conformal (angle-preserving) and map circles onto circles. Now, doesn’t that tangle of circles through − 1 look like something the cat got into?




Figure 12.7: Illustration of the Range of the Sine Function

PIC
We are used to seeing sin looking like a wiggly ocean wave, graphed vertically as a function of the real axis only. Here is a different view. The entire real axis is mapped to the segment [−1,1] of the real axis with infinite multiplicity (period ). The imaginary axis is mapped to itself as if by sinh considered as a real function. The origin is mapped to itself. Horizontal lines are mapped to ellipses with foci at ± 1 (note that two horizontal lines equidistant from the real axis will map onto the same ellipse). Vertical lines are mapped to hyperbolas with the same foci. There is a curious accident: the ellipse for horizontal lines at distance ± 1 from the real axis appears to intercept the real axis at ± π∕2 ≈±1.57… but this is not so; the intercepts are actually at ± (e + 1∕e)∕2 ≈±1.54….




Figure 12.8: Illustration of the Range of the Arc Sine Function

PIC
Just as sin grabs horizontal lines and bends them into elliptical loops around the origin, so its inverse asin takes annuli and yanks them more or less horizontally straight. Because sine is not injective, its inverse as a function cannot be surjective. This is just a highfalutin way of saying that the range of the asin function doesn’t cover the entire plane but only a strip π wide; arc sine as a one-to-many relation would cover the plane with an infinite number of copies of this strip side by side, looking for all the world like the tail of a peacock with an infinite number of feathers. The imaginary axis is mapped to itself as if by asinh considered as a real function. The real axis is mapped to a bent path, turning corners at ± π∕2 (the points to which ± 1 are mapped); + ∞ is mapped to π∕2 −∞i, and −∞ to − π∕2 + ∞i.




Figure 12.9: Illustration of the Range of the Cosine Function

PIC
We are used to seeing cos looking exactly like sin, a wiggly ocean wave, only displaced. Indeed the complex mapping of cos is also similar to that of sin, with horizontal and vertical lines mapping to the same ellipses and hyperbolas with foci at ± 1, although mapping to them in a different manner, to be sure. The entire real axis is again mapped to the segment [−1,1] of the real axis, but each half of the imaginary axis is mapped to the real axis to the right of 1 (as if by cosh considered as a real function). Therefore ±∞i both map to + ∞. The origin is mapped to 1. Whereas sin is an odd function, cos is an even function; as a result two points in each annulus, one the negative of the other, are mapped to the same shaded point in this graph; the shading shown here is taken from points in the original upper half-plane.




Figure 12.10: Illustration of the Range of the Arc Cosine Function

PIC
The graph of acos is very much like that of asin. One might think that our nervous peacock has shuffled half a step to the right, but the shading on the annuli shows that we have instead caught the bird exactly in mid-flight while doing a cartwheel. This is easily understood if we recall that arccosz = (π∕2) − arcsinz; negating arcsinz rotates it upside down, and adding the result to π∕2 translates it π∕2 to the right. The imaginary axis is mapped upside down to the vertical line at π∕2. The point + 1 is mapped to the origin, and − 1 to π. The image of the real axis is again cranky; + ∞ is mapped to + ∞i, and −∞ to π −∞i.




Figure 12.11: Illustration of the Range of the Tangent Function

PIC
The usual graph of tan as a real function looks like an infinite chorus line of disco dancers, left hands pointed skyward and right hands to the floor. The tan function is the quotient of sin and cos but it doesn’t much look like either except for having period . This goes for the complex plane as well, although the swoopy loops produced from the annulus between π∕2 and 2 look vaguely like those from the graph of sin inside out. The real axis is mapped onto itself with infinite multiplicity (period ). The imaginary axis is mapped backwards onto [−i,i]: + ∞i is mapped to i and −∞i to + i. Horizontal lines below or above the real axis become circles surrounding + i or i, respectively. Vertical lines become circular arcs from + i to i; two vertical lines separated by (2k + 1)π for integer k together become a complete circle. It seems that two arcs shown hit the real axis at ± π∕2 = ±1.57… but that is a coincidence; they really hit the axis at ± tan1 = 1.55….




Figure 12.12: Illustration of the Range of the Arc Tangent Function

PIC
All I can say is that this peacock is a horse of another color. At first glance, the axes seem to map in the same way as for asin and acos, but look again: this time it’s the imaginary axis doing weird things. All infinities map multiply to the points (2k + 1)π∕2; within the strip of principal values we may say that the real axis is mapped to the interval [−π∕2,+π∕2] and therefore −∞ is mapped to − π∕2 and + ∞ to + π∕2. The point + i is mapped to + ∞i, and i to −∞i, and so the imaginary axis is mapped into three pieces: the segment [−∞i,−i] is mapped to [π∕2,π∕2 −∞i]; the segment [−i,i] is mapped to the imaginary axis [−∞i,+∞i]; and the segment [+i,+∞i] is mapped to [−π∕2 + ∞i,−π∕2].




Figure 12.13: Illustration of the Range of the Hyperbolic Sine Function

PIC
It would seem that the graph of sinh is merely that of sin rotated 90 degrees. If that were so, then we would have sinhz = isinz. Careful inspection of the shading, however, reveals that this is not quite the case; in both graphs the lightest and darkest shades, which initially are adjacent to the positive real axis, remain adjacent to the positive real axis in both cases. To derive the graph of sinh from sin we must therefore first rotate the complex plane by − 90 degrees, then apply sin, then rotate the result by 90 degrees. In other words, sinhz = isin(−i)z; consistently replacing z with iz in this formula yields the familiar identity sinhiz = isinz.




Figure 12.14: Illustration of the Range of the Hyperbolic Arc Sine Function

PIC
The peacock sleeps. Because arcsinh iz = iarcsinz, the graph of asinh is related to that of asin by pre- and post-rotations of the complex plane in the same way as for sinh and sin.




Figure 12.15: Illustration of the Range of the Hyperbolic Cosine Function

PIC
The graph of cosh does not look like that of cos rotated 90 degrees; instead it looks like that of cos unrotated. That is because coshiz is not equal to icosz; rather, coshiz = cosz. Interpreted, that means that the shading is pre-rotated but there is no post-rotation.




Figure 12.16: Illustration of the Range of the Hyperbolic Arc Cosine Function

PIC
Hmm—I’d rather not say what happened to this peacock. This feather looks a bit mangled. Actually it is all right—the principal value for acosh is so chosen that its graph does not look simply like a rotated version of the graph of acos, but if all values were shown, the two graphs would fill the plane in repeating patterns related by a rotation.




Figure 12.17: Illustration of the Range of the Hyperbolic Tangent Function

PIC
The diagram for tanh is simply that of tan turned on its ear: itanz = tanhiz. The imaginary axis is mapped onto itself with infinite multiplicity (period ), and the real axis is mapped onto the segment [−1,+1]: + ∞ is mapped to + 1, and −∞ to − 1. Vertical lines to the left or right of the real axis are mapped to circles surrounding − 1 or 1, respectively. Horizontal lines are mapped to circular arcs anchored at − 1 and + 1; two horizontal lines separated by a distance (2k + 1)π for integer k are together mapped into a complete circle. How do we know these really are circles? Well, tanhz = ((exp2z) − 1)∕((exp2z) + 1), which is the composition of the bilinear transform (z − 1)∕(z + 1), the exponential expz, and the magnification 2z. Magnification maps lines to lines of the same slope; the exponential maps horizontal lines to circles and vertical lines to radial lines; and a bilinear transform maps generalized circles (including lines) to generalized circles. Q.E.D.




Figure 12.18: Illustration of the Range of the Hyperbolic Arc Tangent Function

PIC
A sleeping peacock of another color: arctanh iz = iarctanz.




Figure 12.19: Illustration of the Range of the Function   ------
√ 1 − z2

PIC
Here is a curious graph indeed for so simple a function! The origin is mapped to 1. The real axis segment [0,1] is mapped backwards (and non-linearly) into itself; the segment [1,+∞] is mapped non-linearly onto the positive imaginary axis. The negative real axis is mapped to the same points as the positive real axis. Both halves of the imaginary axis are mapped into [1,+∞] on the real axis. Horizontal lines become vaguely vertical, and vertical lines become vaguely horizontal. Circles centered at the origin are transformed into Cassinian (half-)ovals; the unit circle is mapped to a (half-)lemniscate of Bernoulli. The outermost annulus appears to have its inner edge at π on the real axis and its outer edge at 3 on the imaginary axis, but this is another accident; the intercept on the real axis, for example, is not really at π ≈ 3.14… but at ∘ ---------
  1 − (3i)2 = √ ---
  10 ≈ 3.16….




Figure 12.20: Illustration of the Range of the Function   ------
√ 1 + z2

PIC
The graph of q(z) = √ ----2-
  1+ z looks like that of p(z) = √ ----2-
  1− z except for the shading. You might not expect p and q to be related in the same way that cos and cosh are, but after a little reflection (or perhaps I should say, after turning it around in one’s mind) one can see that q(iz) = p(z). This formula is indeed of exactly the same form as coshiz = cosz. The function √ -----2
  1 + z maps both halves of the real axis into [1,+∞] on the real axis. The segments [0,i] and [0,−i] of the imaginary axis are each mapped backwards onto segment [0,1] of the real axis; [i,+∞i] and [−, −∞i] are each mapped onto the positive imaginary axis (but if minus zero is supported then opposite sides of the imaginary axis map to opposite halves of the imaginary axis—for example, q(+0 + 2i) = √ --
  5i but q(−0 + 2i) = −√ --
  5i).


Here is a sample of the PostScript code that generated figure 12.1, showing the initial scaling, translation, and clipping parameters; the code for one sector of the innermost annulus; and the code for the negative imaginary axis. Comment lines indicate how path or boundary segments were generated separately and then spliced (in order to allow for the places that a singularity might lurk, in which case the generating code can “inch up” to the problematical argument value).

The size of the entire PostScript file for the identity function was about 68 kilobytes (2757 lines, including comments). The smallest files were the plots for atan and atanh, about 65 kilobytes apiece; the largest were the plots for sin, cos, sinh, and cosh, about 138 kilobytes apiece.


% PostScript file for plot of function IDENTITY
% Plot is to fit in a region 4.666666666666667 inches square
%  showing axes extending 4.1 units from the origin.

40.97560975609756 40.97560975609756 scale
4.1 4.1 translate
newpath
  -4.1 -4.1 moveto
  4.1 -4.1 lineto
  4.1 4.1 lineto
  -4.1 4.1 lineto
  closepath
clip
% Moby grid for function IDENTITY
% Annulus 0.25 0.5 4 0.97 0.45
% Sector from 4.7124 to 6.2832 (quadrant 3)
newpath
  0.0 -0.25 moveto
  0.0 -0.375 lineto
  %middle radial
  0.0 -0.375 lineto
  0.0 -0.5 lineto
  %end radial
  0.0 -0.5 lineto
  0.092 -0.4915 lineto
  0.1843 -0.4648 lineto
  0.273 -0.4189 lineto
  0.3536 -0.3536 lineto
  %middle circumferential
  0.3536 -0.3536 lineto
  0.413 -0.2818 lineto
  0.4594 -0.1974 lineto
  0.4894 -0.1024 lineto
  0.5 0.0 lineto
  %end circumferential
  0.5 0.0 lineto
  0.375 0.0 lineto
  %middle radial
  0.375 0.0 lineto
  0.25 0.0 lineto
  %end radial
  0.25 0.0 lineto
  0.2297 -0.0987 lineto
  0.1768 -0.1768 lineto
  %middle circumferential
  0.1768 -0.1768 lineto
  0.0922 -0.2324 lineto
  0.0 -0.25 lineto
  %end circumferential
  closepath
currentgray   0.45 setgray   fill   setgray

[2598 lines omitted]

% Vertical line from (0.0, -0.5) to (0.0, 0.0)
newpath
  0.0 -0.5 moveto
  0.0 0.0 lineto
0.05 setlinewidth   1 setlinecap  stroke
% Vertical line from (0.0, -0.5) to (0.0, -1.0)
newpath
  0.0 -0.5 moveto
  0.0 -1.0 lineto
0.05 setlinewidth   1 setlinecap  stroke
% Vertical line from (0.0, -2.0) to (0.0, -1.0)
newpath
  0.0 -2.0 moveto
  0.0 -1.0 lineto
0.05 setlinewidth   1 setlinecap  stroke
% Vertical line from (0.0, -2.0) to (0.0, -1.1579208923731617E77)
newpath
  0.0 -2.0 moveto
  0.0 -6.3553 lineto
  0.0 -6.378103166302659 lineto
  0.0 -6.378103166302659 lineto
  0.0 -6.378103166302659 lineto
0.05 setlinewidth 1 setlinecap stroke

[84 lines omitted]

% End of PostScript file for plot of function IDENTITY

Here is the program that generated the PostScript code for the graphs shown in figures 12.1 through 12.20. It contains a mixture of fairly general mechanisms and ad hoc kludges for plotting functions of a single complex argument while gracefully handling extremely large and small values, branch cuts, singularities, and periodic behavior. The aim was to provide a simple user interface that would not require the caller to provide special advice for each function to be plotted. The file for figure 12.1, for example, was generated by the call (picture ’identity), which resulted in the writing of a file named identity-plot.ps.

The program assumes that any periodic behavior will have a period that is a multiple of ; that branch cuts will fall along the real or imaginary axis; and that singularities or very large or small values will occur only at the origin, at ± 1 or ±i, or on the boundaries of the annuli (particularly those with radius π∕2 or π). The central function is parametric-path, which accepts four arguments: two real numbers that are the endpoints of an interval of real numbers, a function that maps this interval into a path in the complex plane, and the function to be plotted; the task of parametric-path is to generate PostScript code (a series of lineto operations) that will plot an approximation to the image of the parametric path as transformed by the function to be plotted. Each of the functions hline, vline, -hline, -vline, radial, and circumferential takes appropriate parameters and returns a function suitable for use as the third argument to parametric-path. There is some code that defends against errors (by using ignore-errors) and against certain peculiarities of IEEE floating-point arithmetic (the code that checks for not-a-number (NaN) results).

The program is offered here without further comment or apology.


(defparameter units-to-show 4.1)
(defparameter text-width-in-picas 28.0)
(defparameter device-pixels-per-inch 300)
(defparameter pixels-per-unit
  (* (/ (/ text-width-in-picas 6)
        (* units-to-show 2))
     device-pixels-per-inch))

(defparameter big (sqrt (sqrt most-positive-single-float)))
(defparameter tiny (sqrt (sqrt least-positive-single-float)))

(defparameter path-really-losing 1000.0)
(defparameter path-outer-limit (* units-to-show (sqrt 2) 1.1))
(defparameter path-minimal-delta (/ 10 pixels-per-unit))
(defparameter path-outer-delta (* path-outer-limit 0.3))
(defparameter path-relative-closeness 0.00001)
(defparameter back-off-delta 0.0005)

(defun comment-line (stream &rest stuff)
  (format stream "~%% ")
  (apply #’format stream stuff)
  (format t "~%% ")
  (apply #’format t stuff))

(defun parametric-path (from to paramfn plotfn)
  (assert (and (plusp from) (plusp to)))
  (flet ((domainval (x) (funcall paramfn x))
         (rangeval (x) (funcall plotfn (funcall paramfn x)))
         (losing (x) (or (null x)
                         (/= (realpart x) (realpart x))  ;NaN?
                         (/= (imagpart x) (imagpart x))  ;NaN?
                         (> (abs (realpart x)) path-really-losing)
                         (> (abs (imagpart x)) path-really-losing))))
    (when (> to 1000.0)
      (let ((f0 (rangeval from))
            (f1 (rangeval (+ from 1)))
            (f2 (rangeval (+ from (* 2 pi))))
            (f3 (rangeval (+ from 1 (* 2 pi))))
            (f4 (rangeval (+ from (* 4 pi)))))
        (flet ((close (x y)
                 (or (< (careful-abs (- x y)) path-minimal-delta)
                     (< (careful-abs (- x y))
                        (* (+ (careful-abs x) (careful-abs y))
                           path-relative-closeness)))))
          (when (and (close f0 f2)
                     (close f2 f4)
                     (close f1 f3)
                     (or (and (close f0 f1)
                              (close f2 f3))
                         (and (not (close f0 f1))
                              (not (close f2 f3)))))
            (format t "~&Periodicity detected.")
            (setq to (+ from (* (signum (- to from)) 2 pi)))))))
     (let ((fromrange (ignore-errors (rangeval from)))
          (torange (ignore-errors (rangeval to))))
      (if (losing fromrange)
          (if (losing torange)
              ’()
              (parametric-path (back-off from to) to paramfn plotfn))
          (if (losing torange)
              (parametric-path from (back-off to from) paramfn plotfn)
              (expand-path (refine-path (list from to) #’rangeval)
                           #’rangeval))))))

(defun back-off (point other)
  (if (or (> point 10.0) (< point 0.1))
      (let ((sp (sqrt point)))
        (if (or (> point sp other) (< point sp other))
            sp
            (* sp (sqrt other))))
      (+ point (* (signum (- other point)) back-off-delta))))

(defun careful-abs (z)
  (cond ((or (> (realpart z) big)
             (< (realpart z) (- big))
             (> (imagpart z) big)
             (< (imagpart z) (- big)))
         big)
        ((complexp z) (abs z))
        ((minusp z) (- z))
        (t z)))

(defparameter max-refinements 5000)

(defun refine-path (original-path rangevalfn)
  (flet ((rangeval (x) (funcall rangevalfn x)))
    (let ((path original-path))
      (do ((j 0 (+ j 1)))
          ((null (rest path)))
        (when (zerop (mod (+ j 1) max-refinements))
              (break "Runaway path"))
        (let* ((from (first path))
               (to (second path))
               (fromrange (rangeval from))
               (torange (rangeval to))
               (dist (careful-abs (- torange fromrange)))
               (mid (* (sqrt from) (sqrt to)))
               (midrange (rangeval mid)))
          (cond ((or (and (far-out fromrange) (far-out torange))
                     (and (< dist path-minimal-delta)
                          (< (abs (- midrange fromrange))
                             path-minimal-delta)
                          ;; Next test is intentionally asymmetric to
                          ;;  avoid problems with periodic functions.
                          (< (abs (- (rangeval (/ (+ to (* from 1.5))
                                                  2.5))
                                     fromrange))
                             path-minimal-delta)))
                 (pop path))
                ((= mid from) (pop path))
                ((= mid to) (pop path))
                (t (setf (rest path) (cons mid (rest path)))))))))
  original-path)

(defun expand-path (path rangevalfn)
  (flet ((rangeval (x) (funcall rangevalfn x)))
    (let ((final-path (list (rangeval (first path)))))
      (do ((p (rest path) (cdr p)))
          ((null p)
           (unless (rest final-path)
             (break "Singleton path"))
           (reverse final-path))
        (let ((v (rangeval (car p))))
          (cond ((and (rest final-path)
                      (not (far-out v))
                      (not (far-out (first final-path)))
                      (between v (first final-path)
                                 (second final-path)))
                 (setf (first final-path) v))
                ((null (rest p))   ;Mustn’t omit last point
                 (push v final-path))
                ((< (abs (- v (first final-path))) path-minimal-delta))
                ((far-out v)
                 (unless (and (far-out (first final-path))
                              (< (abs (- v (first final-path)))
                                 path-outer-delta))
                   (push (* 1.01 path-outer-limit (signum v))
                         final-path)))
                (t (push v final-path))))))))

(defun far-out (x)
  (> (careful-abs x) path-outer-limit))

(defparameter between-tolerance 0.000001)

(defun between (p q r)
  (let ((px (realpart p)) (py (imagpart p))
        (qx (realpart q)) (qy (imagpart q))
        (rx (realpart r)) (ry (imagpart r)))
    (and (or (<= px qx rx) (>= px qx rx))
         (or (<= py qy ry) (>= py qy ry))
         (< (abs (- (* (- qx px) (- ry qy))
                    (* (- rx qx) (- qy py))))
            between-tolerance))))

(defun circle (radius)
  #’(lambda (angle) (* radius (cis angle))))

(defun hline (imag)
  #’(lambda (real) (complex real imag)))

(defun vline (real)
  #’(lambda (imag) (complex real imag)))

(defun -hline (imag)
  #’(lambda (real) (complex (- real) imag)))

(defun -vline (real)
  #’(lambda (imag) (complex real (- imag))))

(defun radial (phi quadrant)
  #’(lambda (rho) (repair-quadrant (* rho (cis phi)) quadrant)))

(defun circumferential (rho quadrant)
  #’(lambda (phi) (repair-quadrant (* rho (cis phi)) quadrant)))

;;; Quadrant is 0, 1, 2, or 3, meaning I, II, III, or IV.

(defun repair-quadrant (z quadrant)
  (complex (* (+ (abs (realpart z)) tiny)
              (case quadrant (0 1.0) (1 -1.0) (2 -1.0) (3 1.0)))
           (* (+ (abs (imagpart z)) tiny)
              (case quadrant (0 1.0) (1 1.0) (2 -1.0) (3 -1.0)))))

(defun clamp-real (x)
  (if (far-out x)
      (* (signum x) path-outer-limit)
      (round-real x)))

(defun round-real (x)
  (/ (round (* x 10000.0)) 10000.0))

(defun round-point (z)
  (complex (round-real (realpart z)) (round-real (imagpart z))))

(defparameter hiringshade 0.97)
(defparameter loringshade 0.45)

(defparameter ticklength 0.12)
(defparameter smallticklength 0.09)

;;; This determines the pattern of lines and annuli to be drawn.
(defun moby-grid (&optional (fn ’sqrt) (stream t))
  (comment-line stream "Moby grid for function ~S" fn)
  (shaded-annulus 0.25 0.5 4 hiringshade loringshade fn stream)
  (shaded-annulus 0.75 1.0 8 hiringshade loringshade fn stream)
  (shaded-annulus (/ pi 2) 2.0 16 hiringshade loringshade fn stream)
  (shaded-annulus 3 pi 32 hiringshade loringshade fn stream)
  (moby-lines :horizontal 1.0 fn stream)
  (moby-lines :horizontal -1.0 fn stream)
  (moby-lines :vertical 1.0 fn stream)
  (moby-lines :vertical -1.0 fn stream)
  (let ((tickline 0.015)
        (axisline 0.008))
    (flet ((tick (n) (straight-line (complex n ticklength)
                                    (complex n (- ticklength))
                                    tickline
                                    stream))
           (smalltick (n) (straight-line (complex n smallticklength)
                                         (complex n (- smallticklength))
                                         tickline
                                         stream)))
      (comment-line stream "Real axis")
      (straight-line #c(-5 0) #c(5 0) axisline stream)
      (dotimes (j (floor units-to-show))
        (let ((q (+ j 1))) (tick q) (tick (- q))))
      (dotimes (j (floor units-to-show (/ pi 2)))
        (let ((q (* (/ pi 2) (+ j 1))))
          (smalltick q)
          (smalltick (- q)))))
    (flet ((tick (n) (straight-line (complex ticklength n)
                                    (complex (- ticklength) n)
                                    tickline
                                    stream))
           (smalltick (n) (straight-line (complex smallticklength n)
                                         (complex (- smallticklength) n)
                                         tickline
                                         stream)))
      (comment-line stream "Imaginary axis")
      (straight-line #c(0 -5) #c(0 5) axisline stream)
      (dotimes (j (floor units-to-show))
        (let ((q (+ j 1))) (tick q) (tick (- q))))
      (dotimes (j (floor units-to-show (/ pi 2)))
        (let ((q (* (/ pi 2) (+ j 1))))
          (smalltick q)
          (smalltick (- q)))))))

(defun straight-line (from to wid stream)
  (format stream
          "~%newpath  ~S ~S moveto  ~S ~S lineto  ~S ~
           setlinewidth  1  setlinecap  stroke"
          (realpart from)
          (imagpart from)
          (realpart to)
          (imagpart to)
          wid))

;;; This function draws the lines for the pattern.
(defun moby-lines (orientation signum plotfn stream)
  (let ((paramfn (ecase orientation
                   (:horizontal (if (< signum 0) #’-hline #’hline))
                   (:vertical (if (< signum 0) #’-vline #’vline)))))
    (flet ((foo (from to other wid)
             (ecase orientation
               (:horizontal
                (comment-line stream
                              "Horizontal line from (~S, ~S) to (~S, ~S)"
                              (round-real (* signum from))
                              (round-real other)
                              (round-real (* signum to))
                              (round-real other)))
               (:vertical
                (comment-line stream
                              "Vertical line from (~S, ~S) to (~S, ~S)"
                              (round-real other)
                              (round-real (* signum from))
                              (round-real other)
                              (round-real (* signum to)))))
             (postscript-path
               stream
               (parametric-path from
                                to
                                (funcall paramfn other)
                                plotfn))
             (postscript-penstroke stream wid)))
      (let* ((thick 0.05)
             (thin 0.02))
        ;; Main axis
        (foo 0.5 tiny 0.0 thick)
        (foo 0.5 1.0 0.0 thick)
        (foo 2.0 1.0 0.0 thick)
        (foo 2.0 big 0.0 thick)
        ;; Parallels at 1 and -1
        (foo 2.0 tiny 1.0 thin)
        (foo 2.0 big 1.0 thin)
        (foo 2.0 tiny -1.0 thin)
        (foo 2.0 big -1.0 thin)
        ;; Parallels at 2, 3, -2, -3
        (foo tiny big 2.0 thin)
        (foo tiny big -2.0 thin)
        (foo tiny big 3.0 thin)
        (foo tiny big -3.0 thin)))))

(defun splice (p q)
  (let ((v (car (last p)))
        (w (first q)))
    (and (far-out v)
         (far-out w)
         (>= (abs (- v w)) path-outer-delta)
         ;; Two far-apart far-out points.  Try to walk around
         ;;  outside the perimeter, in the shorter direction.
         (let* ((pdiff (phase (/ v w)))
                (npoints (floor (abs pdiff) (asin .2)))
                (delta (/ pdiff (+ npoints 1)))
                (incr (cis delta)))
           (do ((j 0 (+ j 1))
                (p (list w "end splice") (cons (* (car p) incr) p)))
               ((= j npoints) (cons "start splice" p)))))))

;;; This function draws the annuli for the pattern.
(defun shaded-annulus (inner outer sectors firstshade lastshade fn stream)
  (assert (zerop (mod sectors 4)))
  (comment-line stream "Annulus ~S ~S ~S ~S ~S"
                (round-real inner) (round-real outer)
                sectors firstshade lastshade)
  (dotimes (jj sectors)
    (let ((j (- sectors jj 1)))
      (let* ((lophase (+ tiny (* 2 pi (/ j sectors))))
             (hiphase (* 2 pi (/ (+ j 1) sectors)))
             (midphase (/ (+ lophase hiphase) 2.0))
             (midradius (/ (+ inner outer) 2.0))
             (quadrant (floor (* j 4) sectors)))
        (comment-line stream "Sector from ~S to ~S (quadrant ~S)"
                      (round-real lophase)
                      (round-real hiphase)
                      quadrant)
        (let ((p0 (reverse (parametric-path midradius
                                            inner
                                            (radial lophase quadrant)
                                            fn)))
              (p1 (parametric-path midradius
                                   outer
                                   (radial lophase quadrant)
                                   fn))
              (p2 (reverse (parametric-path midphase
                                            lophase
                                            (circumferential outer
                                                             quadrant)
                                            fn)))
              (p3 (parametric-path midphase
                                   hiphase
                                   (circumferential outer quadrant)
                                   fn))
              (p4 (reverse (parametric-path midradius
                                            outer
                                            (radial hiphase quadrant)
                                            fn)))
              (p5 (parametric-path midradius
                                   inner
                                   (radial hiphase quadrant)
                                   fn))
              (p6 (reverse (parametric-path midphase
                                            hiphase
                                            (circumferential inner
                                                             quadrant)
                                            fn)))
              (p7 (parametric-path midphase
                                   lophase
                                   (circumferential inner quadrant)
                                   fn)))
          (postscript-closed-path stream
            (append
              p0 (splice p0 p1) ’("middle radial")
              p1 (splice p1 p2) ’("end radial")
              p2 (splice p2 p3) ’("middle circumferential")
              p3 (splice p3 p4) ’("end circumferential")
              p4 (splice p4 p5) ’("middle radial")
              p5 (splice p5 p6) ’("end radial")
              p6 (splice p6 p7) ’("middle circumferential")
              p7 (splice p7 p0) ’("end circumferential")
              )))

        (postscript-shade stream
                          (/ (+ (* firstshade (- (- sectors 1) j))
                                (* lastshade j))
                             (- sectors 1)))))))

(defun postscript-penstroke (stream wid)
  (format stream "~%~S setlinewidth   1 setlinecap  stroke"
          wid))

(defun postscript-shade (stream shade)
  (format stream "~%currentgray   ~S setgray   fill   setgray"
          shade))

(defun postscript-closed-path (stream path)
  (unless (every #’far-out (remove-if-not #’numberp path))
    (postscript-raw-path stream path)
    (format stream "~%  closepath")))

(defun postscript-path (stream path)
  (unless (every #’far-out (remove-if-not #’numberp path))
    (postscript-raw-path stream path)))

;;; Print a path as a series of PostScript "lineto" commands.
(defun postscript-raw-path (stream path)
  (format stream "~%newpath")
  (let ((fmt "~%  ~S ~S moveto"))
    (dolist (pt path)
      (cond ((stringp pt)
             (format stream "~%  %~A" pt))
            (t (format stream
                       fmt
                       (clamp-real (realpart pt))
                       (clamp-real (imagpart pt)))
               (setq fmt "~%  ~S ~S lineto"))))))

;;; Definitions of functions to be plotted that are not
;;; standard Common Lisp functions.

(defun one-plus-over-one-minus (x) (/ (+ 1 x) (- 1 x)))

(defun one-minus-over-one-plus (x) (/ (- 1 x) (+ 1 x)))

(defun sqrt-square-minus-one (x) (sqrt (- 1 (* x x))))

(defun sqrt-one-plus-square (x) (sqrt (+ 1 (* x x))))

;;; Because X3J13 voted for a new definition of the atan function,
;;; the following definition was used in place of the atan function
;;; provided by the Common Lisp implementation I was using.

(defun good-atan (x)
  (/ (- (log (+ 1 (* x #c(0 1))))
        (log (- 1 (* x #c(0 1)))))
     #c(0 2)))

;;; Because the first edition had an erroneous definition of atanh,
;;; the following definition was used in place of the atanh function
;;; provided by the Common Lisp implementation I was using.

(defun really-good-atanh (x)
  (/ (- (log (+ 1 x))
        (log (- 1 x)))
     2))

;;; This is the main procedure that is intended to be called by a user.
(defun picture (&optional (fn #’sqrt))
  (with-open-file (stream (concatenate ’string
                                       (string-downcase (string fn))
                                       "-plot.ps")
                          :direction :output)
    (format stream "% PostScript file for plot of function ~S~%" fn)
    (format stream "% Plot is to fit in a region ~S inches square~%"
            (/ text-width-in-picas 6.0))
    (format stream
            "%  showing axes extending ~S units from the origin.~%"
            units-to-show)
    (let ((scaling (/ (* text-width-in-picas 12) (* units-to-show 2))))
      (format stream "~%~S ~:*~S scale" scaling))
    (format stream "~%~S ~:*~S translate" units-to-show)
    (format stream "~%newpath")
    (format stream "~%  ~S ~S moveto" (- units-to-show) (- units-to-show))
    (format stream "~%  ~S ~S lineto" units-to-show (- units-to-show))
    (format stream "~%  ~S ~S lineto" units-to-show units-to-show)
    (format stream "~%  ~S ~S lineto" (- units-to-show) units-to-show)
    (format stream "~%  closepath")
    (format stream "~%clip")
    (moby-grid fn stream)
    (format stream
            "~%% End of PostScript file for plot of function ~S"
            fn)
    (terpri stream)))