diff --git a/src/doc/en/reference/discrete_geometry/index.rst b/src/doc/en/reference/discrete_geometry/index.rst index 3d027326933..6975fa144a7 100644 --- a/src/doc/en/reference/discrete_geometry/index.rst +++ b/src/doc/en/reference/discrete_geometry/index.rst @@ -105,8 +105,10 @@ Miscellaneous .. toctree:: :maxdepth: 1 + sage/geometry/convex_set sage/geometry/linear_expression sage/geometry/newton_polygon + sage/geometry/relative_interior sage/geometry/ribbon_graph sage/geometry/pseudolines sage/geometry/voronoi_diagram diff --git a/src/sage/geometry/cone.py b/src/sage/geometry/cone.py index 79c75ad7841..fd2074676c7 100644 --- a/src/sage/geometry/cone.py +++ b/src/sage/geometry/cone.py @@ -181,9 +181,18 @@ """ # **************************************************************************** -# Copyright (C) 2010 Volker Braun -# Copyright (C) 2012 Andrey Novoseltsev -# Copyright (C) 2010 William Stein +# Copyright (C) 2010-2014 Volker Braun +# Copyright (C) 2010-2018 Andrey Novoseltsev +# Copyright (C) 2010 William Stein +# Copyright (C) 2012 Christian Stump +# Copyright (C) 2014-2018 Frédéric Chapoton +# Copyright (C) 2014 Peter Bruin +# Copyright (C) 2015-2017 Jori Mäntysalo +# Copyright (C) 2015-2020 Michael Orlitzky +# Copyright (C) 2016-2020 John H. Palmieri +# Copyright (C) 2018 David Coudert +# Copyright (C) 2019-2020 Jonathan Kliem +# Copyright (C) 2020-2021 Matthias Koeppe # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -205,6 +214,7 @@ from sage.geometry.toric_lattice import (ToricLattice, is_ToricLattice, is_ToricLatticeQuotient) from sage.geometry.toric_plotter import ToricPlotter, label_list +from sage.geometry.relative_interior import RelativeInterior from sage.graphs.digraph import DiGraph from sage.matrix.all import column_matrix, matrix, MatrixSpace from sage.misc.all import cached_method, flatten, latex @@ -213,6 +223,7 @@ from sage.structure.all import SageObject, parent from sage.structure.richcmp import richcmp_method, richcmp from sage.geometry.integral_points import parallelotope_points +from sage.geometry.convex_set import ConvexSet_closed from sage.misc.lazy_import import lazy_import from sage.features import PythonModule @@ -970,6 +981,26 @@ def lattice(self): """ return self._lattice + def ambient_vector_space(self, base_field=None): + r""" + Return the ambient vector space. + + It is the ambient lattice (:meth:`lattice`) tensored with a field. + + INPUT:: + + - ``base_field`` -- (default: the rationals) a field. + + EXAMPLES:: + + sage: c = Cone([(1,0)]) + sage: c.ambient_vector_space() + Vector space of dimension 2 over Rational Field + sage: c.ambient_vector_space(AA) + Vector space of dimension 2 over Algebraic Real Field + """ + return self.lattice().vector_space(base_field=base_field) + @cached_method def dual_lattice(self): r""" @@ -1009,6 +1040,8 @@ def lattice_dim(self): r""" Return the dimension of the ambient lattice of ``self``. + An alias is :meth:`ambient_dim`. + OUTPUT: - integer. @@ -1023,6 +1056,8 @@ def lattice_dim(self): """ return self.lattice().dimension() + ambient_dim = lattice_dim + def nrays(self): r""" Return the number of rays of ``self``. @@ -1210,8 +1245,11 @@ def codim(self): sage: K.codim() == K.dual().lineality() True """ + # same as ConvexSet_base.codim; the main point is the much more detailed + # docstring. return (self.lattice_dim() - self.dim()) + codimension = codim def span(self, base_ring=None): r""" @@ -1375,7 +1413,7 @@ def classify_cone_2d(ray0, ray1, check=True): # and ``ambient_ray_indices`` keyword parameters. See ``intersection`` method # for an example why this is needed. @richcmp_method -class ConvexRationalPolyhedralCone(IntegralRayCollection, Container): +class ConvexRationalPolyhedralCone(IntegralRayCollection, Container, ConvexSet_closed): r""" Create a convex rational polyhedral cone. @@ -1711,6 +1749,43 @@ def interior_contains(self, *args): point = point[0] return self._contains(point, 'interior') + @cached_method + def interior(self): + r""" + Return the interior of ``self``. + + OUTPUT: + + - either ``self``, an empty polyhedron, or an instance of + :class:`~sage.geometry.relative_interior.RelativeInterior`. + + EXAMPLES:: + + sage: c = Cone([(1,0,0), (0,1,0)]); c + 2-d cone in 3-d lattice N + sage: c.interior() + The empty polyhedron in ZZ^3 + + sage: origin = cones.trivial(2); origin + 0-d cone in 2-d lattice N + sage: origin.interior() + The empty polyhedron in ZZ^2 + + sage: K = cones.nonnegative_orthant(2); K + 2-d cone in 2-d lattice N + sage: K.interior() + Relative interior of 2-d cone in 2-d lattice N + + sage: K2 = Cone([(1,0),(-1,0),(0,1),(0,-1)]); K2 + 2-d cone in 2-d lattice N + sage: K2.interior() is K2 + True + + """ + if self.is_solid(): + return self.relative_interior() + return Polyhedron(ambient_dim=self.lattice_dim()) + def relative_interior_contains(self, *args): r""" Check if a given point is contained in the relative interior of ``self``. @@ -1752,6 +1827,42 @@ def relative_interior_contains(self, *args): point = point[0] return self._contains(point, 'relative interior') + @cached_method + def relative_interior(self): + r""" + Return the relative interior of ``self``. + + OUTPUT: + + - either ``self`` or an instance of + :class:`~sage.geometry.relative_interior.RelativeInterior`. + + EXAMPLES:: + + sage: c = Cone([(1,0,0), (0,1,0)]); c + 2-d cone in 3-d lattice N + sage: c.relative_interior() + Relative interior of 2-d cone in 3-d lattice N + + sage: origin = cones.trivial(2); origin + 0-d cone in 2-d lattice N + sage: origin.relative_interior() is origin + True + + sage: K1 = Cone([(1,0), (-1,0)]); K1 + 1-d cone in 2-d lattice N + sage: K1.relative_interior() is K1 + True + + sage: K2 = Cone([(1,0),(-1,0),(0,1),(0,-1)]); K2 + 2-d cone in 2-d lattice N + sage: K2.relative_interior() is K2 + True + """ + if self.is_relatively_open(): + return self + return RelativeInterior(self) + def cartesian_product(self, other, lattice=None): r""" Return the Cartesian product of ``self`` with ``other``. @@ -3190,6 +3301,21 @@ def is_smooth(self): return False return self.rays().matrix().elementary_divisors() == [1] * self.nrays() + def is_empty(self): + """ + Return whether ``self`` is the empty set. + + Because a cone always contains the origin, this method returns ``False``. + + EXAMPLES:: + + sage: trivial_cone = cones.trivial(3) + sage: trivial_cone.is_empty() + False + + """ + return False + def is_trivial(self): """ Checks if the cone has no rays. @@ -3208,6 +3334,8 @@ def is_trivial(self): """ return self.nrays() == 0 + is_compact = is_trivial + def is_strictly_convex(self): r""" Check if ``self`` is strictly convex. @@ -4447,6 +4575,8 @@ def is_solid(self): A cone is said to be solid if it has nonempty interior. That is, if its extreme rays span the entire ambient space. + An alias is :meth:`is_full_dimensional`. + OUTPUT: ``True`` if this cone is solid, and ``False`` otherwise. @@ -4486,6 +4616,8 @@ def is_solid(self): """ return (self.dim() == self.lattice_dim()) + is_full_dimensional = is_solid + def is_proper(self): r""" Check if this cone is proper. @@ -4536,6 +4668,8 @@ def is_full_space(self): r""" Check if this cone is equal to its ambient vector space. + An alias is :meth:`is_universe`. + OUTPUT: ``True`` if this cone equals its entire ambient vector @@ -4573,6 +4707,8 @@ def is_full_space(self): """ return self.linear_subspace() == self.lattice().vector_space() + is_universe = is_full_space + def lineality(self): r""" Return the lineality of this cone. @@ -4645,6 +4781,27 @@ def lineality(self): """ return self.linear_subspace().dimension() + def is_relatively_open(self): + r""" + Return whether ``self`` is relatively open. + + OUTPUT: + + Boolean. + + EXAMPLES:: + + sage: K = cones.nonnegative_orthant(3) + sage: K.is_relatively_open() + False + + sage: K1 = Cone([(1,0), (-1,0)]); K1 + 1-d cone in 2-d lattice N + sage: K1.is_relatively_open() + True + """ + return self.lineality() == self.dim() + @cached_method def discrete_complementarity_set(self): r""" diff --git a/src/sage/geometry/convex_set.py b/src/sage/geometry/convex_set.py new file mode 100644 index 00000000000..c6dc7967e4d --- /dev/null +++ b/src/sage/geometry/convex_set.py @@ -0,0 +1,739 @@ +r""" +Convex Sets +""" + +# **************************************************************************** +# Copyright (C) 2021 Matthias Koeppe +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# https://www.gnu.org/licenses/ +# **************************************************************************** + +from sage.structure.sage_object import SageObject +from sage.misc.abstract_method import abstract_method + +class ConvexSet_base(SageObject): + """ + Abstract base class for convex sets. + """ + + def is_empty(self): + r""" + Test whether ``self`` is the empty set. + + OUTPUT: + + Boolean. + + EXAMPLES:: + + sage: p = LatticePolytope([], lattice=ToricLattice(3).dual()); p + -1-d lattice polytope in 3-d lattice M + sage: p.is_empty() + True + """ + return self.dim() < 0 + + def is_universe(self): + r""" + Test whether ``self`` is the whole ambient space. + + OUTPUT: + + Boolean. + + TESTS:: + + sage: from sage.geometry.convex_set import ConvexSet_base + sage: C = ConvexSet_base() + sage: C.is_universe() + Traceback (most recent call last): + ... + NotImplementedError: + """ + if not self.is_full_dimensional(): + return False + raise NotImplementedError + + @abstract_method + def dim(self): + r""" + Return the dimension of ``self``. + + Subclasses must provide an implementation of this method. + + TESTS:: + + sage: from sage.geometry.convex_set import ConvexSet_base + sage: C = ConvexSet_base() + sage: C.dim() + Traceback (most recent call last): + ... + NotImplementedError: + """ + + def dimension(self): + r""" + Return the dimension of ``self``. + + This is the same as :meth:`dim`. + + EXAMPLES:: + + sage: from sage.geometry.convex_set import ConvexSet_base + sage: class ExampleSet(ConvexSet_base): + ....: def dim(self): + ....: return 42 + sage: ExampleSet().dimension() + 42 + """ + return self.dim() + + @abstract_method + def ambient_vector_space(self, base_field=None): + r""" + Return the ambient vector space. + + Subclasses must provide an implementation of this method. + + The default implementations of :meth:`ambient`, :meth:`ambient_dim`, + :meth:`ambient_dimension` use this method. + + EXAMPLES:: + + sage: from sage.geometry.convex_set import ConvexSet_base + sage: C = ConvexSet_base() + sage: C.ambient_vector_space() + Traceback (most recent call last): + ... + NotImplementedError: + """ + + def ambient(self): + r""" + Return the ambient convex set or space. + + The default implementation delegates to :meth:`ambient_vector_space`. + + EXAMPLES:: + + sage: from sage.geometry.convex_set import ConvexSet_base + sage: class ExampleSet(ConvexSet_base): + ....: def ambient_vector_space(self, base_field=None): + ....: return (base_field or QQ)^2001 + sage: ExampleSet().ambient_dim() + 2001 + """ + return self.ambient_vector_space() + + def ambient_dim(self): + r""" + Return the dimension of the ambient convex set or space. + + The default implementation obtains it from :meth:`ambient`. + + EXAMPLES:: + + sage: from sage.geometry.convex_set import ConvexSet_base + sage: class ExampleSet(ConvexSet_base): + ....: def ambient(self): + ....: return QQ^7 + sage: ExampleSet().ambient_dim() + 7 + """ + return self.ambient().dimension() + + def ambient_dimension(self): + r""" + Return the dimension of the ambient convex set or space. + + This is the same as :meth:`ambient_dim`. + + EXAMPLES:: + + sage: from sage.geometry.convex_set import ConvexSet_base + sage: class ExampleSet(ConvexSet_base): + ....: def ambient_dim(self): + ....: return 91 + sage: ExampleSet().ambient_dimension() + 91 + """ + return self.ambient_dim() + + def codimension(self): + r""" + Return the codimension of ``self`` in `self.ambient()``. + + EXAMPLES:: + + sage: P = Polyhedron(vertices=[(1,2,3)], rays=[(1,0,0)]) + sage: P.codimension() + 2 + + An alias is :meth:`codim`:: + + sage: P.codim() + 2 + """ + return self.ambient_dim() - self.dim() + + codim = codimension + + def is_full_dimensional(self): + r""" + Return whether ``self`` is full dimensional. + + OUTPUT: + + Boolean. Whether the polyhedron is not contained in any strict + affine subspace. + + EXAMPLES:: + + sage: c = Cone([(1,0)]) + sage: c.is_full_dimensional() + False + + sage: polytopes.hypercube(3).is_full_dimensional() + True + sage: Polyhedron(vertices=[(1,2,3)], rays=[(1,0,0)]).is_full_dimensional() + False + """ + return self.dim() == self.ambient_dim() + + def is_open(self): + r""" + Return whether ``self`` is open. + + The default implementation of this method only knows that the + empty set and the ambient space are open. + + OUTPUT: + + Boolean. + + EXAMPLES:: + + sage: from sage.geometry.convex_set import ConvexSet_base + sage: class ExampleSet(ConvexSet_base): + ....: def is_empty(self): + ....: return False + ....: def is_universe(self): + ....: return True + sage: ExampleSet().is_open() + True + """ + if self.is_empty() or self.is_universe(): + return True + raise NotImplementedError + + def is_relatively_open(self): + r""" + Return whether ``self`` is relatively open. + + The default implementation of this method only knows that open + sets are also relatively open, and in addition singletons are + relatively open. + + OUTPUT: + + Boolean. + + EXAMPLES:: + + sage: from sage.geometry.convex_set import ConvexSet_base + sage: class ExampleSet(ConvexSet_base): + ....: def is_open(self): + ....: return True + sage: ExampleSet().is_relatively_open() + True + """ + if self.is_open(): + return True + if self.dim() == 0: + return True + raise NotImplementedError + + def is_closed(self): + r""" + Return whether ``self`` is closed. + + The default implementation of this method only knows that the + empty set, a singleton set, and the ambient space are closed. + + OUTPUT: + + Boolean. + + EXAMPLES:: + + sage: from sage.geometry.convex_set import ConvexSet_base + sage: class ExampleSet(ConvexSet_base): + ....: def dim(self): + ....: return 0 + sage: ExampleSet().is_closed() + True + """ + if self.is_empty() or self.dim() == 0 or self.is_universe(): + return True + raise NotImplementedError + + def is_compact(self): + r""" + Return whether ``self`` is compact. + + The default implementation of this method only knows that a + non-closed set cannot be compact, and that the empty set and + a singleton set are compact. + + OUTPUT: + + Boolean. + + sage: from sage.geometry.convex_set import ConvexSet_base + sage: class ExampleSet(ConvexSet_base): + ....: def dim(self): + ....: return 0 + sage: ExampleSet().is_compact() + True + """ + if not self.is_closed(): + return False + if self.dim() < 1: + return True + raise NotImplementedError + + def closure(self): + r""" + Return the topological closure of ``self``. + + EXAMPLES:: + + sage: from sage.geometry.convex_set import ConvexSet_closed + sage: C = ConvexSet_closed() + sage: C.closure() is C + True + """ + if self.is_closed(): + return self + raise NotImplementedError + + def interior(self): + r""" + Return the topological interior of ``self``. + + EXAMPLES:: + + sage: from sage.geometry.convex_set import ConvexSet_open + sage: C = ConvexSet_open() + sage: C.interior() is C + True + """ + if self.is_open(): + return self + raise NotImplementedError + + def relative_interior(self): + r""" + Return the relative interior of ``self``. + + EXAMPLES:: + + sage: from sage.geometry.convex_set import ConvexSet_relatively_open + sage: C = ConvexSet_relatively_open() + sage: C.relative_interior() is C + True + """ + if self.is_relatively_open(): + return self + raise NotImplementedError + + def _test_convex_set(self, tester=None, **options): + """ + Run some tests on the methods of :class:`ConvexSet_base`. + + TESTS:: + + sage: from sage.geometry.convex_set import ConvexSet_open + sage: class FaultyConvexSet(ConvexSet_open): + ....: def ambient(self): + ....: return QQ^55 + ....: def ambient_vector_space(self, base_field=None): + ....: return QQ^16 + ....: def is_universe(self): + ....: return True + ....: def dim(self): + ....: return 42 + ....: def ambient_dim(self): + ....: return 91 + sage: TestSuite(FaultyConvexSet()).run(skip=('_test_pickling', '_test_contains')) + Failure in _test_convex_set: + ... + The following tests failed: _test_convex_set + + sage: class BiggerOnTheInside(ConvexSet_open): + ....: def dim(self): + ....: return 100000 + ....: def ambient_vector_space(self): + ....: return QQ^3 + ....: def ambient(self): + ....: return QQ^3 + ....: def ambient_dim(self): + ....: return 3 + sage: TestSuite(BiggerOnTheInside()).run(skip=('_test_pickling', '_test_contains')) + Failure in _test_convex_set: + ... + The following tests failed: _test_convex_set + + """ + if tester is None: + tester = self._tester(**options) + dim = self.dim() + codim = self.codim() + tester.assertTrue(dim <= self.ambient_dim()) + if dim >= 0: + tester.assertTrue(dim + codim == self.ambient_dim()) + if self.is_empty(): + tester.assertTrue(dim == -1) + if self.is_universe(): + tester.assertTrue(self.is_full_dimensional()) + cl_self = self.closure() + try: + int_self = self.interior() + except NotImplementedError: + int_self = None + try: + relint_self = self.relative_interior() + except NotImplementedError: + relint_self = None + if self.is_full_dimensional(): + tester.assertTrue(int_self == relint_self) + if self.is_relatively_open(): + tester.assertTrue(self == relint_self) + if self.is_open(): + tester.assertTrue(self == int_self) + if self.is_closed(): + tester.assertTrue(self == cl_self) + if self.is_compact(): + tester.assertTrue(self.is_closed()) + from sage.misc.sage_unittest import TestSuite + if relint_self is not None and relint_self is not self: + tester.info("\n Running the test suite of self.relative_interior()") + TestSuite(relint_self).run(verbose=tester._verbose, + prefix=tester._prefix + " ") + tester.info(tester._prefix + " ", newline=False) + + # Optional methods + + @abstract_method(optional=True) + def affine_hull(self): + r""" + Return the affine hull of ``self``. + + TESTS:: + + sage: from sage.geometry.convex_set import ConvexSet_base + sage: C = ConvexSet_base() + sage: C.affine_hull() + Traceback (most recent call last): + ... + TypeError: 'NotImplementedType' object is not callable + """ + + @abstract_method(optional=True) + def cartesian_product(self, other): + """ + Return the Cartesian product. + + INPUT: + + - ``other`` -- another convex set + + OUTPUT: + + The Cartesian product of ``self`` and ``other``. + + TESTS:: + + sage: from sage.geometry.convex_set import ConvexSet_base + sage: C = ConvexSet_base() + sage: C.cartesian_product(C) + Traceback (most recent call last): + ... + TypeError: 'NotImplementedType' object is not callable + """ + + @abstract_method(optional=True) + def contains(self, point): + """ + Test whether ``self`` contains the given ``point``. + + INPUT: + + - ``point`` -- a point or its coordinates + + TESTS:: + + sage: from sage.geometry.convex_set import ConvexSet_base + sage: C = ConvexSet_base() + sage: C.contains(vector([0, 0])) + Traceback (most recent call last): + ... + TypeError: 'NotImplementedType' object is not callable + """ + + def _test_contains(self, tester=None, **options): + """ + Test the ``contains`` method. + + EXAMPLES:: + + sage: from sage.geometry.convex_set import ConvexSet_closed + sage: class FaultyConvexSet(ConvexSet_closed): + ....: def ambient_vector_space(self, base_field=QQ): + ....: return base_field^2 + ....: ambient = ambient_vector_space + ....: def contains(self, point): + ....: if isinstance(point, (tuple, list)): + ....: return all(x in ZZ for x in point) + ....: return point.parent() == ZZ^2 + sage: FaultyConvexSet()._test_contains() + Traceback (most recent call last): + ... + AssertionError: False != True + + sage: class AlsoFaultyConvexSet(ConvexSet_closed): + ....: def ambient_vector_space(self, base_field=QQ): + ....: return base_field^2 + ....: def ambient(self): + ....: return ZZ^2 + ....: def contains(self, point): + ....: return point in ZZ^2 + sage: AlsoFaultyConvexSet()._test_contains() + Traceback (most recent call last): + ... + AssertionError: True != False + """ + if tester is None: + tester = self._tester(**options) + ambient = self.ambient() + space = self.ambient_vector_space() + try: + ambient_point = ambient.an_element() + except (AttributeError, NotImplementedError): + ambient_point = None + space_point = space.an_element() + else: + space_point = space(ambient_point) + space_coords = space.coordinates(space_point) + if self.contains != NotImplemented: + contains_space_point = self.contains(space_point) + if ambient_point is not None: + tester.assertEqual(contains_space_point, self.contains(ambient_point)) + tester.assertEqual(contains_space_point, self.contains(space_coords)) + if space.base_ring().is_exact(): + from sage.rings.qqbar import AA + ext_space = self.ambient_vector_space(AA) + ext_space_point = ext_space(space_point) + tester.assertEqual(contains_space_point, self.contains(ext_space_point)) + from sage.symbolic.ring import SR + symbolic_space = self.ambient_vector_space(SR) + symbolic_space_point = symbolic_space(space_point) + # Only test that it can accept SR vectors without error. + self.contains(symbolic_space_point) + + @abstract_method(optional=True) + def intersection(self, other): + r""" + Return the intersection of ``self`` and ``other``. + + INPUT: + + - ``other`` -- another convex set + + OUTPUT: + + The intersection. + + TESTS:: + + sage: from sage.geometry.convex_set import ConvexSet_base + sage: C = ConvexSet_base() + sage: C.intersection(C) + Traceback (most recent call last): + ... + TypeError: 'NotImplementedType' object is not callable + """ + + +class ConvexSet_closed(ConvexSet_base): + r""" + Abstract base class for closed convex sets. + """ + + def is_closed(self): + r""" + Return whether ``self`` is closed. + + OUTPUT: + + Boolean. + + EXAMPLES:: + + sage: hcube = polytopes.hypercube(5) + sage: hcube.is_closed() + True + """ + return True + + def is_open(self): + r""" + Return whether ``self`` is open. + + OUTPUT: + + Boolean. + + EXAMPLES:: + + sage: hcube = polytopes.hypercube(5) + sage: hcube.is_open() + False + + sage: zerocube = polytopes.hypercube(0) + sage: zerocube.is_open() + True + """ + return self.is_empty() or self.is_universe() + + +class ConvexSet_compact(ConvexSet_closed): + r""" + Abstract base class for compact convex sets. + """ + + def is_universe(self): + r""" + Return whether ``self`` is the whole ambient space + + OUTPUT: + + Boolean. + + EXAMPLES:: + + sage: cross3 = lattice_polytope.cross_polytope(3) + sage: cross3.is_universe() + False + sage: point0 = LatticePolytope([[]]); point0 + 0-d reflexive polytope in 0-d lattice M + sage: point0.is_universe() + True + """ + return self.ambient_dim() == 0 and not self.is_empty() + + def is_compact(self): + r""" + Return whether ``self`` is compact. + + OUTPUT: + + Boolean. + + EXAMPLES:: + + sage: cross3 = lattice_polytope.cross_polytope(3) + sage: cross3.is_compact() + True + """ + return True + + is_relatively_open = ConvexSet_closed.is_open + + +class ConvexSet_relatively_open(ConvexSet_base): + r""" + Abstract base class for relatively open convex sets. + """ + + def is_relatively_open(self): + r""" + Return whether ``self`` is relatively open. + + OUTPUT: + + Boolean. + + EXAMPLES:: + + sage: segment = Polyhedron([[1, 2], [3, 4]]) + sage: ri_segment = segment.relative_interior() + sage: ri_segment.is_relatively_open() + True + """ + return True + + def is_open(self): + r""" + Return whether ``self`` is open. + + OUTPUT: + + Boolean. + + EXAMPLES:: + + sage: segment = Polyhedron([[1, 2], [3, 4]]) + sage: ri_segment = segment.relative_interior() + sage: ri_segment.is_open() + False + """ + return self.is_empty() or self.is_full_dimensional() + + +class ConvexSet_open(ConvexSet_relatively_open): + r""" + Abstract base class for open convex sets. + """ + + def is_open(self): + r""" + Return whether ``self`` is open. + + OUTPUT: + + Boolean. + + EXAMPLES:: + + sage: from sage.geometry.convex_set import ConvexSet_open + sage: b = ConvexSet_open() + sage: b.is_open() + True + """ + return True + + def is_closed(self): + r""" + Return whether ``self`` is closed. + + OUTPUT: + + Boolean. + + EXAMPLES:: + + sage: from sage.geometry.convex_set import ConvexSet_open + sage: class OpenBall(ConvexSet_open): + ....: def dim(self): + ....: return 3 + ....: def is_universe(self): + ....: return False + sage: OpenBall().is_closed() + False + """ + return self.is_empty() or self.is_universe() diff --git a/src/sage/geometry/lattice_polytope.py b/src/sage/geometry/lattice_polytope.py index cc5d4303897..bde2f0a6be7 100644 --- a/src/sage/geometry/lattice_polytope.py +++ b/src/sage/geometry/lattice_polytope.py @@ -135,6 +135,7 @@ from sage.structure.all import Sequence from sage.structure.sage_object import SageObject from sage.structure.richcmp import richcmp_method, richcmp +from sage.geometry.convex_set import ConvexSet_compact from copy import copy from collections.abc import Hashable @@ -464,7 +465,7 @@ def is_LatticePolytope(x): return isinstance(x, LatticePolytopeClass) @richcmp_method -class LatticePolytopeClass(SageObject, Hashable): +class LatticePolytopeClass(ConvexSet_compact, Hashable): r""" Create a lattice polytope. @@ -517,6 +518,8 @@ def __init__(self, points=None, compute_vertices=None, sage: LatticePolytope([(1,2,3), (4,5,6)]) # indirect test 1-d lattice polytope in 3-d lattice M + sage: TestSuite(_).run() + """ if ambient is None: self._ambient = self @@ -2596,6 +2599,8 @@ def lattice_dim(self): r""" Return the dimension of the ambient lattice of ``self``. + An alias is :meth:`ambient_dim`. + OUTPUT: - integer. @@ -2610,6 +2615,28 @@ def lattice_dim(self): """ return self.lattice().dimension() + ambient_dim = lattice_dim + + def ambient_vector_space(self, base_field=None): + r""" + Return the ambient vector space. + + It is the ambient lattice (:meth:`lattice`) tensored with a field. + + INPUT:: + + - ``base_field`` -- (default: the rationals) a field. + + EXAMPLES:: + + sage: p = LatticePolytope([(1,0)]) + sage: p.ambient_vector_space() + Vector space of dimension 2 over Rational Field + sage: p.ambient_vector_space(AA) + Vector space of dimension 2 over Algebraic Real Field + """ + return self.lattice().vector_space(base_field=base_field) + def linearly_independent_vertices(self): r""" Return a maximal set of linearly independent vertices. diff --git a/src/sage/geometry/polyhedron/base.py b/src/sage/geometry/polyhedron/base.py index 23ffed867da..82af7e683fc 100644 --- a/src/sage/geometry/polyhedron/base.py +++ b/src/sage/geometry/polyhedron/base.py @@ -51,8 +51,10 @@ from sage.functions.other import sqrt, floor, ceil from sage.groups.matrix_gps.finitely_generated import MatrixGroup from sage.graphs.graph import Graph +from sage.geometry.convex_set import ConvexSet_closed from .constructor import Polyhedron +from sage.geometry.relative_interior import RelativeInterior from sage.categories.sets_cat import EmptySetError ######################################################################### @@ -98,7 +100,7 @@ def is_Polyhedron(X): ######################################################################### -class Polyhedron_base(Element): +class Polyhedron_base(Element, ConvexSet_closed): """ Base class for Polyhedron objects @@ -2460,7 +2462,7 @@ def bounded_edges(self): def Vrepresentation_space(self): r""" - Return the ambient vector space. + Return the ambient free module. OUTPUT: @@ -2478,6 +2480,36 @@ def Vrepresentation_space(self): ambient_space = Vrepresentation_space + def ambient_vector_space(self, base_field=None): + r""" + Return the ambient vector space. + + It is the ambient free module (:meth:`Vrepresentation_space`) tensored + with a field. + + INPUT:: + + - ``base_field`` -- (default: the fraction field of the base ring) a field. + + EXAMPLES:: + + sage: poly_test = Polyhedron(vertices = [[1,0,0,0],[0,1,0,0]]) + sage: poly_test.ambient_vector_space() + Vector space of dimension 4 over Rational Field + sage: poly_test.ambient_vector_space() is poly_test.ambient() + True + + sage: poly_test.ambient_vector_space(AA) + Vector space of dimension 4 over Algebraic Real Field + sage: poly_test.ambient_vector_space(RR) + Vector space of dimension 4 over Real Field with 53 bits of precision + sage: poly_test.ambient_vector_space(SR) + Vector space of dimension 4 over Symbolic Ring + """ + return self.Vrepresentation_space().vector_space(base_field=base_field) + + ambient = ambient_vector_space + def Hrepresentation_space(self): r""" Return the linear space containing the H-representation vectors. @@ -4765,6 +4797,11 @@ def product(self, other): sage: P1 * 2.0 A 1-dimensional polyhedron in RDF^1 defined as the convex hull of 2 vertices + An alias is :meth:`cartesian_product`:: + + sage: P1.cartesian_product(P2) == P1.product(P2) + True + TESTS: Check that :trac:`15253` is fixed:: @@ -4815,6 +4852,8 @@ def product(self, other): _mul_ = product + cartesian_product = product + def _test_product(self, tester=None, **options): """ Run tests on the method :meth:`.product`. @@ -8394,6 +8433,46 @@ def contains(self, point): __contains__ = contains + @cached_method + def interior(self): + """ + The interior of ``self``. + + OUTPUT: + + - either an empty polyhedron or an instance of + :class:`~sage.geometry.relative_interior.RelativeInterior` + + EXAMPLES: + + If the polyhedron is full-dimensional, the result is the + same as that of :meth:`relative_interior`:: + + sage: P_full = Polyhedron(vertices=[[0,0],[1,1],[1,-1]]) + sage: P_full.interior() + Relative interior of + a 2-dimensional polyhedron in ZZ^2 defined as the convex hull of 3 vertices + + If the polyhedron is of strictly smaller dimension than the + ambient space, its interior is empty:: + + sage: P_lower = Polyhedron(vertices=[[0,1], [0,-1]]) + sage: P_lower.interior() + The empty polyhedron in ZZ^2 + + TESTS:: + + sage: Empty = Polyhedron(ambient_dim=2); Empty + The empty polyhedron in ZZ^2 + sage: Empty.interior() is Empty + True + """ + if self.is_open(): + return self + if not self.is_full_dimensional(): + return self.parent().element_class(self.parent(), None, None) + return self.relative_interior() + def interior_contains(self, point): """ Test whether the interior of the polyhedron contains the @@ -8451,6 +8530,71 @@ def interior_contains(self, point): return False return True + def is_relatively_open(self): + r""" + Return whether ``self`` is relatively open. + + OUTPUT: + + Boolean. + + EXAMPLES:: + + sage: P = Polyhedron(vertices=[(1,0), (-1,0)]); P + A 1-dimensional polyhedron in ZZ^2 defined as the convex hull of 2 vertices + sage: P.is_relatively_open() + False + + sage: P0 = Polyhedron(vertices=[[1, 2]]); P0 + A 0-dimensional polyhedron in ZZ^2 defined as the convex hull of 1 vertex + sage: P0.is_relatively_open() + True + + sage: Empty = Polyhedron(ambient_dim=2); Empty + The empty polyhedron in ZZ^2 + sage: Empty.is_relatively_open() + True + + sage: Line = Polyhedron(vertices=[(1, 1)], lines=[(1, 0)]); Line + A 1-dimensional polyhedron in QQ^2 defined as the convex hull of 1 vertex and 1 line + sage: Line.is_relatively_open() + True + + """ + return not self.inequalities() + + @cached_method + def relative_interior(self): + """ + Return the relative interior of ``self``. + + EXAMPLES:: + + sage: P = Polyhedron(vertices=[(1,0), (-1,0)]) + sage: ri_P = P.relative_interior(); ri_P + Relative interior of + a 1-dimensional polyhedron in ZZ^2 defined as the convex hull of 2 vertices + sage: (0, 0) in ri_P + True + sage: (1, 0) in ri_P + False + + sage: P0 = Polyhedron(vertices=[[1, 2]]) + sage: P0.relative_interior() is P0 + True + + sage: Empty = Polyhedron(ambient_dim=2) + sage: Empty.relative_interior() is Empty + True + + sage: Line = Polyhedron(vertices=[(1, 1)], lines=[(1, 0)]) + sage: Line.relative_interior() is Line + True + """ + if self.is_relatively_open(): + return self + return RelativeInterior(self) + def relative_interior_contains(self, point): """ Test whether the relative interior of the polyhedron @@ -9715,24 +9859,6 @@ def edge_label(i, j, c_ij): else: return MatrixGroup(matrices) - def is_full_dimensional(self): - """ - Return whether the polyhedron is full dimensional. - - OUTPUT: - - Boolean. Whether the polyhedron is not contained in any strict - affine subspace. - - EXAMPLES:: - - sage: polytopes.hypercube(3).is_full_dimensional() - True - sage: Polyhedron(vertices=[(1,2,3)], rays=[(1,0,0)]).is_full_dimensional() - False - """ - return self.dim() == self.ambient_dim() - def is_combinatorially_isomorphic(self, other, algorithm='bipartite_graph'): r""" Return whether the polyhedron is combinatorially isomorphic to another polyhedron. diff --git a/src/sage/geometry/polyhedron/face.py b/src/sage/geometry/polyhedron/face.py index 7a00f46ab19..56305d035e8 100644 --- a/src/sage/geometry/polyhedron/face.py +++ b/src/sage/geometry/polyhedron/face.py @@ -78,12 +78,12 @@ from sage.misc.all import cached_method from sage.modules.free_module_element import vector from sage.matrix.constructor import matrix - +from sage.geometry.convex_set import ConvexSet_closed ######################################################################### @richcmp_method -class PolyhedronFace(SageObject): +class PolyhedronFace(ConvexSet_closed): r""" A face of a polyhedron. @@ -121,6 +121,11 @@ class PolyhedronFace(SageObject): (An inequality (1, 1, 1) x + 1 >= 0,) sage: face.ambient_Vrepresentation() (A vertex at (-1, 0, 0), A vertex at (0, -1, 0), A vertex at (0, 0, -1)) + + TESTS:: + + sage: TestSuite(face).run(skip='_test_pickling') + """ def __init__(self, polyhedron, V_indices, H_indices): @@ -147,6 +152,7 @@ def __init__(self, polyhedron, V_indices, H_indices): sage: from sage.geometry.polyhedron.face import PolyhedronFace sage: PolyhedronFace(Polyhedron(), [], []) # indirect doctest A -1-dimensional face of a Polyhedron in ZZ^0 + sage: TestSuite(_).run(skip='_test_pickling') """ self._polyhedron = polyhedron self._ambient_Vrepresentation_indices = tuple(V_indices) @@ -180,6 +186,10 @@ def vertex_generator(self): A vertex at (1, 1) sage: type(face.vertex_generator()) <... 'generator'> + + TESTS:: + + sage: TestSuite(face).run(skip='_test_pickling') """ for V in self.ambient_Vrepresentation(): if V.is_vertex(): @@ -200,6 +210,10 @@ def vertices(self): sage: face = triangle.faces(1)[2] sage: face.vertices() (A vertex at (0, 1), A vertex at (1, 0)) + + TESTS:: + + sage: TestSuite(face).run(skip='_test_pickling') """ return tuple(self.vertex_generator()) @@ -218,6 +232,10 @@ def n_vertices(self): sage: face = Q.faces(2)[0] sage: face.n_vertices() 3 + + TESTS:: + + sage: TestSuite(face).run(skip='_test_pickling') """ return len(self.vertices()) @@ -231,6 +249,10 @@ def ray_generator(self): sage: face = pi.faces(1)[1] sage: next(face.ray_generator()) A ray in the direction (1, 0) + + TESTS:: + + sage: TestSuite(face).run(skip='_test_pickling') """ for V in self.ambient_Vrepresentation(): if V.is_ray(): @@ -282,6 +304,10 @@ def line_generator(self): sage: face = pr.faces(1)[0] sage: next(face.line_generator()) A line in the direction (1, 0) + + TESTS:: + + sage: TestSuite(face).run(skip='_test_pickling') """ for V in self.ambient_Vrepresentation(): if V.is_line(): @@ -649,6 +675,68 @@ def polyhedron(self): """ return self._polyhedron + ambient = polyhedron + + def ambient_vector_space(self, base_field=None): + r""" + Return the ambient vector space. + + It is the ambient free module of the containing polyhedron tensored + with a field. + + INPUT:: + + - ``base_field`` -- (default: the fraction field of the base ring) a field. + + EXAMPLES:: + + sage: half_plane = Polyhedron(ieqs=[(0,1,0)]) + sage: line = half_plane.faces(1)[0]; line + A 1-dimensional face of a Polyhedron in QQ^2 defined as the convex hull of 1 vertex and 1 line + sage: line.ambient_vector_space() + Vector space of dimension 2 over Rational Field + sage: line.ambient_vector_space(AA) + Vector space of dimension 2 over Algebraic Real Field + """ + return self.polyhedron().ambient_vector_space(base_field=base_field) + + def is_relatively_open(self): + r""" + Return whether ``self`` is relatively open. + + OUTPUT: + + Boolean. + + EXAMPLES:: + + sage: half_plane = Polyhedron(ieqs=[(0,1,0)]) + sage: line = half_plane.faces(1)[0]; line + A 1-dimensional face of a Polyhedron in QQ^2 defined as the convex hull of 1 vertex and 1 line + sage: line.is_relatively_open() + True + """ + return self.as_polyhedron().is_relatively_open() + + def is_compact(self): + r""" + Return whether ``self`` is compact. + + OUTPUT: + + Boolean. + + EXAMPLES:: + + sage: half_plane = Polyhedron(ieqs=[(0,1,0)]) + sage: line = half_plane.faces(1)[0]; line + A 1-dimensional face of a Polyhedron in QQ^2 defined as the convex hull of 1 vertex and 1 line + sage: line.is_compact() + False + """ + return not any(V.is_ray() or V.is_line() + for V in self.ambient_Vrepresentation()) + @cached_method def as_polyhedron(self): """ @@ -676,6 +764,49 @@ def as_polyhedron(self): Vrep = (self.vertices(), self.rays(), self.lines()) return P.__class__(parent, Vrep, None) + def contains(self, point): + """ + Test whether the polyhedron contains the given ``point``. + + INPUT: + + - ``point`` -- a point or its coordinates + + EXAMPLES:: + + sage: half_plane = Polyhedron(ieqs=[(0,1,0)]) + sage: line = half_plane.faces(1)[0]; line + A 1-dimensional face of a Polyhedron in QQ^2 defined as the convex hull of 1 vertex and 1 line + sage: line.contains([0, 1]) + True + + As a shorthand, one may use the usual ``in`` operator:: + + sage: [5, 7] in line + False + """ + # preprocess in the same way as Polyhedron_base.contains + try: + p = vector(point) + except TypeError: # point not iterable or no common ring for elements + if len(point) > 0: + return False + else: + p = vector(self.polyhedron().base_ring(), []) + + if len(p) != self.ambient_dim(): + return False + + if not self.polyhedron().contains(p): + return False + + for H in self.ambient_Hrepresentation(): + if H.eval(p) != 0: + return False + return True + + __contains__ = contains + @cached_method def normal_cone(self, direction='outer'): """ diff --git a/src/sage/geometry/relative_interior.py b/src/sage/geometry/relative_interior.py new file mode 100644 index 00000000000..c714faabcb9 --- /dev/null +++ b/src/sage/geometry/relative_interior.py @@ -0,0 +1,300 @@ +r""" +Relative Interiors of Polyhedra and Cones +""" + +# **************************************************************************** +# Copyright (C) 2021 Matthias Koeppe +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# https://www.gnu.org/licenses/ +# **************************************************************************** + +from sage.geometry.convex_set import ConvexSet_relatively_open + + +class RelativeInterior(ConvexSet_relatively_open): + r""" + The relative interior of a polyhedron or cone + + This class should not be used directly. Use methods + :meth:`~sage.geometry.polyhedron.Polyhedron_base.relative_interior`, + :meth:`~sage.geometry.polyhedron.Polyhedron_base.interior`, + :meth:`~sage.geometry.cone.ConvexRationalPolyhedralCone.relative_interior`, + :meth:`~sage.geometry.cone.ConvexRationalPolyhedralCone.interior` instead. + + EXAMPLES:: + + sage: segment = Polyhedron([[1, 2], [3, 4]]) + sage: segment.relative_interior() + Relative interior of + a 1-dimensional polyhedron in ZZ^2 defined as the convex hull of 2 vertices + sage: octant = Cone([(1,0,0), (0,1,0), (0,0,1)]) + sage: octant.relative_interior() + Relative interior of 3-d cone in 3-d lattice N + """ + + def __init__(self, polyhedron): + r""" + Initialize ``self``. + + INPUT: + + - ``polyhedron`` - an instance of :class:`Polyhedron_base` or + :class:`ConvexRationalPolyhedralCone`. + + TESTS:: + + sage: P = Polyhedron([[1, 2], [3, 4]]) + sage: from sage.geometry.relative_interior import RelativeInterior + sage: TestSuite(RelativeInterior(P)).run() + """ + self._polyhedron = polyhedron + + def __contains__(self, point): + r""" + Return whether ``self`` contains ``point``. + + EXAMPLES:: + + sage: octant = Cone([(1,0,0), (0,1,0), (0,0,1)]) + sage: ri_octant = octant.relative_interior(); ri_octant + Relative interior of 3-d cone in 3-d lattice N + sage: (1, 1, 1) in ri_octant + True + sage: (1, 0, 0) in ri_octant + False + """ + return self._polyhedron.relative_interior_contains(point) + + def ambient(self): + r""" + Return the ambient convex set or space. + + EXAMPLES:: + + sage: segment = Polyhedron([[1, 2], [3, 4]]) + sage: ri_segment = segment.relative_interior(); ri_segment + Relative interior of + a 1-dimensional polyhedron in ZZ^2 defined as the convex hull of 2 vertices + sage: ri_segment.ambient() + """ + return self._polyhedron.ambient() + + def ambient_vector_space(self, base_field=None): + r""" + Return the ambient vector space. + + EXAMPLES:: + + sage: segment = Polyhedron([[1, 2], [3, 4]]) + sage: ri_segment = segment.relative_interior(); ri_segment + Relative interior of + a 1-dimensional polyhedron in ZZ^2 defined as the convex hull of 2 vertices + sage: ri_segment.ambient_vector_space() + """ + return self._polyhedron.ambient_vector_space(base_field=base_field) + + def ambient_dim(self): + r""" + Return the dimension of the ambient space. + + EXAMPLES:: + + sage: segment = Polyhedron([[1, 2], [3, 4]]) + sage: segment.ambient_dim() + 2 + sage: ri_segment = segment.relative_interior(); ri_segment + Relative interior of + a 1-dimensional polyhedron in ZZ^2 defined as the convex hull of 2 vertices + sage: ri_segment.ambient_dim() + 2 + """ + return self._polyhedron.ambient_dim() + + def dim(self): + r""" + Return the dimension of ``self``. + + EXAMPLES:: + + sage: segment = Polyhedron([[1, 2], [3, 4]]) + sage: segment.dim() + 1 + sage: ri_segment = segment.relative_interior(); ri_segment + Relative interior of + a 1-dimensional polyhedron in ZZ^2 defined as the convex hull of 2 vertices + sage: ri_segment.dim() + 1 + """ + return self._polyhedron.dim() + + def interior(self): + r""" + Return the interior of ``self``. + + EXAMPLES:: + + sage: segment = Polyhedron([[1, 2], [3, 4]]) + sage: ri_segment = segment.relative_interior(); ri_segment + Relative interior of + a 1-dimensional polyhedron in ZZ^2 defined as the convex hull of 2 vertices + sage: ri_segment.interior() + The empty polyhedron in ZZ^2 + + sage: octant = Cone([(1,0,0), (0,1,0), (0,0,1)]) + sage: ri_octant = octant.relative_interior(); ri_octant + Relative interior of 3-d cone in 3-d lattice N + sage: ri_octant.interior() is ri_octant + True + """ + return self._polyhedron.interior() + + def relative_interior(self): + r""" + Return the relative interior of ``self``. + + As ``self`` is already relatively open, this method just returns ``self``. + + EXAMPLES:: + + sage: segment = Polyhedron([[1, 2], [3, 4]]) + sage: ri_segment = segment.relative_interior(); ri_segment + Relative interior of + a 1-dimensional polyhedron in ZZ^2 defined as the convex hull of 2 vertices + sage: ri_segment.relative_interior() is ri_segment + True + """ + return self + + def closure(self): + r""" + Return the topological closure of ``self``. + + EXAMPLES:: + + sage: segment = Polyhedron([[1, 2], [3, 4]]) + sage: ri_segment = segment.relative_interior(); ri_segment + Relative interior of + a 1-dimensional polyhedron in ZZ^2 defined as the convex hull of 2 vertices + sage: ri_segment.closure() is segment + True + """ + return self._polyhedron + + def is_universe(self): + r""" + Return whether ``self`` is the whole ambient space + + OUTPUT: + + Boolean. + + EXAMPLES:: + + sage: segment = Polyhedron([[1, 2], [3, 4]]) + sage: ri_segment = segment.relative_interior(); ri_segment + Relative interior of + a 1-dimensional polyhedron in ZZ^2 defined as the convex hull of 2 vertices + sage: ri_segment.is_universe() + False + """ + # Relies on ``self`` not set up for polyhedra that are already + # relatively open themselves. + assert not self._polyhedron.is_universe() + return False + + def is_closed(self): + r""" + Return whether ``self`` is closed. + + OUTPUT: + + Boolean. + + EXAMPLES:: + + sage: segment = Polyhedron([[1, 2], [3, 4]]) + sage: ri_segment = segment.relative_interior(); ri_segment + Relative interior of a 1-dimensional polyhedron in ZZ^2 defined as the convex hull of 2 vertices + sage: ri_segment.is_closed() + False + """ + # Relies on ``self`` not set up for polyhedra that are already + # relatively open themselves. + assert not self._polyhedron.is_relatively_open() + return False + + def _repr_(self): + r""" + Return a description of ``self``. + + EXAMPLES:: + + sage: P = Polyhedron(vertices = [[1,2,3,4],[2,1,3,4],[4,3,2,1]]) + sage: P.relative_interior()._repr_() + 'Relative interior of a 2-dimensional polyhedron in ZZ^4 defined as the convex hull of 3 vertices' + sage: P.rename('A') + sage: P.relative_interior()._repr_() + 'Relative interior of A' + """ + repr_P = repr(self._polyhedron) + if repr_P.startswith('A '): + repr_P = 'a ' + repr_P[2:] + return 'Relative interior of ' + repr_P + + def __eq__(self, other): + r""" + Compare ``self`` and ``other``. + + INPUT: + + - ``other`` -- any object + + EXAMPLES:: + + sage: segment = Polyhedron([[1, 2], [3, 4]]) + sage: ri_segment = segment.relative_interior(); ri_segment + Relative interior of + a 1-dimensional polyhedron in ZZ^2 defined as the convex hull of 2 vertices + sage: segment2 = Polyhedron([[1, 2], [3, 4]], base_ring=AA) + sage: ri_segment2 = segment2.relative_interior(); ri_segment2 + Relative interior of + a 1-dimensional polyhedron in AA^2 defined as the convex hull of 2 vertices + sage: ri_segment == ri_segment2 + True + + TESTS:: + + sage: empty = Polyhedron(ambient_dim=2) + sage: ri_segment == empty + False + """ + if type(self) != type(other): + return False + return self._polyhedron == other._polyhedron + + def __ne__(self, other): + r""" + Compare ``self`` and ``other``. + + INPUT: + + - ``other`` -- any object + + TESTS:: + + sage: segment = Polyhedron([[1, 2], [3, 4]]) + sage: ri_segment = segment.relative_interior(); ri_segment + Relative interior of + a 1-dimensional polyhedron in ZZ^2 defined as the convex hull of 2 vertices + sage: segment2 = Polyhedron([[1, 2], [3, 4]], base_ring=AA) + sage: ri_segment2 = segment2.relative_interior(); ri_segment2 + Relative interior of + a 1-dimensional polyhedron in AA^2 defined as the convex hull of 2 vertices + sage: ri_segment != ri_segment2 + False + """ + return not (self == other)