Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagelib
Path: blob/master/sage/plot/plot3d/plot3d.py
4038 views
1
r"""
2
Plotting Functions
3
4
EXAMPLES::
5
6
sage: def f(x,y):
7
... return math.sin(y*y+x*x)/math.sqrt(x*x+y*y+.0001)
8
...
9
sage: P = plot3d(f,(-3,3),(-3,3), adaptive=True, color=rainbow(60, 'rgbtuple'), max_bend=.1, max_depth=15)
10
sage: P.show()
11
12
::
13
14
sage: def f(x,y):
15
... return math.exp(x/5)*math.sin(y)
16
...
17
sage: P = plot3d(f,(-5,5),(-5,5), adaptive=True, color=['red','yellow'])
18
sage: from sage.plot.plot3d.plot3d import axes
19
sage: S = P + axes(6, color='black')
20
sage: S.show()
21
22
We plot "cape man"::
23
24
sage: S = sphere(size=.5, color='yellow')
25
26
::
27
28
sage: from sage.plot.plot3d.shapes import Cone
29
sage: S += Cone(.5, .5, color='red').translate(0,0,.3)
30
31
::
32
33
sage: S += sphere((.45,-.1,.15), size=.1, color='white') + sphere((.51,-.1,.17), size=.05, color='black')
34
sage: S += sphere((.45, .1,.15),size=.1, color='white') + sphere((.51, .1,.17), size=.05, color='black')
35
sage: S += sphere((.5,0,-.2),size=.1, color='yellow')
36
sage: def f(x,y): return math.exp(x/5)*math.cos(y)
37
sage: P = plot3d(f,(-5,5),(-5,5), adaptive=True, color=['red','yellow'], max_depth=10)
38
sage: cape_man = P.scale(.2) + S.translate(1,0,0)
39
sage: cape_man.show(aspect_ratio=[1,1,1])
40
41
AUTHORS:
42
43
- Tom Boothby: adaptive refinement triangles
44
45
- Josh Kantor: adaptive refinement triangles
46
47
- Robert Bradshaw (2007-08): initial version of this file
48
49
- William Stein (2007-12, 2008-01): improving 3d plotting
50
51
- Oscar Lazo, William Cauchois, Jason Grout (2009-2010): Adding coordinate transformations
52
"""
53
54
55
#TODO:
56
# -- smooth triangles
57
58
#*****************************************************************************
59
# Copyright (C) 2007 Robert Bradshaw <[email protected]>
60
#
61
# Distributed under the terms of the GNU General Public License (GPL)
62
#
63
# This code is distributed in the hope that it will be useful,
64
# but WITHOUT ANY WARRANTY; without even the implied warranty of
65
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
66
# General Public License for more details.
67
#
68
# The full text of the GPL is available at:
69
#
70
# http://www.gnu.org/licenses/
71
#*****************************************************************************
72
73
74
from tri_plot import TrianglePlot
75
from index_face_set import IndexFaceSet
76
from shapes import arrow3d
77
from base import Graphics3dGroup
78
from sage.plot.colors import rainbow
79
from texture import Texture
80
81
from sage.ext.fast_eval import fast_float_arg
82
83
from sage.functions.trig import cos, sin
84
85
class _Coordinates(object):
86
"""
87
This abstract class encapsulates a new coordinate system for plotting.
88
Sub-classes must implement the :meth:`transform` method which, given
89
symbolic variables to use, generates a 3-tuple of functions in terms of
90
those variables that can be used to find the cartesian (X, Y, and Z)
91
coordinates for any point in this space.
92
"""
93
def __init__(self, dep_var, indep_vars):
94
"""
95
INPUT:
96
97
- ``dep_var`` - The dependent variable (the function value will be
98
substituted for this).
99
100
- ``indep_vars`` - A list of independent variables (the parameters will be
101
substituted for these).
102
103
TESTS:
104
105
Because the base :class:`_Coordinates` class automatically checks the
106
initializing variables with the transform method, :class:`_Coordinates`
107
cannot be instantiated by itself. We test a subclass.
108
109
sage: from sage.plot.plot3d.plot3d import _ArbitraryCoordinates as arb
110
sage: x,y,z=var('x,y,z')
111
sage: arb((x+z,y*z,z), z, (x,y))
112
Arbitrary Coordinates coordinate transform (z in terms of x, y)
113
"""
114
import inspect
115
all_vars=inspect.getargspec(self.transform).args[1:]
116
if set(all_vars) != set(indep_vars + [dep_var]):
117
raise ValueError, 'variables were specified incorrectly for this coordinate system; incorrect variables were %s'%list(set(all_vars).symmetric_difference(set(indep_vars+[dep_var])))
118
self.dep_var = dep_var
119
self.indep_vars = indep_vars
120
121
@property
122
def _name(self):
123
"""
124
A default name for a coordinate system. Override this in a
125
subclass to set a different name.
126
127
TESTS::
128
129
sage: from sage.plot.plot3d.plot3d import _ArbitraryCoordinates as arb
130
sage: x,y,z=var('x,y,z')
131
sage: c=arb((x+z,y*z,z), z, (x,y))
132
sage: c._name
133
'Arbitrary Coordinates'
134
"""
135
return self.__class__.__name__
136
137
def transform(self, **kwds):
138
"""
139
Return the transformation for this coordinate system in terms of the
140
specified variables (which should be keywords).
141
142
TESTS::
143
144
sage: from sage.plot.plot3d.plot3d import _ArbitraryCoordinates as arb
145
sage: x,y,z=var('x,y,z')
146
sage: c=arb((x+z,y*z,z), z, (x,y))
147
sage: c.transform(x=1,y=2,z=3)
148
(4, 6, 3)
149
"""
150
raise NotImplementedError
151
152
def to_cartesian(self, func, params=None):
153
"""
154
Returns a 3-tuple of functions, parameterized over ``params``, that
155
represents the cartesian coordinates of the value of ``func``.
156
157
INPUT:
158
159
- ``func`` - A function in this coordinate space. Corresponds to the
160
independent variable.
161
162
- ``params`` - The parameters of func. Corresponds to the dependent
163
variables.
164
165
EXAMPLE::
166
167
sage: from sage.plot.plot3d.plot3d import _ArbitraryCoordinates
168
sage: x, y, z = var('x y z')
169
sage: T = _ArbitraryCoordinates((x + y, x - y, z), z,[x,y])
170
sage: f(x, y) = 2*x+y
171
sage: T.to_cartesian(f, [x, y])
172
(x + y, x - y, 2*x + y)
173
sage: [h(1,2) for h in T.to_cartesian(lambda x,y: 2*x+y)]
174
[3.0, -1.0, 4.0]
175
176
We try to return a function having the same variable names as
177
the function passed in::
178
179
sage: from sage.plot.plot3d.plot3d import _ArbitraryCoordinates
180
sage: x, y, z = var('x y z')
181
sage: T = _ArbitraryCoordinates((x + y, x - y, z), z,[x,y])
182
sage: f(a, b) = 2*a+b
183
sage: T.to_cartesian(f, [a, b])
184
(a + b, a - b, 2*a + b)
185
sage: t1,t2,t3=T.to_cartesian(lambda a,b: 2*a+b)
186
sage: import inspect
187
sage: inspect.getargspec(t1)
188
ArgSpec(args=['a', 'b'], varargs=None, keywords=None, defaults=None)
189
sage: inspect.getargspec(t2)
190
ArgSpec(args=['a', 'b'], varargs=None, keywords=None, defaults=None)
191
sage: inspect.getargspec(t3)
192
ArgSpec(args=['a', 'b'], varargs=None, keywords=None, defaults=None)
193
sage: def g(a,b): return 2*a+b
194
sage: t1,t2,t3=T.to_cartesian(g)
195
sage: inspect.getargspec(t1)
196
ArgSpec(args=['a', 'b'], varargs=None, keywords=None, defaults=None)
197
sage: t1,t2,t3=T.to_cartesian(2*a+b)
198
sage: inspect.getargspec(t1)
199
ArgSpec(args=['a', 'b'], varargs=None, keywords=None, defaults=None)
200
201
If we cannot guess the right parameter names, then the
202
parameters are named `u` and `v`::
203
204
sage: from sage.plot.plot3d.plot3d import _ArbitraryCoordinates
205
sage: x, y, z = var('x y z')
206
sage: T = _ArbitraryCoordinates((x + y, x - y, z), z,[x,y])
207
sage: t1,t2,t3=T.to_cartesian(operator.add)
208
sage: inspect.getargspec(t1)
209
ArgSpec(args=['u', 'v'], varargs=None, keywords=None, defaults=None)
210
sage: [h(1,2) for h in T.to_cartesian(operator.mul)]
211
[3.0, -1.0, 2.0]
212
sage: [h(u=1,v=2) for h in T.to_cartesian(operator.mul)]
213
[3.0, -1.0, 2.0]
214
215
The output of the function `func` is coerced to a float when
216
it is evaluated if the function is something like a lambda or
217
python callable. This takes care of situations like f returning a
218
singleton numpy array, for example.
219
220
sage: from numpy import array
221
sage: v_phi=array([ 0., 1.57079637, 3.14159274, 4.71238911, 6.28318548])
222
sage: v_theta=array([ 0., 0.78539819, 1.57079637, 2.35619456, 3.14159274])
223
sage: m_r=array([[ 0.16763356, 0.25683223, 0.16649297, 0.10594339, 0.55282422],
224
... [ 0.16763356, 0.19993708, 0.31403568, 0.47359696, 0.55282422],
225
... [ 0.16763356, 0.25683223, 0.16649297, 0.10594339, 0.55282422],
226
... [ 0.16763356, 0.19993708, 0.31403568, 0.47359696, 0.55282422],
227
... [ 0.16763356, 0.25683223, 0.16649297, 0.10594339, 0.55282422]])
228
sage: import scipy.interpolate
229
sage: f=scipy.interpolate.RectBivariateSpline(v_phi,v_theta,m_r)
230
sage: spherical_plot3d(f,(0,2*pi),(0,pi))
231
232
"""
233
from sage.symbolic.expression import is_Expression
234
from sage.rings.real_mpfr import is_RealNumber
235
from sage.rings.integer import is_Integer
236
if params is not None and (is_Expression(func) or is_RealNumber(func) or is_Integer(func)):
237
return self.transform(**{
238
self.dep_var: func,
239
self.indep_vars[0]: params[0],
240
self.indep_vars[1]: params[1]
241
})
242
else:
243
# func might be a lambda or a Python callable; this makes it slightly
244
# more complex.
245
import sage.symbolic.ring
246
dep_var_dummy = sage.symbolic.ring.var(self.dep_var)
247
indep_var_dummies = sage.symbolic.ring.var(','.join(self.indep_vars))
248
transformation = self.transform(**{
249
self.dep_var: dep_var_dummy,
250
self.indep_vars[0]: indep_var_dummies[0],
251
self.indep_vars[1]: indep_var_dummies[1]
252
})
253
if params is None:
254
if callable(func):
255
params = _find_arguments_for_callable(func)
256
if params is None:
257
params=['u','v']
258
else:
259
raise ValueError, "function is not callable"
260
def subs_func(t):
261
# We use eval so that the lambda function has the same
262
# variable names as the original function
263
ll="""lambda {x},{y}: t.subs({{
264
dep_var_dummy: float(func({x}, {y})),
265
indep_var_dummies[0]: float({x}),
266
indep_var_dummies[1]: float({y})
267
}})""".format(x=params[0], y=params[1])
268
return eval(ll,dict(t=t, func=func, dep_var_dummy=dep_var_dummy,
269
indep_var_dummies=indep_var_dummies))
270
return map(subs_func, transformation)
271
272
def __repr__(self):
273
"""
274
Print out a coordinate system
275
276
::
277
278
sage: from sage.plot.plot3d.plot3d import _ArbitraryCoordinates as arb
279
sage: x,y,z=var('x,y,z')
280
sage: c=arb((x+z,y*z,z), z, (x,y))
281
sage: c
282
Arbitrary Coordinates coordinate transform (z in terms of x, y)
283
sage: c.__dict__['_name'] = 'My Special Coordinates'
284
sage: c
285
My Special Coordinates coordinate transform (z in terms of x, y)
286
"""
287
return '%s coordinate transform (%s in terms of %s)' % \
288
(self._name, self.dep_var, ', '.join(self.indep_vars))
289
290
291
import inspect
292
293
def _find_arguments_for_callable(func):
294
"""
295
Find the names of arguments (that do not have default values) for
296
a callable function, taking care of several special cases in Sage.
297
If the parameters cannot be found, then return None.
298
299
EXAMPLES::
300
301
sage: from sage.plot.plot3d.plot3d import _find_arguments_for_callable
302
sage: _find_arguments_for_callable(lambda x,y: x+y)
303
['x', 'y']
304
sage: def f(a,b,c): return a+b+c
305
sage: _find_arguments_for_callable(f)
306
['a', 'b', 'c']
307
sage: _find_arguments_for_callable(lambda x,y,z=2: x+y+z)
308
['x', 'y']
309
sage: def f(a,b,c,d=2,e=1): return a+b+c+d+e
310
sage: _find_arguments_for_callable(f)
311
['a', 'b', 'c']
312
sage: g(w,r,t)=w+r+t
313
sage: _find_arguments_for_callable(g)
314
['w', 'r', 't']
315
sage: a,b = var('a,b')
316
sage: _find_arguments_for_callable(a+b)
317
['a', 'b']
318
sage: _find_arguments_for_callable(operator.add)
319
"""
320
if inspect.isfunction(func):
321
f_args=inspect.getargspec(func)
322
if f_args.defaults is None:
323
params=f_args.args
324
else:
325
params=f_args.args[:-len(f_args.defaults)]
326
else:
327
try:
328
f_args=inspect.getargspec(func.__call__)
329
if f_args.defaults is None:
330
params=f_args.args
331
else:
332
params=f_args.args[:-len(f_args.defaults)]
333
except TypeError:
334
# func.__call__ may be a built-in (or Cython) function
335
if hasattr(func, 'arguments'):
336
params=[repr(s) for s in func.arguments()]
337
else:
338
params=None
339
return params
340
341
342
class _ArbitraryCoordinates(_Coordinates):
343
"""
344
An arbitrary coordinate system.
345
"""
346
_name = "Arbitrary Coordinates"
347
348
def __init__(self, custom_trans, dep_var, indep_vars):
349
"""
350
Initialize an arbitrary coordinate system.
351
352
INPUT:
353
354
- ``custom_trans`` - A 3-tuple of transformation
355
functions.
356
357
- ``dep_var`` - The dependent (function) variable.
358
359
- ``indep_vars`` - a list of the two other independent
360
variables.
361
362
EXAMPLES::
363
364
sage: from sage.plot.plot3d.plot3d import _ArbitraryCoordinates
365
sage: x, y, z = var('x y z')
366
sage: T = _ArbitraryCoordinates((x + y, x - y, z), z,[x,y])
367
sage: f(x, y) = 2*x + y
368
sage: T.to_cartesian(f, [x, y])
369
(x + y, x - y, 2*x + y)
370
sage: [h(1,2) for h in T.to_cartesian(lambda x,y: 2*x+y)]
371
[3.0, -1.0, 4.0]
372
"""
373
self.dep_var = str(dep_var)
374
self.indep_vars = [str(i) for i in indep_vars]
375
self.custom_trans = tuple(custom_trans)
376
377
def transform(self, **kwds):
378
"""
379
EXAMPLE::
380
381
sage: from sage.plot.plot3d.plot3d import _ArbitraryCoordinates
382
sage: x, y, z = var('x y z')
383
sage: T = _ArbitraryCoordinates((x + y, x - y, z), x,[y,z])
384
385
sage: T.transform(x=z,y=1)
386
(z + 1, z - 1, z)
387
"""
388
return tuple(t.subs(**kwds) for t in self.custom_trans)
389
390
class Spherical(_Coordinates):
391
"""
392
A spherical coordinate system for use with ``plot3d(transformation=...)``
393
where the position of a point is specified by three numbers:
394
395
- the *radial distance* (``radius``) from the origin
396
397
- the *azimuth angle* (``azimuth``) from the positive `x`-axis
398
399
- the *inclination angle* (``inclination``) from the positive `z`-axis
400
401
These three variables must be specified in the constructor.
402
403
EXAMPLES:
404
405
Construct a spherical transformation for a function for the radius
406
in terms of the azimuth and inclination::
407
408
sage: T = Spherical('radius', ['azimuth', 'inclination'])
409
410
If we construct some concrete variables, we can get a
411
transformation in terms of those variables::
412
413
sage: r, phi, theta = var('r phi theta')
414
sage: T.transform(radius=r, azimuth=theta, inclination=phi)
415
(r*sin(phi)*cos(theta), r*sin(phi)*sin(theta), r*cos(phi))
416
417
We can plot with this transform. Remember that the dependent
418
variable is the radius, and the independent variables are the
419
azimuth and the inclination (in that order)::
420
421
sage: plot3d(phi * theta, (theta, 0, pi), (phi, 0, 1), transformation=T)
422
423
We next graph the function where the inclination angle is constant::
424
425
sage: S=Spherical('inclination', ['radius', 'azimuth'])
426
sage: r,theta=var('r,theta')
427
sage: plot3d(3, (r,0,3), (theta, 0, 2*pi), transformation=S)
428
429
See also :func:`spherical_plot3d` for more examples of plotting in spherical
430
coordinates.
431
"""
432
433
def transform(self, radius=None, azimuth=None, inclination=None):
434
"""
435
A spherical coordinates transform.
436
437
EXAMPLE::
438
439
sage: T = Spherical('radius', ['azimuth', 'inclination'])
440
sage: T.transform(radius=var('r'), azimuth=var('theta'), inclination=var('phi'))
441
(r*sin(phi)*cos(theta), r*sin(phi)*sin(theta), r*cos(phi))
442
"""
443
return (radius * sin(inclination) * cos(azimuth),
444
radius * sin(inclination) * sin(azimuth),
445
radius * cos(inclination))
446
447
class SphericalElevation(_Coordinates):
448
"""
449
A spherical coordinate system for use with ``plot3d(transformation=...)``
450
where the position of a point is specified by three numbers:
451
452
- the *radial distance* (``radius``) from the origin
453
454
- the *azimuth angle* (``azimuth``) from the positive `x`-axis
455
456
- the *elevation angle* (``elevation``) from the `xy`-plane toward the
457
positive `z`-axis
458
459
These three variables must be specified in the constructor.
460
461
EXAMPLES:
462
463
Construct a spherical transformation for the radius
464
in terms of the azimuth and elevation. Then, get a
465
transformation in terms of those variables::
466
467
sage: T = SphericalElevation('radius', ['azimuth', 'elevation'])
468
sage: r, theta, phi = var('r theta phi')
469
sage: T.transform(radius=r, azimuth=theta, elevation=phi)
470
(r*cos(phi)*cos(theta), r*sin(theta)*cos(phi), r*sin(phi))
471
472
We can plot with this transform. Remember that the dependent
473
variable is the radius, and the independent variables are the
474
azimuth and the elevation (in that order)::
475
476
sage: plot3d(phi * theta, (theta, 0, pi), (phi, 0, 1), transformation=T)
477
478
We next graph the function where the elevation angle is constant. This
479
should be compared to the similar example for the ``Spherical`` coordinate
480
system::
481
482
sage: SE=SphericalElevation('elevation', ['radius', 'azimuth'])
483
sage: r,theta=var('r,theta')
484
sage: plot3d(3, (r,0,3), (theta, 0, 2*pi), transformation=SE)
485
486
Plot a sin curve wrapped around the equator::
487
488
sage: P1=plot3d( (pi/12)*sin(8*theta), (r,0.99,1), (theta, 0, 2*pi), transformation=SE, plot_points=(10,200))
489
sage: P2=sphere(center=(0,0,0), size=1, color='red', opacity=0.3)
490
sage: P1+P2
491
492
Now we graph several constant elevation functions alongside several constant
493
inclination functions. This example illustrates the difference between the
494
``Spherical`` coordinate system and the ``SphericalElevation`` coordinate
495
system::
496
497
sage: r, phi, theta = var('r phi theta')
498
sage: SE = SphericalElevation('elevation', ['radius', 'azimuth'])
499
sage: angles = [pi/18, pi/12, pi/6]
500
sage: P1 = [plot3d( a, (r,0,3), (theta, 0, 2*pi), transformation=SE, opacity=0.85, color='blue') for a in angles]
501
502
sage: S = Spherical('inclination', ['radius', 'azimuth'])
503
sage: P2 = [plot3d( a, (r,0,3), (theta, 0, 2*pi), transformation=S, opacity=0.85, color='red') for a in angles]
504
sage: show(sum(P1+P2), aspect_ratio=1)
505
506
See also :func:`spherical_plot3d` for more examples of plotting in spherical
507
coordinates.
508
"""
509
510
def transform(self, radius=None, azimuth=None, elevation=None):
511
"""
512
A spherical elevation coordinates transform.
513
514
EXAMPLE::
515
516
sage: T = SphericalElevation('radius', ['azimuth', 'elevation'])
517
sage: T.transform(radius=var('r'), azimuth=var('theta'), elevation=var('phi'))
518
(r*cos(phi)*cos(theta), r*sin(theta)*cos(phi), r*sin(phi))
519
"""
520
return (radius * cos(elevation) * cos(azimuth),
521
radius * cos(elevation) * sin(azimuth),
522
radius * sin(elevation))
523
524
class Cylindrical(_Coordinates):
525
"""
526
A cylindrical coordinate system for use with ``plot3d(transformation=...)``
527
where the position of a point is specified by three numbers:
528
529
- the *radial distance* (``radius``) from the `z`-axis
530
531
- the *azimuth angle* (``azimuth``) from the positive `x`-axis
532
533
- the *height* or *altitude* (``height``) above the `xy`-plane
534
535
These three variables must be specified in the constructor.
536
537
EXAMPLES:
538
539
Construct a cylindrical transformation for a function for ``height`` in terms of
540
``radius`` and ``azimuth``::
541
542
sage: T = Cylindrical('height', ['radius', 'azimuth'])
543
544
If we construct some concrete variables, we can get a transformation::
545
546
sage: r, theta, z = var('r theta z')
547
sage: T.transform(radius=r, azimuth=theta, height=z)
548
(r*cos(theta), r*sin(theta), z)
549
550
We can plot with this transform. Remember that the dependent
551
variable is the height, and the independent variables are the
552
radius and the azimuth (in that order)::
553
554
sage: plot3d(9-r^2, (r, 0, 3), (theta, 0, pi), transformation=T)
555
556
We next graph the function where the radius is constant::
557
558
sage: S=Cylindrical('radius', ['azimuth', 'height'])
559
sage: theta,z=var('theta, z')
560
sage: plot3d(3, (theta,0,2*pi), (z, -2, 2), transformation=S)
561
562
See also :func:`cylindrical_plot3d` for more examples of plotting in cylindrical
563
coordinates.
564
"""
565
566
def transform(self, radius=None, azimuth=None, height=None):
567
"""
568
A cylindrical coordinates transform.
569
570
EXAMPLE::
571
572
sage: T = Cylindrical('height', ['azimuth', 'radius'])
573
sage: T.transform(radius=var('r'), azimuth=var('theta'), height=var('z'))
574
(r*cos(theta), r*sin(theta), z)
575
"""
576
return (radius * cos(azimuth),
577
radius * sin(azimuth),
578
height)
579
580
class TrivialTriangleFactory:
581
"""
582
Class emulating behavior of :class:`~sage.plot.plot3d.tri_plot.TriangleFactory`
583
but simply returning a list of vertices for both regular and
584
smooth triangles.
585
"""
586
def triangle(self, a, b, c, color = None):
587
"""
588
Function emulating behavior of
589
:meth:`~sage.plot.plot3d.tri_plot.TriangleFactory.triangle`
590
but simply returning a list of vertices.
591
592
INPUT:
593
594
- ``a``, ``b``, ``c`` : triples (x,y,z) representing corners
595
on a triangle in 3-space
596
- ``color``: ignored
597
598
OUTPUT:
599
600
- the list ``[a,b,c]``
601
602
TESTS::
603
604
sage: from sage.plot.plot3d.plot3d import TrivialTriangleFactory
605
sage: factory = TrivialTriangleFactory()
606
sage: tri = factory.triangle([0,0,0],[0,0,1],[1,1,0])
607
sage: tri
608
[[0, 0, 0], [0, 0, 1], [1, 1, 0]]
609
"""
610
return [a,b,c]
611
def smooth_triangle(self, a, b, c, da, db, dc, color = None):
612
"""
613
Function emulating behavior of
614
:meth:`~sage.plot.plot3d.tri_plot.TriangleFactory.smooth_triangle`
615
but simply returning a list of vertices.
616
617
INPUT:
618
619
- ``a``, ``b``, ``c`` : triples (x,y,z) representing corners
620
on a triangle in 3-space
621
- ``da``, ``db``, ``dc`` : ignored
622
- ``color`` : ignored
623
624
OUTPUT:
625
626
- the list ``[a,b,c]``
627
628
TESTS::
629
630
sage: from sage.plot.plot3d.plot3d import TrivialTriangleFactory
631
sage: factory = TrivialTriangleFactory()
632
sage: sm_tri = factory.smooth_triangle([0,0,0],[0,0,1],[1,1,0],[0,0,1],[0,2,0],[1,0,0])
633
sage: sm_tri
634
[[0, 0, 0], [0, 0, 1], [1, 1, 0]]
635
"""
636
return [a,b,c]
637
638
import parametric_plot3d
639
def plot3d(f, urange, vrange, adaptive=False, transformation=None, **kwds):
640
"""
641
INPUT:
642
643
644
- ``f`` - a symbolic expression or function of 2
645
variables
646
647
- ``urange`` - a 2-tuple (u_min, u_max) or a 3-tuple
648
(u, u_min, u_max)
649
650
- ``vrange`` - a 2-tuple (v_min, v_max) or a 3-tuple
651
(v, v_min, v_max)
652
653
- ``adaptive`` - (default: False) whether to use
654
adaptive refinement to draw the plot (slower, but may look better).
655
This option does NOT work in conjuction with a transformation
656
(see below).
657
658
- ``mesh`` - bool (default: False) whether to display
659
mesh grid lines
660
661
- ``dots`` - bool (default: False) whether to display
662
dots at mesh grid points
663
664
- ``plot_points`` - (default: "automatic") initial number of sample
665
points in each direction; an integer or a pair of integers
666
667
668
- ``transformation`` - (default: None) a transformation to
669
apply. May be a 3 or 4-tuple (x_func, y_func, z_func,
670
independent_vars) where the first 3 items indicate a
671
transformation to cartesian coordinates (from your coordinate
672
system) in terms of u, v, and the function variable fvar (for
673
which the value of f will be substituted). If a 3-tuple is
674
specified, the independent variables are chosen from the range
675
variables. If a 4-tuple is specified, the 4th element is a list
676
of independent variables. ``transformation`` may also be a
677
predefined coordinate system transformation like Spherical or
678
Cylindrical.
679
680
.. note::
681
682
``mesh`` and ``dots`` are not supported when using the Tachyon
683
raytracer renderer.
684
685
EXAMPLES: We plot a 3d function defined as a Python function::
686
687
sage: plot3d(lambda x, y: x^2 + y^2, (-2,2), (-2,2))
688
689
We plot the same 3d function but using adaptive refinement::
690
691
sage: plot3d(lambda x, y: x^2 + y^2, (-2,2), (-2,2), adaptive=True)
692
693
Adaptive refinement but with more points::
694
695
sage: plot3d(lambda x, y: x^2 + y^2, (-2,2), (-2,2), adaptive=True, initial_depth=5)
696
697
We plot some 3d symbolic functions::
698
699
sage: var('x,y')
700
(x, y)
701
sage: plot3d(x^2 + y^2, (x,-2,2), (y,-2,2))
702
sage: plot3d(sin(x*y), (x, -pi, pi), (y, -pi, pi))
703
704
We give a plot with extra sample points::
705
706
sage: var('x,y')
707
(x, y)
708
sage: plot3d(sin(x^2+y^2),(x,-5,5),(y,-5,5), plot_points=200)
709
sage: plot3d(sin(x^2+y^2),(x,-5,5),(y,-5,5), plot_points=[10,100])
710
711
A 3d plot with a mesh::
712
713
sage: var('x,y')
714
(x, y)
715
sage: plot3d(sin(x-y)*y*cos(x),(x,-3,3),(y,-3,3), mesh=True)
716
717
Two wobby translucent planes::
718
719
sage: x,y = var('x,y')
720
sage: P = plot3d(x+y+sin(x*y), (x,-10,10),(y,-10,10), opacity=0.87, color='blue')
721
sage: Q = plot3d(x-2*y-cos(x*y),(x,-10,10),(y,-10,10),opacity=0.3,color='red')
722
sage: P + Q
723
724
We draw two parametric surfaces and a transparent plane::
725
726
sage: L = plot3d(lambda x,y: 0, (-5,5), (-5,5), color="lightblue", opacity=0.8)
727
sage: P = plot3d(lambda x,y: 4 - x^3 - y^2, (-2,2), (-2,2), color='green')
728
sage: Q = plot3d(lambda x,y: x^3 + y^2 - 4, (-2,2), (-2,2), color='orange')
729
sage: L + P + Q
730
731
We draw the "Sinus" function (water ripple-like surface)::
732
733
sage: x, y = var('x y')
734
sage: plot3d(sin(pi*(x^2+y^2))/2,(x,-1,1),(y,-1,1))
735
736
Hill and valley (flat surface with a bump and a dent)::
737
738
sage: x, y = var('x y')
739
sage: plot3d( 4*x*exp(-x^2-y^2), (x,-2,2), (y,-2,2))
740
741
An example of a transformation::
742
743
sage: r, phi, z = var('r phi z')
744
sage: trans=(r*cos(phi),r*sin(phi),z)
745
sage: plot3d(cos(r),(r,0,17*pi/2),(phi,0,2*pi),transformation=trans,opacity=0.87).show(aspect_ratio=(1,1,2),frame=False)
746
747
Many more examples of transformations::
748
749
sage: u, v, w = var('u v w')
750
sage: rectangular=(u,v,w)
751
sage: spherical=(w*cos(u)*sin(v),w*sin(u)*sin(v),w*cos(v))
752
sage: cylindric_radial=(w*cos(u),w*sin(u),v)
753
sage: cylindric_axial=(v*cos(u),v*sin(u),w)
754
sage: parabolic_cylindrical=(w*v,(v^2-w^2)/2,u)
755
756
Plot a constant function of each of these to get an idea of what it does::
757
758
sage: A = plot3d(2,(u,-pi,pi),(v,0,pi),transformation=rectangular,plot_points=[100,100])
759
sage: B = plot3d(2,(u,-pi,pi),(v,0,pi),transformation=spherical,plot_points=[100,100])
760
sage: C = plot3d(2,(u,-pi,pi),(v,0,pi),transformation=cylindric_radial,plot_points=[100,100])
761
sage: D = plot3d(2,(u,-pi,pi),(v,0,pi),transformation=cylindric_axial,plot_points=[100,100])
762
sage: E = plot3d(2,(u,-pi,pi),(v,-pi,pi),transformation=parabolic_cylindrical,plot_points=[100,100])
763
sage: @interact
764
... def _(which_plot=[A,B,C,D,E]):
765
... show(which_plot)
766
<html>...
767
768
Now plot a function::
769
770
sage: g=3+sin(4*u)/2+cos(4*v)/2
771
sage: F = plot3d(g,(u,-pi,pi),(v,0,pi),transformation=rectangular,plot_points=[100,100])
772
sage: G = plot3d(g,(u,-pi,pi),(v,0,pi),transformation=spherical,plot_points=[100,100])
773
sage: H = plot3d(g,(u,-pi,pi),(v,0,pi),transformation=cylindric_radial,plot_points=[100,100])
774
sage: I = plot3d(g,(u,-pi,pi),(v,0,pi),transformation=cylindric_axial,plot_points=[100,100])
775
sage: J = plot3d(g,(u,-pi,pi),(v,0,pi),transformation=parabolic_cylindrical,plot_points=[100,100])
776
sage: @interact
777
... def _(which_plot=[F, G, H, I, J]):
778
... show(which_plot)
779
<html>...
780
781
TESTS:
782
783
Make sure the transformation plots work::
784
785
sage: show(A + B + C + D + E)
786
sage: show(F + G + H + I + J)
787
788
Listing the same plot variable twice gives an error::
789
790
sage: x, y = var('x y')
791
sage: plot3d( 4*x*exp(-x^2-y^2), (x,-2,2), (x,-2,2))
792
Traceback (most recent call last):
793
...
794
ValueError: range variables should be distinct, but there are duplicates
795
"""
796
if transformation is not None:
797
params=None
798
from sage.symbolic.callable import is_CallableSymbolicExpression
799
# First, determine the parameters for f (from the first item of urange
800
# and vrange, preferably).
801
if len(urange) == 3 and len(vrange) == 3:
802
params = (urange[0], vrange[0])
803
elif is_CallableSymbolicExpression(f):
804
params = f.variables()
805
806
if isinstance(transformation, (tuple, list)):
807
if len(transformation)==3:
808
if params is None:
809
raise ValueError, "must specify independent variable names in the ranges when using generic transformation"
810
indep_vars = params
811
elif len(transformation)==4:
812
indep_vars = transformation[3]
813
transformation = transformation[0:3]
814
else:
815
raise ValueError, "unknown transformation type"
816
# find out which variable is the function variable by
817
# eliminating the parameter variables.
818
all_vars = set(sum([list(s.variables()) for s in transformation],[]))
819
dep_var=all_vars - set(indep_vars)
820
if len(dep_var)==1:
821
dep_var = dep_var.pop()
822
transformation = _ArbitraryCoordinates(transformation, dep_var, indep_vars)
823
else:
824
raise ValueError, "unable to determine the function variable in the transform"
825
826
if isinstance(transformation, _Coordinates):
827
R = transformation.to_cartesian(f, params)
828
return parametric_plot3d.parametric_plot3d(R, urange, vrange, **kwds)
829
else:
830
raise ValueError, 'unknown transformation type'
831
elif adaptive:
832
P = plot3d_adaptive(f, urange, vrange, **kwds)
833
else:
834
u=fast_float_arg(0)
835
v=fast_float_arg(1)
836
P=parametric_plot3d.parametric_plot3d((u,v,f), urange, vrange, **kwds)
837
P.frame_aspect_ratio([1.0,1.0,0.5])
838
return P
839
840
def plot3d_adaptive(f, x_range, y_range, color="automatic",
841
grad_f=None,
842
max_bend=.5, max_depth=5, initial_depth=4, num_colors=128, **kwds):
843
r"""
844
Adaptive 3d plotting of a function of two variables.
845
846
This is used internally by the plot3d command when the option
847
``adaptive=True`` is given.
848
849
INPUT:
850
851
852
- ``f`` - a symbolic function or a Python function of
853
3 variables.
854
855
- ``x_range`` - x range of values: 2-tuple (xmin,
856
xmax) or 3-tuple (x,xmin,xmax)
857
858
- ``y_range`` - y range of values: 2-tuple (ymin,
859
ymax) or 3-tuple (y,ymin,ymax)
860
861
- ``grad_f`` - gradient of f as a Python function
862
863
- ``color`` - "automatic" - a rainbow of num_colors
864
colors
865
866
- ``num_colors`` - (default: 128) number of colors to
867
use with default color
868
869
- ``max_bend`` - (default: 0.5)
870
871
- ``max_depth`` - (default: 5)
872
873
- ``initial_depth`` - (default: 4)
874
875
- ``**kwds`` - standard graphics parameters
876
877
878
EXAMPLES:
879
880
We plot `\sin(xy)`::
881
882
sage: from sage.plot.plot3d.plot3d import plot3d_adaptive
883
sage: x,y=var('x,y'); plot3d_adaptive(sin(x*y), (x,-pi,pi), (y,-pi,pi), initial_depth=5)
884
"""
885
if initial_depth >= max_depth:
886
max_depth = initial_depth
887
888
from sage.plot.misc import setup_for_eval_on_grid
889
g, ranges = setup_for_eval_on_grid(f, [x_range,y_range], plot_points=2)
890
xmin,xmax = ranges[0][:2]
891
ymin,ymax = ranges[1][:2]
892
893
opacity = kwds.get('opacity',1)
894
895
if color == "automatic":
896
texture = rainbow(num_colors, 'rgbtuple')
897
else:
898
if isinstance(color, list):
899
texture = color
900
else:
901
kwds['color'] = color
902
texture = Texture(kwds)
903
904
factory = TrivialTriangleFactory()
905
plot = TrianglePlot(factory, g, (xmin, xmax), (ymin, ymax), g = grad_f,
906
min_depth=initial_depth, max_depth=max_depth,
907
max_bend=max_bend, num_colors = None)
908
909
P = IndexFaceSet(plot._objects)
910
if isinstance(texture, (list, tuple)):
911
if len(texture) == 2:
912
# do a grid coloring
913
xticks = (xmax - xmin)/2**initial_depth
914
yticks = (ymax - ymin)/2**initial_depth
915
parts = P.partition(lambda x,y,z: (int((x-xmin)/xticks) + int((y-ymin)/yticks)) % 2)
916
else:
917
# do a topo coloring
918
bounds = P.bounding_box()
919
min_z = bounds[0][2]
920
max_z = bounds[1][2]
921
if max_z == min_z:
922
span = 0
923
else:
924
span = (len(texture)-1) / (max_z - min_z) # max to avoid dividing by 0
925
parts = P.partition(lambda x,y,z: int((z-min_z)*span))
926
all = []
927
for k, G in parts.iteritems():
928
G.set_texture(texture[k], opacity=opacity)
929
all.append(G)
930
P = Graphics3dGroup(all)
931
else:
932
P.set_texture(texture)
933
934
P.frame_aspect_ratio([1.0,1.0,0.5])
935
P._set_extra_kwds(kwds)
936
return P
937
938
def spherical_plot3d(f, urange, vrange, **kwds):
939
"""
940
Plots a function in spherical coordinates. This function is
941
equivalent to::
942
943
sage: r,u,v=var('r,u,v')
944
sage: f=u*v; urange=(u,0,pi); vrange=(v,0,pi)
945
sage: T = (r*cos(u)*sin(v), r*sin(u)*sin(v), r*cos(v), [u,v])
946
sage: plot3d(f, urange, vrange, transformation=T)
947
948
or equivalently::
949
950
sage: T = Spherical('radius', ['azimuth', 'inclination'])
951
sage: f=lambda u,v: u*v; urange=(u,0,pi); vrange=(v,0,pi)
952
sage: plot3d(f, urange, vrange, transformation=T)
953
954
INPUT:
955
956
- ``f`` - a symbolic expression or function of two variables.
957
958
- ``urange`` - a 3-tuple (u, u_min, u_max), the domain of the azimuth variable.
959
960
- ``vrange`` - a 3-tuple (v, v_min, v_max), the domain of the inclination variable.
961
962
EXAMPLES:
963
964
A sphere of radius 2::
965
966
sage: x,y=var('x,y')
967
sage: spherical_plot3d(2,(x,0,2*pi),(y,0,pi))
968
969
The real and imaginary parts of a spherical harmonic with `l=2` and `m=1`::
970
971
sage: phi, theta = var('phi, theta')
972
sage: Y = spherical_harmonic(2, 1, theta, phi)
973
sage: rea = spherical_plot3d(abs(real(Y)), (phi,0,2*pi), (theta,0,pi), color='blue', opacity=0.6)
974
sage: ima = spherical_plot3d(abs(imag(Y)), (phi,0,2*pi), (theta,0,pi), color='red', opacity=0.6)
975
sage: (rea + ima).show(aspect_ratio=1) # long time (4s on sage.math, 2011)
976
977
A drop of water::
978
979
sage: x,y=var('x,y')
980
sage: spherical_plot3d(e^-y,(x,0,2*pi),(y,0,pi),opacity=0.5).show(frame=False)
981
982
An object similar to a heart::
983
984
sage: x,y=var('x,y')
985
sage: spherical_plot3d((2+cos(2*x))*(y+1),(x,0,2*pi),(y,0,pi),rgbcolor=(1,.1,.1))
986
987
Some random figures:
988
989
::
990
991
sage: x,y=var('x,y')
992
sage: spherical_plot3d(1+sin(5*x)/5,(x,0,2*pi),(y,0,pi),rgbcolor=(1,0.5,0),plot_points=(80,80),opacity=0.7)
993
994
::
995
996
sage: x,y=var('x,y')
997
sage: spherical_plot3d(1+2*cos(2*y),(x,0,3*pi/2),(y,0,pi)).show(aspect_ratio=(1,1,1))
998
"""
999
return plot3d(f, urange, vrange, transformation=Spherical('radius', ['azimuth', 'inclination']), **kwds)
1000
1001
def cylindrical_plot3d(f, urange, vrange, **kwds):
1002
"""
1003
Plots a function in cylindrical coordinates. This function is
1004
equivalent to::
1005
1006
sage: r,u,v=var('r,u,v')
1007
sage: f=u*v; urange=(u,0,pi); vrange=(v,0,pi)
1008
sage: T = (r*cos(u), r*sin(u), v, [u,v])
1009
sage: plot3d(f, urange, vrange, transformation=T)
1010
1011
or equivalently::
1012
1013
sage: T = Cylindrical('radius', ['azimuth', 'height'])
1014
sage: f=lambda u,v: u*v; urange=(u,0,pi); vrange=(v,0,pi)
1015
sage: plot3d(f, urange, vrange, transformation=T)
1016
1017
1018
INPUT:
1019
1020
- ``f`` - a symbolic expression or function of two variables,
1021
representing the radius from the `z`-axis.
1022
1023
- ``urange`` - a 3-tuple (u, u_min, u_max), the domain of the
1024
azimuth variable.
1025
1026
- ``vrange`` - a 3-tuple (v, v_min, v_max), the domain of the
1027
elevation (`z`) variable.
1028
1029
EXAMPLES:
1030
1031
A portion of a cylinder of radius 2::
1032
1033
sage: theta,z=var('theta,z')
1034
sage: cylindrical_plot3d(2,(theta,0,3*pi/2),(z,-2,2))
1035
1036
Some random figures:
1037
1038
::
1039
1040
sage: cylindrical_plot3d(cosh(z),(theta,0,2*pi),(z,-2,2))
1041
1042
::
1043
1044
sage: cylindrical_plot3d(e^(-z^2)*(cos(4*theta)+2)+1,(theta,0,2*pi),(z,-2,2),plot_points=[80,80]).show(aspect_ratio=(1,1,1))
1045
"""
1046
return plot3d(f, urange, vrange, transformation=Cylindrical('radius', ['azimuth', 'height']), **kwds)
1047
1048
def axes(scale=1, radius=None, **kwds):
1049
"""
1050
Creates basic axes in three dimensions. Each axis is a three
1051
dimensional arrow object.
1052
1053
INPUT:
1054
1055
- ``scale`` - (default: 1) The length of the axes (all three
1056
will be the same).
1057
- ``radius`` - (default: .01) The radius of the axes as arrows.
1058
1059
EXAMPLES::
1060
1061
sage: from sage.plot.plot3d.plot3d import axes
1062
sage: S = axes(6, color='black'); S
1063
1064
::
1065
1066
sage: T = axes(2, .5); T
1067
"""
1068
if radius is None:
1069
radius = scale/100.0
1070
return Graphics3dGroup([arrow3d((0,0,0),(scale,0,0), radius, **kwds),
1071
arrow3d((0,0,0),(0,scale,0), radius, **kwds),
1072
arrow3d((0,0,0),(0,0,scale), radius, **kwds)])
1073
1074