Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagelib
Path: blob/master/sage/calculus/riemann.pyx
4084 views
1
"""
2
Riemann Mapping
3
4
AUTHORS:
5
6
- Ethan Van Andel (2009): initial version
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) 2009 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 "../ext/stdsage.pxi"
29
include "../ext/interrupt.pxi"
30
31
from sage.plot.primitive import GraphicPrimitive
32
from sage.misc.decorators import options
33
from sage.plot.all import list_plot, Graphics
34
from sage.plot.misc import setup_for_eval_on_grid
35
36
from sage.ext.fast_eval import fast_callable
37
38
from sage.rings.all import CDF
39
40
from sage.misc.misc import srange
41
42
from sage.gsl.interpolation import spline
43
44
from sage.plot.complex_plot import ComplexPlot
45
46
import numpy as np
47
cimport numpy as np
48
49
from math import pi
50
from math import sin
51
from math import cos
52
53
from cmath import exp
54
from cmath import phase
55
56
cdef double PI = pi
57
cdef double TWOPI = 2*PI
58
cdef I = complex(0,1)
59
60
FLOAT = np.float64
61
ctypedef np.float64_t FLOAT_T
62
63
64
cdef class Riemann_Map:
65
"""
66
The ``Riemann_Map`` class computes a Riemann or Ahlfors map from
67
supplied data. It also has various methods to provide information about
68
the map. A Riemann map conformally maps a simply connected region in
69
the complex plane to the unit disc. The Ahlfors map does the same thing
70
for multiply connected regions.
71
72
Note that all the methods are numeric rather than analytic, for unusual
73
regions or insufficient collocation points may give very inaccurate
74
results.
75
76
INPUT:
77
78
- ``fs`` -- A list of the boundaries of the region, given as
79
complex-valued functions with domain ``0`` to ``2*pi``. Note that the
80
outer boundary must be parameterized counter clockwise
81
(i.e. ``e^(I*t)``) while the inner boundaries must be clockwise
82
(i.e. ``e^(-I*t)``).
83
84
- ``fprimes`` -- A list of the derivatives of the boundary functions.
85
Must be in the same order as ``fs``.
86
87
- ``a`` -- Complex, the center of the Riemann map. Will be mapped to
88
the origin of the unit disc.
89
90
The following inputs must all be passed in as named parameters:
91
92
- ``N`` -- integer (default: ``500``), the number of collocation points
93
used to compute the map. More points will give more accurate results,
94
especially near the boundaries, but will take longer to compute.
95
96
- ``ncorners`` -- integer (default: ``4``), if mapping a figure with
97
(equally t-spaced) corners, better results may be obtained by
98
accurately giving this parameter. Used to add the proper constant to
99
the theta correspondance function.
100
101
- ``opp`` -- boolean (default: ``False``), set to ``True`` in very rare
102
cases where the theta correspondance function is off by ``pi``, that
103
is, if red is mapped left of the origin in the color plot.
104
105
EXAMPLES:
106
107
The unit circle identity map::
108
109
sage: f(t) = e^(I*t)
110
sage: fprime(t) = I*e^(I*t)
111
sage: m = Riemann_Map([f], [fprime], 0) # long time (4 sec)
112
113
The unit circle with a small hole::
114
115
sage: f(t) = e^(I*t)
116
sage: fprime(t) = I*e^(I*t)
117
sage: hf(t) = 0.5*e^(-I*t)
118
sage: hfprime(t) = 0.5*-I*e^(-I*t)
119
sage: m = Riemann_Map([f, hf], [fprime, hfprime], 0.5 + 0.5*I)
120
121
A square::
122
123
sage: ps = polygon_spline([(-1, -1), (1, -1), (1, 1), (-1, 1)]) # long time
124
sage: f = lambda t: ps.value(real(t)) # long time
125
sage: fprime = lambda t: ps.derivative(real(t)) # long time
126
sage: m = Riemann_Map([f], [fprime], 0.25, ncorners=4) # long time
127
sage: m.plot_colored() + m.plot_spiderweb() # long time
128
129
Compute rough error for this map::
130
131
sage: x = 0.75 # long time
132
sage: print "error =", m.inverse_riemann_map(m.riemann_map(x)) - x # long time
133
error = (-0.000...+0.0016...j)
134
135
ALGORITHM:
136
137
This class computes the Riemann Map via the Szego kernel using an
138
adaptation of the method described by [KT]_.
139
140
REFERENCES:
141
142
.. [KT] N. Kerzman and M. R. Trummer. "Numerical Conformal Mapping via
143
the Szego kernel". Journal of Computational and Applied Mathematics,
144
14(1-2): 111--123, 1986.
145
"""
146
cdef int N, B, ncorners
147
cdef f
148
cdef opp
149
cdef double complex a
150
cdef np.ndarray tk, tk2, cps, dps, szego, p_vector, pre_q_vector
151
cdef np.ndarray p_vector_inverse, sinalpha, cosalpha, theta_array
152
cdef x_range, y_range
153
154
def __init__(self, fs, fprimes, a, int N=500, int ncorners=4, opp=False):
155
"""
156
Initializes the ``Riemann_Map`` class. See the class ``Riemann_Map``
157
for full documentation on the input of this initialization method.
158
159
TESTS::
160
161
sage: f(t) = e^(I*t) - 0.5*e^(-I*t)
162
sage: fprime(t) = I*e^(I*t) + 0.5*I*e^(-I*t)
163
sage: m = Riemann_Map([f], [fprime], 0)
164
"""
165
a = np.complex128(a)
166
if N <= 2:
167
raise ValueError(
168
"The number of collocation points must be > 2.")
169
try:
170
fs = [fast_callable(f, domain=CDF) for f in fs]
171
fprimes = [fast_callable(f, domain=CDF) for f in fprimes]
172
except AttributeError:
173
pass
174
self.f = fs[0]
175
self.a = a
176
self.ncorners = ncorners
177
self.N = N # Number of collocation pts
178
self.opp = opp
179
cdef int i, k
180
self.tk = np.array(np.arange(N) * TWOPI / N + 0.001 / N,
181
dtype=np.float64)
182
self.tk2 = np.zeros(N + 1, dtype=np.float64)
183
for i in xrange(N):
184
self.tk2[i] = self.tk[i]
185
self.tk2[N] = TWOPI
186
self.B = len(fs) # number of boundaries of the figure
187
self.cps = np.zeros([self.B, N], dtype=np.complex128)
188
self.dps = np.zeros([self.B, N], dtype=np.complex128)
189
# Find the points on the boundaries and their derivatives.
190
for k in xrange(self.B):
191
for i in xrange(N):
192
self.cps[k, i] = np.complex(fs[k](self.tk[i]))
193
self.dps[k, i] = np.complex(fprimes[k](self.tk[i]))
194
cdef double xmax = self.cps.real.max()
195
cdef double xmin = self.cps.real.min()
196
cdef double ymax = self.cps.imag.max()
197
cdef double ymin = self.cps.imag.min()
198
cdef double space = 0.1 * max(xmax - xmin, ymax - ymin)
199
#The default plotting window, mainly for color plot.
200
self.x_range = (xmin - space, xmax + space)
201
self.y_range = (ymin - space, ymax + space)
202
self._generate_theta_array()
203
self._generate_interior_mapper()
204
self._generate_inverse_mapper()
205
206
def _repr_(self):
207
"""
208
Return a string representation of this ``Riemann_Map`` object.
209
210
TESTS::
211
212
sage: f(t) = e^(I*t) - 0.5*e^(-I*t)
213
sage: fprime(t) = I*e^(I*t) + 0.5*I*e^(-I*t)
214
sage: isinstance(Riemann_Map([f], [fprime], 0)._repr_(), str) # long time
215
True
216
"""
217
return "A Riemann mapping of a figure to the unit circle."
218
219
cdef _generate_theta_array(self):
220
"""
221
Generates the essential data for the Riemann map, primarily the
222
Szego kernel and boundary correspondance.
223
224
TESTS::
225
226
sage: f(t) = e^(I*t) - 0.5*e^(-I*t)
227
sage: fprime(t) = I*e^(I*t) + 0.5*I*e^(-I*t)
228
sage: m = Riemann_Map([f], [fprime], 0)
229
"""
230
cp = self.cps.flatten()
231
dp = self.dps.flatten()
232
cdef int N = self.N
233
cdef int NB = N * self.B
234
cdef int B = self.B
235
cdef int i, k
236
cdef FLOAT_T saa, t0
237
cdef np.ndarray[FLOAT_T, ndim=1] adp
238
cdef np.ndarray[FLOAT_T, ndim=1] sadp
239
cdef np.ndarray h
240
cdef np.ndarray hconj
241
cdef np.ndarray g
242
cdef np.ndarray K
243
cdef np.ndarray phi
244
cdef np.ndarray[FLOAT_T, ndim=2] theta_array
245
# Seting things up to use the Nystrom method
246
adp = abs(dp)
247
sadp = np.sqrt(adp)
248
h = 1 / (TWOPI * I) * ((dp / adp) / (self.a - cp))
249
hconj = np.array(map(np.complex.conjugate, h), dtype=np.complex128)
250
g = -sadp * hconj
251
normalized_dp=dp/adp
252
C = I / N * sadp # equivalent to -TWOPI / N * 1 / (TWOPI * I) * sadp
253
olderr = np.geterr()['invalid'] # checks the current error handling
254
np.seterr(invalid='ignore')
255
K = np.array(
256
[C * sadp[t] *
257
(normalized_dp/(cp-cp[t]) - (normalized_dp[t]/(cp-cp[t])).conjugate())
258
for t in np.arange(NB)], dtype=np.complex128)
259
np.seterr(invalid=olderr) # resets the error handling
260
for i in xrange(NB):
261
K[i, i] = 1
262
phi = np.linalg.solve(K, g) / NB * TWOPI # Nystrom
263
# the all-important Szego kernel
264
szego = np.array(phi.flatten() / np.sqrt(dp), dtype=np.complex128)
265
self.szego = szego.reshape([B, N])
266
start = 0
267
# Finding the theta correspondance using phase. Misbehaves for some
268
# regions.
269
if B != 1:
270
theta_array = np.zeros([1, NB])
271
for i in xrange(NB):
272
theta_array[0, i] = phase(-I * np.power(phi[i], 2) * dp[i])
273
self.theta_array = np.concatenate(
274
[theta_array.reshape([B, N]), np.zeros([B, 1])], axis=1)
275
for k in xrange(B):
276
self.theta_array[k, N] = self.theta_array[k, 0] + TWOPI
277
# Finding the theta correspondance using ab. Well behaved, but
278
# doesn't work on multiply connected domains.
279
else:
280
phi2 = phi.reshape([self.B, N])
281
theta_array = np.zeros([B, N + 1], dtype=np.float64)
282
for k in xrange(B):
283
phik = phi2[k]
284
saa = (np.dot(abs(phi), abs(phi))) * TWOPI / NB
285
theta_array[k, 0] = 0
286
for i in xrange(1, N):
287
theta_array[k, i] = (
288
theta_array[k, i - 1] +
289
((TWOPI / NB * TWOPI *
290
abs(np.power(phi[1 * i], 2)) / saa +
291
TWOPI / NB * TWOPI *
292
abs(np.power(phi[1 * i - 1], 2)) / saa)) / 2)
293
tmax = int(0.5 * N / self.ncorners)
294
# Finding the initial value of the theta function.
295
phimax = -I * phik[tmax]**2 * self.dps[k, tmax]
296
if self.opp:
297
t0 = theta_array[k, tmax] + phase(phimax)
298
else:
299
t0 = theta_array[k, tmax] - phase(phimax)
300
for i in xrange(N):
301
theta_array[k, i] = theta_array[k, i] - t0
302
theta_array[k, N] = TWOPI + theta_array[k, 0]
303
self.theta_array = theta_array
304
305
def get_szego(self, int boundary=-1, absolute_value=False):
306
"""
307
Returns a discretized version of the Szego kernel for each boundary
308
function.
309
310
INPUT:
311
312
The following inputs must all be passed in as named parameters:
313
314
- ``boundary`` -- integer (default: ``-1``) if < 0,
315
``get_theta_points()`` will return the points for all boundaries.
316
If >= 0, ``get_theta_points()`` will return only the points for
317
the boundary specified.
318
319
- ``absolute_value`` -- boolean (default: ``False``) if ``True``, will
320
return the absolute value of the (complex valued) Szego kernel
321
instead of the kernel itself. Useful for plotting.
322
323
OUTPUT:
324
325
A list of points of the form
326
``[t value, value of the Szego kernel at that t]``.
327
328
EXAMPLES:
329
330
Generic use::
331
332
sage: f(t) = e^(I*t) - 0.5*e^(-I*t)
333
sage: fprime(t) = I*e^(I*t) + 0.5*I*e^(-I*t)
334
sage: m = Riemann_Map([f], [fprime], 0)
335
sage: sz = m.get_szego(boundary=0)
336
sage: points = m.get_szego(absolute_value=True)
337
sage: list_plot(points)
338
339
Extending the points by a spline::
340
341
sage: s = spline(points)
342
sage: s(3*pi / 4)
343
0.00121587378429...
344
345
The unit circle with a small hole::
346
347
sage: f(t) = e^(I*t)
348
sage: fprime(t) = I*e^(I*t)
349
sage: hf(t) = 0.5*e^(-I*t)
350
sage: hfprime(t) = 0.5*-I*e^(-I*t)
351
sage: m = Riemann_Map([f, hf], [fprime, hfprime], 0.5 + 0.5*I)
352
353
Getting the szego for a specifc boundary::
354
355
sage: sz0 = m.get_szego(boundary=0)
356
sage: sz1 = m.get_szego(boundary=1)
357
"""
358
cdef int k, B
359
if boundary < 0:
360
temptk = self.tk
361
for i in xrange(self.B - 1):
362
temptk = np.concatenate([temptk, self.tk])
363
if absolute_value:
364
return np.column_stack(
365
[temptk, abs(self.szego.flatten())]).tolist()
366
else:
367
return np.column_stack([temptk, self.szego.flatten()]).tolist()
368
else:
369
if absolute_value:
370
return np.column_stack(
371
[self.tk, abs(self.szego[boundary])]).tolist()
372
else:
373
return np.column_stack(
374
[self.tk, self.szego[boundary]]).tolist()
375
376
def get_theta_points(self, int boundary=-1):
377
"""
378
Returns an array of points of the form
379
``[t value, theta in e^(I*theta)]``, that is, a discretized version
380
of the theta/boundary correspondence function. For multiply
381
connected domains, ``get_theta_points`` will list the points for
382
each boundary in the order that they were supplied.
383
384
INPUT:
385
386
The following input must all be passed in as named parameters:
387
388
- ``boundary`` -- integer (default: ``-``1) if < 0,
389
``get_theta_points()`` will return the points for all boundaries.
390
If >= 0, ``get_theta_points()`` will return only the points for
391
the boundary specified.
392
393
OUTPUT:
394
395
A list of points of the form ``[t value, theta in e^(I*theta)]``.
396
397
EXAMPLES:
398
399
Getting the list of points, extending it via a spline, getting the
400
points for only the outside of a multiply connected domain::
401
402
sage: f(t) = e^(I*t) - 0.5*e^(-I*t)
403
sage: fprime(t) = I*e^(I*t) + 0.5*I*e^(-I*t)
404
sage: m = Riemann_Map([f], [fprime], 0)
405
sage: points = m.get_theta_points()
406
sage: list_plot(points)
407
408
Extending the points by a spline::
409
410
sage: s = spline(points)
411
sage: s(3*pi / 4)
412
1.62766037996...
413
414
The unit circle with a small hole::
415
416
sage: f(t) = e^(I*t)
417
sage: fprime(t) = I*e^(I*t)
418
sage: hf(t) = 0.5*e^(-I*t)
419
sage: hfprime(t) = 0.5*-I*e^(-I*t)
420
sage: m = Riemann_Map([f, hf], [hf, hfprime], 0.5 + 0.5*I)
421
422
Getting the szego for a specifc boundary::
423
424
sage: tp0 = m.get_theta_points(boundary=0)
425
sage: tp1 = m.get_theta_points(boundary=1)
426
"""
427
if boundary < 0:
428
temptk = self.tk2
429
for i in xrange(self.B - 1):
430
temptk = np.concatenate([temptk, self.tk2])
431
return np.column_stack(
432
[temptk, self.theta_array.flatten()]).tolist()
433
else:
434
return np.column_stack(
435
[self.tk2, self.theta_array[boundary]]).tolist()
436
437
cdef _generate_interior_mapper(self):
438
"""
439
Generates the data necessary to use the ``reimann_map()`` function.
440
As much setup as possible is done here to minimize what has to be
441
done in ``riemann_map()``.
442
443
TESTS::
444
445
sage: f(t) = e^(I*t) - 0.5*e^(-I*t)
446
sage: fprime(t) = I*e^(I*t) + 0.5*I*e^(-I*t)
447
sage: m = Riemann_Map([f], [fprime], 0)
448
"""
449
cdef int N = self.N
450
cdef double complex coeff = 3*I / (8*N)
451
dps = self.dps
452
theta_array = self.theta_array
453
cdef np.ndarray[double complex, ndim=2] p_vector = np.zeros(
454
[self.B, N + 1], dtype=np.complex128)
455
cdef int k, i
456
# Lots of setup for Simpson's method of integration.
457
for k in xrange(self.B):
458
for i in xrange(N // 3):
459
p_vector[k, 3*i] = (2*coeff * dps[k, 3*i] *
460
exp(I * theta_array[k, 3*i]))
461
p_vector[k, 3*i + 1] = (3*coeff * dps[k, 3*i + 1] *
462
exp(I * theta_array[k, 3*i + 1]))
463
p_vector[k, 3*i + 2] = (3*coeff * dps[k, 3*i + 2] *
464
exp(I * theta_array[k, 3*i + 2]))
465
p_vector[k, 0] = 1*coeff * dps[k, 0] * exp(I * theta_array[k, 0])
466
if N % 3 == 0:
467
p_vector[k, N] = 1*coeff * dps[k, 0] * exp(I*theta_array[k, 0])
468
elif (N - 2) % 3 == 0:
469
p_vector[k, N - 2] = ((coeff + I/(3*N)) * dps[k, N- 2 ] *
470
exp(I * theta_array[k, N - 2]))
471
p_vector[k, N- 1 ] = (4*I / (3*N) * dps[k, N - 1] *
472
exp(I * theta_array[k, N - 1]))
473
p_vector[k, N] = (I / (3*N) * dps[k, 0] *
474
exp(I * theta_array[k, 0]))
475
else:
476
p_vector[k, N - 4] = ((coeff + I / (3*N)) * dps[k, N - 4] *
477
exp(I * theta_array[k, N - 4]))
478
p_vector[k, N - 3] = (4*I / (3*N) * dps[k, N - 3] *
479
exp(I * theta_array[k, N - 3]))
480
p_vector[k, N - 2] = (2*I / (3*N) * dps[k, N - 2] *
481
exp(I * theta_array[k, N - 2]))
482
p_vector[k, N - 1] = (4*I / (3*N) * dps[k, N - 1] *
483
exp(I * theta_array[k, N - 1]))
484
p_vector[k, N] = (I / (3*N) * dps[k, 0] *
485
exp(I * theta_array[k, 0]))
486
self.p_vector = p_vector.flatten()
487
cdef np.ndarray[double complex, ndim=1] pq = self.cps[:,list(range(N))+[0]].flatten()
488
self.pre_q_vector = pq
489
490
cpdef riemann_map(self, pt):
491
"""
492
Returns the Riemann mapping of a point. That is, given ``pt`` on
493
the interior of the mapped region, ``riemann_map()`` will return
494
the point on the unit disk that ``pt`` maps to. Note that this
495
method only works for interior points; it breaks down very close
496
to the boundary. To get boundary corrospondance, use
497
``get_theta_points()``.
498
499
INPUT:
500
501
- ``pt`` -- A complex number representing the point to be
502
inverse mapped.
503
504
OUTPUT:
505
506
A complex number representing the point on the unit circle that
507
the input point maps to.
508
509
EXAMPLES:
510
511
Can work for different types of complex numbers::
512
513
sage: f(t) = e^(I*t) - 0.5*e^(-I*t)
514
sage: fprime(t) = I*e^(I*t) + 0.5*I*e^(-I*t)
515
sage: m = Riemann_Map([f], [fprime], 0)
516
sage: m.riemann_map(0.25 + sqrt(-0.5))
517
(0.137514...+0.87669602...j)
518
sage: m.riemann_map(1.3*I)
519
(-1.56...e-05+0.989694...j)
520
sage: I = CDF.gen()
521
sage: m.riemann_map(0.4)
522
(0.733242677...+3.2...e-06j)
523
sage: import numpy as np
524
sage: m.riemann_map(np.complex(-3, 0.0001))
525
(1.405757...e-05+8.06...e-10j)
526
"""
527
pt1 = np.complex(pt)
528
cdef np.ndarray[double complex, ndim=1] q_vector = 1 / (
529
self.pre_q_vector - pt1)
530
return -np.dot(self.p_vector, q_vector)
531
532
cdef _generate_inverse_mapper(self):
533
"""
534
Generates the data necessary to use the
535
``inverse_reimann_map()`` function. As much setup as possible is
536
done here to minimize what has to be done in
537
``inverse_riemann_map()``.
538
539
TESTS::
540
541
sage: f(t) = e^(I*t) - 0.5*e^(-I*t)
542
sage: fprime(t) = I*e^(I*t) + 0.5*I*e^(-I*t)
543
sage: m = Riemann_Map([f], [fprime], 0)
544
"""
545
cdef int N = self.N
546
cdef int B = self.B
547
cdef float di
548
theta_array = self.theta_array
549
self.p_vector_inverse = np.zeros([B, N], dtype=np.complex128)
550
# Setup for trapezoid integration because integration points are
551
# not equally spaced.
552
for k in xrange(B):
553
for i in xrange(N):
554
di = theta_array[k, (i + 1) % N] - theta_array[k, (i - 1) % N]
555
if di > PI:
556
di = di - TWOPI
557
elif di < -PI:
558
di = di + TWOPI
559
self.p_vector_inverse[k, i] = di / 2
560
self.sinalpha = np.zeros([B, N], dtype=np.float64)
561
for k in xrange(B):
562
for i in xrange(N):
563
self.sinalpha[k, i] = sin(-theta_array[k, i])
564
self.cosalpha = np.zeros([B, N], dtype=np.float64)
565
for k in xrange(B):
566
for i in xrange(N):
567
self.cosalpha[k, i] = cos(-theta_array[k, i])
568
569
def inverse_riemann_map(self, pt):
570
"""
571
Returns the inverse Riemann mapping of a point. That is, given ``pt``
572
on the interior of the unit disc, ``inverse_reimann_map()`` will
573
return the point on the original region that would be Riemann
574
mapped to ``pt``.
575
576
.. NOTE::
577
578
This method does not work for multiply connected domains.
579
580
INPUT:
581
582
- ``pt`` -- A complex number (usually with absolute value <= 1)
583
representing the point to be inverse mapped.
584
585
OUTPUT:
586
587
The point on the region that Riemann maps to the input point.
588
589
EXAMPLES:
590
591
Can work for different types of complex numbers::
592
593
sage: f(t) = e^(I*t) - 0.5*e^(-I*t)
594
sage: fprime(t) = I*e^(I*t) + 0.5*I*e^(-I*t)
595
sage: m = Riemann_Map([f], [fprime], 0)
596
sage: m.inverse_riemann_map(0.5 + sqrt(-0.5))
597
(0.406880548363...+0.361470279816...j)
598
sage: m.inverse_riemann_map(0.95)
599
(0.486319431795...-4.90019052...j)
600
sage: m.inverse_riemann_map(0.25 - 0.3*I)
601
(0.165324498558...-0.180936785500...j)
602
sage: import numpy as np
603
sage: m.inverse_riemann_map(np.complex(-0.2, 0.5))
604
(-0.156280570579...+0.321819151891...j)
605
"""
606
pt = np.complex128(pt)
607
r = abs(pt)
608
if r == 0:
609
stheta = 0
610
ctheta = 0
611
else:
612
stheta = pt.imag / r
613
ctheta = pt.real / r
614
k = 0
615
return (1 - r**2) / TWOPI * np.dot(
616
self.p_vector_inverse[k] * self.cps[k],
617
1 / (1 + r**2 - 2*r *
618
(ctheta * self.cosalpha[k] - stheta * self.sinalpha[k])))
619
620
def plot_boundaries(self, plotjoined=True, rgbcolor=[0,0,0], thickness=1):
621
"""
622
Plots the boundaries of the region for the Riemann map. Note that
623
this method DOES work for multiply connected domains.
624
625
INPUT:
626
627
The following inputs must all be passed in as named parameters:
628
629
- ``plotjoined`` -- boolean (default: ``True``) If ``False``,
630
discrete points will be drawn; otherwise they will be connected
631
by lines. In this case, if ``plotjoined=False``, the points shown
632
will be the original collocation points used to generate the
633
Riemann map.
634
635
- ``rgbcolor`` -- float array (default: ``[0,0,0]``) the
636
red-green-blue color of the boundary.
637
638
- ``thickness`` -- positive float (default: ``1``) the thickness of
639
the lines or points in the boundary.
640
641
EXAMPLES:
642
643
General usage::
644
645
sage: f(t) = e^(I*t) - 0.5*e^(-I*t)
646
sage: fprime(t) = I*e^(I*t) + 0.5*I*e^(-I*t)
647
sage: m = Riemann_Map([f], [fprime], 0)
648
649
Default plot::
650
651
sage: m.plot_boundaries()
652
653
Big blue collocation points::
654
655
sage: m.plot_boundaries(plotjoined=False, rgbcolor=[0,0,1], thickness=6)
656
"""
657
plots = range(self.B)
658
for k in xrange(self.B):
659
# This should be eliminated when the thickness/pointsize issue
660
# is resolved later. Same for the others in plot_spiderweb().
661
if plotjoined:
662
plots[k] = list_plot(
663
comp_pt(self.cps[k], 1), plotjoined=True,
664
rgbcolor=rgbcolor, thickness=thickness)
665
else:
666
plots[k] = list_plot(
667
comp_pt(self.cps[k], 1), rgbcolor=rgbcolor,
668
pointsize=thickness)
669
return sum(plots)
670
671
def plot_spiderweb(self, spokes=16, circles=4, pts=32, linescale=0.99,
672
rgbcolor=[0,0,0], thickness=1, plotjoined=True):
673
"""
674
Generates a traditional "spiderweb plot" of the Riemann map. Shows
675
what concentric circles and radial lines map to. Note that this
676
method DOES NOT work for multiply connected domains.
677
678
INPUT:
679
680
The following inputs must all be passed in as named parameters:
681
682
- ``spokes`` -- integer (default: ``16``) the number of equally
683
spaced radial lines to plot.
684
685
- ``circles`` -- integer (default: ``4``) the number of equally
686
spaced circles about the center to plot.
687
688
- ``pts`` -- integer (default: ``32``) the number of points to
689
plot. Each radial line is made by ``1*pts`` points, each circle
690
has ``2*pts`` points. Note that high values may cause erratic
691
behavior of the radial lines near the boundaries.
692
693
- ``linescale`` -- float between 0 and 1. Shrinks the radial lines
694
away from the boundary to reduce erratic behavior.
695
696
- ``rgbcolor`` -- float array (default: ``[0,0,0]``) the
697
red-green-blue color of the spiderweb.
698
699
- ``thickness`` -- positive float (default: ``1``) the thickness of
700
the lines or points in the spiderweb.
701
702
- ``plotjoined`` -- boolean (default: ``True``) If ``False``,
703
discrete points will be drawn; otherwise they will be connected
704
by lines.
705
706
EXAMPLES:
707
708
General usage::
709
710
sage: f(t) = e^(I*t) - 0.5*e^(-I*t)
711
sage: fprime(t) = I*e^(I*t) + 0.5*I*e^(-I*t)
712
sage: m = Riemann_Map([f], [fprime], 0)
713
714
Default plot::
715
716
sage: m.plot_spiderweb()
717
718
Simplified plot with many discrete points::
719
720
sage: m.plot_spiderweb(spokes=4, circles=1, pts=400, linescale=0.95, plotjoined=False)
721
722
Plot with thick, red lines::
723
724
sage: m.plot_spiderweb(rgbcolor=[1,0,0], thickness=3)
725
726
To generate the unit circle map, it's helpful to see what the
727
original spiderweb looks like::
728
729
sage: f(t) = e^(I*t)
730
sage: fprime(t) = I*e^(I*t)
731
sage: m = Riemann_Map([f], [fprime], 0, 1000)
732
sage: m.plot_spiderweb()
733
"""
734
edge = self.plot_boundaries(plotjoined=plotjoined, rgbcolor=rgbcolor,
735
thickness=thickness)
736
circle_list = range(circles)
737
theta_array = self.theta_array[0]
738
s = spline(np.column_stack([self.theta_array[0], self.tk2]).tolist())
739
tmax = self.theta_array[0, self.N]
740
tmin = self.theta_array[0, 0]
741
cdef int k, i
742
for k in xrange(circles):
743
temp = range(pts*2)
744
for i in xrange(2*pts):
745
temp[i] = self.inverse_riemann_map(
746
(k + 1) / (circles + 1.0) * exp(I*i * TWOPI / (2*pts)))
747
if plotjoined:
748
circle_list[k] = list_plot(
749
comp_pt(temp, 1), rgbcolor=rgbcolor, thickness=thickness,
750
plotjoined=True)
751
else:
752
circle_list[k] = list_plot(
753
comp_pt(temp, 1), rgbcolor=rgbcolor, pointsize=thickness)
754
line_list = range(spokes)
755
for k in xrange(spokes):
756
temp = range(pts)
757
angle = (k*1.0) / spokes * TWOPI
758
if angle >= tmax:
759
angle -= TWOPI
760
elif angle <= tmin:
761
angle += TWOPI
762
for i in xrange(pts - 1):
763
temp[i] = self.inverse_riemann_map(
764
(i * 1.0) / (pts * 1.0) * exp(I * angle) * linescale)
765
temp[pts - 1] = np.complex(
766
self.f(s(angle)) if angle <= tmax else self.f(s(angle-TWOPI)))
767
if plotjoined:
768
line_list[k] = list_plot(
769
comp_pt(temp, 0), rgbcolor=rgbcolor, thickness=thickness,
770
plotjoined=True)
771
else:
772
line_list[k] = list_plot(
773
comp_pt(temp, 0), rgbcolor=rgbcolor, pointsize=thickness)
774
return edge + sum(circle_list) + sum(line_list)
775
776
@options(interpolation='catrom')
777
def plot_colored(self, plot_range=[], int plot_points=100, **options):
778
"""
779
Draws a colored plot of the Riemann map. A red point on the
780
colored plot corresponds to a red point on the unit disc. Note that
781
this method DOES work for multiply connected domains.
782
783
INPUT:
784
785
The following inputs must all be passed in as named parameters:
786
787
- ``plot_range`` -- (default: ``[]``) list of 4 values
788
``(xmin, xmax, ymin, ymax)``. Declare if you do not want the plot
789
to use the default range for the figure.
790
791
- ``plot_points`` -- integer (default: ``100``), number of points
792
to plot in each direction of the grid. Note that very large values
793
can cause this function to run slowly.
794
795
EXAMPLES:
796
797
Given a Riemann map m, general usage::
798
799
sage: f(t) = e^(I*t) - 0.5*e^(-I*t)
800
sage: fprime(t) = I*e^(I*t) + 0.5*I*e^(-I*t)
801
sage: m = Riemann_Map([f], [fprime], 0)
802
sage: m.plot_colored()
803
804
Plot zoomed in on a specific spot::
805
806
sage: m.plot_colored(plot_range=[0,1,.25,.75])
807
808
High resolution plot::
809
810
sage: m.plot_colored(plot_points=1000) # long time (30s on sage.math, 2011)
811
812
To generate the unit circle map, it's helpful to see what the
813
colors correspond to::
814
815
sage: f(t) = e^(I*t)
816
sage: fprime(t) = I*e^(I*t)
817
sage: m = Riemann_Map([f], [fprime], 0, 1000)
818
sage: m.plot_colored()
819
"""
820
cdef int i, j
821
cdef double xmin
822
cdef double xmax
823
cdef double ymin
824
cdef double ymax
825
if plot_range == []:
826
xmin = self.x_range[0]
827
xmax = self.x_range[1]
828
ymin = self.y_range[0]
829
ymax = self.y_range[1]
830
else:
831
xmin = plot_range[0]
832
xmax = plot_range[1]
833
ymin = plot_range[2]
834
ymax = plot_range[3]
835
xstep = (xmax - xmin) / plot_points
836
ystep = (ymax - ymin) / plot_points
837
cdef np.ndarray z_values = np.empty(
838
[plot_points, plot_points], dtype=np.complex128)
839
for i in xrange(plot_points):
840
for j in xrange(plot_points):
841
z_values[j, i] = self.riemann_map(
842
np.complex(xmin + 0.5*xstep + i*xstep,
843
ymin + 0.5*ystep + j*ystep))
844
g = Graphics()
845
g.add_primitive(ComplexPlot(complex_to_rgb(z_values), (xmin, xmax), (ymin, ymax),options))
846
return g
847
848
cdef comp_pt(clist, loop=True):
849
"""
850
This function converts a list of complex numbers to the plottable
851
`(x,y)` form. If ``loop=True``, then the first point will be
852
added as the last, i.e. to plot a closed circle.
853
854
INPUT:
855
856
- ``clist`` -- a list of complex numbers.
857
858
- ``loop`` -- boolean (default: ``True``) controls whether or not the
859
first point will be added as the last to plot a closed circle.
860
861
EXAMPLES:
862
863
This tests it implicitly::
864
865
sage: f(t) = e^(I*t) - 0.5*e^(-I*t)
866
sage: fprime(t) = I*e^(I*t) + 0.5*I*e^(-I*t)
867
sage: m = Riemann_Map([f], [fprime], 0)
868
sage: m.plot_spiderweb()
869
"""
870
list2 = range(len(clist) + 1) if loop else range(len(clist))
871
for i in xrange(len(clist)):
872
list2[i] = (clist[i].real, clist[i].imag)
873
if loop:
874
list2[len(clist)] = list2[0]
875
return list2
876
877
cdef inline double mag_to_lightness(double r):
878
"""
879
Tweak this to adjust how the magnitude affects the color.
880
881
INPUT:
882
883
- ``r`` -- a non-negative real number.
884
885
OUTPUT:
886
887
A value between `-1` (black) and `+1` (white), inclusive.
888
889
EXAMPLES:
890
891
This tests it implicitly::
892
893
sage: from sage.calculus.riemann import complex_to_rgb
894
sage: import numpy
895
sage: complex_to_rgb(numpy.array([[0, 1, 1000]],dtype = numpy.complex128))
896
array([[[ 1., 1., 1.],
897
[ 1., 0., 0.],
898
[-998., 0., 0.]]])
899
"""
900
return 1 - r
901
902
cpdef complex_to_rgb(np.ndarray z_values):
903
r"""
904
Convert from a (Numpy) array of complex numbers to its corresponding
905
matrix of RGB values. For internal use of :meth:`~Riemann_Map.plot_colored`
906
only.
907
908
INPUT:
909
910
- ``z_values`` -- A Numpy array of complex numbers.
911
912
OUTPUT:
913
914
An `N \times M \times 3` floating point Numpy array ``X``, where
915
``X[i,j]`` is an (r,g,b) tuple.
916
917
EXAMPLES::
918
919
sage: from sage.calculus.riemann import complex_to_rgb
920
sage: import numpy
921
sage: complex_to_rgb(numpy.array([[0, 1, 1000]],dtype = numpy.complex128))
922
array([[[ 1., 1., 1.],
923
[ 1., 0., 0.],
924
[-998., 0., 0.]]])
925
sage: complex_to_rgb(numpy.array([[0, 1j, 1000j]],dtype = numpy.complex128))
926
array([[[ 1.00000000e+00, 1.00000000e+00, 1.00000000e+00],
927
[ 5.00000000e-01, 1.00000000e+00, 0.00000000e+00],
928
[ -4.99000000e+02, -9.98000000e+02, 0.00000000e+00]]])
929
930
TESTS::
931
932
sage: complex_to_rgb([[0, 1, 10]])
933
Traceback (most recent call last):
934
...
935
TypeError: Argument 'z_values' has incorrect type (expected numpy.ndarray, got list)
936
"""
937
cdef unsigned int i, j, imax, jmax
938
cdef double x, y, mag, arg
939
cdef double lightness, hue, top, bot
940
cdef double r, g, b
941
cdef int ihue
942
cdef z
943
944
from cmath import phase
945
946
imax = len(z_values)
947
jmax = len(z_values[0])
948
cdef np.ndarray[np.float_t, ndim=3, mode="c"] rgb = np.empty(
949
dtype=np.float, shape=(imax, jmax, 3))
950
951
sig_on()
952
for i from 0 <= i < imax:
953
row = z_values[i]
954
for j from 0 <= j < jmax:
955
z = row[j]
956
mag = abs(z)
957
arg = phase(z)
958
lightness = mag_to_lightness(mag)
959
# in hsv, variable value, full saturation (s=1, v=1+lightness)
960
if lightness < 0:
961
bot = 0
962
top = (1 + lightness)
963
# in hsv, variable saturation, full value (v=1, s=1-lightness)
964
else:
965
bot = lightness
966
top = 1
967
# Note that does same thing as colorsys module hsv_to_rgb for
968
# this setup, but in Cython.
969
hue = 3*arg / PI
970
# usual hsv hue is thus h=arg/(2*pi) for positive,
971
# h=arg/(2*PI)+1 for negative
972
if hue < 0:
973
hue += 6
974
ihue = <int>hue
975
if ihue == 0:
976
r = top
977
g = bot + hue * (top - bot)
978
b = bot
979
elif ihue == 1:
980
r = bot + (2 - hue) * (top - bot)
981
g = top
982
b = bot
983
elif ihue == 2:
984
r = bot
985
g = top
986
b = bot + (hue - 2) * (top - bot)
987
elif ihue == 3:
988
r = bot
989
g = bot + (4 - hue) * (top - bot)
990
b = top
991
elif ihue == 4:
992
r = bot + (hue - 4) * (top - bot)
993
g = bot
994
b = top
995
else:
996
r = top
997
g = bot
998
b = bot + (6 - hue) * (top - bot)
999
rgb[i, j, 0] = r
1000
rgb[i, j, 1] = g
1001
rgb[i, j, 2] = b
1002
sig_off()
1003
return rgb
1004
1005
1006