Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagesmc
Path: blob/master/src/sage/geometry/polyhedron/plot.py
8817 views
1
"""
2
Functions for plotting polyhedra
3
"""
4
5
########################################################################
6
# Copyright (C) 2008 Marshall Hampton <[email protected]>
7
# Copyright (C) 2011 Volker Braun <[email protected]>
8
#
9
# Distributed under the terms of the GNU General Public License (GPL)
10
#
11
# http://www.gnu.org/licenses/
12
########################################################################
13
14
15
from sage.rings.all import RDF
16
from sage.structure.sage_object import SageObject
17
from sage.modules.free_module_element import vector
18
from sage.matrix.constructor import matrix, identity_matrix
19
from sage.misc.functional import norm
20
from sage.misc.latex import LatexExpr
21
from sage.symbolic.constants import pi
22
from sage.structure.sequence import Sequence
23
24
from sage.plot.all import point2d, line2d, arrow, polygon2d
25
from sage.plot.plot3d.all import point3d, line3d, arrow3d, polygon3d
26
from sage.plot.plot3d.transform import rotate_arbitrary
27
28
from base import is_Polyhedron
29
30
31
32
#############################################################
33
def render_2d(projection, point_opts={}, line_opts={}, polygon_opts={}):
34
"""
35
Return 2d rendering of the projection of a polyhedron into
36
2-dimensional ambient space.
37
38
EXAMPLES::
39
40
sage: p1 = Polyhedron(vertices=[[1,1]], rays=[[1,1]])
41
sage: q1 = p1.projection()
42
sage: p2 = Polyhedron(vertices=[[1,0], [0,1], [0,0]])
43
sage: q2 = p2.projection()
44
sage: p3 = Polyhedron(vertices=[[1,2]])
45
sage: q3 = p3.projection()
46
sage: p4 = Polyhedron(vertices=[[2,0]], rays=[[1,-1]], lines=[[1,1]])
47
sage: q4 = p4.projection()
48
sage: q1.show() + q2.show() + q3.show() + q4.show()
49
sage: from sage.geometry.polyhedron.plot import render_2d
50
sage: q = render_2d(p1.projection())
51
sage: q._objects
52
[Point set defined by 1 point(s),
53
Arrow from (1.0,1.0) to (2.0,2.0),
54
Polygon defined by 3 points]
55
"""
56
if is_Polyhedron(projection):
57
projection = Projection(projection)
58
from sage.plot.graphics import Graphics
59
plt = Graphics()
60
if isinstance(point_opts, dict):
61
point_opts.setdefault('zorder', 2)
62
point_opts.setdefault('pointsize', 10)
63
plt += projection.render_points_2d(**point_opts)
64
if isinstance(line_opts, dict):
65
line_opts.setdefault('zorder', 1)
66
plt += projection.render_outline_2d(**line_opts)
67
if isinstance(polygon_opts, dict):
68
polygon_opts.setdefault('zorder', 0)
69
plt += projection.render_fill_2d(**polygon_opts)
70
return plt
71
72
73
def render_3d(projection, point_opts={}, line_opts={}, polygon_opts={}):
74
"""
75
Return 3d rendering of a polyhedron projected into
76
3-dimensional ambient space.
77
78
.. NOTE::
79
80
This method, ``render_3d``, is used in the ``show()``
81
method of a polyhedron if it is in 3 dimensions.
82
83
EXAMPLES::
84
85
sage: p1 = Polyhedron(vertices=[[1,1,1]], rays=[[1,1,1]])
86
sage: p2 = Polyhedron(vertices=[[2,0,0], [0,2,0], [0,0,2]])
87
sage: p3 = Polyhedron(vertices=[[1,0,0], [0,1,0], [0,0,1]], rays=[[-1,-1,-1]])
88
sage: p1.projection().show() + p2.projection().show() + p3.projection().show()
89
90
It correctly handles various degenerate cases::
91
92
sage: Polyhedron(lines=[[1,0,0],[0,1,0],[0,0,1]]).show() # whole space
93
sage: Polyhedron(vertices=[[1,1,1]], rays=[[1,0,0]], lines=[[0,1,0],[0,0,1]]).show() # half space
94
sage: Polyhedron(vertices=[[1,1,1]], lines=[[0,1,0],[0,0,1]]).show() # R^2 in R^3
95
sage: Polyhedron(rays=[[0,1,0],[0,0,1]], lines=[[1,0,0]]).show() # quadrant wedge in R^2
96
sage: Polyhedron(rays=[[0,1,0]], lines=[[1,0,0]]).show() # upper half plane in R^3
97
sage: Polyhedron(lines=[[1,0,0]]).show() # R^1 in R^2
98
sage: Polyhedron(rays=[[0,1,0]]).show() # Half-line in R^3
99
sage: Polyhedron(vertices=[[1,1,1]]).show() # point in R^3
100
"""
101
if is_Polyhedron(projection):
102
projection = Projection(projection)
103
from sage.plot.plot3d.base import Graphics3d
104
plt = Graphics3d()
105
if isinstance(point_opts, dict):
106
point_opts.setdefault('width', 3)
107
plt += projection.render_vertices_3d(**point_opts)
108
if isinstance(line_opts, dict):
109
line_opts.setdefault('width', 3)
110
plt += projection.render_wireframe_3d(**line_opts)
111
if isinstance(polygon_opts, dict):
112
plt += projection.render_solid_3d(**polygon_opts)
113
return plt
114
115
def render_4d(polyhedron, point_opts={}, line_opts={}, polygon_opts={}, projection_direction=None):
116
"""
117
Return a 3d rendering of the Schlegel projection of a 4d
118
polyhedron projected into 3-dimensional space.
119
120
.. NOTE::
121
122
The ``show()`` method of ``Polyhedron()`` uses this to draw itself
123
if the ambient dimension is 4.
124
125
INPUT:
126
127
- ``polyhedron`` -- A
128
:mod:`~sage.geometry.polyhedron.constructor.Polyhedron` object.
129
130
- ``point_opts``, ``line_opts``, ``polygon_opts`` -- dictionaries
131
of plot keywords or ``False`` to disable.
132
133
- ``projection_direction`` -- list/tuple/iterable of coordinates
134
or ``None`` (default). Sets the projetion direction of the
135
Schlegel projection. If it is not given, the center of a facet
136
is used.
137
138
EXAMPLES::
139
140
sage: poly = polytopes.twenty_four_cell()
141
sage: poly
142
A 4-dimensional polyhedron in QQ^4 defined as the convex hull of 24 vertices
143
sage: poly.show()
144
sage: poly.show(projection_direction=[2,5,11,17])
145
sage: type( poly.show() )
146
<class 'sage.plot.plot3d.base.Graphics3dGroup'>
147
148
TESTS::
149
150
sage: from sage.geometry.polyhedron.plot import render_4d
151
sage: p = polytopes.n_cube(4)
152
sage: q = render_4d(p)
153
sage: tach_str = q.tachyon()
154
sage: tach_str.count('FCylinder')
155
32
156
"""
157
if projection_direction is None:
158
for ineq in polyhedron.inequality_generator():
159
center = [v() for v in ineq.incident() if v.is_vertex()]
160
center = sum(center) / len(center)
161
if not center.is_zero():
162
projection_direction = center
163
break
164
projection_3d = Projection(polyhedron).schlegel(projection_direction)
165
return render_3d(projection_3d, point_opts, line_opts, polygon_opts)
166
167
168
169
#############################################################
170
def cyclic_sort_vertices_2d(Vlist):
171
"""
172
Return the vertices/rays in cyclic order if possible.
173
174
.. NOTE::
175
176
This works if and only if each vertex/ray is adjacent to exactly
177
two others. For example, any 2-dimensional polyhedron satisfies
178
this.
179
180
See
181
:meth:`~sage.geometry.polyhedron.base.Polyhedron_base.vertex_adjacency_matrix`
182
for a discussion of "adjacent".
183
184
EXAMPLES::
185
186
sage: from sage.geometry.polyhedron.plot import cyclic_sort_vertices_2d
187
sage: square = Polyhedron([[1,0],[-1,0],[0,1],[0,-1]])
188
sage: vertices = [v for v in square.vertex_generator()]
189
sage: vertices
190
[A vertex at (-1, 0),
191
A vertex at (0, -1),
192
A vertex at (0, 1),
193
A vertex at (1, 0)]
194
sage: cyclic_sort_vertices_2d(vertices)
195
[A vertex at (1, 0),
196
A vertex at (0, -1),
197
A vertex at (-1, 0),
198
A vertex at (0, 1)]
199
200
Rays are allowed, too::
201
202
sage: P = Polyhedron(vertices=[(0, 1), (1, 0), (2, 0), (3, 0), (4, 1)], rays=[(0,1)])
203
sage: P.adjacency_matrix()
204
[0 1 0 1 0]
205
[1 0 1 0 0]
206
[0 1 0 0 1]
207
[1 0 0 0 1]
208
[0 0 1 1 0]
209
sage: cyclic_sort_vertices_2d(P.Vrepresentation())
210
[A vertex at (3, 0),
211
A vertex at (1, 0),
212
A vertex at (0, 1),
213
A ray in the direction (0, 1),
214
A vertex at (4, 1)]
215
216
sage: P = Polyhedron(vertices=[(0, 1), (1, 0), (2, 0), (3, 0), (4, 1)], rays=[(0,1), (1,1)])
217
sage: P.adjacency_matrix()
218
[0 1 0 0 0]
219
[1 0 1 0 0]
220
[0 1 0 0 1]
221
[0 0 0 0 1]
222
[0 0 1 1 0]
223
sage: cyclic_sort_vertices_2d(P.Vrepresentation())
224
[A ray in the direction (1, 1),
225
A vertex at (3, 0),
226
A vertex at (1, 0),
227
A vertex at (0, 1),
228
A ray in the direction (0, 1)]
229
230
sage: P = Polyhedron(vertices=[(1,2)], rays=[(0,1)], lines=[(1,0)])
231
sage: P.adjacency_matrix()
232
[0 0 1]
233
[0 0 0]
234
[1 0 0]
235
sage: cyclic_sort_vertices_2d(P.Vrepresentation())
236
[A vertex at (0, 2),
237
A line in the direction (1, 0),
238
A ray in the direction (0, 1)]
239
"""
240
if len(Vlist)==0: return Vlist
241
Vlist = list(Vlist)
242
result = []
243
adjacency_matrix = Vlist[0].polyhedron().vertex_adjacency_matrix()
244
245
# Any object in Vlist has 0,1, or 2 adjacencies. Break into connected chains:
246
chain = [ Vlist.pop() ]
247
while len(Vlist)>0:
248
first_index = chain[0].index()
249
last_index = chain[-1].index()
250
for v in Vlist:
251
v_index = v.index()
252
if adjacency_matrix[last_index, v_index] == 1:
253
chain = chain + [v]
254
Vlist.remove(v)
255
break;
256
if adjacency_matrix[first_index, v.index()] == 1:
257
chain = [v] + chain
258
Vlist.remove(v)
259
break;
260
else:
261
result += chain
262
chain = [ Vlist.pop() ]
263
result += chain
264
return result
265
266
267
268
269
#########################################################################
270
def projection_func_identity(x):
271
"""
272
The identity projection.
273
274
EXAMPLES::
275
276
sage: from sage.geometry.polyhedron.plot import projection_func_identity
277
sage: projection_func_identity((1,2,3))
278
[1, 2, 3]
279
"""
280
return list(x)
281
282
283
284
class ProjectionFuncStereographic():
285
"""
286
The stereographic (or perspective) projection.
287
288
EXAMPLES::
289
290
sage: from sage.geometry.polyhedron.plot import ProjectionFuncStereographic
291
sage: cube = polytopes.n_cube(3).vertices()
292
sage: proj = ProjectionFuncStereographic([1.2, 3.4, 5.6])
293
sage: ppoints = [proj(vector(x)) for x in cube]
294
sage: ppoints[0]
295
(-0.0486511..., 0.0859565...)
296
"""
297
def __init__(self, projection_point):
298
"""
299
Create a stereographic projection function.
300
301
INPUT:
302
303
- ``projection_point`` -- a list of coordinates in the
304
appropriate dimension, which is the point projected from.
305
306
EXAMPLES::
307
308
sage: from sage.geometry.polyhedron.plot import ProjectionFuncStereographic
309
sage: proj = ProjectionFuncStereographic([1.0,1.0])
310
sage: proj.__init__([1.0,1.0])
311
sage: proj.house
312
[-0.7071067811... 0.7071067811...]
313
[ 0.7071067811... 0.7071067811...]
314
sage: TestSuite(proj).run(skip='_test_pickling')
315
"""
316
self.projection_point = vector(projection_point)
317
self.dim = self.projection_point.degree()
318
319
pproj = vector(RDF,self.projection_point)
320
self.psize = norm(pproj)
321
if (self.psize).is_zero():
322
raise ValueError, "projection direction must be a non-zero vector."
323
v = vector(RDF, [0.0]*(self.dim-1) + [self.psize]) - pproj
324
polediff = matrix(RDF,v).transpose()
325
denom = RDF((polediff.transpose()*polediff)[0][0])
326
if denom.is_zero():
327
self.house = identity_matrix(RDF,self.dim)
328
else:
329
self.house = identity_matrix(RDF,self.dim) \
330
- 2*polediff*polediff.transpose()/denom # Householder reflector
331
332
def __call__(self, x):
333
"""
334
Action of the stereographic projection.
335
336
INPUT:
337
338
- ``x`` -- a vector or anything convertible to a vector.
339
340
OUTPUT:
341
342
First reflects ``x`` with a Householder reflection which takes
343
the projection point to ``(0,...,0,self.psize)`` where
344
``psize`` is the length of the projection point, and then
345
dilates by ``1/(zdiff)`` where ``zdiff`` is the difference
346
between the last coordinate of ``x`` and ``psize``.
347
348
EXAMPLES::
349
350
sage: from sage.geometry.polyhedron.plot import ProjectionFuncStereographic
351
sage: proj = ProjectionFuncStereographic([1.0,1.0])
352
sage: proj.__call__(vector([1,2]))
353
(-1.0)
354
sage: proj = ProjectionFuncStereographic([2.0,1.0])
355
sage: proj.__call__(vector([1,2]))
356
(3.0)
357
sage: proj = ProjectionFuncStereographic([0,0,2])
358
sage: proj.__call__(vector([0,0,1]))
359
(0.0, 0.0)
360
sage: proj.__call__(vector([1,0,0]))
361
(0.5, 0.0)
362
"""
363
img = self.house * x
364
denom = self.psize-img[self.dim-1]
365
if denom.is_zero():
366
raise ValueError, 'Point cannot coincide with ' \
367
'coordinate singularity at ' + repr(x)
368
return vector(RDF, [img[i]/denom for i in range(self.dim-1)])
369
370
371
class ProjectionFuncSchlegel():
372
"""
373
The Schlegel projection from the given input point.
374
375
EXAMPLES::
376
377
sage: from sage.geometry.polyhedron.plot import ProjectionFuncSchlegel
378
sage: proj = ProjectionFuncSchlegel([2,2,2])
379
sage: proj(vector([1.1,1.1,1.11]))[0]
380
0.0302...
381
"""
382
def __init__(self, projection_direction, height = 1.1):
383
"""
384
Initializes the projection.
385
386
EXAMPLES::
387
388
sage: from sage.geometry.polyhedron.plot import ProjectionFuncSchlegel
389
sage: proj = ProjectionFuncSchlegel([2,2,2])
390
sage: proj.__init__([2,2,2])
391
sage: proj(vector([1.1,1.1,1.11]))[0]
392
0.0302...
393
sage: TestSuite(proj).run(skip='_test_pickling')
394
"""
395
self.projection_dir = vector(RDF, projection_direction)
396
if norm(self.projection_dir).is_zero():
397
raise ValueError, "projection direction must be a non-zero vector."
398
self.dim = self.projection_dir.degree()
399
spcenter = height * self.projection_dir/norm(self.projection_dir)
400
self.height = height
401
v = vector(RDF, [0.0]*(self.dim-1) + [self.height]) - spcenter
402
polediff = matrix(RDF,v).transpose()
403
denom = (polediff.transpose()*polediff)[0][0]
404
if denom.is_zero():
405
self.house = identity_matrix(RDF,self.dim)
406
else:
407
self.house = identity_matrix(RDF,self.dim) \
408
- 2*polediff*polediff.transpose()/denom #Householder reflector
409
410
def __call__(self, x):
411
"""
412
Apply the projection to a vector.
413
414
- ``x`` -- a vector or anything convertible to a vector.
415
416
EXAMPLES::
417
418
sage: from sage.geometry.polyhedron.plot import ProjectionFuncSchlegel
419
sage: proj = ProjectionFuncSchlegel([2,2,2])
420
sage: proj.__call__([1,2,3])
421
(0.56162854..., 2.09602626...)
422
"""
423
v = vector(RDF,x)
424
if v.is_zero():
425
raise ValueError, "The origin must not be a vertex."
426
v = v/norm(v) # normalize vertices to unit sphere
427
v = self.house*v # reflect so self.projection_dir is at "north pole"
428
denom = self.height-v[self.dim-1]
429
if denom.is_zero():
430
raise ValueError, 'Point cannot coincide with ' \
431
'coordinate singularity at ' + repr(x)
432
return vector(RDF, [ v[i]/denom for i in range(self.dim-1) ])
433
434
435
436
#########################################################################
437
class Projection(SageObject):
438
"""
439
The projection of a :class:`Polyhedron`.
440
441
This class keeps track of the necessary data to plot the input
442
polyhedron.
443
"""
444
445
def __init__(self, polyhedron, proj=projection_func_identity):
446
"""
447
Initialize the projection of a Polyhedron() object.
448
449
INPUT:
450
451
- ``polyhedron`` -- a ``Polyhedron()`` object
452
453
- ``proj`` -- a projection function for the points
454
455
.. NOTE::
456
457
Once initialized, the polyhedral data is fixed. However, the
458
projection can be changed later on.
459
460
EXAMPLES::
461
462
sage: p = polytopes.icosahedron()
463
sage: from sage.geometry.polyhedron.plot import Projection
464
sage: Projection(p)
465
The projection of a polyhedron into 3 dimensions
466
sage: def pr_12(x): return [x[1],x[2]]
467
sage: Projection(p, pr_12)
468
The projection of a polyhedron into 2 dimensions
469
sage: Projection(p, lambda x: [x[1],x[2]] ) # another way of doing the same projection
470
The projection of a polyhedron into 2 dimensions
471
sage: _.show() # plot of the projected icosahedron in 2d
472
sage: proj = Projection(p)
473
sage: proj.stereographic([1,2,3])
474
The projection of a polyhedron into 2 dimensions
475
sage: proj.show()
476
sage: TestSuite(proj).run(skip='_test_pickling')
477
"""
478
self.parent_polyhedron = polyhedron
479
self.coords = Sequence([])
480
self.points = Sequence([])
481
self.lines = Sequence([])
482
self.arrows = Sequence([])
483
self.polygons = Sequence([])
484
self.polyhedron_ambient_dim = polyhedron.ambient_dim()
485
486
if polyhedron.ambient_dim() == 2:
487
self._init_from_2d(polyhedron)
488
elif polyhedron.ambient_dim() == 3:
489
self._init_from_3d(polyhedron)
490
else:
491
self._init_points(polyhedron)
492
self._init_lines_arrows(polyhedron)
493
494
self.coords.set_immutable()
495
self.points.set_immutable()
496
self.lines.set_immutable()
497
self.arrows.set_immutable()
498
self.polygons.set_immutable()
499
500
self.__call__(proj)
501
502
503
def _repr_(self):
504
"""
505
Return a string describing the projection.
506
507
EXAMPLES::
508
509
sage: p = polytopes.n_cube(3)
510
sage: from sage.geometry.polyhedron.plot import Projection
511
sage: proj = Projection(p)
512
sage: print proj._repr_()
513
The projection of a polyhedron into 3 dimensions
514
"""
515
s = 'The projection of a polyhedron into ' + \
516
repr(self.dimension) + ' dimensions'
517
return s
518
519
520
def __call__(self, proj=projection_func_identity):
521
"""
522
Apply a projection.
523
524
EXAMPLES::
525
526
sage: p = polytopes.icosahedron()
527
sage: from sage.geometry.polyhedron.plot import Projection
528
sage: pproj = Projection(p)
529
sage: from sage.geometry.polyhedron.plot import ProjectionFuncStereographic
530
sage: pproj_stereo = pproj.__call__(proj = ProjectionFuncStereographic([1,2,3]))
531
sage: pproj_stereo.polygons[0]
532
[10, 4, 6]
533
"""
534
self.transformed_coords = \
535
Sequence([proj(p) for p in self.coords])
536
self._init_dimension()
537
return self
538
539
540
def identity(self):
541
"""
542
Return the identity projection of the polyhedron.
543
544
EXAMPLES::
545
546
sage: p = polytopes.icosahedron()
547
sage: from sage.geometry.polyhedron.plot import Projection
548
sage: pproj = Projection(p)
549
sage: ppid = pproj.identity()
550
sage: ppid.dimension
551
3
552
"""
553
return self.__call__(projection_func_identity)
554
555
556
def stereographic(self, projection_point=None):
557
r"""
558
Return the stereographic projection.
559
560
INPUT:
561
562
- ``projection_point`` - The projection point. This must be
563
distinct from the polyhedron's vertices. Default is `(1,0,\dots,0)`
564
565
EXAMPLES::
566
567
sage: from sage.geometry.polyhedron.plot import Projection
568
sage: proj = Projection(polytopes.buckyball()) #long time
569
sage: proj #long time
570
The projection of a polyhedron into 3 dimensions
571
sage: proj.stereographic([5,2,3]).show() #long time
572
sage: Projection( polytopes.twenty_four_cell() ).stereographic([2,0,0,0])
573
The projection of a polyhedron into 3 dimensions
574
"""
575
if projection_point == None:
576
projection_point = [1] + [0]*(self.polyhedron_ambient_dim-1)
577
return self.__call__(ProjectionFuncStereographic(projection_point))
578
579
580
def schlegel(self, projection_direction=None, height = 1.1):
581
"""
582
Return the Schlegel projection.
583
584
The vertices are normalized to the unit sphere, and
585
stereographically projected from a point slightly outside of
586
the sphere.
587
588
INPUT:
589
590
- ``projection_direction`` - The direction of the Schlegel
591
projection. By default, the vector consisting of the first n
592
primes is chosen.
593
594
EXAMPLES::
595
596
sage: cube4 = polytopes.n_cube(4)
597
sage: from sage.geometry.polyhedron.plot import Projection
598
sage: Projection(cube4).schlegel([1,0,0,0])
599
The projection of a polyhedron into 3 dimensions
600
sage: _.show()
601
602
TESTS::
603
604
sage: Projection(cube4).schlegel()
605
The projection of a polyhedron into 3 dimensions
606
607
"""
608
if projection_direction == None:
609
for poly in self.polygons:
610
center = sum([self.coords[i] for i in poly]) / len(poly)
611
print center, "\n"
612
if not center.is_zero():
613
projection_direction = center
614
break
615
if projection_direction == None:
616
from sage.rings.arith import primes_first_n
617
projection_direction = primes_first_n(self.polyhedron_ambient_dim)
618
return self.__call__(ProjectionFuncSchlegel(projection_direction, height = height))
619
620
621
def coord_index_of(self, v):
622
"""
623
Convert a coordinate vector to its internal index.
624
625
EXAMPLES::
626
627
sage: p = polytopes.n_cube(3)
628
sage: proj = p.projection()
629
sage: proj.coord_index_of(vector((1,1,1)))
630
7
631
"""
632
try:
633
return self.coords.index(v)
634
except ValueError:
635
self.coords.append(v)
636
return len(self.coords)-1
637
638
639
def coord_indices_of(self, v_list):
640
"""
641
Convert list of coordinate vectors to the corresponding list
642
of internal indices.
643
644
EXAMPLES::
645
646
sage: p = polytopes.n_cube(3)
647
sage: proj = p.projection()
648
sage: proj.coord_indices_of([vector((1,1,1)),vector((1,-1,1))])
649
[7, 5]
650
"""
651
return [self.coord_index_of(v) for v in v_list]
652
653
654
def coordinates_of(self, coord_index_list):
655
"""
656
Given a list of indices, return the projected coordinates.
657
658
EXAMPLES::
659
660
sage: p = polytopes.n_simplex(4).projection()
661
sage: p.coordinates_of([1])
662
[[0, -81649/100000, 7217/25000, 22361/100000]]
663
"""
664
return [self.transformed_coords[i] for i in coord_index_list]
665
666
667
def _init_dimension(self):
668
"""
669
Internal function: Initialize from 2d polyhedron. Must always
670
be called after a coordinate projection.
671
672
TESTS::
673
674
sage: from sage.geometry.polyhedron.plot import Projection, render_2d
675
sage: p = polytopes.n_simplex(2).projection()
676
sage: test = p._init_dimension()
677
sage: p.show.__doc__ == render_2d.__doc__
678
True
679
"""
680
self.dimension = len(self.transformed_coords[0])
681
682
if self.dimension == 2:
683
self.show = lambda **kwds: render_2d(self,**kwds)
684
self.show.__doc__ = render_2d.__doc__
685
elif self.dimension == 3:
686
self.show = lambda **kwds: render_3d(self,**kwds)
687
self.show.__doc__ = render_3d.__doc__
688
else:
689
try:
690
del self.show
691
except AttributeError:
692
pass
693
694
695
def _init_from_2d(self, polyhedron):
696
"""
697
Internal function: Initialize from 2d polyhedron.
698
699
TESTS::
700
701
sage: p = Polyhedron(vertices = [[0,0],[0,1],[1,0],[1,1]])
702
sage: proj = p.projection()
703
sage: [proj.coordinates_of([i]) for i in proj.points]
704
[[[0, 0]], [[0, 1]], [[1, 0]], [[1, 1]]]
705
sage: proj._init_from_2d
706
<bound method Projection._init_from_2d of The projection
707
of a polyhedron into 2 dimensions>
708
"""
709
assert polyhedron.ambient_dim() == 2, "Requires polyhedron in 2d"
710
self.dimension = 2
711
self._init_points(polyhedron)
712
self._init_lines_arrows(polyhedron)
713
self._init_area_2d(polyhedron)
714
715
716
def _init_from_3d(self, polyhedron):
717
"""
718
Internal function: Initialize from 3d polyhedron.
719
720
TESTS::
721
722
sage: p = Polyhedron(vertices = [[0,0,1],[0,1,2],[1,0,3],[1,1,5]])
723
sage: proj = p.projection()
724
sage: [proj.coordinates_of([i]) for i in proj.points]
725
[[[0, 0, 1]], [[0, 1, 2]], [[1, 0, 3]], [[1, 1, 5]]]
726
sage: proj._init_from_3d
727
<bound method Projection._init_from_3d of The projection
728
of a polyhedron into 3 dimensions>
729
"""
730
assert polyhedron.ambient_dim() == 3, "Requires polyhedron in 3d"
731
self.dimension = 3
732
self._init_points(polyhedron)
733
self._init_lines_arrows(polyhedron)
734
self._init_solid_3d(polyhedron)
735
736
737
def _init_points(self, polyhedron):
738
"""
739
Internal function: Initialize points (works in arbitrary
740
dimensions).
741
742
TESTS::
743
744
sage: p = polytopes.n_cube(2)
745
sage: pp = p.projection()
746
sage: del pp.points
747
sage: pp.points = Sequence([])
748
sage: pp._init_points(p)
749
sage: pp.points
750
[0, 1, 2, 3]
751
"""
752
for v in polyhedron.vertex_generator():
753
self.points.append( self.coord_index_of(v.vector()) )
754
755
756
def _init_lines_arrows(self, polyhedron):
757
"""
758
Internal function: Initialize compact and non-compact edges
759
(works in arbitrary dimensions).
760
761
TESTS::
762
763
sage: p = Polyhedron(ieqs = [[1, 0, 0, 1],[1,1,0,0]])
764
sage: pp = p.projection()
765
sage: pp.arrows
766
[[0, 1], [0, 2]]
767
sage: del pp.arrows
768
sage: pp.arrows = Sequence([])
769
sage: pp._init_lines_arrows(p)
770
sage: pp.arrows
771
[[0, 1], [0, 2]]
772
"""
773
obj = polyhedron.Vrepresentation()
774
for i in range(len(obj)):
775
if not obj[i].is_vertex(): continue
776
for j in range(len(obj)):
777
if polyhedron.vertex_adjacency_matrix()[i,j] == 0: continue
778
if i<j and obj[j].is_vertex():
779
l = [obj[i].vector(), obj[j].vector()]
780
self.lines.append( [ self.coord_index_of(l[0]),
781
self.coord_index_of(l[1]) ] )
782
if obj[j].is_ray():
783
l = [obj[i].vector(), obj[i].vector() + obj[j].vector()]
784
self.arrows.append( [ self.coord_index_of(l[0]),
785
self.coord_index_of(l[1]) ] )
786
if obj[j].is_line():
787
l1 = [obj[i].vector(), obj[i].vector() + obj[j].vector()]
788
l2 = [obj[i].vector(), obj[i].vector() - obj[j].vector()]
789
self.arrows.append( [ self.coord_index_of(l1[0]),
790
self.coord_index_of(l1[1]) ] )
791
self.arrows.append( [ self.coord_index_of(l2[0]),
792
self.coord_index_of(l2[1]) ] )
793
794
795
def _init_area_2d(self, polyhedron):
796
"""
797
Internal function: Initialize polygon area for 2d polyhedron.
798
799
TESTS::
800
801
sage: p = polytopes.cyclic_polytope(2,4)
802
sage: proj = p.projection()
803
sage: proj.polygons = Sequence([])
804
sage: proj._init_area_2d(p)
805
sage: proj.polygons
806
[[3, 0, 1, 2]]
807
"""
808
assert polyhedron.ambient_dim() == 2, "Requires polyhedron in 2d"
809
vertices = [v for v in polyhedron.Vrep_generator()]
810
vertices = cyclic_sort_vertices_2d(vertices)
811
coords = []
812
813
def adjacent_vertices(i):
814
n = len(vertices)
815
if vertices[(i-1) % n].is_vertex(): yield vertices[(i-1) % n]
816
if vertices[(i+1) % n].is_vertex(): yield vertices[(i+1) % n]
817
818
for i in range(len(vertices)):
819
v = vertices[i]
820
if v.is_vertex():
821
coords.append(v())
822
if v.is_ray():
823
for a in adjacent_vertices(i):
824
coords.append(a() + v())
825
826
if polyhedron.n_lines() == 0:
827
self.polygons.append( self.coord_indices_of(coords) )
828
return
829
830
polygons = []
831
832
if polyhedron.n_lines() == 1:
833
aline = polyhedron.line_generator().next()
834
for shift in [aline(), -aline()]:
835
for i in range(len(coords)):
836
polygons.append( [ coords[i-1],coords[i],
837
coords[i]+shift, coords[i-1]+shift ] )
838
839
if polyhedron.n_lines() == 2:
840
[line1, line2] = [l for l in polyhedron.lines()]
841
assert len(coords)==1, "Can have only a single vertex!"
842
v = coords[0]
843
l1 = line1()
844
l2 = line2()
845
polygons = [ [v-l1-l2, v+l1-l2, v+l1+l2, v-l1+l2] ]
846
847
polygons = [ self.coord_indices_of(p) for p in polygons ]
848
self.polygons.extend(polygons)
849
850
851
852
def _init_solid_3d(self, polyhedron):
853
"""
854
Internal function: Initialize facet polygons for 3d polyhedron.
855
856
TESTS::
857
858
sage: p = polytopes.cyclic_polytope(3,4)
859
sage: proj = p.projection()
860
sage: proj.polygons = Sequence([])
861
sage: proj._init_solid_3d(p)
862
sage: proj.polygons
863
[[2, 0, 1], [3, 0, 1], [3, 0, 2], [3, 1, 2]]
864
"""
865
assert polyhedron.ambient_dim() == 3, "Requires polyhedron in 3d"
866
867
if polyhedron.dim() <= 1: # empty or 0d or 1d polyhedron => no polygon
868
return None
869
870
def defining_equation(): # corresponding to a polygon
871
if polyhedron.dim() < 3:
872
yield polyhedron.equation_generator().next()
873
else:
874
for ineq in polyhedron.inequality_generator():
875
yield ineq
876
877
faces = []
878
face_inequalities = []
879
for facet_equation in defining_equation():
880
vertices = [v for v in facet_equation.incident()]
881
face_inequalities.append(facet_equation)
882
vertices = cyclic_sort_vertices_2d(vertices)
883
coords = []
884
885
def adjacent_vertices(i):
886
n = len(vertices)
887
if vertices[(i-1) % n].is_vertex(): yield vertices[(i-1) % n]
888
if vertices[(i+1) % n].is_vertex(): yield vertices[(i+1) % n]
889
890
for i in range(len(vertices)):
891
v = vertices[i]
892
if v.is_vertex():
893
coords.append(v())
894
if v.is_ray():
895
for a in adjacent_vertices(i):
896
coords.append(a() + v())
897
898
faces.append(coords)
899
self.face_inequalities = face_inequalities
900
901
if polyhedron.n_lines() == 0:
902
assert len(faces)>0, "no vertices?"
903
self.polygons.extend( [self.coord_indices_of(f) for f in faces] )
904
return
905
906
# now some special cases if there are lines (dim < ambient_dim)
907
polygons = []
908
909
if polyhedron.n_lines()==1:
910
assert len(faces)>0, "no vertices?"
911
aline = polyhedron.line_generator().next()
912
for shift in [aline(), -aline()]:
913
for coords in faces:
914
assert len(coords)==2, "There must be two points."
915
polygons.append( [ coords[0],coords[1],
916
coords[1]+shift, coords[0]+shift ] )
917
918
if polyhedron.n_lines()==2:
919
[line1, line2] = [l for l in polyhedron.line_generator()]
920
l1 = line1()
921
l2 = line2()
922
for v in polyhedron.vertex_generator():
923
polygons.append( [v()-l1-l2, v()+l1-l2, v()+l1+l2, v()-l1+l2] )
924
925
self.polygons.extend( [self.coord_indices_of(p) for p in polygons] )
926
927
928
def render_points_2d(self, **kwds):
929
"""
930
Return the points of a polyhedron in 2d.
931
932
EXAMPLES::
933
934
sage: hex = polytopes.regular_polygon(6)
935
sage: proj = hex.projection()
936
sage: hex_points = proj.render_points_2d()
937
sage: hex_points._objects
938
[Point set defined by 6 point(s)]
939
"""
940
return point2d(self.coordinates_of(self.points), **kwds)
941
942
943
def render_outline_2d(self, **kwds):
944
"""
945
Return the outline (edges) of a polyhedron in 2d.
946
947
EXAMPLES::
948
949
sage: penta = polytopes.regular_polygon(5)
950
sage: outline = penta.projection().render_outline_2d()
951
sage: outline._objects[0]
952
Line defined by 2 points
953
"""
954
wireframe = [];
955
for l in self.lines:
956
l_coords = self.coordinates_of(l)
957
wireframe.append( line2d(l_coords, **kwds) )
958
for a in self.arrows:
959
a_coords = self.coordinates_of(a)
960
wireframe.append( arrow(a_coords[0], a_coords[1], **kwds) )
961
return sum(wireframe)
962
963
964
def render_fill_2d(self, **kwds):
965
"""
966
Return the filled interior (a polygon) of a polyhedron in 2d.
967
968
EXAMPLES::
969
970
sage: cps = [i^3 for i in srange(-2,2,1/5)]
971
sage: p = Polyhedron(vertices = [[(t^2-1)/(t^2+1),2*t/(t^2+1)] for t in cps])
972
sage: proj = p.projection()
973
sage: filled_poly = proj.render_fill_2d()
974
sage: filled_poly.axes_width()
975
0.8
976
"""
977
poly = [polygon2d(self.coordinates_of(p), **kwds)
978
for p in self.polygons]
979
return sum(poly)
980
981
982
def render_vertices_3d(self, **kwds):
983
"""
984
Return the 3d rendering of the vertices.
985
986
EXAMPLES::
987
988
sage: p = polytopes.cross_polytope(3)
989
sage: proj = p.projection()
990
sage: verts = proj.render_vertices_3d()
991
sage: verts.bounding_box()
992
((-1.0, -1.0, -1.0), (1.0, 1.0, 1.0))
993
"""
994
return point3d(self.coordinates_of(self.points), **kwds)
995
996
997
def render_wireframe_3d(self, **kwds):
998
r"""
999
Return the 3d wireframe rendering.
1000
1001
EXAMPLES::
1002
1003
sage: cube = polytopes.n_cube(3)
1004
sage: cube_proj = cube.projection()
1005
sage: wire = cube_proj.render_wireframe_3d()
1006
sage: print wire.tachyon().split('\n')[77] # for testing
1007
FCylinder base -1.0 1.0 -1.0 apex -1.0 -1.0 -1.0 rad 0.005 texture...
1008
"""
1009
wireframe = [];
1010
for l in self.lines:
1011
l_coords = self.coordinates_of(l)
1012
wireframe.append( line3d(l_coords, **kwds))
1013
for a in self.arrows:
1014
a_coords = self.coordinates_of(a)
1015
wireframe.append(arrow3d(a_coords[0], a_coords[1], **kwds))
1016
return sum(wireframe)
1017
1018
1019
def render_solid_3d(self, **kwds):
1020
"""
1021
Return solid 3d rendering of a 3d polytope.
1022
1023
EXAMPLES::
1024
1025
sage: p = polytopes.n_cube(3).projection()
1026
sage: p_solid = p.render_solid_3d(opacity = .7)
1027
sage: type(p_solid)
1028
<class 'sage.plot.plot3d.base.Graphics3dGroup'>
1029
"""
1030
return sum([ polygon3d(self.coordinates_of(f), **kwds)
1031
for f in self.polygons ])
1032
1033
def tikz(self, view=[0,0,1], angle=0, scale=2,
1034
edge_color='blue!95!black', facet_color='blue!95!black',
1035
opacity=0.8, vertex_color='green', axis=False):
1036
r"""
1037
Return a string ``tikz_pic`` consisting of a tikz picture of ``self``
1038
according to a projection ``view`` and an angle ``angle``
1039
obtained via Jmol through the current state property.
1040
1041
INPUT:
1042
1043
- ``view`` - list (default: [0,0,1]) representing the rotation axis (see note below).
1044
- ``angle`` - integer (default: 0) angle of rotation in degree from 0 to 360 (see note
1045
below).
1046
- ``scale`` - integer (default: 2) specifying the scaling of the tikz picture.
1047
- ``edge_color`` - string (default: 'blue!95!black') representing colors which tikz
1048
recognize.
1049
- ``facet_color`` - string (default: 'blue!95!black') representing colors which tikz
1050
recognize.
1051
- ``vertex_color`` - string (default: 'green') representing colors which tikz
1052
recognize.
1053
- ``opacity`` - real number (default: 0.8) between 0 and 1 giving the opacity of
1054
the front facets.
1055
- ``axis`` - Boolean (default: False) draw the axes at the origin or not.
1056
1057
OUTPUT:
1058
1059
- LatexExpr -- containing the TikZ picture.
1060
1061
.. NOTE::
1062
1063
The inputs ``view`` and ``angle`` can be obtained from the
1064
viewer Jmol::
1065
1066
1) Right click on the image
1067
2) Select ``Console``
1068
3) Select the tab ``State``
1069
4) Scroll to the line ``moveto``
1070
1071
It reads something like::
1072
1073
moveto 0.0 {x y z angle} Scale
1074
1075
The ``view`` is then [x,y,z] and ``angle`` is angle.
1076
The following number is the scale.
1077
1078
Jmol performs a rotation of ``angle`` degrees along the
1079
vector [x,y,z] and show the result from the z-axis.
1080
1081
EXAMPLES::
1082
1083
sage: P1 = polytopes.small_rhombicuboctahedron()
1084
sage: Image1 = P1.projection().tikz([1,3,5], 175, scale=4)
1085
sage: type(Image1)
1086
<class 'sage.misc.latex.LatexExpr'>
1087
sage: print '\n'.join(Image1.splitlines()[:4])
1088
\begin{tikzpicture}%
1089
[x={(-0.939161cm, 0.244762cm)},
1090
y={(0.097442cm, -0.482887cm)},
1091
z={(0.329367cm, 0.840780cm)},
1092
sage: open('polytope-tikz1.tex', 'w').write(Image1) # not tested
1093
1094
sage: P2 = Polyhedron(vertices=[[1, 1],[1, 2],[2, 1]])
1095
sage: Image2 = P2.projection().tikz(scale=3, edge_color='blue!95!black', facet_color='orange!95!black', opacity=0.4, vertex_color='yellow', axis=True)
1096
sage: type(Image2)
1097
<class 'sage.misc.latex.LatexExpr'>
1098
sage: print '\n'.join(Image2.splitlines()[:4])
1099
\begin{tikzpicture}%
1100
[scale=3.000000,
1101
back/.style={loosely dotted, thin},
1102
edge/.style={color=blue!95!black, thick},
1103
sage: open('polytope-tikz2.tex', 'w').write(Image2) # not tested
1104
1105
sage: P3 = Polyhedron(vertices=[[-1, -1, 2],[-1, 2, -1],[2, -1, -1]])
1106
sage: P3
1107
A 2-dimensional polyhedron in ZZ^3 defined as the convex hull of 3 vertices
1108
sage: Image3 = P3.projection().tikz([0.5,-1,-0.1], 55, scale=3, edge_color='blue!95!black',facet_color='orange!95!black', opacity=0.7, vertex_color='yellow', axis=True)
1109
sage: print '\n'.join(Image3.splitlines()[:4])
1110
\begin{tikzpicture}%
1111
[x={(0.658184cm, -0.242192cm)},
1112
y={(-0.096240cm, 0.912008cm)},
1113
z={(-0.746680cm, -0.331036cm)},
1114
sage: open('polytope-tikz3.tex', 'w').write(Image3) # not tested
1115
1116
sage: P=Polyhedron(vertices=[[1,1,0,0],[1,2,0,0],[2,1,0,0],[0,0,1,0],[0,0,0,1]])
1117
sage: P
1118
A 4-dimensional polyhedron in ZZ^4 defined as the convex hull of 5 vertices
1119
sage: P.projection().tikz()
1120
Traceback (most recent call last):
1121
...
1122
NotImplementedError: The polytope has to live in 2 or 3 dimensions.
1123
1124
.. TODO::
1125
1126
Make it possible to draw Schlegel diagram for 4-polytopes. ::
1127
1128
sage: P=Polyhedron(vertices=[[1,1,0,0],[1,2,0,0],[2,1,0,0],[0,0,1,0],[0,0,0,1]])
1129
sage: P
1130
A 4-dimensional polyhedron in ZZ^4 defined as the convex hull of 5 vertices
1131
sage: P.projection().tikz()
1132
Traceback (most recent call last):
1133
...
1134
NotImplementedError: The polytope has to live in 2 or 3 dimensions.
1135
1136
Make it possible to draw 3-polytopes living in higher dimension.
1137
"""
1138
if self.polyhedron_ambient_dim > 3 or self.polyhedron_ambient_dim < 2:
1139
raise NotImplementedError("The polytope has to live in 2 or 3 dimensions.")
1140
elif self.polyhedron_ambient_dim == 2:
1141
return self._tikz_2d(scale, edge_color, facet_color, opacity,
1142
vertex_color, axis)
1143
elif self.dimension == 2:
1144
return self._tikz_2d_in_3d(view, angle, scale, edge_color,
1145
facet_color, opacity, vertex_color, axis)
1146
else:
1147
return self._tikz_3d_in_3d(view, angle, scale, edge_color,
1148
facet_color, opacity, vertex_color, axis)
1149
1150
def _tikz_2d(self, scale, edge_color, facet_color, opacity, vertex_color, axis):
1151
r"""
1152
Return a string ``tikz_pic`` consisting of a tikz picture of ``self``
1153
1154
INPUT:
1155
1156
- ``scale`` - integer specifying the scaling of the tikz picture.
1157
- ``edge_color`` - string representing colors which tikz
1158
recognize.
1159
- ``facet_color`` - string representing colors which tikz
1160
recognize.
1161
- ``vertex_color`` - string representing colors which tikz
1162
recognize.
1163
- ``opacity`` - real number between 0 and 1 giving the opacity of
1164
the front facets.
1165
- ``axis`` - Boolean (default: False) draw the axes at the origin or not.
1166
1167
OUTPUT:
1168
1169
- LatexExpr -- containing the TikZ picture.
1170
1171
EXAMPLES::
1172
1173
sage: P = Polyhedron(vertices=[[1, 1],[1, 2],[2, 1]])
1174
sage: Image = P.projection()._tikz_2d(scale=3, edge_color='black', facet_color='orange', opacity=0.75, vertex_color='yellow', axis=True)
1175
sage: type(Image)
1176
<class 'sage.misc.latex.LatexExpr'>
1177
sage: print '\n'.join(Image.splitlines()[:4])
1178
\begin{tikzpicture}%
1179
[scale=3.000000,
1180
back/.style={loosely dotted, thin},
1181
edge/.style={color=black, thick},
1182
sage: open('polytope-tikz2.tex', 'w').write(Image) # not tested
1183
1184
.. NOTE::
1185
1186
The ``facet_color`` is the filing color of the polytope (polygon).
1187
"""
1188
1189
# Creates the nodes, coordinate and tag for every vertex of the polytope.
1190
# The tag is used to draw the front facets later on.
1191
1192
dict_drawing = {}
1193
edges = ''
1194
for vert in self.points:
1195
v = self.coords[vert]
1196
v_vect = str([i.n(digits=3) for i in v])
1197
v_vect = v_vect.replace('[', '(')
1198
v_vect = v_vect.replace(']', ')')
1199
tag = '%s' %v_vect
1200
node = "\\node[%s] at %s {};\n" % ('vertex', tag)
1201
coord = '\coordinate %s at %s;\n' % (tag, tag)
1202
dict_drawing[vert] = node, coord, tag
1203
1204
for index1, index2 in self.lines:
1205
# v1 = self.coords[index1]
1206
# v2 = self.coords[index2]
1207
edges += "\\draw[%s] %s -- %s;\n" % ('edge',
1208
dict_drawing[index1][2],
1209
dict_drawing[index2][2])
1210
1211
# Start to write the output
1212
tikz_pic = ''
1213
tikz_pic += '\\begin{tikzpicture}%\n'
1214
tikz_pic += '\t[scale=%f,\n' % scale
1215
tikz_pic += '\tback/.style={loosely dotted, thin},\n'
1216
tikz_pic += '\tedge/.style={color=%s, thick},\n' % edge_color
1217
tikz_pic += '\tfacet/.style={fill=%s,fill opacity=%f},\n' % (facet_color,opacity)
1218
tikz_pic += '\tvertex/.style={inner sep=1pt,circle,draw=%s!25!black,' % vertex_color
1219
tikz_pic += 'fill=%s!75!black,thick,anchor=base}]\n%%\n%%\n' % vertex_color
1220
1221
# Draws the axes if True
1222
if axis:
1223
tikz_pic += '\\draw[color=black,thick,->] (0,0,0) -- (1,0,0) node[anchor=north east]{$x$};\n'
1224
tikz_pic += '\\draw[color=black,thick,->] (0,0,0) -- (0,1,0) node[anchor=north west]{$y$};\n'
1225
1226
# Create the coordinate of the vertices:
1227
tikz_pic += '%% Coordinate of the vertices:\n%%\n'
1228
for v in dict_drawing:
1229
tikz_pic += dict_drawing[v][1]
1230
1231
# Draw the interior by going in a cycle
1232
vertices = list(self.parent_polyhedron.Vrep_generator())
1233
tikz_pic += '%%\n%%\n%% Drawing the interior\n%%\n'
1234
cyclic_vert = cyclic_sort_vertices_2d(list(self.parent_polyhedron.Vrep_generator()))
1235
cyclic_indices = [vertices.index(v) for v in cyclic_vert]
1236
tikz_pic += '\\fill[facet] '
1237
for v in cyclic_indices:
1238
if v in dict_drawing:
1239
tikz_pic += '%s -- ' % dict_drawing[v][2]
1240
tikz_pic += 'cycle {};\n'
1241
1242
# Draw the edges
1243
tikz_pic += '%%\n%%\n%% Drawing edges\n%%\n'
1244
tikz_pic += edges
1245
1246
# Finally, the vertices in front are drawn on top of everything.
1247
tikz_pic += '%%\n%%\n%% Drawing the vertices\n%%\n'
1248
for v in dict_drawing:
1249
tikz_pic += dict_drawing[v][0]
1250
tikz_pic += '%%\n%%\n\\end{tikzpicture}'
1251
1252
return LatexExpr(tikz_pic)
1253
1254
def _tikz_2d_in_3d(self, view, angle, scale, edge_color, facet_color,
1255
opacity, vertex_color, axis):
1256
r"""
1257
Return a string ``tikz_pic`` consisting of a tikz picture of ``self``
1258
according to a projection ``view`` and an angle ``angle``
1259
obtained via Jmol through the current state property.
1260
1261
INPUT:
1262
1263
- ``view`` - list (default: [0,0,1]) representing the rotation axis.
1264
- ``angle`` - integer angle of rotation in degree from 0 to 360.
1265
- ``scale`` - integer specifying the scaling of the tikz picture.
1266
- ``edge_color`` - string representing colors which tikz
1267
recognize.
1268
- ``facet_color`` - string representing colors which tikz
1269
recognize.
1270
- ``vertex_color`` - string representing colors which tikz
1271
recognize.
1272
- ``opacity`` - real number between 0 and 1 giving the opacity of
1273
the front facets.
1274
- ``axis`` - Boolean draw the axes at the origin or not.
1275
1276
OUTPUT:
1277
1278
- LatexExpr -- containing the TikZ picture.
1279
1280
EXAMPLE::
1281
1282
sage: P = Polyhedron(vertices=[[-1, -1, 2],[-1, 2, -1],[2, -1, -1]])
1283
sage: P
1284
A 2-dimensional polyhedron in ZZ^3 defined as the convex hull of 3 vertices
1285
sage: Image = P.projection()._tikz_2d_in_3d(view=[0.5,-1,-0.5], angle=55, scale=3, edge_color='blue!95!black', facet_color='orange', opacity=0.5, vertex_color='yellow', axis=True)
1286
sage: print '\n'.join(Image.splitlines()[:4])
1287
\begin{tikzpicture}%
1288
[x={(0.644647cm, -0.476559cm)},
1289
y={(0.192276cm, 0.857859cm)},
1290
z={(-0.739905cm, -0.192276cm)},
1291
sage: open('polytope-tikz3.tex', 'w').write(Image) # not tested
1292
1293
.. NOTE::
1294
1295
The ``facet_color`` is the filing color of the polytope (polygon).
1296
"""
1297
view_vector = vector(RDF, view)
1298
rot = rotate_arbitrary(view_vector,-(angle/360)*2*pi)
1299
rotation_matrix = rot[:2].transpose()
1300
1301
# Creates the nodes, coordinate and tag for every vertex of the polytope.
1302
# The tag is used to draw the front facets later on.
1303
dict_drawing = {}
1304
edges = ''
1305
for vert in self.points:
1306
v = self.coords[vert]
1307
v_vect = str([i.n(digits=3) for i in v])
1308
v_vect = v_vect.replace('[','(')
1309
v_vect = v_vect.replace(']',')')
1310
tag = '%s' %v_vect
1311
node = "\\node[%s] at %s {};\n" % ('vertex', tag)
1312
coord = '\coordinate %s at %s;\n' % (tag, tag)
1313
dict_drawing[vert] = node, coord, tag
1314
1315
for index1, index2 in self.lines:
1316
# v1 = self.coords[index1]
1317
# v2 = self.coords[index2]
1318
edges += "\\draw[%s] %s -- %s;\n" % ('edge',
1319
dict_drawing[index1][2],
1320
dict_drawing[index2][2])
1321
1322
# Start to write the output
1323
tikz_pic = ''
1324
tikz_pic += '\\begin{tikzpicture}%\n'
1325
tikz_pic += '\t[x={(%fcm, %fcm)},\n' % (RDF(rotation_matrix[0][0]),
1326
RDF(rotation_matrix[0][1]))
1327
tikz_pic += '\ty={(%fcm, %fcm)},\n' % (RDF(rotation_matrix[1][0]),
1328
RDF(rotation_matrix[1][1]))
1329
tikz_pic += '\tz={(%fcm, %fcm)},\n' % (RDF(rotation_matrix[2][0]),
1330
RDF(rotation_matrix[2][1]))
1331
tikz_pic += '\tscale=%f,\n' % scale
1332
tikz_pic += '\tback/.style={loosely dotted, thin},\n'
1333
tikz_pic += '\tedge/.style={color=%s, thick},\n' % edge_color
1334
tikz_pic += '\tfacet/.style={fill=%s,fill opacity=%f},\n' % (facet_color,opacity)
1335
tikz_pic += '\tvertex/.style={inner sep=1pt,circle,draw=%s!25!black,' % vertex_color
1336
tikz_pic += 'fill=%s!75!black,thick,anchor=base}]\n%%\n%%\n' % vertex_color
1337
1338
# Draws the axes if True
1339
if axis:
1340
tikz_pic += '\\draw[color=black,thick,->] (0,0,0) -- (1,0,0) node[anchor=north east]{$x$};\n'
1341
tikz_pic += '\\draw[color=black,thick,->] (0,0,0) -- (0,1,0) node[anchor=north west]{$y$};\n'
1342
tikz_pic += '\\draw[color=black,thick,->] (0,0,0) -- (0,0,1) node[anchor=south]{$z$};\n'
1343
1344
# Create the coordinate of the vertices:
1345
tikz_pic += '%% Coordinate of the vertices:\n%%\n'
1346
for v in dict_drawing:
1347
tikz_pic += dict_drawing[v][1]
1348
1349
# Draw the interior by going in a cycle
1350
vertices = list(self.parent_polyhedron.Vrep_generator())
1351
tikz_pic += '%%\n%%\n%% Drawing the interior\n%%\n'
1352
cyclic_vert = cyclic_sort_vertices_2d(list(self.parent_polyhedron.Vrep_generator()))
1353
cyclic_indices = [vertices.index(v) for v in cyclic_vert]
1354
tikz_pic += '\\fill[facet] '
1355
for v in cyclic_indices:
1356
if v in dict_drawing:
1357
tikz_pic += '%s -- ' % dict_drawing[v][2]
1358
tikz_pic += 'cycle {};\n'
1359
1360
# Draw the edges in the front
1361
tikz_pic += '%%\n%%\n%% Drawing edges\n%%\n'
1362
tikz_pic += edges
1363
1364
# Finally, the vertices in front are drawn on top of everything.
1365
tikz_pic += '%%\n%%\n%% Drawing the vertices\n%%\n'
1366
for v in dict_drawing:
1367
tikz_pic += dict_drawing[v][0]
1368
tikz_pic += '%%\n%%\n\\end{tikzpicture}'
1369
1370
return LatexExpr(tikz_pic)
1371
1372
def _tikz_3d_in_3d(self, view, angle, scale, edge_color,
1373
facet_color, opacity, vertex_color, axis):
1374
r"""
1375
Return a string ``tikz_pic`` consisting of a tikz picture of ``self``
1376
according to a projection ``view`` and an angle ``angle``
1377
obtained via Jmol through the current state property.
1378
1379
INPUT:
1380
1381
- ``view`` - list (default: [0,0,1]) representing the rotation axis.
1382
- ``angle`` - integer angle of rotation in degree from 0 to 360.
1383
- ``scale`` - integer specifying the scaling of the tikz picture.
1384
- ``edge_color`` - string representing colors which tikz
1385
recognize.
1386
- ``facet_color`` - string representing colors which tikz
1387
recognize.
1388
- ``vertex_color`` - string representing colors which tikz
1389
recognize.
1390
- ``opacity`` - real number between 0 and 1 giving the opacity of
1391
the front facets.
1392
- ``axis`` - Boolean draw the axes at the origin or not.
1393
1394
OUTPUT:
1395
1396
- LatexExpr -- containing the TikZ picture.
1397
1398
EXAMPLES::
1399
1400
sage: P = polytopes.small_rhombicuboctahedron()
1401
sage: Image = P.projection()._tikz_3d_in_3d([3,7,5], 100, scale=3, edge_color='blue', facet_color='orange', opacity=0.5, vertex_color='green', axis=True)
1402
sage: type(Image)
1403
<class 'sage.misc.latex.LatexExpr'>
1404
sage: print '\n'.join(Image.splitlines()[:4])
1405
\begin{tikzpicture}%
1406
[x={(-0.046385cm, 0.837431cm)},
1407
y={(-0.243536cm, 0.519228cm)},
1408
z={(0.968782cm, 0.170622cm)},
1409
sage: open('polytope-tikz1.tex', 'w').write(Image) # not tested
1410
"""
1411
view_vector = vector(RDF, view)
1412
rot = rotate_arbitrary(view_vector, -(angle/360)*2*pi)
1413
rotation_matrix = rot[:2].transpose()
1414
proj_vector = (rot**(-1))*vector(RDF, [0, 0, 1])
1415
1416
# First compute the back and front vertices and facets
1417
facets = self.face_inequalities
1418
front_facets = []
1419
back_facets = []
1420
for index_facet in range(len(facets)):
1421
A = facets[index_facet].vector()[1:]
1422
B = facets[index_facet].vector()[0]
1423
if A*(2000*proj_vector)+B < 0:
1424
front_facets += [index_facet]
1425
else:
1426
back_facets += [index_facet]
1427
1428
vertices = list(self.parent_polyhedron.Vrep_generator())
1429
front_vertices = []
1430
for index_facet in front_facets:
1431
A = facets[index_facet].vector()[1:]
1432
B = facets[index_facet].vector()[0]
1433
for v in self.points:
1434
if A*self.coords[v]+B < 0.0005 and v not in front_vertices:
1435
front_vertices += [v]
1436
1437
back_vertices = []
1438
for index_facet in back_facets:
1439
A = facets[index_facet].vector()[1:]
1440
B = facets[index_facet].vector()[0]
1441
for v in self.points:
1442
if A*self.coords[v]+B < 0.0005 and v not in back_vertices:
1443
back_vertices += [v]
1444
1445
# Creates the nodes, coordinate and tag for every vertex of the polytope.
1446
# The tag is used to draw the front facets later on.
1447
dict_drawing = {}
1448
back_part = ''
1449
front_part = ''
1450
1451
for vert in self.points:
1452
v = self.coords[vert]
1453
v_vect = str([i.n(digits=3) for i in v])
1454
v_vect = v_vect.replace('[','(')
1455
v_vect = v_vect.replace(']',')')
1456
tag = '%s' %v_vect
1457
node = "\\node[%s] at %s {};\n" % ('vertex', tag)
1458
coord = '\coordinate %s at %s;\n' %(tag, tag)
1459
dict_drawing[vert] = node, coord, tag
1460
1461
# Separate the edges between back and front
1462
1463
for index1, index2 in self.lines:
1464
# v1 = self.coords[index1]
1465
# v2 = self.coords[index2]
1466
1467
H_v1 = set(self.parent_polyhedron.Vrepresentation(index1).incident())
1468
H_v2 = set(self.parent_polyhedron.Vrepresentation(index2).incident())
1469
H_v12 = [h for h in H_v1.intersection(H_v2) if h in back_facets]
1470
1471
# The back edge has to be between two vertices in the Back
1472
# AND such that the 2 facets touching them are in the Back
1473
if index1 in back_vertices and index2 in back_vertices and len(H_v12)==2:
1474
back_part += "\\draw[%s,back] %s -- %s;\n" % ('edge', dict_drawing[index1][2], dict_drawing[index2][2])
1475
else:
1476
front_part += "\\draw[%s] %s -- %s;\n" % ('edge',dict_drawing[index1][2],dict_drawing[index2][2])
1477
1478
# Start to write the output
1479
tikz_pic = ''
1480
tikz_pic += '\\begin{tikzpicture}%\n'
1481
tikz_pic += '\t[x={(%fcm, %fcm)},\n' % (RDF(rotation_matrix[0][0]),
1482
RDF(rotation_matrix[0][1]))
1483
tikz_pic += '\ty={(%fcm, %fcm)},\n' % (RDF(rotation_matrix[1][0]),
1484
RDF(rotation_matrix[1][1]))
1485
tikz_pic += '\tz={(%fcm, %fcm)},\n' % (RDF(rotation_matrix[2][0]),
1486
RDF(rotation_matrix[2][1]))
1487
tikz_pic += '\tscale=%f,\n' % scale
1488
tikz_pic += '\tback/.style={loosely dotted, thin},\n'
1489
tikz_pic += '\tedge/.style={color=%s, thick},\n' % edge_color
1490
tikz_pic += '\tfacet/.style={fill=%s,fill opacity=%f},\n' % (facet_color,opacity)
1491
tikz_pic += '\tvertex/.style={inner sep=1pt,circle,draw=%s!25!black,' % vertex_color
1492
tikz_pic += 'fill=%s!75!black,thick,anchor=base}]\n%%\n%%\n' % vertex_color
1493
1494
# Draws the axes if True
1495
if axis:
1496
tikz_pic += '\\draw[color=black,thick,->] (0,0,0) -- (1,0,0) node[anchor=north east]{$x$};\n'
1497
tikz_pic += '\\draw[color=black,thick,->] (0,0,0) -- (0,1,0) node[anchor=north west]{$y$};\n'
1498
tikz_pic += '\\draw[color=black,thick,->] (0,0,0) -- (0,0,1) node[anchor=south]{$z$};\n'
1499
1500
# Create the coordinate of the vertices
1501
tikz_pic += '%% Coordinate of the vertices:\n%%\n'
1502
for v in dict_drawing:
1503
tikz_pic += dict_drawing[v][1]
1504
1505
# Draw the edges in the back
1506
tikz_pic += '%%\n%%\n%% Drawing edges in the back\n%%\n'
1507
tikz_pic += back_part
1508
1509
# Draw the vertices on top of the back-edges
1510
tikz_pic += '%%\n%%\n%% Drawing vertices in the back\n%%\n'
1511
for v in back_vertices:
1512
if not v in front_vertices and v in dict_drawing:
1513
tikz_pic += dict_drawing[v][0]
1514
1515
# Draw the facets in the front by going in cycles for every facet.
1516
tikz_pic += '%%\n%%\n%% Drawing the facets\n%%\n'
1517
for index_facet in front_facets:
1518
cyclic_vert = cyclic_sort_vertices_2d(list(facets[index_facet].incident()))
1519
cyclic_indices = [vertices.index(v) for v in cyclic_vert]
1520
tikz_pic += '\\fill[facet] '
1521
for v in cyclic_indices:
1522
if v in dict_drawing:
1523
tikz_pic += '%s -- ' % dict_drawing[v][2]
1524
tikz_pic += 'cycle {};\n'
1525
1526
# Draw the edges in the front
1527
tikz_pic += '%%\n%%\n%% Drawing edges in the front\n%%\n'
1528
tikz_pic += front_part
1529
1530
# Finally, the vertices in front are drawn on top of everything.
1531
tikz_pic += '%%\n%%\n%% Drawing the vertices in the front\n%%\n'
1532
for v in self.points:
1533
if v in front_vertices:
1534
if v in dict_drawing:
1535
tikz_pic += dict_drawing[v][0]
1536
tikz_pic += '%%\n%%\n\\end{tikzpicture}'
1537
return LatexExpr(tikz_pic)
1538
1539