Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagelib
Path: blob/master/sage/plot/complex_plot.pyx
4034 views
1
"""
2
Complex Plots
3
"""
4
5
#*****************************************************************************
6
# Copyright (C) 2009 Robert Bradshaw <[email protected]>,
7
#
8
# Distributed under the terms of the GNU General Public License (GPL)
9
#
10
# This code is distributed in the hope that it will be useful,
11
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13
# General Public License for more details.
14
#
15
# The full text of the GPL is available at:
16
#
17
# http://www.gnu.org/licenses/
18
#*****************************************************************************
19
20
# TODO: use NumPy buffers and complex fast_callable (when supported)
21
22
include "../ext/stdsage.pxi"
23
include "../ext/interrupt.pxi"
24
25
cimport numpy as cnumpy
26
27
from sage.plot.primitive import GraphicPrimitive
28
from sage.misc.decorators import options
29
from sage.rings.complex_double cimport ComplexDoubleElement
30
from sage.misc.misc import srange
31
32
cdef extern from "math.h":
33
double hypot(double, double)
34
double atan2(double, double)
35
double atan(double)
36
double log(double)
37
double exp(double)
38
double sqrt(double)
39
double PI
40
41
cdef inline ComplexDoubleElement new_CDF_element(double x, double y):
42
cdef ComplexDoubleElement z = <ComplexDoubleElement>PY_NEW(ComplexDoubleElement)
43
z._complex.dat[0] = x
44
z._complex.dat[1] = y
45
return z
46
47
cdef inline double mag_to_lightness(double r):
48
"""
49
Tweak this to adjust how the magnitude affects the color.
50
For instance, changing ``sqrt(r)`` to ``r`` will cause
51
anything near a zero to be much darker and poles to be
52
much lighter, while ``r**(.25)`` would cause the reverse
53
effect.
54
55
INPUT:
56
57
- ``r`` - a non-negative real number
58
59
OUTPUT:
60
61
A value between `-1` (black) and `+1` (white), inclusive.
62
63
EXAMPLES:
64
65
This tests it implicitly::
66
67
sage: from sage.plot.complex_plot import complex_to_rgb
68
sage: complex_to_rgb([[0, 1, 10]])
69
array([[[ 0. , 0. , 0. ],
70
[ 0.77172568, 0. , 0. ],
71
[ 1. , 0.22134776, 0.22134776]]])
72
"""
73
return atan(log(sqrt(r)+1)) * (4/PI) - 1
74
75
def complex_to_rgb(z_values):
76
"""
77
INPUT:
78
79
- ``z_values`` -- A grid of complex numbers, as a list of lists
80
81
OUTPUT:
82
83
An `N \\times M \\times 3` floating point Numpy array ``X``, where
84
``X[i,j]`` is an (r,g,b) tuple.
85
86
EXAMPLES::
87
88
sage: from sage.plot.complex_plot import complex_to_rgb
89
sage: complex_to_rgb([[0, 1, 1000]])
90
array([[[ 0. , 0. , 0. ],
91
[ 0.77172568, 0. , 0. ],
92
[ 1. , 0.64421177, 0.64421177]]])
93
sage: complex_to_rgb([[0, 1j, 1000j]])
94
array([[[ 0. , 0. , 0. ],
95
[ 0.38586284, 0.77172568, 0. ],
96
[ 0.82210588, 1. , 0.64421177]]])
97
"""
98
import numpy
99
cdef unsigned int i, j, imax, jmax
100
cdef double x, y, mag, arg
101
cdef double lightness, hue, top, bot
102
cdef double r, g, b
103
cdef int ihue
104
cdef ComplexDoubleElement z
105
from sage.rings.complex_double import CDF
106
107
imax = len(z_values)
108
jmax = len(z_values[0])
109
cdef cnumpy.ndarray[cnumpy.float_t, ndim=3, mode='c'] rgb = numpy.empty(dtype=numpy.float, shape=(imax, jmax, 3))
110
111
sig_on()
112
for i from 0 <= i < imax:
113
114
row = z_values[i]
115
for j from 0 <= j < jmax:
116
117
zz = row[j]
118
z = <ComplexDoubleElement>(zz if PY_TYPE_CHECK_EXACT(zz, ComplexDoubleElement) else CDF(zz))
119
x, y = z._complex.dat[0], z._complex.dat[1]
120
mag = hypot(x, y)
121
arg = atan2(y, x) # math module arctan has range from -pi to pi, so cut along negative x-axis
122
123
lightness = mag_to_lightness(mag)
124
if lightness < 0: # in hsv, variable value, full saturation (s=1, v=1+lightness)
125
bot = 0
126
top = (1+lightness)
127
else: # in hsv, variable saturation, full value (v=1, s=1-lightness)
128
bot = lightness
129
top = 1
130
131
hue = 3*arg/PI # Note that does same thing as colorsys module hsv_to_rgb for this setup, but in Cython
132
if hue < 0: hue += 6 # usual hsv hue is thus h=arg/(2*pi) for positive, h=arg/(2*PI)+1 for negative
133
ihue = <int>hue
134
if ihue == 0:
135
r = top
136
g = bot + hue * (top-bot)
137
b = bot
138
elif ihue == 1:
139
r = bot + (2-hue) * (top-bot)
140
g = top
141
b = bot
142
elif ihue == 2:
143
r = bot
144
g = top
145
b = bot + (hue-2) * (top-bot)
146
elif ihue == 3:
147
r = bot
148
g = bot + (4-hue) * (top-bot)
149
b = top
150
elif ihue == 4:
151
r = bot + (hue-4) * (top-bot)
152
g = bot
153
b = top
154
else:
155
r = top
156
g = bot
157
b = bot + (6-hue) * (top-bot)
158
159
rgb[i, j, 0] = r
160
rgb[i, j, 1] = g
161
rgb[i, j, 2] = b
162
163
sig_off()
164
return rgb
165
166
class ComplexPlot(GraphicPrimitive):
167
"""
168
The GraphicsPrimitive to display complex functions in using the domain
169
coloring method
170
171
INPUT:
172
173
- ``rgb_data`` -- An array of colored points to be plotted.
174
175
- ``xrange`` -- A minimum and maximum x value for the plot.
176
177
- ``yrange`` -- A minimum and maximum y value for the plot.
178
179
TESTS::
180
181
sage: p = complex_plot(lambda z: z^2-1, (-2, 2), (-2, 2))
182
"""
183
def __init__(self, rgb_data, xrange, yrange, options):
184
"""
185
TESTS::
186
187
sage: p = complex_plot(lambda z: z^2-1, (-2, 2), (-2, 2))
188
"""
189
self.xrange = xrange
190
self.yrange = yrange
191
#self.z_values = z_values
192
self.x_count = len(rgb_data)
193
self.y_count = len(rgb_data[0])
194
self.rgb_data = rgb_data
195
GraphicPrimitive.__init__(self, options)
196
197
def get_minmax_data(self):
198
"""
199
Returns a dictionary with the bounding box data.
200
201
EXAMPLES::
202
203
sage: p = complex_plot(lambda z: z, (-1, 2), (-3, 4))
204
sage: sorted(p.get_minmax_data().items())
205
[('xmax', 2.0), ('xmin', -1.0), ('ymax', 4.0), ('ymin', -3.0)]
206
"""
207
from sage.plot.plot import minmax_data
208
return minmax_data(self.xrange, self.yrange, dict=True)
209
210
def _allowed_options(self):
211
"""
212
TESTS::
213
214
sage: isinstance(complex_plot(lambda z: z, (-1,1), (-1,1))[0]._allowed_options(), dict)
215
True
216
"""
217
return {'plot_points':'How many points to use for plotting precision',
218
'interpolation':'What interpolation method to use'}
219
220
def _repr_(self):
221
"""
222
TESTS::
223
224
sage: isinstance(complex_plot(lambda z: z, (-1,1), (-1,1))[0]._repr_(), str)
225
True
226
"""
227
return "ComplexPlot defined by a %s x %s data grid"%(self.x_count, self.y_count)
228
229
def _render_on_subplot(self, subplot):
230
"""
231
TESTS::
232
233
sage: complex_plot(lambda x: x^2, (-5, 5), (-5, 5))
234
"""
235
options = self.options()
236
x0,x1 = float(self.xrange[0]), float(self.xrange[1])
237
y0,y1 = float(self.yrange[0]), float(self.yrange[1])
238
subplot.imshow(self.rgb_data, origin='lower', extent=(x0,x1,y0,y1), interpolation=options['interpolation'])
239
240
@options(plot_points=100, interpolation='catrom')
241
def complex_plot(f, xrange, yrange, **options):
242
r"""
243
``complex_plot`` takes a complex function of one variable,
244
`f(z)` and plots output of the function over the specified
245
``xrange`` and ``yrange`` as demonstrated below. The magnitude of the
246
output is indicated by the brightness (with zero being black and
247
infinity being white) while the argument is represented by the
248
hue (with red being positive real, and increasing through orange,
249
yellow, ... as the argument increases).
250
251
``complex_plot(f, (xmin, xmax), (ymin, ymax), ...)``
252
253
INPUT:
254
255
- ``f`` -- a function of a single complex value `x + iy`
256
257
- ``(xmin, xmax)`` -- 2-tuple, the range of ``x`` values
258
259
- ``(ymin, ymax)`` -- 2-tuple, the range of ``y`` values
260
261
The following inputs must all be passed in as named parameters:
262
263
- ``plot_points`` -- integer (default: 100); number of points to plot
264
in each direction of the grid
265
266
- ``interpolation`` -- string (default: ``'catrom'``), the interpolation
267
method to use: ``'bilinear'``, ``'bicubic'``, ``'spline16'``,
268
``'spline36'``, ``'quadric'``, ``'gaussian'``, ``'sinc'``,
269
``'bessel'``, ``'mitchell'``, ``'lanczos'``, ``'catrom'``,
270
``'hermite'``, ``'hanning'``, ``'hamming'``, ``'kaiser'``
271
272
273
EXAMPLES:
274
275
Here we plot a couple of simple functions::
276
277
sage: complex_plot(sqrt(x), (-5, 5), (-5, 5))
278
279
::
280
281
sage: complex_plot(sin(x), (-5, 5), (-5, 5))
282
283
::
284
285
sage: complex_plot(log(x), (-10, 10), (-10, 10))
286
287
::
288
289
sage: complex_plot(exp(x), (-10, 10), (-10, 10))
290
291
A function with some nice zeros and a pole::
292
293
sage: f(z) = z^5 + z - 1 + 1/z
294
sage: complex_plot(f, (-3, 3), (-3, 3))
295
296
Here is the identity, useful for seeing what values map to what colors::
297
298
sage: complex_plot(lambda z: z, (-3, 3), (-3, 3))
299
300
The Riemann Zeta function::
301
302
sage: complex_plot(zeta, (-30,30), (-30,30))
303
304
Extra options will get passed on to show(), as long as they are valid::
305
306
sage: complex_plot(lambda z: z, (-3, 3), (-3, 3), figsize=[1,1])
307
308
::
309
310
sage: complex_plot(lambda z: z, (-3, 3), (-3, 3)).show(figsize=[1,1]) # These are equivalent
311
312
TESTS:
313
314
Test to make sure that using fast_callable functions works::
315
316
sage: f(x) = x^2
317
sage: g = fast_callable(f, domain=CC, vars='x')
318
sage: h = fast_callable(f, domain=CDF, vars='x')
319
sage: P = complex_plot(f, (-10, 10), (-10, 10))
320
sage: Q = complex_plot(g, (-10, 10), (-10, 10))
321
sage: R = complex_plot(h, (-10, 10), (-10, 10))
322
sage: S = complex_plot(exp(x)-sin(x), (-10, 10), (-10, 10))
323
sage: P; Q; R; S
324
325
Test to make sure symbolic functions still work without declaring
326
a variable. (We don't do this in practice because it doesn't use
327
fast_callable, so it is much slower.)
328
329
::
330
331
sage: complex_plot(sqrt, (-5, 5), (-5, 5))
332
"""
333
from sage.plot.all import Graphics
334
from sage.plot.misc import setup_for_eval_on_grid
335
from sage.ext.fast_callable import fast_callable
336
from sage.rings.complex_double import CDF
337
338
try:
339
f = fast_callable(f, domain=CDF, expect_one_var=True)
340
except (AttributeError, TypeError, ValueError):
341
pass
342
343
cdef double x, y
344
ignore, ranges = setup_for_eval_on_grid([], [xrange, yrange], options['plot_points'])
345
xrange,yrange=[r[:2] for r in ranges]
346
sig_on()
347
z_values = [[ f(new_CDF_element(x, y)) for x in srange(*ranges[0], include_endpoint=True)]
348
for y in srange(*ranges[1], include_endpoint=True)]
349
sig_off()
350
g = Graphics()
351
g._set_extra_kwds(Graphics._extract_kwds_for_show(options, ignore=['xmin', 'xmax']))
352
g.add_primitive(ComplexPlot(complex_to_rgb(z_values), xrange, yrange, options))
353
return g
354
355