Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagesmc
Path: blob/master/src/sage/plot/complex_plot.pyx
8815 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 "sage/ext/stdsage.pxi"
23
include "sage/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.x_count = len(rgb_data)
192
self.y_count = len(rgb_data[0])
193
self.rgb_data = rgb_data
194
GraphicPrimitive.__init__(self, options)
195
196
def get_minmax_data(self):
197
"""
198
Returns a dictionary with the bounding box data.
199
200
EXAMPLES::
201
202
sage: p = complex_plot(lambda z: z, (-1, 2), (-3, 4))
203
sage: sorted(p.get_minmax_data().items())
204
[('xmax', 2.0), ('xmin', -1.0), ('ymax', 4.0), ('ymin', -3.0)]
205
"""
206
from sage.plot.plot import minmax_data
207
return minmax_data(self.xrange, self.yrange, dict=True)
208
209
def _allowed_options(self):
210
"""
211
TESTS::
212
213
sage: isinstance(complex_plot(lambda z: z, (-1,1), (-1,1))[0]._allowed_options(), dict)
214
True
215
"""
216
return {'plot_points':'How many points to use for plotting precision',
217
'interpolation':'What interpolation method to use'}
218
219
def _repr_(self):
220
"""
221
TESTS::
222
223
sage: isinstance(complex_plot(lambda z: z, (-1,1), (-1,1))[0]._repr_(), str)
224
True
225
"""
226
return "ComplexPlot defined by a %s x %s data grid"%(self.x_count, self.y_count)
227
228
def _render_on_subplot(self, subplot):
229
"""
230
TESTS::
231
232
sage: complex_plot(lambda x: x^2, (-5, 5), (-5, 5))
233
"""
234
options = self.options()
235
x0,x1 = float(self.xrange[0]), float(self.xrange[1])
236
y0,y1 = float(self.yrange[0]), float(self.yrange[1])
237
subplot.imshow(self.rgb_data, origin='lower', extent=(x0,x1,y0,y1), interpolation=options['interpolation'])
238
239
@options(plot_points=100, interpolation='catrom')
240
def complex_plot(f, xrange, yrange, **options):
241
r"""
242
``complex_plot`` takes a complex function of one variable,
243
`f(z)` and plots output of the function over the specified
244
``xrange`` and ``yrange`` as demonstrated below. The magnitude of the
245
output is indicated by the brightness (with zero being black and
246
infinity being white) while the argument is represented by the
247
hue (with red being positive real, and increasing through orange,
248
yellow, ... as the argument increases).
249
250
``complex_plot(f, (xmin, xmax), (ymin, ymax), ...)``
251
252
INPUT:
253
254
- ``f`` -- a function of a single complex value `x + iy`
255
256
- ``(xmin, xmax)`` -- 2-tuple, the range of ``x`` values
257
258
- ``(ymin, ymax)`` -- 2-tuple, the range of ``y`` values
259
260
The following inputs must all be passed in as named parameters:
261
262
- ``plot_points`` -- integer (default: 100); number of points to plot
263
in each direction of the grid
264
265
- ``interpolation`` -- string (default: ``'catrom'``), the interpolation
266
method to use: ``'bilinear'``, ``'bicubic'``, ``'spline16'``,
267
``'spline36'``, ``'quadric'``, ``'gaussian'``, ``'sinc'``,
268
``'bessel'``, ``'mitchell'``, ``'lanczos'``, ``'catrom'``,
269
``'hermite'``, ``'hanning'``, ``'hamming'``, ``'kaiser'``
270
271
272
EXAMPLES:
273
274
Here we plot a couple of simple functions::
275
276
sage: complex_plot(sqrt(x), (-5, 5), (-5, 5))
277
278
::
279
280
sage: complex_plot(sin(x), (-5, 5), (-5, 5))
281
282
::
283
284
sage: complex_plot(log(x), (-10, 10), (-10, 10))
285
286
::
287
288
sage: complex_plot(exp(x), (-10, 10), (-10, 10))
289
290
A function with some nice zeros and a pole::
291
292
sage: f(z) = z^5 + z - 1 + 1/z
293
sage: complex_plot(f, (-3, 3), (-3, 3))
294
295
Here is the identity, useful for seeing what values map to what colors::
296
297
sage: complex_plot(lambda z: z, (-3, 3), (-3, 3))
298
299
The Riemann Zeta function::
300
301
sage: complex_plot(zeta, (-30,30), (-30,30))
302
303
Extra options will get passed on to show(), as long as they are valid::
304
305
sage: complex_plot(lambda z: z, (-3, 3), (-3, 3), figsize=[1,1])
306
307
::
308
309
sage: complex_plot(lambda z: z, (-3, 3), (-3, 3)).show(figsize=[1,1]) # These are equivalent
310
311
TESTS:
312
313
Test to make sure that using fast_callable functions works::
314
315
sage: f(x) = x^2
316
sage: g = fast_callable(f, domain=CC, vars='x')
317
sage: h = fast_callable(f, domain=CDF, vars='x')
318
sage: P = complex_plot(f, (-10, 10), (-10, 10))
319
sage: Q = complex_plot(g, (-10, 10), (-10, 10))
320
sage: R = complex_plot(h, (-10, 10), (-10, 10))
321
sage: S = complex_plot(exp(x)-sin(x), (-10, 10), (-10, 10))
322
sage: P; Q; R; S
323
324
Test to make sure symbolic functions still work without declaring
325
a variable. (We don't do this in practice because it doesn't use
326
fast_callable, so it is much slower.)
327
328
::
329
330
sage: complex_plot(sqrt, (-5, 5), (-5, 5))
331
"""
332
from sage.plot.all import Graphics
333
from sage.plot.misc import setup_for_eval_on_grid
334
from sage.ext.fast_callable import fast_callable
335
from sage.rings.complex_double import CDF
336
337
try:
338
f = fast_callable(f, domain=CDF, expect_one_var=True)
339
except (AttributeError, TypeError, ValueError):
340
pass
341
342
cdef double x, y
343
ignore, ranges = setup_for_eval_on_grid([], [xrange, yrange], options['plot_points'])
344
xrange,yrange=[r[:2] for r in ranges]
345
sig_on()
346
z_values = [[ f(new_CDF_element(x, y)) for x in srange(*ranges[0], include_endpoint=True)]
347
for y in srange(*ranges[1], include_endpoint=True)]
348
sig_off()
349
g = Graphics()
350
g._set_extra_kwds(Graphics._extract_kwds_for_show(options, ignore=['xmin', 'xmax']))
351
g.add_primitive(ComplexPlot(complex_to_rgb(z_values), xrange, yrange, options))
352
return g
353
354