Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagesmc
Path: blob/master/src/sage/calculus/riemann.pyx
8815 views
1
"""
2
Riemann Mapping
3
4
AUTHORS:
5
6
- Ethan Van Andel (2009-2011): initial version and upgrades
7
8
- Robert Bradshaw (2009): his "complex_plot" was adapted for plot_colored
9
10
Development supported by NSF award No. 0702939.
11
"""
12
13
#*****************************************************************************
14
# Copyright (C) 2011 Ethan Van Andel <[email protected]>,
15
#
16
# Distributed under the terms of the GNU General Public License (GPL)
17
#
18
# This code is distributed in the hope that it will be useful,
19
# but WITHOUT ANY WARRANTY; without even the implied warranty of
20
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
21
# General Public License for more details.
22
#
23
# The full text of the GPL is available at:
24
#
25
# http://www.gnu.org/licenses/
26
#*****************************************************************************
27
28
include "sage/ext/stdsage.pxi"
29
include "sage/ext/interrupt.pxi"
30
31
from sage.misc.decorators import options
32
from sage.plot.all import list_plot, Graphics
33
34
from sage.ext.fast_eval import fast_callable
35
36
from sage.rings.all import CDF
37
38
from sage.misc.misc import srange
39
40
from sage.gsl.interpolation import spline
41
42
from sage.plot.complex_plot import ComplexPlot
43
44
from sage.gsl.integration import numerical_integral
45
46
47
import numpy as np
48
cimport numpy as np
49
50
from math import pi
51
from math import sin
52
from math import cos
53
from math import sqrt
54
55
from math import log # used for complex plot lightness
56
from math import atan
57
58
from cmath import exp
59
from cmath import phase
60
61
from random import uniform # used for accuracy tests
62
63
FLOAT = np.float64
64
ctypedef np.float64_t FLOAT_T
65
66
COMPLEX = np.complex128
67
ctypedef np.complex128_t COMPLEX_T
68
69
cdef FLOAT_T PI = pi
70
cdef FLOAT_T TWOPI = 2*PI
71
cdef COMPLEX_T I = complex(0,1)
72
73
cdef class Riemann_Map:
74
"""
75
76
The ``Riemann_Map`` class computes an interior or exterior Riemann map,
77
or an Ahlfors map of a region given by the supplied boundary curve(s)
78
and center point. The class also provides various methods to
79
evaluate, visualize, or extract data from the map.
80
81
A Riemann map conformally maps a simply connected region in
82
the complex plane to the unit disc. The Ahlfors map does the same thing
83
for multiply connected regions.
84
85
Note that all the methods are numerical. As a result all answers have
86
some imprecision. Moreover, maps computed with small number of
87
collocation points, or for unusually shaped regions, may be very
88
inaccurate. Error computations for the ellipse can be found in the
89
documentation for :meth:`analytic_boundary` and :meth:`analytic_interior`.
90
91
[BSV]_ provides an overview of the Riemann map and discusses the research
92
that lead to the creation of this module.
93
94
INPUT:
95
96
- ``fs`` -- A list of the boundaries of the region, given as
97
complex-valued functions with domain `0` to `2*pi`. Note that the
98
outer boundary must be parameterized counter clockwise
99
(i.e. ``e^(I*t)``) while the inner boundaries must be clockwise
100
(i.e. ``e^(-I*t)``).
101
102
- ``fprimes`` -- A list of the derivatives of the boundary functions.
103
Must be in the same order as ``fs``.
104
105
- ``a`` -- Complex, the center of the Riemann map. Will be mapped to
106
the origin of the unit disc. Note that ``a`` MUST be within
107
the region in order for the results to be mathematically valid.
108
109
The following inputs may be passed in as named parameters:
110
111
- ``N`` -- integer (default: ``500``), the number of collocation points
112
used to compute the map. More points will give more accurate results,
113
especially near the boundaries, but will take longer to compute.
114
115
- ``exterior`` -- boolean (default: ``False``), if set to ``True``, the
116
exterior map will be computed, mapping the exterior of the region to the
117
exterior of the unit circle.
118
119
The following inputs may be passed as named parameters in unusual
120
circumstances:
121
122
- ``ncorners`` -- integer (default: ``4``), if mapping a figure with
123
(equally t-spaced) corners -- corners that make a significant change in
124
the direction of the boundary -- better results may be sometimes obtained by
125
accurately giving this parameter. Used to add the proper constant to
126
the theta correspondence function.
127
128
- ``opp`` -- boolean (default: ``False``), set to ``True`` in very rare
129
cases where the theta correspondence function is off by ``pi``, that
130
is, if red is mapped left of the origin in the color plot.
131
132
133
EXAMPLES:
134
135
The unit circle identity map::
136
137
sage: f(t) = e^(I*t)
138
sage: fprime(t) = I*e^(I*t)
139
sage: m = Riemann_Map([f], [fprime], 0) # long time (4 sec)
140
sage: m.plot_colored() + m.plot_spiderweb() # long time
141
142
The exterior map for the unit circle::
143
144
sage: m = Riemann_Map([f], [fprime], 0, exterior=True) # long time (4 sec)
145
sage: #spiderwebs are not supported for exterior maps
146
sage: m.plot_colored() # long time
147
148
The unit circle with a small hole::
149
150
sage: f(t) = e^(I*t)
151
sage: fprime(t) = I*e^(I*t)
152
sage: hf(t) = 0.5*e^(-I*t)
153
sage: hfprime(t) = 0.5*-I*e^(-I*t)
154
sage: m = Riemann_Map([f, hf], [fprime, hfprime], 0.5 + 0.5*I)
155
sage: #spiderweb and color plots cannot be added for multiply
156
sage: #connected regions. Instead we do this.
157
sage: m.plot_spiderweb(withcolor = True) # long time
158
159
A square::
160
161
sage: ps = polygon_spline([(-1, -1), (1, -1), (1, 1), (-1, 1)])
162
sage: f = lambda t: ps.value(real(t))
163
sage: fprime = lambda t: ps.derivative(real(t))
164
sage: m = Riemann_Map([f], [fprime], 0.25, ncorners=4)
165
sage: m.plot_colored() + m.plot_spiderweb() # long time
166
167
Compute rough error for this map::
168
169
sage: x = 0.75 # long time
170
sage: print "error =", m.inverse_riemann_map(m.riemann_map(x)) - x # long time
171
error = (-0.000...+0.0016...j)
172
173
A fun, complex region for demonstration purposes::
174
175
sage: f(t) = e^(I*t)
176
sage: fp(t) = I*e^(I*t)
177
sage: ef1(t) = .2*e^(-I*t) +.4+.4*I
178
sage: ef1p(t) = -I*.2*e^(-I*t)
179
sage: ef2(t) = .2*e^(-I*t) -.4+.4*I
180
sage: ef2p(t) = -I*.2*e^(-I*t)
181
sage: pts = [(-.5,-.15-20/1000),(-.6,-.27-10/1000),(-.45,-.45),(0,-.65+10/1000),(.45,-.45),(.6,-.27-10/1000),(.5,-.15-10/1000),(0,-.43+10/1000)]
182
sage: pts.reverse()
183
sage: cs = complex_cubic_spline(pts)
184
sage: mf = lambda x:cs.value(x)
185
sage: mfprime = lambda x: cs.derivative(x)
186
sage: m = Riemann_Map([f,ef1,ef2,mf],[fp,ef1p,ef2p,mfprime],0,N = 400) # long time
187
sage: p = m.plot_colored(plot_points = 400) # long time
188
189
ALGORITHM:
190
191
This class computes the Riemann Map via the Szego kernel using an
192
adaptation of the method described by [KT]_.
193
194
REFERENCES:
195
196
.. [KT] N. Kerzman and M. R. Trummer. "Numerical Conformal Mapping via
197
the Szego kernel". Journal of Computational and Applied Mathematics,
198
14(1-2): 111--123, 1986.
199
200
.. [BSV] M. Bolt, S. Snoeyink, E. Van Andel. "Visual representation of
201
the Riemann map and Ahlfors map via the Kerzman-Stein equation".
202
Involve 3-4 (2010), 405-420.
203
204
"""
205
cdef int N, B, ncorners
206
cdef f
207
cdef opp
208
cdef COMPLEX_T a
209
cdef np.ndarray tk, tk2
210
cdef np.ndarray cps, dps, szego, p_vector, pre_q_vector
211
cdef np.ndarray p_vector_inverse, sinalpha, cosalpha, theta_array
212
cdef x_range, y_range
213
cdef exterior
214
215
def __init__(self, fs, fprimes, COMPLEX_T a, int N=500, int ncorners=4,
216
opp=False, exterior = False):
217
218
"""
219
Initializes the ``Riemann_Map`` class. See the class :class:`Riemann_Map`
220
for full documentation on the input of this initialization method.
221
222
TESTS::
223
224
sage: f(t) = e^(I*t) - 0.5*e^(-I*t)
225
sage: fprime(t) = I*e^(I*t) + 0.5*I*e^(-I*t)
226
sage: m = Riemann_Map([f], [fprime], 0, N = 10)
227
"""
228
cdef double xmax, xmin, ymax, ymin, space
229
cdef int i, k
230
if N <= 2:
231
raise ValueError(
232
"The number of collocation points must be > 2.")
233
if exterior and a!=0:
234
raise ValueError("The exterior map requires a=0")
235
try:
236
fs = [fast_callable(f, domain=CDF) for f in fs]
237
fprimes = [fast_callable(f, domain=CDF) for f in fprimes]
238
except AttributeError:
239
pass
240
self.f = fs[0]
241
self.a = a
242
self.ncorners = ncorners
243
self.N = N # Number of collocation pts
244
self.opp = opp
245
self.exterior = exterior
246
self.tk = np.array(np.arange(N) * TWOPI / N + 0.001 / N,
247
dtype=FLOAT)
248
self.tk2 = np.zeros(N + 1, dtype=FLOAT)
249
for i in xrange(N):
250
self.tk2[i] = self.tk[i]
251
self.tk2[N] = TWOPI
252
self.B = len(fs) # number of boundaries of the figure
253
if self.exterior and (self.B > 1):
254
raise ValueError(
255
"The exterior map is undefined for multiply connected domains")
256
cdef np.ndarray[COMPLEX_T,ndim=2] cps = np.zeros([self.B, N],
257
dtype=COMPLEX)
258
cdef np.ndarray[COMPLEX_T,ndim=2] dps = np.zeros([self.B, N],
259
dtype=COMPLEX)
260
# Find the points on the boundaries and their derivatives.
261
if self.exterior:
262
for k in xrange(self.B):
263
for i in xrange(N):
264
fk = fs[k](self.tk[N-i-1])
265
cps[k, i] = np.complex(1/fk)
266
dps[k, i] = np.complex(1/fk**2*fprimes[k](self.tk[N-i-1]))
267
else:
268
for k in xrange(self.B):
269
for i in xrange(N):
270
cps[k, i] = np.complex(fs[k](self.tk[i]))
271
dps[k, i] = np.complex(fprimes[k](self.tk[i]))
272
if self.exterior:
273
xmax = (1/cps).real.max()
274
xmin = (1/cps).real.min()
275
ymax = (1/cps).imag.max()
276
ymin = (1/cps).imag.min()
277
else:
278
xmax = cps.real.max()
279
xmin = cps.real.min()
280
ymax = cps.imag.max()
281
ymin = cps.imag.min()
282
space = 0.1 * max(xmax - xmin, ymax - ymin)
283
#The default plotting window for this map.
284
self.cps = cps
285
self.dps = dps
286
self.x_range = (xmin - space, xmax + space)
287
self.y_range = (ymin - space, ymax + space)
288
# Now we do some more computation
289
self._generate_theta_array()
290
self._generate_interior_mapper()
291
self._generate_inverse_mapper()
292
293
294
def _repr_(self):
295
"""
296
Return a string representation of this :class:`Riemann_Map` object.
297
298
TESTS::
299
300
sage: f(t) = e^(I*t) - 0.5*e^(-I*t)
301
sage: fprime(t) = I*e^(I*t) + 0.5*I*e^(-I*t)
302
sage: isinstance(Riemann_Map([f], [fprime], 0, N = 10)._repr_(), str) # long time
303
True
304
"""
305
return "A Riemann or Ahlfors mapping of a figure to the unit circle."
306
307
cdef _generate_theta_array(self):
308
"""
309
Generates the essential data for the Riemann map, primarily the
310
Szego kernel and boundary correspondence. See [KT]_ for the algorithm.
311
312
TESTS::
313
314
sage: f(t) = e^(I*t) - 0.5*e^(-I*t)
315
sage: fprime(t) = I*e^(I*t) + 0.5*I*e^(-I*t)
316
sage: m = Riemann_Map([f], [fprime], 0, N = 10)
317
"""
318
cdef np.ndarray[COMPLEX_T,ndim =1] cp = self.cps.flatten()
319
cdef np.ndarray[COMPLEX_T,ndim =1] dp = self.dps.flatten()
320
cdef int N = self.N
321
cdef int NB = N * self.B
322
cdef int B = self.B
323
cdef int i, k
324
cdef FLOAT_T saa, t0
325
cdef np.ndarray[FLOAT_T, ndim=1] adp, sadp
326
cdef np.ndarray[COMPLEX_T,ndim =1] h, hconj, g, normalized_dp, C, phi
327
cdef np.ndarray[COMPLEX_T,ndim =2] K
328
cdef np.ndarray[FLOAT_T, ndim=2] theta_array
329
# Setting things up to use the Nystrom method
330
adp = abs(dp)
331
sadp = np.sqrt(adp)
332
h = 1 / (TWOPI * I) * ((dp / adp) / (self.a - cp))
333
hconj = np.array(map(np.complex.conjugate, h), dtype=COMPLEX)
334
g = -sadp * hconj
335
normalized_dp=dp/adp
336
C = I / N * sadp # equivalent to -TWOPI / N * 1 / (TWOPI * I) * sadp
337
errinvalid = np.geterr()['invalid'] # checks the current error handling for invalid
338
errdivide = np.geterr()['divide'] # checks the current error handling for divide
339
np.seterr(divide='ignore',invalid='ignore')
340
K = np.array([C * sadp[t] * (normalized_dp/(cp-cp[t]) -
341
(normalized_dp[t]/(cp-cp[t])).conjugate())
342
for t in np.arange(NB)], dtype=np.complex128)
343
np.seterr(divide=errdivide,invalid=errinvalid) # resets the error handling
344
for i in xrange(NB):
345
K[i, i] = 1
346
# Nystrom Method for solving 2nd kind integrals
347
phi = np.linalg.solve(K, g) / NB * TWOPI
348
# the all-important Szego kernel
349
szego = np.array(phi.flatten() / np.sqrt(dp), dtype=COMPLEX)
350
self.szego = szego.reshape([B, N])
351
start = 0
352
# Finding the theta correspondence using phase. Misbehaves for some
353
# regions.
354
if B != 1:
355
theta_array = np.zeros([1, NB])
356
for i in xrange(NB):
357
theta_array[0, i] = phase(-I * np.power(phi[i], 2) * dp[i])
358
self.theta_array = np.concatenate(
359
[theta_array.reshape([B, N]), np.zeros([B, 1])], axis=1)
360
for k in xrange(B):
361
self.theta_array[k, N] = self.theta_array[k, 0] + TWOPI
362
# Finding the theta correspondence using abs. Well behaved, but
363
# doesn't work on multiply connected domains.
364
else:
365
phi2 = phi.reshape([self.B, N])
366
theta_array = np.zeros([B, N + 1], dtype=np.float64)
367
for k in xrange(B):
368
phik = phi2[k]
369
saa = (np.dot(abs(phi), abs(phi))) * TWOPI / NB
370
theta_array[k, 0] = 0
371
for i in xrange(1, N):
372
theta_array[k, i] = (
373
theta_array[k, i - 1] +
374
((TWOPI / NB * TWOPI *
375
abs(np.power(phi[1 * i], 2)) / saa +
376
TWOPI / NB * TWOPI *
377
abs(np.power(phi[1 * i - 1], 2)) / saa)) / 2)
378
tmax = int(0.5 * N / self.ncorners)
379
# Finding the initial value of the theta function.
380
phimax = -I * phik[tmax]**2 * self.dps[k, tmax]
381
if self.opp:
382
t0 = theta_array[k, tmax] + phase(phimax)
383
else:
384
t0 = theta_array[k, tmax] - phase(phimax)
385
for i in xrange(N):
386
theta_array[k, i] = theta_array[k, i] - t0
387
theta_array[k, N] = TWOPI + theta_array[k, 0]
388
self.theta_array = theta_array
389
390
def get_szego(self, int boundary=-1, absolute_value=False):
391
"""
392
Returns a discretized version of the Szego kernel for each boundary
393
function.
394
395
INPUT:
396
397
The following inputs may be passed in as named parameters:
398
399
- ``boundary`` -- integer (default: ``-1``) if < 0,
400
:meth:`get_theta_points` will return the points for all boundaries.
401
If >= 0, :meth:`get_theta_points` will return only the points for
402
the boundary specified.
403
404
- ``absolute_value`` -- boolean (default: ``False``) if ``True``, will
405
return the absolute value of the (complex valued) Szego kernel
406
instead of the kernel itself. Useful for plotting.
407
408
OUTPUT:
409
410
A list of points of the form
411
``[t value, value of the Szego kernel at that t]``.
412
413
EXAMPLES:
414
415
Generic use::
416
417
sage: f(t) = e^(I*t) - 0.5*e^(-I*t)
418
sage: fprime(t) = I*e^(I*t) + 0.5*I*e^(-I*t)
419
sage: m = Riemann_Map([f], [fprime], 0)
420
sage: sz = m.get_szego(boundary=0)
421
sage: points = m.get_szego(absolute_value=True)
422
sage: list_plot(points)
423
424
Extending the points by a spline::
425
426
sage: s = spline(points)
427
sage: s(3*pi / 4)
428
0.0012158...
429
sage: plot(s,0,2*pi) # plot the kernel
430
431
The unit circle with a small hole::
432
433
sage: f(t) = e^(I*t)
434
sage: fprime(t) = I*e^(I*t)
435
sage: hf(t) = 0.5*e^(-I*t)
436
sage: hfprime(t) = 0.5*-I*e^(-I*t)
437
sage: m = Riemann_Map([f, hf], [fprime, hfprime], 0.5 + 0.5*I)
438
439
Getting the szego for a specifc boundary::
440
441
sage: sz0 = m.get_szego(boundary=0)
442
sage: sz1 = m.get_szego(boundary=1)
443
"""
444
cdef int k, B
445
if boundary < 0:
446
temptk = self.tk
447
for i in xrange(self.B - 1):
448
temptk = np.concatenate([temptk, self.tk])
449
if absolute_value:
450
return np.column_stack(
451
[temptk, abs(self.szego.flatten())]).tolist()
452
else:
453
return np.column_stack([temptk, self.szego.flatten()]).tolist()
454
else:
455
if absolute_value:
456
return np.column_stack(
457
[self.tk, abs(self.szego[boundary])]).tolist()
458
else:
459
return np.column_stack(
460
[self.tk, self.szego[boundary]]).tolist()
461
462
def get_theta_points(self, int boundary=-1):
463
"""
464
Returns an array of points of the form
465
``[t value, theta in e^(I*theta)]``, that is, a discretized version
466
of the theta/boundary correspondence function. In other words, a point
467
in this array [t1, t2] represents that the boundary point given by f(t1)
468
is mapped to a point on the boundary of the unit circle given by e^(I*t2).
469
470
For multiply connected domains, ``get_theta_points`` will list the
471
points for each boundary in the order that they were supplied.
472
473
INPUT:
474
475
The following input must all be passed in as named parameters:
476
477
- ``boundary`` -- integer (default: ``-1``) if < 0,
478
``get_theta_points()`` will return the points for all boundaries.
479
If >= 0, ``get_theta_points()`` will return only the points for
480
the boundary specified.
481
482
OUTPUT:
483
484
A list of points of the form ``[t value, theta in e^(I*theta)]``.
485
486
EXAMPLES:
487
488
Getting the list of points, extending it via a spline, getting the
489
points for only the outside of a multiply connected domain::
490
491
sage: f(t) = e^(I*t) - 0.5*e^(-I*t)
492
sage: fprime(t) = I*e^(I*t) + 0.5*I*e^(-I*t)
493
sage: m = Riemann_Map([f], [fprime], 0)
494
sage: points = m.get_theta_points()
495
sage: list_plot(points)
496
497
Extending the points by a spline::
498
499
sage: s = spline(points)
500
sage: s(3*pi / 4)
501
1.627660...
502
503
The unit circle with a small hole::
504
505
sage: f(t) = e^(I*t)
506
sage: fprime(t) = I*e^(I*t)
507
sage: hf(t) = 0.5*e^(-I*t)
508
sage: hfprime(t) = 0.5*-I*e^(-I*t)
509
sage: m = Riemann_Map([f, hf], [hf, hfprime], 0.5 + 0.5*I)
510
511
Getting the boundary correspondence for a specifc boundary::
512
513
sage: tp0 = m.get_theta_points(boundary=0)
514
sage: tp1 = m.get_theta_points(boundary=1)
515
"""
516
if boundary < 0:
517
temptk = self.tk2
518
for i in xrange(self.B - 1):
519
temptk = np.concatenate([temptk, self.tk2])
520
return np.column_stack(
521
[temptk, self.theta_array.flatten()]).tolist()
522
else:
523
return np.column_stack(
524
[self.tk2, self.theta_array[boundary]]).tolist()
525
526
cdef _generate_interior_mapper(self):
527
"""
528
Generates the data necessary to use the :meth:`riemann_map` function.
529
As much setup as possible is done here to minimize the computation
530
that must be done in ``riemann_map``.
531
532
TESTS::
533
534
sage: f(t) = e^(I*t) - 0.5*e^(-I*t)
535
sage: fprime(t) = I*e^(I*t) + 0.5*I*e^(-I*t)
536
sage: m = Riemann_Map([f], [fprime], 0, N = 10) # indirect doctest
537
"""
538
cdef int N = self.N
539
cdef double complex coeff = 3*I / (8*N)
540
dps = self.dps
541
theta_array = self.theta_array
542
cdef np.ndarray[double complex, ndim=2] p_vector = np.zeros(
543
[self.B, N + 1], dtype=np.complex128)
544
cdef int k, i
545
# Lots of setup for Simpson's method of integration.
546
for k in xrange(self.B):
547
for i in xrange(N // 3):
548
p_vector[k, 3*i] = (2*coeff * dps[k, 3*i] *
549
exp(I * theta_array[k, 3*i]))
550
p_vector[k, 3*i + 1] = (3*coeff * dps[k, 3*i + 1] *
551
exp(I * theta_array[k, 3*i + 1]))
552
p_vector[k, 3*i + 2] = (3*coeff * dps[k, 3*i + 2] *
553
exp(I * theta_array[k, 3*i + 2]))
554
p_vector[k, 0] = 1*coeff * dps[k, 0] * exp(I * theta_array[k, 0])
555
if N % 3 == 0:
556
p_vector[k, N] = 1*coeff * dps[k, 0] * exp(I*theta_array[k, 0])
557
elif (N - 2) % 3 == 0:
558
p_vector[k, N - 2] = ((coeff + I/(3*N)) * dps[k, N- 2 ] *
559
exp(I * theta_array[k, N - 2]))
560
p_vector[k, N- 1 ] = (4*I / (3*N) * dps[k, N - 1] *
561
exp(I * theta_array[k, N - 1]))
562
p_vector[k, N] = (I / (3*N) * dps[k, 0] *
563
exp(I * theta_array[k, 0]))
564
else:
565
p_vector[k, N - 4] = ((coeff + I / (3*N)) * dps[k, N - 4] *
566
exp(I * theta_array[k, N - 4]))
567
p_vector[k, N - 3] = (4*I / (3*N) * dps[k, N - 3] *
568
exp(I * theta_array[k, N - 3]))
569
p_vector[k, N - 2] = (2*I / (3*N) * dps[k, N - 2] *
570
exp(I * theta_array[k, N - 2]))
571
p_vector[k, N - 1] = (4*I / (3*N) * dps[k, N - 1] *
572
exp(I * theta_array[k, N - 1]))
573
p_vector[k, N] = (I / (3*N) * dps[k, 0] *
574
exp(I * theta_array[k, 0]))
575
self.p_vector = p_vector.flatten()
576
cdef np.ndarray[double complex, ndim=1] pq = self.cps[:,list(range(N))+[0]].flatten()
577
self.pre_q_vector = pq
578
579
cpdef riemann_map(self, COMPLEX_T pt):
580
"""
581
Returns the Riemann mapping of a point. That is, given ``pt`` on
582
the interior of the mapped region, ``riemann_map`` will return
583
the point on the unit disk that ``pt`` maps to. Note that this
584
method only works for interior points; accuracy breaks down very close
585
to the boundary. To get boundary corrospondance, use
586
:meth:`get_theta_points`.
587
588
INPUT:
589
590
- ``pt`` -- A complex number representing the point to be
591
inverse mapped.
592
593
OUTPUT:
594
595
A complex number representing the point on the unit circle that
596
the input point maps to.
597
598
EXAMPLES:
599
600
Can work for different types of complex numbers::
601
602
sage: f(t) = e^(I*t) - 0.5*e^(-I*t)
603
sage: fprime(t) = I*e^(I*t) + 0.5*I*e^(-I*t)
604
sage: m = Riemann_Map([f], [fprime], 0)
605
sage: m.riemann_map(0.25 + sqrt(-0.5))
606
(0.137514...+0.876696...j)
607
sage: I = CDF.gen()
608
sage: m.riemann_map(1.3*I)
609
(-1.56...e-05+0.989694...j)
610
sage: m.riemann_map(0.4)
611
(0.73324...+3.2...e-06j)
612
sage: import numpy as np
613
sage: m.riemann_map(np.complex(-3, 0.0001))
614
(1.405757...e-05+8.06...e-10j)
615
"""
616
617
cdef COMPLEX_T pt1
618
cdef np.ndarray[COMPLEX_T, ndim=1] q_vector
619
if self.exterior:
620
pt1 = 1/pt
621
q_vector = 1 / (
622
self.pre_q_vector - pt1)
623
return -1/np.dot(self.p_vector, q_vector)
624
else:
625
pt1 = pt
626
q_vector = 1 / (
627
self.pre_q_vector - pt1)
628
return -np.dot(self.p_vector, q_vector)
629
630
cdef _generate_inverse_mapper(self):
631
"""
632
Generates the data necessary to use the
633
:meth:`inverse_riemann_map` function. As much setup as possible is
634
done here to minimize the computation that must be done in
635
``inverse_riemann_map``.
636
637
TESTS::
638
639
sage: f(t) = e^(I*t) - 0.5*e^(-I*t)
640
sage: fprime(t) = I*e^(I*t) + 0.5*I*e^(-I*t)
641
sage: m = Riemann_Map([f], [fprime], 0, N = 10) # indirect doctest
642
"""
643
cdef int N = self.N
644
cdef int B = self.B
645
cdef float di
646
theta_array = self.theta_array
647
self.p_vector_inverse = np.zeros([B, N], dtype=np.complex128)
648
# Setup for trapezoid integration because integration points are
649
# not equally spaced.
650
for k in xrange(B):
651
for i in xrange(N):
652
di = theta_array[k, (i + 1) % N] - theta_array[k, (i - 1) % N]
653
if di > PI:
654
di = di - TWOPI
655
elif di < -PI:
656
di = di + TWOPI
657
self.p_vector_inverse[k, i] = di / 2
658
self.sinalpha = np.zeros([B, N], dtype=np.float64)
659
for k in xrange(B):
660
for i in xrange(N):
661
self.sinalpha[k, i] = sin(-theta_array[k, i])
662
self.cosalpha = np.zeros([B, N], dtype=np.float64)
663
for k in xrange(B):
664
for i in xrange(N):
665
self.cosalpha[k, i] = cos(-theta_array[k, i])
666
667
cpdef inverse_riemann_map(self, COMPLEX_T pt):
668
"""
669
Returns the inverse Riemann mapping of a point. That is, given ``pt``
670
on the interior of the unit disc, ``inverse_riemann_map()`` will
671
return the point on the original region that would be Riemann
672
mapped to ``pt``. Note that this method does not work for multiply
673
connected domains.
674
675
INPUT:
676
677
- ``pt`` -- A complex number (usually with absolute value <= 1)
678
representing the point to be inverse mapped.
679
680
OUTPUT:
681
682
The point on the region that Riemann maps to the input point.
683
684
EXAMPLES:
685
686
Can work for different types of complex numbers::
687
688
sage: f(t) = e^(I*t) - 0.5*e^(-I*t)
689
sage: fprime(t) = I*e^(I*t) + 0.5*I*e^(-I*t)
690
sage: m = Riemann_Map([f], [fprime], 0)
691
sage: m.inverse_riemann_map(0.5 + sqrt(-0.5))
692
(0.406880...+0.3614702...j)
693
sage: m.inverse_riemann_map(0.95)
694
(0.486319...-4.90019052...j)
695
sage: m.inverse_riemann_map(0.25 - 0.3*I)
696
(0.1653244...-0.180936...j)
697
sage: import numpy as np
698
sage: m.inverse_riemann_map(np.complex(-0.2, 0.5))
699
(-0.156280...+0.321819...j)
700
"""
701
if self.exterior:
702
pt = 1/pt
703
r = abs(pt)
704
if r == 0:
705
stheta = 0
706
ctheta = 0
707
else:
708
stheta = pt.imag / r
709
ctheta = pt.real / r
710
k = 0
711
mapped = (1 - r**2) / TWOPI * np.dot(
712
self.p_vector_inverse[k] * self.cps[k],
713
1 / (1 + r**2 - 2*r *
714
(ctheta * self.cosalpha[k] - stheta * self.sinalpha[k])))
715
if self.exterior:
716
return 1/mapped
717
else:
718
return mapped
719
720
def plot_boundaries(self, plotjoined=True, rgbcolor=[0,0,0], thickness=1):
721
"""
722
Plots the boundaries of the region for the Riemann map. Note that
723
this method DOES work for multiply connected domains.
724
725
INPUT:
726
727
The following inputs may be passed in as named parameters:
728
729
- ``plotjoined`` -- boolean (default: ``True``) If ``False``,
730
discrete points will be drawn; otherwise they will be connected
731
by lines. In this case, if ``plotjoined=False``, the points shown
732
will be the original collocation points used to generate the
733
Riemann map.
734
735
- ``rgbcolor`` -- float array (default: ``[0,0,0]``) the
736
red-green-blue color of the boundary.
737
738
- ``thickness`` -- positive float (default: ``1``) the thickness of
739
the lines or points in the boundary.
740
741
EXAMPLES:
742
743
General usage::
744
745
sage: f(t) = e^(I*t) - 0.5*e^(-I*t)
746
sage: fprime(t) = I*e^(I*t) + 0.5*I*e^(-I*t)
747
sage: m = Riemann_Map([f], [fprime], 0)
748
749
Default plot::
750
751
sage: m.plot_boundaries()
752
753
Big blue collocation points::
754
755
sage: m.plot_boundaries(plotjoined=False, rgbcolor=[0,0,1], thickness=6)
756
"""
757
plots = range(self.B)
758
for k in xrange(self.B):
759
# This conditional should be eliminated when the thickness/pointsize
760
# issue is resolved later. Same for the others in plot_spiderweb().
761
if plotjoined:
762
plots[k] = list_plot(
763
comp_pt(self.cps[k], 1), plotjoined=True,
764
rgbcolor=rgbcolor, thickness=thickness)
765
else:
766
plots[k] = list_plot(
767
comp_pt(self.cps[k], 1), rgbcolor=rgbcolor,
768
pointsize=thickness)
769
return sum(plots)
770
771
772
cpdef compute_on_grid(self, plot_range, int x_points):
773
"""
774
Computes the riemann map on a grid of points. Note that these points
775
are complex of the form z = x + y*i.
776
777
INPUT:
778
779
- ``plot_range`` -- a tuple of the form ``[xmin, xmax, ymin, ymax]``.
780
If the value is ``[]``, the default plotting window of the map will
781
be used.
782
783
- ``x_points`` -- int, the size of the grid in the x direction
784
The number of points in the y_direction is scaled accordingly
785
786
OUTPUT:
787
788
- a tuple containing ``[z_values, xmin, xmax, ymin, ymax]``
789
where ``z_values`` is the evaluation of the map on the specified grid.
790
791
EXAMPLES:
792
793
General usage::
794
795
sage: f(t) = e^(I*t) - 0.5*e^(-I*t)
796
sage: fprime(t) = I*e^(I*t) + 0.5*I*e^(-I*t)
797
sage: m = Riemann_Map([f], [fprime], 0)
798
sage: data = m.compute_on_grid([],5)
799
sage: print data[0][8,1]
800
(-0.0879...+0.9709...j)
801
"""
802
cdef FLOAT_T xmin, xmax, xstep, ymin, ymax, ystep
803
cdef int y_points
804
if plot_range == []:
805
xmin = self.x_range[0]
806
xmax = self.x_range[1]
807
ymin = self.y_range[0]
808
ymax = self.y_range[1]
809
else:
810
xmin = plot_range[0]
811
xmax = plot_range[1]
812
ymin = plot_range[2]
813
ymax = plot_range[3]
814
xstep = (xmax - xmin) / x_points
815
ystep = xstep
816
y_points = int((ymax-ymin)/ ystep)
817
cdef Py_ssize_t i, j
818
cdef COMPLEX_T pt
819
cdef np.ndarray[COMPLEX_T] pre_q_vector = self.pre_q_vector
820
cdef np.ndarray[COMPLEX_T] p_vector = self.p_vector
821
cdef np.ndarray[COMPLEX_T, ndim=2] z_values = np.empty(
822
[y_points, x_points], dtype=np.complex128)
823
if self.exterior:
824
for i in xrange(x_points):
825
for j in xrange(y_points):
826
pt = 1/(xmin + 0.5*xstep + i*xstep + I*(ymin + 0.5*ystep + j*ystep))
827
z_values[j, i] = 1/(-np.dot(p_vector,1/(pre_q_vector - pt)))
828
else:
829
for i in xrange(x_points):
830
for j in xrange(y_points):
831
pt = xmin + 0.5*xstep + i*xstep + I*(ymin + 0.5*ystep + j*ystep)
832
z_values[j, i] = -np.dot(p_vector,1/(pre_q_vector - pt))
833
return z_values, xmin, xmax, ymin, ymax
834
835
836
@options(interpolation='catrom')
837
def plot_spiderweb(self, spokes=16, circles=4, pts=32, linescale=0.99,
838
rgbcolor=[0,0,0], thickness=1, plotjoined=True, withcolor = False,
839
plot_points = 200, min_mag = 0.001, **options):
840
"""
841
Generates a traditional "spiderweb plot" of the Riemann map. Shows
842
what concentric circles and radial lines map to. The radial lines
843
may exhibit erratic behavior near the boundary; if this occurs,
844
decreasing ``linescale`` may mitigate the problem.
845
846
For multiply connected domains the spiderweb is by necessity
847
generated using the forward mapping. This method is more
848
computationally intensive. In addition, these spiderwebs cannot
849
be ``added`` to color plots. Instead the ``withcolor`` option
850
must be used.
851
852
In addition, spiderweb plots are not currently supported for
853
exterior maps.
854
855
INPUT:
856
857
The following inputs may be passed in as named parameters:
858
859
- ``spokes`` -- integer (default: ``16``) the number of equally
860
spaced radial lines to plot.
861
862
- ``circles`` -- integer (default: ``4``) the number of equally
863
spaced circles about the center to plot.
864
865
- ``pts`` -- integer (default: ``32``) the number of points to
866
plot. Each radial line is made by ``1*pts`` points, each circle
867
has ``2*pts`` points. Note that high values may cause erratic
868
behavior of the radial lines near the boundaries.
869
- only for simply connected domains
870
871
- ``linescale`` -- float between 0 and 1. Shrinks the radial lines
872
away from the boundary to reduce erratic behavior.
873
- only for simply connected domains
874
875
- ``rgbcolor`` -- float array (default: ``[0,0,0]``) the
876
red-green-blue color of the spiderweb.
877
878
- ``thickness`` -- positive float (default: ``1``) the thickness of
879
the lines or points in the spiderweb.
880
881
- ``plotjoined`` -- boolean (default: ``True``) If ``False``,
882
discrete points will be drawn; otherwise they will be connected
883
by lines.
884
- only for simply connected domains
885
886
- ``withcolor`` -- boolean (default: ``False``) If ``True``,
887
The spiderweb will be overlaid on the basic color plot.
888
889
- ``plot_points`` -- integer (default: ``200``) the size of the grid in the x direction
890
The number of points in the y_direction is scaled accordingly.
891
Note that very large values can cause this function to run slowly.
892
- only for multiply connected domains
893
894
- ``min_mag`` -- float (default: ``0.001``) The magnitude cutoff
895
below which spiderweb points are not drawn. This only applies
896
to multiply connected domains and is designed to prevent
897
"fuzz" at the edge of the domain. Some complicated multiply
898
connected domains (particularly those with corners) may
899
require a larger value to look clean outside.
900
901
EXAMPLES:
902
903
General usage::
904
905
sage: f(t) = e^(I*t) - 0.5*e^(-I*t)
906
sage: fprime(t) = I*e^(I*t) + 0.5*I*e^(-I*t)
907
sage: m = Riemann_Map([f], [fprime], 0)
908
909
Default plot::
910
911
sage: m.plot_spiderweb()
912
913
Simplified plot with many discrete points::
914
915
sage: m.plot_spiderweb(spokes=4, circles=1, pts=400, linescale=0.95, plotjoined=False)
916
917
Plot with thick, red lines::
918
919
sage: m.plot_spiderweb(rgbcolor=[1,0,0], thickness=3)
920
921
To generate the unit circle map, it's helpful to see what the
922
original spiderweb looks like::
923
924
sage: f(t) = e^(I*t)
925
sage: fprime(t) = I*e^(I*t)
926
sage: m = Riemann_Map([f], [fprime], 0, 1000)
927
sage: m.plot_spiderweb()
928
929
A multiply connected region with corners. We set ``min_mag`` higher
930
to remove "fuzz" outside the domain::
931
932
sage: ps = polygon_spline([(-4,-2),(4,-2),(4,2),(-4,2)])
933
sage: z1 = lambda t: ps.value(t); z1p = lambda t: ps.derivative(t)
934
sage: z2(t) = -2+exp(-I*t); z2p(t) = -I*exp(-I*t)
935
sage: z3(t) = 2+exp(-I*t); z3p(t) = -I*exp(-I*t)
936
sage: m = Riemann_Map([z1,z2,z3],[z1p,z2p,z3p],0,ncorners=4) # long time
937
sage: p = m.plot_spiderweb(withcolor=True,plot_points=500, thickness = 2.0, min_mag=0.1) # long time
938
"""
939
cdef int k, i
940
if self.exterior:
941
raise ValueError(
942
"Spiderwebs for exterior maps are not currently supported")
943
if self.B == 1: #The efficient simply connected
944
edge = self.plot_boundaries(plotjoined=plotjoined,
945
rgbcolor=rgbcolor, thickness=thickness)
946
circle_list = range(circles)
947
theta_array = self.theta_array[0]
948
s = spline(np.column_stack([self.theta_array[0], self.tk2]).tolist())
949
tmax = self.theta_array[0, self.N]
950
tmin = self.theta_array[0, 0]
951
for k in xrange(circles):
952
temp = range(pts*2)
953
for i in xrange(2*pts):
954
temp[i] = self.inverse_riemann_map(
955
(k + 1) / (circles + 1.0) * exp(I*i * TWOPI / (2*pts)))
956
if plotjoined:
957
circle_list[k] = list_plot(comp_pt(temp, 1),
958
rgbcolor=rgbcolor, thickness=thickness, plotjoined=True)
959
else:
960
circle_list[k] = list_plot(comp_pt(temp, 1),
961
rgbcolor=rgbcolor, pointsize=thickness)
962
line_list = range(spokes)
963
for k in xrange(spokes):
964
temp = range(pts)
965
angle = (k*1.0) / spokes * TWOPI
966
if angle >= tmax:
967
angle -= TWOPI
968
elif angle <= tmin:
969
angle += TWOPI
970
for i in xrange(pts - 1):
971
temp[i] = self.inverse_riemann_map(
972
(i * 1.0) / (pts * 1.0) * exp(I * angle) * linescale)
973
temp[pts - 1] = np.complex(
974
self.f(s(angle)) if angle <= tmax else self.f(s(angle-TWOPI)))
975
if plotjoined:
976
line_list[k] = list_plot(
977
comp_pt(temp, 0), rgbcolor=rgbcolor, thickness=thickness,
978
plotjoined=True)
979
else:
980
line_list[k] = list_plot(
981
comp_pt(temp, 0), rgbcolor=rgbcolor, pointsize=thickness)
982
if withcolor:
983
return edge + sum(circle_list) + sum(line_list) + \
984
self.plot_colored(plot_points=plot_points)
985
else:
986
return edge + sum(circle_list) + sum(line_list)
987
else: # The more difficult multiply connected
988
z_values, xmin, xmax, ymin, ymax = self.compute_on_grid([],
989
plot_points)
990
xstep = (xmax-xmin)/plot_points
991
ystep = (ymax-ymin)/plot_points
992
dr, dtheta= get_derivatives(z_values, xstep, ystep) # clean later
993
994
g = Graphics()
995
g.add_primitive(ComplexPlot(complex_to_spiderweb(z_values,dr,dtheta,
996
spokes, circles, rgbcolor,thickness, withcolor, min_mag),
997
(xmin, xmax), (ymin, ymax),options))
998
return g + self.plot_boundaries(thickness = thickness)
999
1000
1001
@options(interpolation='catrom')
1002
def plot_colored(self, plot_range=[], int plot_points=100, **options):
1003
"""
1004
Generates a colored plot of the Riemann map. A red point on the
1005
colored plot corresponds to a red point on the unit disc.
1006
1007
INPUT:
1008
1009
The following inputs may be passed in as named parameters:
1010
1011
- ``plot_range`` -- (default: ``[]``) list of 4 values
1012
``(xmin, xmax, ymin, ymax)``. Declare if you do not want the plot
1013
to use the default range for the figure.
1014
1015
- ``plot_points`` -- integer (default: ``100``), number of points to
1016
plot in the x direction. Points in the y direction are scaled
1017
accordingly. Note that very large values can cause this function to
1018
run slowly.
1019
1020
1021
EXAMPLES:
1022
1023
Given a Riemann map m, general usage::
1024
1025
sage: f(t) = e^(I*t) - 0.5*e^(-I*t)
1026
sage: fprime(t) = I*e^(I*t) + 0.5*I*e^(-I*t)
1027
sage: m = Riemann_Map([f], [fprime], 0)
1028
sage: m.plot_colored()
1029
1030
Plot zoomed in on a specific spot::
1031
1032
sage: m.plot_colored(plot_range=[0,1,.25,.75])
1033
1034
High resolution plot::
1035
1036
sage: m.plot_colored(plot_points=1000) # long time (29s on sage.math, 2012)
1037
1038
To generate the unit circle map, it's helpful to see what the
1039
colors correspond to::
1040
1041
sage: f(t) = e^(I*t)
1042
sage: fprime(t) = I*e^(I*t)
1043
sage: m = Riemann_Map([f], [fprime], 0, 1000)
1044
sage: m.plot_colored()
1045
"""
1046
z_values, xmin, xmax, ymin, ymax = self.compute_on_grid(plot_range,
1047
plot_points)
1048
g = Graphics()
1049
g.add_primitive(ComplexPlot(complex_to_rgb(z_values), (xmin, xmax),
1050
(ymin, ymax),options))
1051
return g
1052
1053
cdef comp_pt(clist, loop=True):
1054
"""
1055
Utility function to convert the list of complex numbers
1056
``xderivs = get_derivatives(z_values, xstep, ystep)[0]`` to the plottable
1057
`(x,y)` form. If ``loop=True``, then the first point will be
1058
added as the last, i.e. to plot a closed circle.
1059
1060
INPUT:
1061
1062
- ``clist`` -- a list of complex numbers.
1063
1064
- ``loop`` -- boolean (default: ``True``) controls whether or not the
1065
first point will be added as the last to plot a closed circle.
1066
1067
EXAMPLES:
1068
1069
This tests it implicitly::
1070
1071
sage: f(t) = e^(I*t) - 0.5*e^(-I*t)
1072
sage: fprime(t) = I*e^(I*t) + 0.5*I*e^(-I*t)
1073
sage: m = Riemann_Map([f], [fprime], 0)
1074
sage: m.plot_spiderweb()
1075
"""
1076
list2 = range(len(clist) + 1) if loop else range(len(clist))
1077
for i in xrange(len(clist)):
1078
list2[i] = (clist[i].real, clist[i].imag)
1079
if loop:
1080
list2[len(clist)] = list2[0]
1081
return list2
1082
1083
cpdef get_derivatives(np.ndarray[COMPLEX_T, ndim=2] z_values, FLOAT_T xstep,
1084
FLOAT_T ystep):
1085
"""
1086
Computes the r*e^(I*theta) form of derivatives from the grid of points. The
1087
derivatives are computed using quick-and-dirty taylor expansion and
1088
assuming analyticity. As such ``get_derivatives`` is primarily intended
1089
to be used for comparisions in ``plot_spiderweb`` and not for
1090
applications that require great precision.
1091
1092
INPUT:
1093
1094
- ``z_values`` -- The values for a complex function evaluated on a grid
1095
in the complex plane, usually from ``compute_on_grid``.
1096
1097
- ``xstep`` -- float, the spacing of the grid points in the real direction
1098
1099
OUTPUT:
1100
1101
- A tuple of arrays, [``dr``, ``dtheta``], with each array 2 less in both
1102
dimensions than ``z_values``
1103
1104
- ``dr`` - the abs of the derivative of the function in the +r direction
1105
- ``dtheta`` - the rate of accumulation of angle in the +theta direction
1106
1107
EXAMPLES:
1108
1109
Standard usage with compute_on_grid::
1110
1111
sage: from sage.calculus.riemann import get_derivatives
1112
sage: f(t) = e^(I*t) - 0.5*e^(-I*t)
1113
sage: fprime(t) = I*e^(I*t) + 0.5*I*e^(-I*t)
1114
sage: m = Riemann_Map([f], [fprime], 0)
1115
sage: data = m.compute_on_grid([],19)
1116
sage: xstep = (data[2]-data[1])/19
1117
sage: ystep = (data[4]-data[3])/19
1118
sage: dr, dtheta = get_derivatives(data[0],xstep,ystep)
1119
sage: dr[8,8]
1120
0.241...
1121
sage: dtheta[5,5]
1122
5.907...
1123
"""
1124
cdef np.ndarray[COMPLEX_T, ndim=2] xderiv
1125
cdef np.ndarray[FLOAT_T, ndim = 2] dr, dtheta, zabs
1126
imax = len(z_values)-2
1127
jmax = len(z_values[0])-2
1128
#(f(x+delta)-f(x-delta))/2delta
1129
xderiv = (z_values[1:-1,2:]-z_values[1:-1,:-2])/(2*xstep)
1130
#b/c the function is analytic, we know the magnitude of its
1131
#derivative is equal in all directions
1132
dr = np.abs(xderiv)
1133
# the abs(derivative) scaled by distance from origin
1134
zabs = np.abs(z_values[1:-1,1:-1])
1135
dtheta = np.divide(dr,zabs)
1136
return dr, dtheta
1137
1138
cpdef complex_to_spiderweb(np.ndarray[COMPLEX_T, ndim = 2] z_values,
1139
np.ndarray[FLOAT_T, ndim = 2] dr, np.ndarray[FLOAT_T, ndim = 2] dtheta,
1140
spokes, circles, rgbcolor, thickness, withcolor, min_mag):
1141
"""
1142
Converts a grid of complex numbers into a matrix containing rgb data
1143
for the Riemann spiderweb plot.
1144
1145
INPUT:
1146
1147
- ``z_values`` -- A grid of complex numbers, as a list of lists.
1148
1149
- ``dr`` -- grid of floats, the r derivative of ``z_values``.
1150
Used to determine precision.
1151
1152
- ``dtheta`` -- grid of floats, the theta derivative of ``z_values``.
1153
Used to determine precision.
1154
1155
- ``spokes`` -- integer - the number of equally spaced radial lines to plot.
1156
1157
- ``circles`` -- integer - the number of equally spaced circles about the
1158
center to plot.
1159
1160
- ``rgbcolor`` -- float array - the red-green-blue color of the
1161
lines of the spiderweb.
1162
1163
- ``thickness`` -- positive float - the thickness of the lines or points
1164
in the spiderweb.
1165
1166
- ``withcolor`` -- boolean - If ``True`` the spiderweb will be overlaid
1167
on the basic color plot.
1168
1169
- ``min_mag`` -- float - The magnitude cutoff below which spiderweb
1170
points are not drawn. This only applies to multiply connected
1171
domains and is designed to prevent "fuzz" at the edge of the
1172
domain. Some complicated multiply connected domains (particularly
1173
those with corners) may require a larger value to look clean
1174
outside.
1175
1176
OUTPUT:
1177
1178
An `N x M x 3` floating point Numpy array ``X``, where
1179
``X[i,j]`` is an (r,g,b) tuple.
1180
1181
EXAMPLES::
1182
1183
sage: from sage.calculus.riemann import complex_to_spiderweb
1184
sage: import numpy
1185
sage: zval = numpy.array([[0, 1, 1000],[.2+.3j,1,-.3j],[0,0,0]],dtype = numpy.complex128)
1186
sage: deriv = numpy.array([[.1]],dtype = numpy.float64)
1187
sage: complex_to_spiderweb(zval, deriv,deriv, 4,4,[0,0,0],1,False,0.001)
1188
array([[[ 1., 1., 1.],
1189
[ 1., 1., 1.],
1190
[ 1., 1., 1.]],
1191
<BLANKLINE>
1192
[[ 1., 1., 1.],
1193
[ 0., 0., 0.],
1194
[ 1., 1., 1.]],
1195
<BLANKLINE>
1196
[[ 1., 1., 1.],
1197
[ 1., 1., 1.],
1198
[ 1., 1., 1.]]])
1199
1200
sage: complex_to_spiderweb(zval, deriv,deriv, 4,4,[0,0,0],1,True,0.001)
1201
array([[[ 1. , 1. , 1. ],
1202
[ 1. , 0.05558355, 0.05558355],
1203
[ 0.17301243, 0. , 0. ]],
1204
<BLANKLINE>
1205
[[ 1. , 0.96804683, 0.48044583],
1206
[ 0. , 0. , 0. ],
1207
[ 0.77351965, 0.5470393 , 1. ]],
1208
<BLANKLINE>
1209
[[ 1. , 1. , 1. ],
1210
[ 1. , 1. , 1. ],
1211
[ 1. , 1. , 1. ]]])
1212
"""
1213
cdef Py_ssize_t i, j, imax, jmax
1214
cdef FLOAT_T x, y, mag, arg, width, target, precision, dmag, darg
1215
cdef COMPLEX_T z
1216
cdef FLOAT_T DMAX = 70 # change to adjust rate_of_change cutoff below
1217
precision = thickness/150.0
1218
imax = len(z_values)
1219
jmax = len(z_values[0])
1220
cdef np.ndarray[FLOAT_T, ndim=3, mode="c"] rgb
1221
if withcolor:
1222
rgb = complex_to_rgb(z_values)
1223
else:
1224
rgb = np.zeros(dtype=FLOAT, shape=(imax, jmax, 3))
1225
rgb += 1
1226
if circles != 0:
1227
circ_radii = srange(0,1.0,1.0/circles)
1228
else:
1229
circ_radii = []
1230
if spokes != 0:
1231
# both -pi and pi are included
1232
spoke_angles = srange(-PI,PI+TWOPI/spokes,TWOPI/spokes)
1233
else:
1234
spoke_angles = []
1235
for i in xrange(imax-2): # the d arrays are 1 smaller on each side
1236
for j in xrange(jmax-2):
1237
z = z_values[i+1,j+1]
1238
mag = abs(z)
1239
arg = phase(z)
1240
dmag = dr[i,j]
1241
darg = dtheta[i,j]
1242
#points that change too rapidly are presumed to be borders
1243
#points that are too small are presumed to be outside
1244
if darg < DMAX and mag > min_mag:
1245
for target in circ_radii:
1246
if abs(mag - target)/dmag < precision:
1247
rgb[i+1,j+1] = rgbcolor
1248
break
1249
for target in spoke_angles:
1250
if abs(arg - target)/darg < precision:
1251
rgb[i+1,j+1] = rgbcolor
1252
break
1253
return rgb
1254
1255
1256
cpdef complex_to_rgb(np.ndarray[COMPLEX_T, ndim = 2] z_values):
1257
r"""
1258
Convert from a (Numpy) array of complex numbers to its corresponding
1259
matrix of RGB values. For internal use of :meth:`~Riemann_Map.plot_colored`
1260
only.
1261
1262
INPUT:
1263
1264
- ``z_values`` -- A Numpy array of complex numbers.
1265
1266
OUTPUT:
1267
1268
An `N \times M \times 3` floating point Numpy array ``X``, where
1269
``X[i,j]`` is an (r,g,b) tuple.
1270
1271
EXAMPLES::
1272
1273
sage: from sage.calculus.riemann import complex_to_rgb
1274
sage: import numpy
1275
sage: complex_to_rgb(numpy.array([[0, 1, 1000]], dtype = numpy.complex128))
1276
array([[[ 1. , 1. , 1. ],
1277
[ 1. , 0.05558355, 0.05558355],
1278
[ 0.17301243, 0. , 0. ]]])
1279
1280
sage: complex_to_rgb(numpy.array([[0, 1j, 1000j]], dtype = numpy.complex128))
1281
array([[[ 1. , 1. , 1. ],
1282
[ 0.52779177, 1. , 0.05558355],
1283
[ 0.08650622, 0.17301243, 0. ]]])
1284
1285
1286
TESTS::
1287
1288
sage: complex_to_rgb([[0, 1, 10]])
1289
Traceback (most recent call last):
1290
...
1291
TypeError: Argument 'z_values' has incorrect type (expected numpy.ndarray, got list)
1292
"""
1293
cdef Py_ssize_t i, j, imax, jmax
1294
cdef FLOAT_T x, y, mag, arg
1295
cdef FLOAT_T lightness, hue, top, bot
1296
cdef FLOAT_T r, g, b
1297
cdef int ihue
1298
cdef COMPLEX_T z
1299
1300
imax = len(z_values)
1301
jmax = len(z_values[0])
1302
cdef np.ndarray[FLOAT_T, ndim=3, mode="c"] rgb = np.empty(
1303
dtype=FLOAT, shape=(imax, jmax, 3))
1304
1305
sig_on()
1306
for i from 0 <= i < imax: #replace with xrange?
1307
row = z_values[i]
1308
for j from 0 <= j < jmax: #replace with xrange?
1309
z = row[j]
1310
mag = abs(z)
1311
arg = phase(z)
1312
# tweak these levels to adjust how bright/dark the colors appear
1313
# output can range from -1 (black) to 1 (white)
1314
lightness = -(atan(log(mag*1.5+1)) * (4/PI) - 1)
1315
# in hsv, variable value, full saturation (s=1, v=1+lightness)
1316
if lightness < 0:
1317
bot = 0
1318
top = (1 + lightness)
1319
# in hsv, variable saturation, full value (v=1, s=1-lightness)
1320
else:
1321
bot = lightness
1322
top = 1
1323
# Note that does same thing as colorsys module hsv_to_rgb for
1324
# this setup, but in Cython.
1325
hue = 3*arg / PI
1326
# usual hsv hue is thus h=arg/(2*pi) for positive,
1327
# h=arg/(2*PI)+1 for negative
1328
if hue < 0:
1329
hue += 6
1330
ihue = <int>hue
1331
if ihue == 0:
1332
r = top
1333
g = bot + hue * (top - bot)
1334
b = bot
1335
elif ihue == 1:
1336
r = bot + (2 - hue) * (top - bot)
1337
g = top
1338
b = bot
1339
elif ihue == 2:
1340
r = bot
1341
g = top
1342
b = bot + (hue - 2) * (top - bot)
1343
elif ihue == 3:
1344
r = bot
1345
g = bot + (4 - hue) * (top - bot)
1346
b = top
1347
elif ihue == 4:
1348
r = bot + (hue - 4) * (top - bot)
1349
g = bot
1350
b = top
1351
else:
1352
r = top
1353
g = bot
1354
b = bot + (6 - hue) * (top - bot)
1355
rgb[i, j, 0] = r
1356
rgb[i, j, 1] = g
1357
rgb[i, j, 2] = b
1358
sig_off()
1359
return rgb
1360
1361
cpdef analytic_boundary(FLOAT_T t, int n, FLOAT_T epsilon):
1362
"""
1363
Provides an exact (for n = infinity) Riemann boundary
1364
correspondence for the ellipse with axes 1 + epsilon and 1 - epsilon. The
1365
boundary is therefore given by e^(I*t)+epsilon*e^(-I*t). It is primarily
1366
useful for testing the accuracy of the numerical :class:`Riemann_Map`.
1367
1368
INPUT:
1369
1370
- ``t`` -- The boundary parameter, from 0 to 2*pi
1371
1372
- ``n`` -- integer - the number of terms to include.
1373
10 is fairly accurate, 20 is very accurate.
1374
1375
- ``epsilon`` -- float - the skew of the ellipse (0 is circular)
1376
1377
OUTPUT:
1378
1379
A theta value from 0 to 2*pi, corresponding to the point on the
1380
circle e^(I*theta)
1381
1382
TESTS:
1383
1384
Checking the accuracy of this function for different n values::
1385
1386
sage: from sage.calculus.riemann import analytic_boundary
1387
sage: t100 = analytic_boundary(pi/2, 100, .3)
1388
sage: abs(analytic_boundary(pi/2, 10, .3) - t100) < 10^-8
1389
True
1390
sage: abs(analytic_boundary(pi/2, 20, .3) - t100) < 10^-15
1391
True
1392
1393
Using this to check the accuracy of the Riemann_Map boundary::
1394
1395
sage: f(t) = e^(I*t)+.3*e^(-I*t)
1396
sage: fp(t) = I*e^(I*t)-I*.3*e^(-I*t)
1397
sage: m = Riemann_Map([f], [fp],0,200)
1398
sage: s = spline(m.get_theta_points())
1399
sage: test_pt = uniform(0,2*pi)
1400
sage: s(test_pt) - analytic_boundary(test_pt,20, .3) < 10^-4
1401
True
1402
"""
1403
cdef FLOAT_T i
1404
cdef FLOAT_T result = t
1405
for i from 1 <= i < n+1:
1406
result += (2*(-1)**i/i)*(epsilon**i/(1+epsilon**(2*i)))*sin(2*i*t)
1407
return result
1408
1409
1410
1411
cpdef cauchy_kernel(t, args):
1412
"""
1413
Intermediate function for the integration in :meth:`~Riemann_Map.analytic_interior`.
1414
1415
INPUT:
1416
1417
- ``t`` -- The boundary parameter, meant to be integrated over
1418
1419
- ``args`` -- a tuple containing:
1420
1421
- ``epsilon`` -- float - the skew of the ellipse (0 is circular)
1422
1423
- ``z`` -- complex - the point to be mapped.
1424
1425
- ``n`` -- integer - the number of terms to include.
1426
10 is fairly accurate, 20 is very accurate.
1427
1428
- ``part`` -- will return the real ('r'), imaginary ('i') or
1429
complex ('c') value of the kernel
1430
1431
TESTS:
1432
1433
This is primarily tested implicitly by :meth:`~Riemann_Map.analytic_interior`.
1434
Here is a simple test::
1435
1436
sage: from sage.calculus.riemann import cauchy_kernel
1437
sage: cauchy_kernel(.5,(.3, .1+.2*I, 10,'c'))
1438
(-0.584136405997...+0.5948650858950...j)
1439
"""
1440
cdef COMPLEX_T result
1441
cdef FLOAT_T epsilon = args[0]
1442
cdef COMPLEX_T z = args[1]
1443
cdef int n = args[2]
1444
part = args[3]
1445
result = exp(I*analytic_boundary(t,n, epsilon))/(exp(I*t)+epsilon*exp(-I*t)-z) * \
1446
(I*exp(I*t)-I*epsilon*exp(-I*t))
1447
if part == 'c':
1448
return result
1449
elif part == 'r':
1450
return result.real
1451
elif part == 'i':
1452
return result.imag
1453
else: return None
1454
1455
cpdef analytic_interior(COMPLEX_T z, int n, FLOAT_T epsilon):
1456
"""
1457
Provides a nearly exact compuation of the Riemann Map of an interior
1458
point of the ellipse with axes 1 + epsilon and 1 - epsilon. It is
1459
primarily useful for testing the accuracy of the numerical Riemann Map.
1460
1461
INPUT:
1462
1463
- ``z`` -- complex - the point to be mapped.
1464
1465
- ``n`` -- integer - the number of terms to include.
1466
10 is fairly accurate, 20 is very accurate.
1467
1468
TESTS:
1469
1470
Testing the accuracy of :class:`Riemann_Map`::
1471
1472
sage: from sage.calculus.riemann import analytic_interior
1473
sage: f(t) = e^(I*t)+.3*e^(-I*t)
1474
sage: fp(t) = I*e^(I*t)-I*.3*e^(-I*t)
1475
sage: m = Riemann_Map([f],[fp],0,200)
1476
sage: abs(m.riemann_map(.5)-analytic_interior(.5, 20, .3)) < 10^-4
1477
True
1478
sage: m = Riemann_Map([f],[fp],0,2000)
1479
sage: abs(m.riemann_map(.5)-analytic_interior(.5, 20, .3)) < 10^-6
1480
True
1481
"""
1482
# evaluates the cauchy integral of the boundary, split into the real
1483
# and imaginary results because numerical_integral can't handle complex data.
1484
rp = 1/(TWOPI)*numerical_integral(cauchy_kernel,0,2*pi,
1485
params = [epsilon,z,n,'i'])[0]
1486
ip = 1/(TWOPI*I)*numerical_integral(cauchy_kernel,0,2*pi,
1487
params = [epsilon,z,n,'r'])[0]
1488
return rp + ip
1489
1490