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