Base class for polyhedra: Methods for plotting and affine hull projection¶
- class sage.geometry.polyhedron.base6.Polyhedron_base6(parent, Vrep, Hrep, Vrep_minimal=None, Hrep_minimal=None, pref_rep=None, mutable=False, **kwds)[source]¶
Bases:
Polyhedron_base5
Methods related to plotting including affine hull projection.
- affine_hull(*args, **kwds)[source]¶
Return the affine hull of
self
as a polyhedron.EXAMPLES:
sage: half_plane_in_space = Polyhedron(ieqs=[(0,1,0,0)], eqns=[(0,0,0,1)]) sage: half_plane_in_space.affine_hull().Hrepresentation() (An equation (0, 0, 1) x + 0 == 0,) sage: polytopes.cube().affine_hull().is_universe() True
>>> from sage.all import * >>> half_plane_in_space = Polyhedron(ieqs=[(Integer(0),Integer(1),Integer(0),Integer(0))], eqns=[(Integer(0),Integer(0),Integer(0),Integer(1))]) >>> half_plane_in_space.affine_hull().Hrepresentation() (An equation (0, 0, 1) x + 0 == 0,) >>> polytopes.cube().affine_hull().is_universe() True
half_plane_in_space = Polyhedron(ieqs=[(0,1,0,0)], eqns=[(0,0,0,1)]) half_plane_in_space.affine_hull().Hrepresentation() polytopes.cube().affine_hull().is_universe()
- affine_hull_manifold(name=None, latex_name=None, start_index=0, ambient_space=None, ambient_chart=None, names=None, **kwds)[source]¶
Return the affine hull of
self
as a manifold.If
self
is full-dimensional, it is just the ambient Euclidean space. Otherwise, it is a Riemannian submanifold of the ambient Euclidean space.INPUT:
ambient_space
– aEuclideanSpace
of the ambient dimension (default: the manifold ofambient_chart
, if provided; otherwise, a new instance ofEuclideanSpace
).ambient_chart
– a chart onambient_space
names
– names for the coordinates on the affine hulloptional arguments accepted by
affine_hull_projection()
The default chart is determined by the optional arguments of
affine_hull_projection()
.EXAMPLES:
sage: # needs sage.symbolic sage: triangle = Polyhedron([(1, 0, 0), (0, 1, 0), (0, 0, 1)]); triangle A 2-dimensional polyhedron in ZZ^3 defined as the convex hull of 3 vertices sage: A = triangle.affine_hull_manifold(name='A'); A 2-dimensional Riemannian submanifold A embedded in the Euclidean space E^3 sage: A.embedding().display() A → E^3 (x0, x1) ↦ (x, y, z) = (t0 + x0, t0 + x1, t0 - x0 - x1 + 1) sage: A.embedding().inverse().display() E^3 → A (x, y, z) ↦ (x0, x1) = (x, y) sage: A.adapted_chart() [Chart (E^3, (x0_E3, x1_E3, t0_E3))] sage: A.normal().display() n = 1/3*sqrt(3) e_x + 1/3*sqrt(3) e_y + 1/3*sqrt(3) e_z sage: A.induced_metric() # Need to call this before volume_form Riemannian metric gamma on the 2-dimensional Riemannian submanifold A embedded in the Euclidean space E^3 sage: A.volume_form() 2-form eps_gamma on the 2-dimensional Riemannian submanifold A embedded in the Euclidean space E^3
>>> from sage.all import * >>> # needs sage.symbolic >>> triangle = Polyhedron([(Integer(1), Integer(0), Integer(0)), (Integer(0), Integer(1), Integer(0)), (Integer(0), Integer(0), Integer(1))]); triangle A 2-dimensional polyhedron in ZZ^3 defined as the convex hull of 3 vertices >>> A = triangle.affine_hull_manifold(name='A'); A 2-dimensional Riemannian submanifold A embedded in the Euclidean space E^3 >>> A.embedding().display() A → E^3 (x0, x1) ↦ (x, y, z) = (t0 + x0, t0 + x1, t0 - x0 - x1 + 1) >>> A.embedding().inverse().display() E^3 → A (x, y, z) ↦ (x0, x1) = (x, y) >>> A.adapted_chart() [Chart (E^3, (x0_E3, x1_E3, t0_E3))] >>> A.normal().display() n = 1/3*sqrt(3) e_x + 1/3*sqrt(3) e_y + 1/3*sqrt(3) e_z >>> A.induced_metric() # Need to call this before volume_form Riemannian metric gamma on the 2-dimensional Riemannian submanifold A embedded in the Euclidean space E^3 >>> A.volume_form() 2-form eps_gamma on the 2-dimensional Riemannian submanifold A embedded in the Euclidean space E^3
# needs sage.symbolic triangle = Polyhedron([(1, 0, 0), (0, 1, 0), (0, 0, 1)]); triangle A = triangle.affine_hull_manifold(name='A'); A A.embedding().display() A.embedding().inverse().display() A.adapted_chart() A.normal().display() A.induced_metric() # Need to call this before volume_form A.volume_form()
Orthogonal version:
sage: A = triangle.affine_hull_manifold(name='A', orthogonal=True); A # needs sage.symbolic 2-dimensional Riemannian submanifold A embedded in the Euclidean space E^3 sage: A.embedding().display() # needs sage.symbolic A → E^3 (x0, x1) ↦ (x, y, z) = (t0 - 1/2*x0 - 1/3*x1 + 1, t0 + 1/2*x0 - 1/3*x1, t0 + 2/3*x1) sage: A.embedding().inverse().display() # needs sage.symbolic E^3 → A (x, y, z) ↦ (x0, x1) = (-x + y + 1, -1/2*x - 1/2*y + z + 1/2)
>>> from sage.all import * >>> A = triangle.affine_hull_manifold(name='A', orthogonal=True); A # needs sage.symbolic 2-dimensional Riemannian submanifold A embedded in the Euclidean space E^3 >>> A.embedding().display() # needs sage.symbolic A → E^3 (x0, x1) ↦ (x, y, z) = (t0 - 1/2*x0 - 1/3*x1 + 1, t0 + 1/2*x0 - 1/3*x1, t0 + 2/3*x1) >>> A.embedding().inverse().display() # needs sage.symbolic E^3 → A (x, y, z) ↦ (x0, x1) = (-x + y + 1, -1/2*x - 1/2*y + z + 1/2)
A = triangle.affine_hull_manifold(name='A', orthogonal=True); A # needs sage.symbolic A.embedding().display() # needs sage.symbolic A.embedding().inverse().display() # needs sage.symbolic
Arrangement of affine hull of facets:
sage: # needs sage.rings.number_field sage.symbolic sage: D = polytopes.dodecahedron() sage: E3 = EuclideanSpace(3) sage: submanifolds = [ # long time ....: F.as_polyhedron().affine_hull_manifold(name=f'F{i}', ....: orthogonal=True, ambient_space=E3) ....: for i, F in enumerate(D.facets())] sage: sum(FM.plot({}, # long time, not tested # needs sage.plot ....: srange(-2, 2, 0.1), srange(-2, 2, 0.1), ....: opacity=0.2) ....: for FM in submanifolds) + D.plot() Graphics3d Object
>>> from sage.all import * >>> # needs sage.rings.number_field sage.symbolic >>> D = polytopes.dodecahedron() >>> E3 = EuclideanSpace(Integer(3)) >>> submanifolds = [ # long time ... F.as_polyhedron().affine_hull_manifold(name=f'F{i}', ... orthogonal=True, ambient_space=E3) ... for i, F in enumerate(D.facets())] >>> sum(FM.plot({}, # long time, not tested # needs sage.plot ... srange(-Integer(2), Integer(2), RealNumber('0.1')), srange(-Integer(2), Integer(2), RealNumber('0.1')), ... opacity=RealNumber('0.2')) ... for FM in submanifolds) + D.plot() Graphics3d Object
# needs sage.rings.number_field sage.symbolic D = polytopes.dodecahedron() E3 = EuclideanSpace(3) submanifolds = [ # long time F.as_polyhedron().affine_hull_manifold(name=f'F{i}', orthogonal=True, ambient_space=E3) for i, F in enumerate(D.facets())] sum(FM.plot({}, # long time, not tested # needs sage.plot srange(-2, 2, 0.1), srange(-2, 2, 0.1), opacity=0.2) for FM in submanifolds) + D.plot()
Full-dimensional case:
sage: cube = polytopes.cube(); cube A 3-dimensional polyhedron in ZZ^3 defined as the convex hull of 8 vertices sage: cube.affine_hull_manifold() # needs sage.symbolic Euclidean space E^3
>>> from sage.all import * >>> cube = polytopes.cube(); cube A 3-dimensional polyhedron in ZZ^3 defined as the convex hull of 8 vertices >>> cube.affine_hull_manifold() # needs sage.symbolic Euclidean space E^3
cube = polytopes.cube(); cube cube.affine_hull_manifold() # needs sage.symbolic
- affine_hull_projection(as_polyhedron, as_affine_map=None, orthogonal=False, orthonormal=False, extend=False, minimal=False, return_all_data=False, as_convex_set=False)[source]¶
Return the polyhedron projected into its affine hull.
Each polyhedron is contained in some smallest affine subspace (possibly the entire ambient space) – its affine hull. We provide an affine linear map that projects the ambient space of the polyhedron to the standard Euclidean space of dimension of the polyhedron, which restricts to a bijection from the affine hull.
The projection map is not unique; some parameters control the choice of the map. Other parameters control the output of the function.
INPUT:
as_polyhedron
,as_convex_set
– boolean or the defaultNone
; one of the two to be setas_affine_map
– boolean (default:False
); control the outputThe default
as_polyhedron=None
translates toas_polyhedron=not as_affine_map
, therefore toas_polyhedron=True
if nothing is specified.If exactly one of either
as_polyhedron
oras_affine_map
is set, then either a polyhedron or the affine transformation is returned. The affine transformation sends the embedded polytope to a fulldimensional one. It is given as a pair(A, b)
, where A is a linear transformation and \(b\) is a vector, and the affine transformation sendsv
toA(v)+b
.If both
as_polyhedron
andas_affine_map
are set, then both are returned, encapsulated in an instance ofAffineHullProjectionData
.return_all_data
– boolean (default:False
)If set, then
as_polyhedron
andas_affine_map
will set (possibly overridden) and additional (internal) data concerning the transformation is returned. Everything is encapsulated in an instance ofAffineHullProjectionData
in this case.orthogonal
– boolean (default:False
); ifTrue
, provide an orthogonal transformationorthonormal
– boolean (default:False
); ifTrue
, provide an orthonormal transformation. If the base ring does not provide the necessary square roots, the extend parameter needs to be set toTrue
.extend
– boolean (default:False
); ifTrue
, allow base ring to be extended if necessary. This becomes relevant when requiring an orthonormal transformation.minimal
– boolean (default:False
); ifTrue
, when doing an extension, it computes the minimal base ring of the extension, otherwise the base ring isAA
.
OUTPUT:
A full-dimensional polyhedron or an affine transformation, depending on the parameters
as_polyhedron
andas_affine_map
, or an instance ofAffineHullProjectionData
containing all data (parameterreturn_all_data
).If the output is an instance of
AffineHullProjectionData
, the following fields may be set:image
– the projection of the original polyhedronprojection_map
– the affine map as a pair whose first component is a linear transformation and its second component a shift; see above.section_map
– an affine map as a pair whose first component is a linear transformation and its second component a shift. It maps the codomain ofaffine_map
to the affine hull ofself
. It is a right inverse ofprojection_map
.
Note that all of these data are compatible.
Todo
make the parameters
orthogonal
andorthonormal
work with unbounded polyhedra.
EXAMPLES:
sage: triangle = Polyhedron([(1,0,0), (0,1,0), (0,0,1)]); triangle A 2-dimensional polyhedron in ZZ^3 defined as the convex hull of 3 vertices sage: triangle.affine_hull_projection() A 2-dimensional polyhedron in ZZ^2 defined as the convex hull of 3 vertices sage: half3d = Polyhedron(vertices=[(3,2,1)], rays=[(1,0,0)]) sage: half3d.affine_hull_projection().Vrepresentation() (A ray in the direction (1), A vertex at (3))
>>> from sage.all import * >>> triangle = Polyhedron([(Integer(1),Integer(0),Integer(0)), (Integer(0),Integer(1),Integer(0)), (Integer(0),Integer(0),Integer(1))]); triangle A 2-dimensional polyhedron in ZZ^3 defined as the convex hull of 3 vertices >>> triangle.affine_hull_projection() A 2-dimensional polyhedron in ZZ^2 defined as the convex hull of 3 vertices >>> half3d = Polyhedron(vertices=[(Integer(3),Integer(2),Integer(1))], rays=[(Integer(1),Integer(0),Integer(0))]) >>> half3d.affine_hull_projection().Vrepresentation() (A ray in the direction (1), A vertex at (3))
triangle = Polyhedron([(1,0,0), (0,1,0), (0,0,1)]); triangle triangle.affine_hull_projection() half3d = Polyhedron(vertices=[(3,2,1)], rays=[(1,0,0)]) half3d.affine_hull_projection().Vrepresentation()
The resulting affine hulls depend on the parameter
orthogonal
andorthonormal
:sage: L = Polyhedron([[1,0], [0,1]]); L A 1-dimensional polyhedron in ZZ^2 defined as the convex hull of 2 vertices sage: A = L.affine_hull_projection(); A A 1-dimensional polyhedron in ZZ^1 defined as the convex hull of 2 vertices sage: A.vertices() (A vertex at (0), A vertex at (1)) sage: A = L.affine_hull_projection(orthogonal=True); A A 1-dimensional polyhedron in QQ^1 defined as the convex hull of 2 vertices sage: A.vertices() (A vertex at (0), A vertex at (2)) sage: A = L.affine_hull_projection(orthonormal=True) # needs sage.rings.number_field Traceback (most recent call last): ... ValueError: the base ring needs to be extended; try with "extend=True" sage: A = L.affine_hull_projection(orthonormal=True, extend=True); A # needs sage.rings.number_field A 1-dimensional polyhedron in AA^1 defined as the convex hull of 2 vertices sage: A.vertices() # needs sage.rings.number_field (A vertex at (1.414213562373095?), A vertex at (0.?e-18))
>>> from sage.all import * >>> L = Polyhedron([[Integer(1),Integer(0)], [Integer(0),Integer(1)]]); L A 1-dimensional polyhedron in ZZ^2 defined as the convex hull of 2 vertices >>> A = L.affine_hull_projection(); A A 1-dimensional polyhedron in ZZ^1 defined as the convex hull of 2 vertices >>> A.vertices() (A vertex at (0), A vertex at (1)) >>> A = L.affine_hull_projection(orthogonal=True); A A 1-dimensional polyhedron in QQ^1 defined as the convex hull of 2 vertices >>> A.vertices() (A vertex at (0), A vertex at (2)) >>> A = L.affine_hull_projection(orthonormal=True) # needs sage.rings.number_field Traceback (most recent call last): ... ValueError: the base ring needs to be extended; try with "extend=True" >>> A = L.affine_hull_projection(orthonormal=True, extend=True); A # needs sage.rings.number_field A 1-dimensional polyhedron in AA^1 defined as the convex hull of 2 vertices >>> A.vertices() # needs sage.rings.number_field (A vertex at (1.414213562373095?), A vertex at (0.?e-18))
L = Polyhedron([[1,0], [0,1]]); L A = L.affine_hull_projection(); A A.vertices() A = L.affine_hull_projection(orthogonal=True); A A.vertices() A = L.affine_hull_projection(orthonormal=True) # needs sage.rings.number_field A = L.affine_hull_projection(orthonormal=True, extend=True); A # needs sage.rings.number_field A.vertices() # needs sage.rings.number_field
More generally:
sage: S = polytopes.simplex(); S A 3-dimensional polyhedron in ZZ^4 defined as the convex hull of 4 vertices sage: S.vertices() (A vertex at (0, 0, 0, 1), A vertex at (0, 0, 1, 0), A vertex at (0, 1, 0, 0), A vertex at (1, 0, 0, 0)) sage: A = S.affine_hull_projection(); A A 3-dimensional polyhedron in ZZ^3 defined as the convex hull of 4 vertices sage: A.vertices() (A vertex at (0, 0, 0), A vertex at (0, 0, 1), A vertex at (0, 1, 0), A vertex at (1, 0, 0)) sage: A = S.affine_hull_projection(orthogonal=True); A A 3-dimensional polyhedron in QQ^3 defined as the convex hull of 4 vertices sage: A.vertices() (A vertex at (0, 0, 0), A vertex at (2, 0, 0), A vertex at (1, 3/2, 0), A vertex at (1, 1/2, 4/3)) sage: A = S.affine_hull_projection(orthonormal=True, extend=True); A # needs sage.rings.number_field A 3-dimensional polyhedron in AA^3 defined as the convex hull of 4 vertices sage: A.vertices() # needs sage.rings.number_field (A vertex at (0.7071067811865475?, 0.4082482904638630?, 1.154700538379252?), A vertex at (0.7071067811865475?, 1.224744871391589?, 0.?e-18), A vertex at (1.414213562373095?, 0.?e-18, 0.?e-18), A vertex at (0.?e-18, 0.?e-18, 0.?e-18))
>>> from sage.all import * >>> S = polytopes.simplex(); S A 3-dimensional polyhedron in ZZ^4 defined as the convex hull of 4 vertices >>> S.vertices() (A vertex at (0, 0, 0, 1), A vertex at (0, 0, 1, 0), A vertex at (0, 1, 0, 0), A vertex at (1, 0, 0, 0)) >>> A = S.affine_hull_projection(); A A 3-dimensional polyhedron in ZZ^3 defined as the convex hull of 4 vertices >>> A.vertices() (A vertex at (0, 0, 0), A vertex at (0, 0, 1), A vertex at (0, 1, 0), A vertex at (1, 0, 0)) >>> A = S.affine_hull_projection(orthogonal=True); A A 3-dimensional polyhedron in QQ^3 defined as the convex hull of 4 vertices >>> A.vertices() (A vertex at (0, 0, 0), A vertex at (2, 0, 0), A vertex at (1, 3/2, 0), A vertex at (1, 1/2, 4/3)) >>> A = S.affine_hull_projection(orthonormal=True, extend=True); A # needs sage.rings.number_field A 3-dimensional polyhedron in AA^3 defined as the convex hull of 4 vertices >>> A.vertices() # needs sage.rings.number_field (A vertex at (0.7071067811865475?, 0.4082482904638630?, 1.154700538379252?), A vertex at (0.7071067811865475?, 1.224744871391589?, 0.?e-18), A vertex at (1.414213562373095?, 0.?e-18, 0.?e-18), A vertex at (0.?e-18, 0.?e-18, 0.?e-18))
S = polytopes.simplex(); S S.vertices() A = S.affine_hull_projection(); A A.vertices() A = S.affine_hull_projection(orthogonal=True); A A.vertices() A = S.affine_hull_projection(orthonormal=True, extend=True); A # needs sage.rings.number_field A.vertices() # needs sage.rings.number_field
With the parameter
minimal
one can get a minimal base ring:sage: # needs sage.rings.number_field sage: s = polytopes.simplex(3) sage: s_AA = s.affine_hull_projection(orthonormal=True, extend=True) sage: s_AA.base_ring() Algebraic Real Field sage: s_full = s.affine_hull_projection(orthonormal=True, extend=True, ....: minimal=True) sage: s_full.base_ring() Number Field in a with defining polynomial y^4 - 4*y^2 + 1 with a = 0.5176380902050415?
>>> from sage.all import * >>> # needs sage.rings.number_field >>> s = polytopes.simplex(Integer(3)) >>> s_AA = s.affine_hull_projection(orthonormal=True, extend=True) >>> s_AA.base_ring() Algebraic Real Field >>> s_full = s.affine_hull_projection(orthonormal=True, extend=True, ... minimal=True) >>> s_full.base_ring() Number Field in a with defining polynomial y^4 - 4*y^2 + 1 with a = 0.5176380902050415?
# needs sage.rings.number_field s = polytopes.simplex(3) s_AA = s.affine_hull_projection(orthonormal=True, extend=True) s_AA.base_ring() s_full = s.affine_hull_projection(orthonormal=True, extend=True, minimal=True) s_full.base_ring()
More examples with the
orthonormal
parameter:sage: P = polytopes.permutahedron(3); P A 2-dimensional polyhedron in ZZ^3 defined as the convex hull of 6 vertices sage: set([F.as_polyhedron().affine_hull_projection( # needs sage.combinat sage.rings.number_field ....: orthonormal=True, extend=True).volume() ....: for F in P.affine_hull_projection().faces(1)]) == {1, sqrt(AA(2))} True sage: set([F.as_polyhedron().affine_hull_projection( # needs sage.combinat sage.rings.number_field ....: orthonormal=True, extend=True).volume() ....: for F in P.affine_hull_projection( ....: orthonormal=True, extend=True).faces(1)]) == {sqrt(AA(2))} True sage: # needs sage.rings.number_field sage: D = polytopes.dodecahedron() sage: F = D.faces(2)[0].as_polyhedron() sage: F.affine_hull_projection(orthogonal=True) A 2-dimensional polyhedron in (Number Field in sqrt5 with defining polynomial x^2 - 5 with sqrt5 = 2.236067977499790?)^2 defined as the convex hull of 5 vertices sage: F.affine_hull_projection(orthonormal=True, extend=True) A 2-dimensional polyhedron in AA^2 defined as the convex hull of 5 vertices sage: # needs sage.rings.number_field sage: K.<sqrt2> = QuadraticField(2) sage: P = Polyhedron([2*[K.zero()],2*[sqrt2]]); P A 1-dimensional polyhedron in (Number Field in sqrt2 with defining polynomial x^2 - 2 with sqrt2 = 1.414213562373095?)^2 defined as the convex hull of 2 vertices sage: P.vertices() (A vertex at (0, 0), A vertex at (sqrt2, sqrt2)) sage: A = P.affine_hull_projection(orthonormal=True); A A 1-dimensional polyhedron in (Number Field in sqrt2 with defining polynomial x^2 - 2 with sqrt2 = 1.414213562373095?)^1 defined as the convex hull of 2 vertices sage: A.vertices() (A vertex at (0), A vertex at (2)) sage: # needs sage.rings.number_field sage: K.<sqrt3> = QuadraticField(3) sage: P = Polyhedron([2*[K.zero()], 2*[sqrt3]]); P A 1-dimensional polyhedron in (Number Field in sqrt3 with defining polynomial x^2 - 3 with sqrt3 = 1.732050807568878?)^2 defined as the convex hull of 2 vertices sage: P.vertices() (A vertex at (0, 0), A vertex at (sqrt3, sqrt3)) sage: A = P.affine_hull_projection(orthonormal=True) Traceback (most recent call last): ... ValueError: the base ring needs to be extended; try with "extend=True" sage: A = P.affine_hull_projection(orthonormal=True, extend=True); A A 1-dimensional polyhedron in AA^1 defined as the convex hull of 2 vertices sage: A.vertices() (A vertex at (0), A vertex at (2.449489742783178?)) sage: sqrt(6).n() 2.44948974278318
>>> from sage.all import * >>> P = polytopes.permutahedron(Integer(3)); P A 2-dimensional polyhedron in ZZ^3 defined as the convex hull of 6 vertices >>> set([F.as_polyhedron().affine_hull_projection( # needs sage.combinat sage.rings.number_field ... orthonormal=True, extend=True).volume() ... for F in P.affine_hull_projection().faces(Integer(1))]) == {Integer(1), sqrt(AA(Integer(2)))} True >>> set([F.as_polyhedron().affine_hull_projection( # needs sage.combinat sage.rings.number_field ... orthonormal=True, extend=True).volume() ... for F in P.affine_hull_projection( ... orthonormal=True, extend=True).faces(Integer(1))]) == {sqrt(AA(Integer(2)))} True >>> # needs sage.rings.number_field >>> D = polytopes.dodecahedron() >>> F = D.faces(Integer(2))[Integer(0)].as_polyhedron() >>> F.affine_hull_projection(orthogonal=True) A 2-dimensional polyhedron in (Number Field in sqrt5 with defining polynomial x^2 - 5 with sqrt5 = 2.236067977499790?)^2 defined as the convex hull of 5 vertices >>> F.affine_hull_projection(orthonormal=True, extend=True) A 2-dimensional polyhedron in AA^2 defined as the convex hull of 5 vertices >>> # needs sage.rings.number_field >>> K = QuadraticField(Integer(2), names=('sqrt2',)); (sqrt2,) = K._first_ngens(1) >>> P = Polyhedron([Integer(2)*[K.zero()],Integer(2)*[sqrt2]]); P A 1-dimensional polyhedron in (Number Field in sqrt2 with defining polynomial x^2 - 2 with sqrt2 = 1.414213562373095?)^2 defined as the convex hull of 2 vertices >>> P.vertices() (A vertex at (0, 0), A vertex at (sqrt2, sqrt2)) >>> A = P.affine_hull_projection(orthonormal=True); A A 1-dimensional polyhedron in (Number Field in sqrt2 with defining polynomial x^2 - 2 with sqrt2 = 1.414213562373095?)^1 defined as the convex hull of 2 vertices >>> A.vertices() (A vertex at (0), A vertex at (2)) >>> # needs sage.rings.number_field >>> K = QuadraticField(Integer(3), names=('sqrt3',)); (sqrt3,) = K._first_ngens(1) >>> P = Polyhedron([Integer(2)*[K.zero()], Integer(2)*[sqrt3]]); P A 1-dimensional polyhedron in (Number Field in sqrt3 with defining polynomial x^2 - 3 with sqrt3 = 1.732050807568878?)^2 defined as the convex hull of 2 vertices >>> P.vertices() (A vertex at (0, 0), A vertex at (sqrt3, sqrt3)) >>> A = P.affine_hull_projection(orthonormal=True) Traceback (most recent call last): ... ValueError: the base ring needs to be extended; try with "extend=True" >>> A = P.affine_hull_projection(orthonormal=True, extend=True); A A 1-dimensional polyhedron in AA^1 defined as the convex hull of 2 vertices >>> A.vertices() (A vertex at (0), A vertex at (2.449489742783178?)) >>> sqrt(Integer(6)).n() 2.44948974278318
P = polytopes.permutahedron(3); P set([F.as_polyhedron().affine_hull_projection( # needs sage.combinat sage.rings.number_field orthonormal=True, extend=True).volume() for F in P.affine_hull_projection().faces(1)]) == {1, sqrt(AA(2))} set([F.as_polyhedron().affine_hull_projection( # needs sage.combinat sage.rings.number_field orthonormal=True, extend=True).volume() for F in P.affine_hull_projection( orthonormal=True, extend=True).faces(1)]) == {sqrt(AA(2))} # needs sage.rings.number_field D = polytopes.dodecahedron() F = D.faces(2)[0].as_polyhedron() F.affine_hull_projection(orthogonal=True) F.affine_hull_projection(orthonormal=True, extend=True) # needs sage.rings.number_field K.<sqrt2> = QuadraticField(2) P = Polyhedron([2*[K.zero()],2*[sqrt2]]); P P.vertices() A = P.affine_hull_projection(orthonormal=True); A A.vertices() # needs sage.rings.number_field K.<sqrt3> = QuadraticField(3) P = Polyhedron([2*[K.zero()], 2*[sqrt3]]); P P.vertices() A = P.affine_hull_projection(orthonormal=True) A = P.affine_hull_projection(orthonormal=True, extend=True); A A.vertices() sqrt(6).n()
The affine hull is combinatorially equivalent to the input:
sage: P.is_combinatorially_isomorphic(P.affine_hull_projection()) # needs sage.rings.number_field True sage: P.is_combinatorially_isomorphic(P.affine_hull_projection( # needs sage.rings.number_field ....: orthogonal=True)) True sage: P.is_combinatorially_isomorphic(P.affine_hull_projection( # needs sage.rings.number_field ....: orthonormal=True, extend=True)) True
>>> from sage.all import * >>> P.is_combinatorially_isomorphic(P.affine_hull_projection()) # needs sage.rings.number_field True >>> P.is_combinatorially_isomorphic(P.affine_hull_projection( # needs sage.rings.number_field ... orthogonal=True)) True >>> P.is_combinatorially_isomorphic(P.affine_hull_projection( # needs sage.rings.number_field ... orthonormal=True, extend=True)) True
P.is_combinatorially_isomorphic(P.affine_hull_projection()) # needs sage.rings.number_field P.is_combinatorially_isomorphic(P.affine_hull_projection( # needs sage.rings.number_field orthogonal=True)) P.is_combinatorially_isomorphic(P.affine_hull_projection( # needs sage.rings.number_field orthonormal=True, extend=True))
The
orthonormal=True
parameter preserves volumes; it provides an isometric copy of the polyhedron:sage: # needs sage.rings.number_field sage: Pentagon = polytopes.dodecahedron().faces(2)[0].as_polyhedron() sage: P = Pentagon.affine_hull_projection(orthonormal=True, extend=True) sage: _, c= P.is_inscribed(certificate=True) sage: c (0.4721359549995794?, 0.6498393924658126?) sage: circumradius = (c - vector(P.vertices()[0])).norm() sage: p = polytopes.regular_polygon(5) sage: p.volume() 2.377641290737884? sage: P.volume() 1.53406271079097? sage: p.volume()*circumradius^2 1.534062710790965? sage: P.volume() == p.volume()*circumradius^2 True
>>> from sage.all import * >>> # needs sage.rings.number_field >>> Pentagon = polytopes.dodecahedron().faces(Integer(2))[Integer(0)].as_polyhedron() >>> P = Pentagon.affine_hull_projection(orthonormal=True, extend=True) >>> _, c= P.is_inscribed(certificate=True) >>> c (0.4721359549995794?, 0.6498393924658126?) >>> circumradius = (c - vector(P.vertices()[Integer(0)])).norm() >>> p = polytopes.regular_polygon(Integer(5)) >>> p.volume() 2.377641290737884? >>> P.volume() 1.53406271079097? >>> p.volume()*circumradius**Integer(2) 1.534062710790965? >>> P.volume() == p.volume()*circumradius**Integer(2) True
# needs sage.rings.number_field Pentagon = polytopes.dodecahedron().faces(2)[0].as_polyhedron() P = Pentagon.affine_hull_projection(orthonormal=True, extend=True) _, c= P.is_inscribed(certificate=True) c circumradius = (c - vector(P.vertices()[0])).norm() p = polytopes.regular_polygon(5) p.volume() P.volume() p.volume()*circumradius^2 P.volume() == p.volume()*circumradius^2
One can also use
orthogonal
parameter to calculate volumes; in this case we don’t need to switch base rings. One has to divide by the square root of the determinant of the linear part of the affine transformation times its transpose:sage: # needs sage.rings.number_field sage: Pentagon = polytopes.dodecahedron().faces(2)[0].as_polyhedron() sage: Pnormal = Pentagon.affine_hull_projection(orthonormal=True, ....: extend=True) sage: Pgonal = Pentagon.affine_hull_projection(orthogonal=True) sage: A, b = Pentagon.affine_hull_projection(orthogonal=True, ....: as_affine_map=True) sage: Adet = (A.matrix().transpose()*A.matrix()).det() sage: Pnormal.volume() 1.53406271079097? sage: Pgonal.volume()/Adet.sqrt(extend=True) -80*(55*sqrt(5) - 123)/sqrt(-6368*sqrt(5) + 14240) sage: Pgonal.volume()/AA(Adet).sqrt().n(digits=20) 1.5340627107909646813 sage: AA(Pgonal.volume()^2) == (Pnormal.volume()^2)*AA(Adet) True
>>> from sage.all import * >>> # needs sage.rings.number_field >>> Pentagon = polytopes.dodecahedron().faces(Integer(2))[Integer(0)].as_polyhedron() >>> Pnormal = Pentagon.affine_hull_projection(orthonormal=True, ... extend=True) >>> Pgonal = Pentagon.affine_hull_projection(orthogonal=True) >>> A, b = Pentagon.affine_hull_projection(orthogonal=True, ... as_affine_map=True) >>> Adet = (A.matrix().transpose()*A.matrix()).det() >>> Pnormal.volume() 1.53406271079097? >>> Pgonal.volume()/Adet.sqrt(extend=True) -80*(55*sqrt(5) - 123)/sqrt(-6368*sqrt(5) + 14240) >>> Pgonal.volume()/AA(Adet).sqrt().n(digits=Integer(20)) 1.5340627107909646813 >>> AA(Pgonal.volume()**Integer(2)) == (Pnormal.volume()**Integer(2))*AA(Adet) True
# needs sage.rings.number_field Pentagon = polytopes.dodecahedron().faces(2)[0].as_polyhedron() Pnormal = Pentagon.affine_hull_projection(orthonormal=True, extend=True) Pgonal = Pentagon.affine_hull_projection(orthogonal=True) A, b = Pentagon.affine_hull_projection(orthogonal=True, as_affine_map=True) Adet = (A.matrix().transpose()*A.matrix()).det() Pnormal.volume() Pgonal.volume()/Adet.sqrt(extend=True) Pgonal.volume()/AA(Adet).sqrt().n(digits=20) AA(Pgonal.volume()^2) == (Pnormal.volume()^2)*AA(Adet)
Another example with
as_affine_map=True
:sage: # needs sage.combinat sage.rings.number_field sage: P = polytopes.permutahedron(4) sage: Q = P.affine_hull_projection(orthonormal=True, extend=True) sage: A, b = P.affine_hull_projection(orthonormal=True, extend=True, ....: as_affine_map=True) sage: Q.center() (0.7071067811865475?, 1.224744871391589?, 1.732050807568878?) sage: A(P.center()) + b == Q.center() True
>>> from sage.all import * >>> # needs sage.combinat sage.rings.number_field >>> P = polytopes.permutahedron(Integer(4)) >>> Q = P.affine_hull_projection(orthonormal=True, extend=True) >>> A, b = P.affine_hull_projection(orthonormal=True, extend=True, ... as_affine_map=True) >>> Q.center() (0.7071067811865475?, 1.224744871391589?, 1.732050807568878?) >>> A(P.center()) + b == Q.center() True
# needs sage.combinat sage.rings.number_field P = polytopes.permutahedron(4) Q = P.affine_hull_projection(orthonormal=True, extend=True) A, b = P.affine_hull_projection(orthonormal=True, extend=True, as_affine_map=True) Q.center() A(P.center()) + b == Q.center()
For unbounded, non full-dimensional polyhedra, the
orthogonal=True
andorthonormal=True
is not implemented:sage: P = Polyhedron(ieqs=[[0, 1, 0], [0, 0, 1], [0, 0, -1]]); P A 1-dimensional polyhedron in QQ^2 defined as the convex hull of 1 vertex and 1 ray sage: P.is_compact() False sage: P.is_full_dimensional() False sage: P.affine_hull_projection(orthogonal=True) Traceback (most recent call last): ... NotImplementedError: "orthogonal=True" and "orthonormal=True" work only for compact polyhedra sage: P.affine_hull_projection(orthonormal=True) Traceback (most recent call last): ... NotImplementedError: "orthogonal=True" and "orthonormal=True" work only for compact polyhedra
>>> from sage.all import * >>> P = Polyhedron(ieqs=[[Integer(0), Integer(1), Integer(0)], [Integer(0), Integer(0), Integer(1)], [Integer(0), Integer(0), -Integer(1)]]); P A 1-dimensional polyhedron in QQ^2 defined as the convex hull of 1 vertex and 1 ray >>> P.is_compact() False >>> P.is_full_dimensional() False >>> P.affine_hull_projection(orthogonal=True) Traceback (most recent call last): ... NotImplementedError: "orthogonal=True" and "orthonormal=True" work only for compact polyhedra >>> P.affine_hull_projection(orthonormal=True) Traceback (most recent call last): ... NotImplementedError: "orthogonal=True" and "orthonormal=True" work only for compact polyhedra
P = Polyhedron(ieqs=[[0, 1, 0], [0, 0, 1], [0, 0, -1]]); P P.is_compact() P.is_full_dimensional() P.affine_hull_projection(orthogonal=True) P.affine_hull_projection(orthonormal=True)
Setting
as_affine_map
toTrue
withoutorthogonal
ororthonormal
set toTrue
:sage: S = polytopes.simplex() sage: S.affine_hull_projection(as_affine_map=True) (Vector space morphism represented by the matrix: [1 0 0] [0 1 0] [0 0 1] [0 0 0] Domain: Vector space of dimension 4 over Rational Field Codomain: Vector space of dimension 3 over Rational Field, (0, 0, 0))
>>> from sage.all import * >>> S = polytopes.simplex() >>> S.affine_hull_projection(as_affine_map=True) (Vector space morphism represented by the matrix: [1 0 0] [0 1 0] [0 0 1] [0 0 0] Domain: Vector space of dimension 4 over Rational Field Codomain: Vector space of dimension 3 over Rational Field, (0, 0, 0))
S = polytopes.simplex() S.affine_hull_projection(as_affine_map=True)
If the polyhedron is full-dimensional, it is returned:
sage: polytopes.cube().affine_hull_projection() A 3-dimensional polyhedron in ZZ^3 defined as the convex hull of 8 vertices sage: polytopes.cube().affine_hull_projection(as_affine_map=True) (Vector space morphism represented by the matrix: [1 0 0] [0 1 0] [0 0 1] Domain: Vector space of dimension 3 over Rational Field Codomain: Vector space of dimension 3 over Rational Field, (0, 0, 0))
>>> from sage.all import * >>> polytopes.cube().affine_hull_projection() A 3-dimensional polyhedron in ZZ^3 defined as the convex hull of 8 vertices >>> polytopes.cube().affine_hull_projection(as_affine_map=True) (Vector space morphism represented by the matrix: [1 0 0] [0 1 0] [0 0 1] Domain: Vector space of dimension 3 over Rational Field Codomain: Vector space of dimension 3 over Rational Field, (0, 0, 0))
polytopes.cube().affine_hull_projection() polytopes.cube().affine_hull_projection(as_affine_map=True)
Return polyhedron and affine map:
sage: S = polytopes.simplex(2) sage: data = S.affine_hull_projection(orthogonal=True, ....: as_polyhedron=True, ....: as_affine_map=True); data AffineHullProjectionData(image=A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 3 vertices, projection_linear_map=Vector space morphism represented by the matrix: [ -1 -1/2] [ 1 -1/2] [ 0 1] Domain: Vector space of dimension 3 over Rational Field Codomain: Vector space of dimension 2 over Rational Field, projection_translation=(1, 1/2), section_linear_map=None, section_translation=None)
>>> from sage.all import * >>> S = polytopes.simplex(Integer(2)) >>> data = S.affine_hull_projection(orthogonal=True, ... as_polyhedron=True, ... as_affine_map=True); data AffineHullProjectionData(image=A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 3 vertices, projection_linear_map=Vector space morphism represented by the matrix: [ -1 -1/2] [ 1 -1/2] [ 0 1] Domain: Vector space of dimension 3 over Rational Field Codomain: Vector space of dimension 2 over Rational Field, projection_translation=(1, 1/2), section_linear_map=None, section_translation=None)
S = polytopes.simplex(2) data = S.affine_hull_projection(orthogonal=True, as_polyhedron=True, as_affine_map=True); data
Return all data:
sage: data = S.affine_hull_projection(orthogonal=True, return_all_data=True); data AffineHullProjectionData(image=A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 3 vertices, projection_linear_map=Vector space morphism represented by the matrix: [ -1 -1/2] [ 1 -1/2] [ 0 1] Domain: Vector space of dimension 3 over Rational Field Codomain: Vector space of dimension 2 over Rational Field, projection_translation=(1, 1/2), section_linear_map=Vector space morphism represented by the matrix: [-1/2 1/2 0] [-1/3 -1/3 2/3] Domain: Vector space of dimension 2 over Rational Field Codomain: Vector space of dimension 3 over Rational Field, section_translation=(1, 0, 0))
>>> from sage.all import * >>> data = S.affine_hull_projection(orthogonal=True, return_all_data=True); data AffineHullProjectionData(image=A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 3 vertices, projection_linear_map=Vector space morphism represented by the matrix: [ -1 -1/2] [ 1 -1/2] [ 0 1] Domain: Vector space of dimension 3 over Rational Field Codomain: Vector space of dimension 2 over Rational Field, projection_translation=(1, 1/2), section_linear_map=Vector space morphism represented by the matrix: [-1/2 1/2 0] [-1/3 -1/3 2/3] Domain: Vector space of dimension 2 over Rational Field Codomain: Vector space of dimension 3 over Rational Field, section_translation=(1, 0, 0))
data = S.affine_hull_projection(orthogonal=True, return_all_data=True); data
The section map is a right inverse of the projection map:
sage: mat = data.section_linear_map.matrix().transpose() sage: data.image.linear_transformation(mat) + data.section_translation == S True
>>> from sage.all import * >>> mat = data.section_linear_map.matrix().transpose() >>> data.image.linear_transformation(mat) + data.section_translation == S True
mat = data.section_linear_map.matrix().transpose() data.image.linear_transformation(mat) + data.section_translation == S
Same without
orthogonal=True
:sage: data = S.affine_hull_projection(return_all_data=True); data AffineHullProjectionData(image=A 2-dimensional polyhedron in ZZ^2 defined as the convex hull of 3 vertices, projection_linear_map=Vector space morphism represented by the matrix: [1 0] [0 1] [0 0] Domain: Vector space of dimension 3 over Rational Field Codomain: Vector space of dimension 2 over Rational Field, projection_translation=(0, 0), section_linear_map=Vector space morphism represented by the matrix: [ 1 0 -1] [ 0 1 -1] Domain: Vector space of dimension 2 over Rational Field Codomain: Vector space of dimension 3 over Rational Field, section_translation=(0, 0, 1)) sage: mat = data.section_linear_map.matrix().transpose() sage: data.image.linear_transformation(mat) + data.section_translation == S True
>>> from sage.all import * >>> data = S.affine_hull_projection(return_all_data=True); data AffineHullProjectionData(image=A 2-dimensional polyhedron in ZZ^2 defined as the convex hull of 3 vertices, projection_linear_map=Vector space morphism represented by the matrix: [1 0] [0 1] [0 0] Domain: Vector space of dimension 3 over Rational Field Codomain: Vector space of dimension 2 over Rational Field, projection_translation=(0, 0), section_linear_map=Vector space morphism represented by the matrix: [ 1 0 -1] [ 0 1 -1] Domain: Vector space of dimension 2 over Rational Field Codomain: Vector space of dimension 3 over Rational Field, section_translation=(0, 0, 1)) >>> mat = data.section_linear_map.matrix().transpose() >>> data.image.linear_transformation(mat) + data.section_translation == S True
data = S.affine_hull_projection(return_all_data=True); data mat = data.section_linear_map.matrix().transpose() data.image.linear_transformation(mat) + data.section_translation == S
sage: P0 = Polyhedron( ....: ieqs=[(0, -1, 0, 1, 1, 1), (0, 1, 1, 0, -1, -1), (0, -1, 1, 1, 0, 0), ....: (0, 1, 0, 0, 0, 0), (0, 0, 1, 1, -1, -1), (0, 0, 0, 0, 0, 1), ....: (0, 0, 0, 0, 1, 0), (0, 0, 0, 1, 0, -1), (0, 0, 1, 0, 0, 0)]) sage: P = P0.intersection(Polyhedron(eqns=[(-1, 1, 1, 1, 1, 1)])) sage: P.dim() 4 sage: P.affine_hull_projection(orthogonal=True, as_affine_map=True)[0] Vector space morphism represented by the matrix: [ 0 0 0 1/3] [ -2/3 -1/6 0 -1/12] [ 1/3 -1/6 1/2 -1/12] [ 0 1/2 0 -1/12] [ 1/3 -1/6 -1/2 -1/12] Domain: Vector space of dimension 5 over Rational Field Codomain: Vector space of dimension 4 over Rational Field
>>> from sage.all import * >>> P0 = Polyhedron( ... ieqs=[(Integer(0), -Integer(1), Integer(0), Integer(1), Integer(1), Integer(1)), (Integer(0), Integer(1), Integer(1), Integer(0), -Integer(1), -Integer(1)), (Integer(0), -Integer(1), Integer(1), Integer(1), Integer(0), Integer(0)), ... (Integer(0), Integer(1), Integer(0), Integer(0), Integer(0), Integer(0)), (Integer(0), Integer(0), Integer(1), Integer(1), -Integer(1), -Integer(1)), (Integer(0), Integer(0), Integer(0), Integer(0), Integer(0), Integer(1)), ... (Integer(0), Integer(0), Integer(0), Integer(0), Integer(1), Integer(0)), (Integer(0), Integer(0), Integer(0), Integer(1), Integer(0), -Integer(1)), (Integer(0), Integer(0), Integer(1), Integer(0), Integer(0), Integer(0))]) >>> P = P0.intersection(Polyhedron(eqns=[(-Integer(1), Integer(1), Integer(1), Integer(1), Integer(1), Integer(1))])) >>> P.dim() 4 >>> P.affine_hull_projection(orthogonal=True, as_affine_map=True)[Integer(0)] Vector space morphism represented by the matrix: [ 0 0 0 1/3] [ -2/3 -1/6 0 -1/12] [ 1/3 -1/6 1/2 -1/12] [ 0 1/2 0 -1/12] [ 1/3 -1/6 -1/2 -1/12] Domain: Vector space of dimension 5 over Rational Field Codomain: Vector space of dimension 4 over Rational Field
P0 = Polyhedron( ieqs=[(0, -1, 0, 1, 1, 1), (0, 1, 1, 0, -1, -1), (0, -1, 1, 1, 0, 0), (0, 1, 0, 0, 0, 0), (0, 0, 1, 1, -1, -1), (0, 0, 0, 0, 0, 1), (0, 0, 0, 0, 1, 0), (0, 0, 0, 1, 0, -1), (0, 0, 1, 0, 0, 0)]) P = P0.intersection(Polyhedron(eqns=[(-1, 1, 1, 1, 1, 1)])) P.dim() P.affine_hull_projection(orthogonal=True, as_affine_map=True)[0]
>>> from sage.all import * >>> P0 = Polyhedron( ... ieqs=[(Integer(0), -Integer(1), Integer(0), Integer(1), Integer(1), Integer(1)), (Integer(0), Integer(1), Integer(1), Integer(0), -Integer(1), -Integer(1)), (Integer(0), -Integer(1), Integer(1), Integer(1), Integer(0), Integer(0)), ... (Integer(0), Integer(1), Integer(0), Integer(0), Integer(0), Integer(0)), (Integer(0), Integer(0), Integer(1), Integer(1), -Integer(1), -Integer(1)), (Integer(0), Integer(0), Integer(0), Integer(0), Integer(0), Integer(1)), ... (Integer(0), Integer(0), Integer(0), Integer(0), Integer(1), Integer(0)), (Integer(0), Integer(0), Integer(0), Integer(1), Integer(0), -Integer(1)), (Integer(0), Integer(0), Integer(1), Integer(0), Integer(0), Integer(0))]) >>> P = P0.intersection(Polyhedron(eqns=[(-Integer(1), Integer(1), Integer(1), Integer(1), Integer(1), Integer(1))])) >>> P.dim() 4 >>> P.affine_hull_projection(orthogonal=True, as_affine_map=True)[Integer(0)] Vector space morphism represented by the matrix: [ 0 0 0 1/3] [ -2/3 -1/6 0 -1/12] [ 1/3 -1/6 1/2 -1/12] [ 0 1/2 0 -1/12] [ 1/3 -1/6 -1/2 -1/12] Domain: Vector space of dimension 5 over Rational Field Codomain: Vector space of dimension 4 over Rational Field
P0 = Polyhedron( ieqs=[(0, -1, 0, 1, 1, 1), (0, 1, 1, 0, -1, -1), (0, -1, 1, 1, 0, 0), (0, 1, 0, 0, 0, 0), (0, 0, 1, 1, -1, -1), (0, 0, 0, 0, 0, 1), (0, 0, 0, 0, 1, 0), (0, 0, 0, 1, 0, -1), (0, 0, 1, 0, 0, 0)]) P = P0.intersection(Polyhedron(eqns=[(-1, 1, 1, 1, 1, 1)])) P.dim() P.affine_hull_projection(orthogonal=True, as_affine_map=True)[0]
- gale_transform()[source]¶
Return the Gale transform of a polytope as described in the reference below.
OUTPUT:
A list of vectors, the Gale transform. The dimension is the dimension of the affine dependencies of the vertices of the polytope.
EXAMPLES:
This is from the reference, for a triangular prism:
sage: p = Polyhedron(vertices = [[0,0],[0,1],[1,0]]) sage: p2 = p.prism() sage: p2.gale_transform() ((-1, 0), (0, -1), (1, 1), (-1, -1), (1, 0), (0, 1))
>>> from sage.all import * >>> p = Polyhedron(vertices = [[Integer(0),Integer(0)],[Integer(0),Integer(1)],[Integer(1),Integer(0)]]) >>> p2 = p.prism() >>> p2.gale_transform() ((-1, 0), (0, -1), (1, 1), (-1, -1), (1, 0), (0, 1))
p = Polyhedron(vertices = [[0,0],[0,1],[1,0]]) p2 = p.prism() p2.gale_transform()
REFERENCES:
Lectures in Geometric Combinatorics, R.R.Thomas, 2006, AMS Press.
See also
gale_transform_to_polyhedron()
.
- plot(point=None, line=None, polygon=None, wireframe='blue', fill='green', position=None, orthonormal=True, **kwds)[source]¶
Return a graphical representation.
INPUT:
point
,line
,polygon
– parameters to pass to point (0d), line (1d), and polygon (2d) plot commands. Allowed values are:A Python dictionary to be passed as keywords to the plot commands.
A string or triple of numbers: The color. This is equivalent to passing the dictionary
{'color':...}
.False
: Switches off the drawing of the corresponding graphics object
wireframe
,fill
– similar topoint
,line
, andpolygon
, butfill
is used for the graphics objects in the dimension of the polytope (or of dimension 2 for higher dimensional polytopes) andwireframe
is used for all lower-dimensional graphics objects (default: ‘green’ forfill
and ‘blue’ forwireframe
)position
– positive number; the position to take the projection point in Schlegel diagramsorthonormal
– boolean (default:True
); whether to use orthonormal projections**kwds
– optional keyword parameters that are passed to all graphics objects
OUTPUT:
A (multipart) graphics object.
EXAMPLES:
sage: square = polytopes.hypercube(2) sage: point = Polyhedron([[1,1]]) sage: line = Polyhedron([[1,1],[2,1]]) sage: cube = polytopes.hypercube(3) sage: hypercube = polytopes.hypercube(4)
>>> from sage.all import * >>> square = polytopes.hypercube(Integer(2)) >>> point = Polyhedron([[Integer(1),Integer(1)]]) >>> line = Polyhedron([[Integer(1),Integer(1)],[Integer(2),Integer(1)]]) >>> cube = polytopes.hypercube(Integer(3)) >>> hypercube = polytopes.hypercube(Integer(4))
square = polytopes.hypercube(2) point = Polyhedron([[1,1]]) line = Polyhedron([[1,1],[2,1]]) cube = polytopes.hypercube(3) hypercube = polytopes.hypercube(4)
By default, the wireframe is rendered in blue and the fill in green:
sage: # needs sage.plot sage: square.plot() Graphics object consisting of 6 graphics primitives sage: point.plot() Graphics object consisting of 1 graphics primitive sage: line.plot() Graphics object consisting of 2 graphics primitives sage: cube.plot() Graphics3d Object sage: hypercube.plot() Graphics3d Object
>>> from sage.all import * >>> # needs sage.plot >>> square.plot() Graphics object consisting of 6 graphics primitives >>> point.plot() Graphics object consisting of 1 graphics primitive >>> line.plot() Graphics object consisting of 2 graphics primitives >>> cube.plot() Graphics3d Object >>> hypercube.plot() Graphics3d Object
# needs sage.plot square.plot() point.plot() line.plot() cube.plot() hypercube.plot()
Draw the lines in red and nothing else:
sage: # needs sage.plot sage: square.plot(point=False, line='red', polygon=False) Graphics object consisting of 4 graphics primitives sage: point.plot(point=False, line='red', polygon=False) Graphics object consisting of 0 graphics primitives sage: line.plot(point=False, line='red', polygon=False) Graphics object consisting of 1 graphics primitive sage: cube.plot(point=False, line='red', polygon=False) Graphics3d Object sage: hypercube.plot(point=False, line='red', polygon=False) Graphics3d Object
>>> from sage.all import * >>> # needs sage.plot >>> square.plot(point=False, line='red', polygon=False) Graphics object consisting of 4 graphics primitives >>> point.plot(point=False, line='red', polygon=False) Graphics object consisting of 0 graphics primitives >>> line.plot(point=False, line='red', polygon=False) Graphics object consisting of 1 graphics primitive >>> cube.plot(point=False, line='red', polygon=False) Graphics3d Object >>> hypercube.plot(point=False, line='red', polygon=False) Graphics3d Object
# needs sage.plot square.plot(point=False, line='red', polygon=False) point.plot(point=False, line='red', polygon=False) line.plot(point=False, line='red', polygon=False) cube.plot(point=False, line='red', polygon=False) hypercube.plot(point=False, line='red', polygon=False)
Draw points in red, no lines, and a blue polygon:
sage: # needs sage.plot sage: square.plot(point={'color':'red'}, line=False, polygon=(0,0,1)) Graphics object consisting of 2 graphics primitives sage: point.plot(point={'color':'red'}, line=False, polygon=(0,0,1)) Graphics object consisting of 1 graphics primitive sage: line.plot(point={'color':'red'}, line=False, polygon=(0,0,1)) Graphics object consisting of 1 graphics primitive sage: cube.plot(point={'color':'red'}, line=False, polygon=(0,0,1)) Graphics3d Object sage: hypercube.plot(point={'color':'red'}, line=False, polygon=(0,0,1)) Graphics3d Object
>>> from sage.all import * >>> # needs sage.plot >>> square.plot(point={'color':'red'}, line=False, polygon=(Integer(0),Integer(0),Integer(1))) Graphics object consisting of 2 graphics primitives >>> point.plot(point={'color':'red'}, line=False, polygon=(Integer(0),Integer(0),Integer(1))) Graphics object consisting of 1 graphics primitive >>> line.plot(point={'color':'red'}, line=False, polygon=(Integer(0),Integer(0),Integer(1))) Graphics object consisting of 1 graphics primitive >>> cube.plot(point={'color':'red'}, line=False, polygon=(Integer(0),Integer(0),Integer(1))) Graphics3d Object >>> hypercube.plot(point={'color':'red'}, line=False, polygon=(Integer(0),Integer(0),Integer(1))) Graphics3d Object
# needs sage.plot square.plot(point={'color':'red'}, line=False, polygon=(0,0,1)) point.plot(point={'color':'red'}, line=False, polygon=(0,0,1)) line.plot(point={'color':'red'}, line=False, polygon=(0,0,1)) cube.plot(point={'color':'red'}, line=False, polygon=(0,0,1)) hypercube.plot(point={'color':'red'}, line=False, polygon=(0,0,1))
If we instead use the
fill
andwireframe
options, the coloring depends on the dimension of the object:sage: # needs sage.plot sage: square.plot(fill='green', wireframe='red') Graphics object consisting of 6 graphics primitives sage: point.plot(fill='green', wireframe='red') Graphics object consisting of 1 graphics primitive sage: line.plot(fill='green', wireframe='red') Graphics object consisting of 2 graphics primitives sage: cube.plot(fill='green', wireframe='red') Graphics3d Object sage: hypercube.plot(fill='green', wireframe='red') Graphics3d Object
>>> from sage.all import * >>> # needs sage.plot >>> square.plot(fill='green', wireframe='red') Graphics object consisting of 6 graphics primitives >>> point.plot(fill='green', wireframe='red') Graphics object consisting of 1 graphics primitive >>> line.plot(fill='green', wireframe='red') Graphics object consisting of 2 graphics primitives >>> cube.plot(fill='green', wireframe='red') Graphics3d Object >>> hypercube.plot(fill='green', wireframe='red') Graphics3d Object
# needs sage.plot square.plot(fill='green', wireframe='red') point.plot(fill='green', wireframe='red') line.plot(fill='green', wireframe='red') cube.plot(fill='green', wireframe='red') hypercube.plot(fill='green', wireframe='red')
It is possible to draw polyhedra up to dimension 4, no matter what the ambient dimension is:
sage: hcube = polytopes.hypercube(5) sage: facet = hcube.facets()[0].as_polyhedron(); facet A 4-dimensional polyhedron in ZZ^5 defined as the convex hull of 16 vertices sage: facet.plot() # needs sage.plot Graphics3d Object
>>> from sage.all import * >>> hcube = polytopes.hypercube(Integer(5)) >>> facet = hcube.facets()[Integer(0)].as_polyhedron(); facet A 4-dimensional polyhedron in ZZ^5 defined as the convex hull of 16 vertices >>> facet.plot() # needs sage.plot Graphics3d Object
hcube = polytopes.hypercube(5) facet = hcube.facets()[0].as_polyhedron(); facet facet.plot() # needs sage.plot
For a 3d plot, we may draw the polygons with rainbow colors, using any of the following ways:
sage: cube.plot(polygon='rainbow') # needs sage.plot Graphics3d Object sage: cube.plot(polygon={'color':'rainbow'}) # needs sage.plot Graphics3d Object sage: cube.plot(fill='rainbow') # needs sage.plot Graphics3d Object
>>> from sage.all import * >>> cube.plot(polygon='rainbow') # needs sage.plot Graphics3d Object >>> cube.plot(polygon={'color':'rainbow'}) # needs sage.plot Graphics3d Object >>> cube.plot(fill='rainbow') # needs sage.plot Graphics3d Object
cube.plot(polygon='rainbow') # needs sage.plot cube.plot(polygon={'color':'rainbow'}) # needs sage.plot cube.plot(fill='rainbow') # needs sage.plot
For a 3d plot, the size of a point, the thickness of a line and the width of an arrow are controlled by the respective parameters:
sage: prism = Polyhedron(vertices=[[0,0,0],[1,0,0],[0,1,0]], rays=[[0,0,1]]) sage: prism.plot(size=20, thickness=30, width=1) # needs sage.plot Graphics3d Object sage: prism.plot(point={'size':20, 'color':'black'}, # needs sage.plot ....: line={'thickness':30, 'width':1, 'color':'black'}, ....: polygon='rainbow') Graphics3d Object
>>> from sage.all import * >>> prism = Polyhedron(vertices=[[Integer(0),Integer(0),Integer(0)],[Integer(1),Integer(0),Integer(0)],[Integer(0),Integer(1),Integer(0)]], rays=[[Integer(0),Integer(0),Integer(1)]]) >>> prism.plot(size=Integer(20), thickness=Integer(30), width=Integer(1)) # needs sage.plot Graphics3d Object >>> prism.plot(point={'size':Integer(20), 'color':'black'}, # needs sage.plot ... line={'thickness':Integer(30), 'width':Integer(1), 'color':'black'}, ... polygon='rainbow') Graphics3d Object
prism = Polyhedron(vertices=[[0,0,0],[1,0,0],[0,1,0]], rays=[[0,0,1]]) prism.plot(size=20, thickness=30, width=1) # needs sage.plot prism.plot(point={'size':20, 'color':'black'}, # needs sage.plot line={'thickness':30, 'width':1, 'color':'black'}, polygon='rainbow')
- projection(projection=None)[source]¶
Return a projection object.
INPUT:
proj
– a projection function
OUTPUT:
The identity projection. This is useful for plotting polyhedra.
See also
schlegel_projection()
for a more interesting projection.EXAMPLES:
sage: p = polytopes.hypercube(3) sage: proj = p.projection() sage: proj The projection of a polyhedron into 3 dimensions
>>> from sage.all import * >>> p = polytopes.hypercube(Integer(3)) >>> proj = p.projection() >>> proj The projection of a polyhedron into 3 dimensions
p = polytopes.hypercube(3) proj = p.projection() proj
- render_solid(**kwds)[source]¶
Return a solid rendering of a 2- or 3-d polytope.
EXAMPLES:
sage: p = polytopes.hypercube(3) sage: p_solid = p.render_solid(opacity=.7) # needs sage.plot sage: type(p_solid) # needs sage.plot <class 'sage.plot.plot3d.index_face_set.IndexFaceSet'>
>>> from sage.all import * >>> p = polytopes.hypercube(Integer(3)) >>> p_solid = p.render_solid(opacity=RealNumber('.7')) # needs sage.plot >>> type(p_solid) # needs sage.plot <class 'sage.plot.plot3d.index_face_set.IndexFaceSet'>
p = polytopes.hypercube(3) p_solid = p.render_solid(opacity=.7) # needs sage.plot type(p_solid) # needs sage.plot
- render_wireframe(**kwds)[source]¶
For polytopes in 2 or 3 dimensions, return the edges as a list of lines.
EXAMPLES:
sage: p = Polyhedron([[1,2,],[1,1],[0,0]]) sage: p_wireframe = p.render_wireframe() # needs sage.plot sage: p_wireframe._objects # needs sage.plot [Line defined by 2 points, Line defined by 2 points, Line defined by 2 points]
>>> from sage.all import * >>> p = Polyhedron([[Integer(1),Integer(2),],[Integer(1),Integer(1)],[Integer(0),Integer(0)]]) >>> p_wireframe = p.render_wireframe() # needs sage.plot >>> p_wireframe._objects # needs sage.plot [Line defined by 2 points, Line defined by 2 points, Line defined by 2 points]
p = Polyhedron([[1,2,],[1,1],[0,0]]) p_wireframe = p.render_wireframe() # needs sage.plot p_wireframe._objects # needs sage.plot
- schlegel_projection(facet=None, position=None)[source]¶
Return the Schlegel projection.
The facet is orthonormally transformed into its affine hull.
The position specifies a point coming out of the barycenter of the facet from which the other vertices will be projected into the facet.
INPUT:
facet
– aPolyhedronFace
The facet into which the Schlegel diagram is created. The default is the first facetposition
– a positive number. Determines a relative distance from the barycenter offacet
. A value close to 0 will place the projection point close to the facet and a large value further away. Default is \(1\). If the given value is too large, an error is returned.
OUTPUT: a
Projection
objectEXAMPLES:
sage: p = polytopes.hypercube(3) sage: sch_proj = p.schlegel_projection() sage: schlegel_edge_indices = sch_proj.lines sage: schlegel_edges = [sch_proj.coordinates_of(x) for x in schlegel_edge_indices] sage: len([x for x in schlegel_edges if x[0][0] > 0]) 8
>>> from sage.all import * >>> p = polytopes.hypercube(Integer(3)) >>> sch_proj = p.schlegel_projection() >>> schlegel_edge_indices = sch_proj.lines >>> schlegel_edges = [sch_proj.coordinates_of(x) for x in schlegel_edge_indices] >>> len([x for x in schlegel_edges if x[Integer(0)][Integer(0)] > Integer(0)]) 8
p = polytopes.hypercube(3) sch_proj = p.schlegel_projection() schlegel_edge_indices = sch_proj.lines schlegel_edges = [sch_proj.coordinates_of(x) for x in schlegel_edge_indices] len([x for x in schlegel_edges if x[0][0] > 0])
The Schlegel projection preserves the convexity of facets, see Issue #30015:
sage: fcube = polytopes.hypercube(4) sage: tfcube = fcube.face_truncation(fcube.faces(0)[0]) sage: tfcube.facets()[-1] A 3-dimensional face of a Polyhedron in QQ^4 defined as the convex hull of 8 vertices sage: sp = tfcube.schlegel_projection(tfcube.facets()[-1]) sage: sp.plot() # needs sage.plot Graphics3d Object
>>> from sage.all import * >>> fcube = polytopes.hypercube(Integer(4)) >>> tfcube = fcube.face_truncation(fcube.faces(Integer(0))[Integer(0)]) >>> tfcube.facets()[-Integer(1)] A 3-dimensional face of a Polyhedron in QQ^4 defined as the convex hull of 8 vertices >>> sp = tfcube.schlegel_projection(tfcube.facets()[-Integer(1)]) >>> sp.plot() # needs sage.plot Graphics3d Object
fcube = polytopes.hypercube(4) tfcube = fcube.face_truncation(fcube.faces(0)[0]) tfcube.facets()[-1] sp = tfcube.schlegel_projection(tfcube.facets()[-1]) sp.plot() # needs sage.plot
The same truncated cube but see inside the tetrahedral facet:
sage: tfcube.facets()[4] A 3-dimensional face of a Polyhedron in QQ^4 defined as the convex hull of 4 vertices sage: sp = tfcube.schlegel_projection(tfcube.facets()[4]) # needs sage.symbolic sage: sp.plot() # needs sage.plot sage.symbolic Graphics3d Object
>>> from sage.all import * >>> tfcube.facets()[Integer(4)] A 3-dimensional face of a Polyhedron in QQ^4 defined as the convex hull of 4 vertices >>> sp = tfcube.schlegel_projection(tfcube.facets()[Integer(4)]) # needs sage.symbolic >>> sp.plot() # needs sage.plot sage.symbolic Graphics3d Object
tfcube.facets()[4] sp = tfcube.schlegel_projection(tfcube.facets()[4]) # needs sage.symbolic sp.plot() # needs sage.plot sage.symbolic
A different values of
position
changes the projection:sage: # needs sage.symbolic sage: sp = tfcube.schlegel_projection(tfcube.facets()[4], 1/2) sage: sp.plot() # needs sage.plot Graphics3d Object sage: sp = tfcube.schlegel_projection(tfcube.facets()[4], 4) sage: sp.plot() # needs sage.plot Graphics3d Object
>>> from sage.all import * >>> # needs sage.symbolic >>> sp = tfcube.schlegel_projection(tfcube.facets()[Integer(4)], Integer(1)/Integer(2)) >>> sp.plot() # needs sage.plot Graphics3d Object >>> sp = tfcube.schlegel_projection(tfcube.facets()[Integer(4)], Integer(4)) >>> sp.plot() # needs sage.plot Graphics3d Object
# needs sage.symbolic sp = tfcube.schlegel_projection(tfcube.facets()[4], 1/2) sp.plot() # needs sage.plot sp = tfcube.schlegel_projection(tfcube.facets()[4], 4) sp.plot() # needs sage.plot
A value which is too large give a projection point that sees more than one facet resulting in a error:
sage: sp = tfcube.schlegel_projection(tfcube.facets()[4], 5) Traceback (most recent call last): ... ValueError: the chosen position is too large
>>> from sage.all import * >>> sp = tfcube.schlegel_projection(tfcube.facets()[Integer(4)], Integer(5)) Traceback (most recent call last): ... ValueError: the chosen position is too large
sp = tfcube.schlegel_projection(tfcube.facets()[4], 5)
- show(**kwds)[source]¶
Display graphics immediately.
This method attempts to display the graphics immediately, without waiting for the currently running code (if any) to return to the command line. Be careful, calling it from within a loop will potentially launch a large number of external viewer programs.
INPUT:
kwds
– optional keyword arguments; seeplot()
for the description of available options
OUTPUT:
This method does not return anything. Use
plot()
if you want to generate a graphics object that can be saved or further transformed.EXAMPLES:
sage: square = polytopes.hypercube(2) sage: square.show(point='red') # needs sage.plot
>>> from sage.all import * >>> square = polytopes.hypercube(Integer(2)) >>> square.show(point='red') # needs sage.plot
square = polytopes.hypercube(2) square.show(point='red') # needs sage.plot
- tikz(view=[0, 0, 1], angle=0, scale=1, edge_color='blue!95!black', facet_color='blue!95!black', opacity=0.8, vertex_color='green', axis=False, output_type=None)[source]¶
Return a tikz picture of
self
as a string or as aTikzPicture
according to a projectionview
and an angleangle
obtained via the threejs viewer.self
must be bounded.INPUT:
view
– list (default: [0,0,1]) representing the rotation axis (see note below)angle
– integer (default: 0); angle of rotation in degree from 0 to 360 (see note below)scale
– integer (default: 1); the scaling of the tikz pictureedge_color
– string (default:'blue!95!black'
); representing colors which tikz recognizesfacet_color
– string (default:'blue!95!black'
); representing colors which tikz recognizesvertex_color
– string (default:'green'
); representing colors which tikz recognizesopacity
– real number (default: 0.8) between 0 and 1 giving the opacity of the front facetsaxis
– boolean (default:False
); draw the axes at the origin or notoutput_type
– string (default:None
); valid values areNone
(deprecated),'LatexExpr'
and'TikzPicture'
, whether to return a LatexExpr object (which inherits from Python str) or aTikzPicture
object from modulesage.misc.latex_standalone
OUTPUT: LatexExpr object or TikzPicture object
Note
This is a wrapper of a method of the projection object
self.projection()
. Seetikz()
for more detail.The inputs
view
andangle
can be obtained by visualizing it using.show(aspect_ratio=1)
. This will open an interactive view in your default browser, where you can rotate the polytope. Once the desired view angle is found, click on the information icon in the lower right-hand corner and select Get Viewpoint. This will copy a string of the form ‘[x,y,z],angle’ to your local clipboard. Go back to Sage and typeImg = P.tikz([x,y,z],angle)
.The inputs
view
andangle
can also be obtained from the viewer Jmol:1) Right click on the image 2) Select ``Console`` 3) Select the tab ``State`` 4) Scroll to the line ``moveto``
It reads something like:
moveto 0.0 {x y z angle} Scale
The
view
is then [x,y,z] andangle
is angle. The following number is the scale.Jmol performs a rotation of
angle
degrees along the vector [x,y,z] and show the result from the z-axis.EXAMPLES:
sage: # needs sage.plot sage: co = polytopes.cuboctahedron() sage: Img = co.tikz([0, 0, 1], 0, output_type='TikzPicture') sage: Img \documentclass[tikz]{standalone} \begin{document} \begin{tikzpicture}% [x={(1.000000cm, 0.000000cm)}, y={(0.000000cm, 1.000000cm)}, z={(0.000000cm, 0.000000cm)}, scale=1.000000, ... Use print to see the full content. ... \node[vertex] at (1.00000, 0.00000, 1.00000) {}; \node[vertex] at (1.00000, 1.00000, 0.00000) {}; %% %% \end{tikzpicture} \end{document} sage: print('\n'.join(Img.content().splitlines()[12:21])) %% with the command: ._tikz_3d_in_3d and parameters: %% view = [0, 0, 1] %% angle = 0 %% scale = 1 %% edge_color = blue!95!black %% facet_color = blue!95!black %% opacity = 0.8 %% vertex_color = green %% axis = False sage: print('\n'.join(Img.content().splitlines()[22:26])) %% Coordinate of the vertices: %% \coordinate (-1.00000, -1.00000, 0.00000) at (-1.00000, -1.00000, 0.00000); \coordinate (-1.00000, 0.00000, -1.00000) at (-1.00000, 0.00000, -1.00000);
>>> from sage.all import * >>> # needs sage.plot >>> co = polytopes.cuboctahedron() >>> Img = co.tikz([Integer(0), Integer(0), Integer(1)], Integer(0), output_type='TikzPicture') >>> Img \documentclass[tikz]{standalone} \begin{document} \begin{tikzpicture}% [x={(1.000000cm, 0.000000cm)}, y={(0.000000cm, 1.000000cm)}, z={(0.000000cm, 0.000000cm)}, scale=1.000000, ... Use print to see the full content. ... \node[vertex] at (1.00000, 0.00000, 1.00000) {}; \node[vertex] at (1.00000, 1.00000, 0.00000) {}; %% %% \end{tikzpicture} \end{document} >>> print('\n'.join(Img.content().splitlines()[Integer(12):Integer(21)])) %% with the command: ._tikz_3d_in_3d and parameters: %% view = [0, 0, 1] %% angle = 0 %% scale = 1 %% edge_color = blue!95!black %% facet_color = blue!95!black %% opacity = 0.8 %% vertex_color = green %% axis = False >>> print('\n'.join(Img.content().splitlines()[Integer(22):Integer(26)])) %% Coordinate of the vertices: %% \coordinate (-1.00000, -1.00000, 0.00000) at (-1.00000, -1.00000, 0.00000); \coordinate (-1.00000, 0.00000, -1.00000) at (-1.00000, 0.00000, -1.00000);
# needs sage.plot co = polytopes.cuboctahedron() Img = co.tikz([0, 0, 1], 0, output_type='TikzPicture') Img print('\n'.join(Img.content().splitlines()[12:21])) print('\n'.join(Img.content().splitlines()[22:26]))
When output type is a
sage.misc.latex_standalone.TikzPicture
:sage: # needs sage.plot sage: co = polytopes.cuboctahedron() sage: t = co.tikz([674, 108, -731], 112, output_type='TikzPicture'); t \documentclass[tikz]{standalone} \begin{document} \begin{tikzpicture}% [x={(0.249656cm, -0.577639cm)}, y={(0.777700cm, -0.358578cm)}, z={(-0.576936cm, -0.733318cm)}, scale=1.000000, ... Use print to see the full content. ... \node[vertex] at (1.00000, 0.00000, 1.00000) {}; \node[vertex] at (1.00000, 1.00000, 0.00000) {}; %% %% \end{tikzpicture} \end{document} sage: path_to_file = t.pdf() # not tested
>>> from sage.all import * >>> # needs sage.plot >>> co = polytopes.cuboctahedron() >>> t = co.tikz([Integer(674), Integer(108), -Integer(731)], Integer(112), output_type='TikzPicture'); t \documentclass[tikz]{standalone} \begin{document} \begin{tikzpicture}% [x={(0.249656cm, -0.577639cm)}, y={(0.777700cm, -0.358578cm)}, z={(-0.576936cm, -0.733318cm)}, scale=1.000000, ... Use print to see the full content. ... \node[vertex] at (1.00000, 0.00000, 1.00000) {}; \node[vertex] at (1.00000, 1.00000, 0.00000) {}; %% %% \end{tikzpicture} \end{document} >>> path_to_file = t.pdf() # not tested
# needs sage.plot co = polytopes.cuboctahedron() t = co.tikz([674, 108, -731], 112, output_type='TikzPicture'); t path_to_file = t.pdf() # not tested