def equals(self, other, allowed_error = EPSILON): """Test approximate equality of two foliations. For the two foliations to be considered equal, the permutation of intervals has to be the same (but possibly with different labels). Also the vectors made from the length and twists parameters must differ by less than ``allowed_error``. INPUT: - ``other`` -- another ``Foliation`` object - ``allowed_error`` -- a positive number, the distance between the two length-twist vectors must be smaller than this in order for equality. EXAMPLES:: sage: f = Foliation('a a b b c c','moebius',[1,2,3]) sage: g = Foliation('1 1 2 2 3 3','moebius',[2.0000001,4,6]) sage: f.equals(g,0.0001) True """ return self.is_bottom_side_moebius() == other.is_bottom_side_moebius()\ and self._gen_perm == other._gen_perm and \ abs(vector(flatten(self._divpoints)) - vector(flatten(other._divpoints))) <= allowed_error
def arithmetic_independence_poset(self): """ Compute the poset of (arithmetic) independent sets of the associated central toric arrangement. This is defined in [Len17b, Definition 5], [Mar18, Definitions 2.1 and 2.2], [DD18, Section 7]. Notice that it is not the same as the independence poset of the underlying matroid. """ # TODO: implement for Q != 0 if self._Q.ncols() > 0: raise NotImplementedError A = self._A.transpose() data = {} # compute Smith normal forms of all submatrices for S_labeled in self.independent_sets(): S = tuple(sorted(self._groundset_to_index[e] for e in S_labeled)) D, U, V = A[S, :].smith_form() # D == U*A[S,:]*V diagonal = [D[i, i] if i < D.ncols() else 0 for i in range(len(S))] data[tuple(S)] = (diagonal, U) # generate all elements of the poset elements = { S: list( vector(ZZ, x) for x in itertools.product( *(range(max(data[tuple(S)][0][i], 1)) for i in range(len(S))))) for S in data.keys() } for l in elements.values(): for v in l: v.set_immutable() all_elements = list((tuple(self._E[i] for i in S), x) for (S, l) in elements.items() for x in l) cover_relations = [] for (S, l) in elements.items(): diagonal_S, U_S = data[S] S_labeled = tuple(self._E[i] for i in S) for s in S: i = S.index(s) # index where the element s appears in S T = tuple(t for t in S if t != s) T_labeled = tuple(self._E[i] for i in T) diagonal_T, U_T = data[T] for x in l: y = U_S**(-1) * x z = U_T * vector(ZZ, y[:i].list() + y[i + 1:].list()) w = vector(ZZ, (a % diagonal_T[j] if diagonal_T[j] > 0 else 0 for j, a in enumerate(z))) w.set_immutable() cover_relations.append(((T_labeled, w), (S_labeled, x))) return Poset(data=(all_elements, cover_relations), cover_relations=True)
def cs_range( f, subset = None): """ For a symmetric semi-positive integral matrix $f$, return a list of all integral $n$-vectors $v$ such that $x^tfx - (v*x)^2 >= 0$ for all $x$. """ n = f.dimensions()[0] b = vector( s.isqrt() for s in f.diagonal()) zv = vector([0]*n) box = [b - vector(t) for t in mrange( (2*b + vector([1]*n)).list()) if b - vector(t) > zv] if subset: box = [v for v in box if v in subset] rge = [v for v in box if min( (f - matrix(v).transpose()*matrix(v)).eigenvalues()) >=0] return rge
def diameter_support_function(A, b): r"""Compute the diameter of a polytope using the H-representation. The diameter is computed in the supremum norm. INPUT: Polyhedron in H-representation, `Ax \leq b`. OUTPUT: - ``diam`` -- scalar, diameter of the polyhedron in the supremum norm - ``u`` -- vector with components `u_j = \max x_j` for `x` in `P` - ``l`` -- vector with components `l_j = \min x_j` for `x` in `P` EXAMPLES:: sage: from polyhedron_tools.misc import diameter_support_function, polyhedron_to_Hrep sage: [A, b] = polyhedron_to_Hrep(7*polytopes.hypercube(5)) sage: diameter_support_function(A, b) (14.0, (7.0, 7.0, 7.0, 7.0, 7.0), (-7.0, -7.0, -7.0, -7.0, -7.0)) """ from polyhedron_tools.misc import support_function # ambient dimension n = A.ncols() # number of constraints m = A.rows() In = identity_matrix(n) # lower : min x_j l = [] for j in range(n): l += [-support_function([A, b], -In.column(j))] l = vector(l) # upper : max x_j u = [] for j in range(n): u += [support_function([A, b], In.column(j))] u = vector(u) diam = max(u - l) return diam, u, l
def vertices(self): r""" Return the vertices as a tuple of `\ZZ`-vectors. OUTPUT: A tuple of `\ZZ`-vectors. Each entry is the coordinate vector of an integral points of the lattice polytope. EXAMPLES:: sage: from sage.geometry.polyhedron.ppl_lattice_polytope import LatticePolytope_PPL sage: p = LatticePolytope_PPL((-9,-6,-1,-1),(0,0,0,1),(0,0,1,0),(0,1,0,0),(1,0,0,0)) sage: p.vertices() ((-9, -6, -1, -1), (0, 0, 0, 1), (0, 0, 1, 0), (0, 1, 0, 0), (1, 0, 0, 0)) sage: p.minimized_generators() Generator_System {point(-9/1, -6/1, -1/1, -1/1), point(0/1, 0/1, 0/1, 1/1), point(0/1, 0/1, 1/1, 0/1), point(0/1, 1/1, 0/1, 0/1), point(1/1, 0/1, 0/1, 0/1)} """ d = self.space_dimension() v = vector(ZZ, d) points = [] for g in self.minimized_generators(): for i in range(0,d): v[i] = g.coefficient(Variable(i)) v_copy = copy.copy(v) v_copy.set_immutable() points.append(v_copy) return tuple(points)
def opposite_polyhedron(P, base_ring=None): r"""Generate the polyhedron whose vertices are oppositve to a given one. The opposite polyhedron `-P` is defined as the polyhedron such that `x \in -P` if and only if `-x \in P`. INPUT: * ``P`` - an object of class Polyhedron. * ``base_ring`` - (default: that of P). The ``base_ring`` passed to construct `-P`. OUTPUT: A polyhedron whose vertices are opposite to those of `P`. EXAMPLES:: sage: from polyhedron_tools.misc import BoxInfty, opposite_polyhedron sage: P = BoxInfty([1,1], 0.5) sage: minusP = opposite_polyhedron(P) sage: P.plot(aspect_ratio=1) + minusP.plot() # not tested (plot) TO-DO: The possibility to receive P in matrix form `(A, b)`. """ if base_ring is None: base_ring = P.base_ring() return Polyhedron(vertices=[-1 * vector(v) for v in P.vertices_list()], base_ring=base_ring)
def evaluate(self, point): """ Evaluate the linear expression. INPUT: - ``point`` -- list/tuple/iterable of coordinates; the coordinates of a point OUTPUT: The linear expression `Ax + b` evaluated at the point `x`. EXAMPLES:: sage: from sage.geometry.linear_expression import LinearExpressionModule sage: L.<x,y> = LinearExpressionModule(QQ) sage: ex = 2*x + 3* y + 4 sage: ex.evaluate([1,1]) 9 sage: ex([1,1]) # syntactic sugar 9 sage: ex([pi, e]) 2*pi + 3*e + 4 """ try: point = self.parent().ambient_module()(point) except TypeError: from sage.matrix.constructor import vector point = vector(point) return self._coeffs * point + self._const
def support_function_ellipsoid(Q, d): r"""Compute support function of an ellipsoid. The ellipsoid is defined as `\max_{x in Q} \langle x, d \rangle`, where `d` is a given vector. The ellipsoid is defined as the set `\{ x \in \mathbb{R}^n : x^T Q x \leq 1\}`. INPUT: * ``Q`` - a square matrix * ``d`` - a vector (or list) where the support function is evaluated OUTPUT: The value of the support function at `d`. """ if (Q.is_singular()): raise ValueError( "The coefficient matrix of the ellipsoid is not invertible.") if (type(d) == list): d = vector(d) return sqrt(d.inner_product((~Q) * d))
def extreme_point (A, b, d) : r""" Extreme point. INPUT: * ``A`` - a matrix of M of size k x n (R) * ``b`` - a vector of Rk * ``d`` - a vector of Rn (direction) """ from sage.numerical.mip import MixedIntegerLinearProgram p = MixedIntegerLinearProgram (maximization = True) x = p.new_variable () p.add_constraint (A*x <= b) f = 0 for i in range (0, d.length ()) : f = f + d[i]*x[i] p.set_objective (f) p.solve () l = [] for i in range (0, d.length ()) : l = l + [p.get_values (x[i])] return vector (l)
def normal_vector (v) : r"""Normal vector. INPUT: * v - a 2d vector """ return vector ([v[1], -v[0]])
def chebyshev_center(P=None, A=None, b=None): r"""Compute the Chebyshev center of a polytope. The Chebyshev center of a polytope is the center of the largest hypersphere enclosed by the polytope. INPUT: * ``A, b`` - Matrix and vector representing the polyhedron, as `Ax \leq b`. * ``P`` - Polyhedron object. OUTPUT: * The Chebyshev center, as a vector; the base ring of ``P`` preserved. """ from sage.numerical.mip import MixedIntegerLinearProgram # parse input got_P, got_Ab = False, False if (P is not None): if (A is None) and (b is None): got_P = True elif (A is not None) and (b is None): b, A = A, P P = [] got_Ab = True else: got_Ab = True if (A is not None and b is not None) else False if got_Ab or got_P: if got_P: [A, b] = polyhedron_to_Hrep(P) base_ring = A.base_ring() p = MixedIntegerLinearProgram(maximization=True) x = p.new_variable() r = p.new_variable() n = A.nrows() m = A.ncols() if not n == b.length(): return [] for i in range(0, n): v = A.row(i) norm = sqrt(v.dot_product(v)) p.add_constraint( sum([v[j] * x[j] for j in range(0, m)]) + norm * r[0] <= b[i]) f = sum([0 * x[i] for i in range(0, m)]) + 1 * r[0] p.set_objective(f) p.solve() cheby_center = [base_ring(p.get_values(x[i])) for i in range(0, m)] return vector(cheby_center) else: raise ValueError( 'The input should be either as a Polyhedron, P, or as matrices, [A, b].' )
def vertex_connections(P): r"""Return the vertices that are connected to each edge. INPUT: "P" - a polygon (Polyhedron object in a two-dimensional ambient space). OUTPUT: A collection of lists ``li`` of integers, where each ``li`` corresponds to a pair of vertices. The vertices are indexed with respect to ``P.vertices_list()``. NOTES: - This has been tested in ``QQ`` and in ``RDF`` base rings. - For ``RDF`` we have a ``tol`` parameter to decide if a vertex satisfies a constraint. """ # tolerance for numerical error (for use in RDF only) tol = 1e-6 got_QQ = True if P.base_ring() == QQ else False constraints_matrix = [] # loop in the constraints for i, constraint in enumerate(P.inequalities_list()): # independent term and coefficient vector bi = constraint[0] ai = vector([constraint[1], constraint[2]]) constraint_i_vertices = [] for j, vj in enumerate(P.vertices_list()): if (got_QQ and (ai * vector(vj) + bi == 0)) or ( not got_QQ and abs(ai * vector(vj) + bi < tol)): constraint_i_vertices.append(j) constraints_matrix.append(constraint_i_vertices) return constraints_matrix
def path_to_vector_teich(self, path, edge_index_to_elevation): edge_coeffs = [0] * len(self._index_to_edge) elevation = 1 for oe in path: index = self._edge_to_index[oe.edge] if oe.direction == -1: elevation /= edge_index_to_elevation[index] edge_coeffs[index] += elevation if oe.direction == 1: elevation *= edge_index_to_elevation[index] return (vector(edge_coeffs), elevation)
def _asphericity_iteration(xk, l, m, p, A, a, B, b, verbose=0, solver='GLPK'): # find better way to share all those parameters, maybe a class #alphak = asphericity(xk) R_xk = max(A.row(i) * vector(xk) + a[i] for i in range(m)) r_xk = min(B.row(j) * vector(xk) + b[j] for j in range(l)) alphak = R_xk / r_xk a_LP = MixedIntegerLinearProgram(maximization=False, solver=solver) x = a_LP.new_variable(integer=False, nonnegative=False) z = a_LP.new_variable(integer=False, nonnegative=False) for i in range(m): for j in range(l): aux = sum( (A.row(i)[k] - alphak * B.row(j)[k]) * x[k] for k in range(p)) a_LP.add_constraint(aux + a[i] - alphak * b[j] <= z[0]) #solve LP a_LP.set_objective(z[0]) if (verbose): print '**** Solve LP ****' a_LP.show() opt_val = a_LP.solve() x_opt = a_LP.get_values(x) z_opt = a_LP.get_values(z) if (verbose): print 'Objective Value:', opt_val for i, v in x_opt.iteritems(): print 'x_%s = %f' % (i, v) for i, v in z_opt.iteritems(): print 'z = %f' % (v) print '\n' return [opt_val, x_opt, z_opt]
def circumradius(P, x): r"""Compute the radius of the box of smallest volume (= ball in the sup-norm) which contains P, with center at x. """ if not (P.contains(x)): return NotImplementedError("The polytope should contain x.") (A, a, B, b, C, c, D, d) = dual_representation(P) m = len(c) return max(A.row(i) * vector(x) + a[i] for i in range(m))
def inradius(P, x): r"""Compute the radius of the box of biggest volume (= ball in the sup-norm) which is contained P, with center at x. """ if not (P.contains(x)): return NotImplementedError("The polytope should contain x.") (A, a, B, b, C, c, D, d) = dual_representation(P) l = D.nrows() return min(B.row(j) * vector(x) + b[j] for j in range(l))
def diameter_vertex_enumeration(P, p=oo): r"""Compute the diameter of a polytope using the V-representation. EXAMPLES: By default, the supremum norm is used:: sage: from polyhedron_tools.misc import diameter_vertex_enumeration sage: diameter_vertex_enumeration(7*polytopes.hypercube(5)) 14 A custom `p`-norm can be specified as well:: sage: diameter_vertex_enumeration(7*polytopes.hypercube(2), p=2) 14*sqrt(2) """ diam = 0 vlist = P.vertices_list() num_vertices = len(vlist) for i in range(num_vertices): for j in range(i + 1, num_vertices): dist_vi_vj = (vector(vlist[i]) - vector(vlist[j])).norm(p=p) diam = max(diam, dist_vi_vj) return diam
def point(self): """ Return the point closest to the origin. OUTPUT: A vector of the ambient vector space. The closest point to the origin in the `L^2`-norm. In finite characteristic a random point will be returned if the norm of the hyperplane normal vector is zero. EXAMPLES:: sage: H.<x,y,z> = HyperplaneArrangements(QQ) sage: h = x + 2*y + 3*z - 4 sage: h.point() (2/7, 4/7, 6/7) sage: h.point() in h True sage: H.<x,y,z> = HyperplaneArrangements(GF(3)) sage: h = 2*x + y + z + 1 sage: h.point() (1, 0, 0) sage: h.point().base_ring() Finite Field of size 3 sage: H.<x,y,z> = HyperplaneArrangements(GF(3)) sage: h = x + y + z + 1 sage: h.point() (2, 0, 0) """ P = self.parent() AA = P.ambient_module() R = P.base_ring() norm2 = sum(x ** 2 for x in self.A()) if norm2 == 0: from sage.matrix.constructor import matrix, vector solution = matrix(R, self.A()).solve_right(vector(R, [-self.b()])) else: solution = [-x * self.b() / norm2 for x in self.A()] return AA(solution)
def point(self): """ Return the point closest to the origin. OUTPUT: A vector of the ambient vector space. The closest point to the origin in the `L^2`-norm. In finite characteristic a random point will be returned if the norm of the hyperplane normal vector is zero. EXAMPLES:: sage: H.<x,y,z> = HyperplaneArrangements(QQ) sage: h = x + 2*y + 3*z - 4 sage: h.point() (2/7, 4/7, 6/7) sage: h.point() in h True sage: H.<x,y,z> = HyperplaneArrangements(GF(3)) sage: h = 2*x + y + z + 1 sage: h.point() (1, 0, 0) sage: h.point().base_ring() Finite Field of size 3 sage: H.<x,y,z> = HyperplaneArrangements(GF(3)) sage: h = x + y + z + 1 sage: h.point() (2, 0, 0) """ P = self.parent() AA = P.ambient_module() R = P.base_ring() norm2 = sum(x**2 for x in self.A()) if norm2 == 0: from sage.matrix.constructor import matrix, vector solution = matrix(R, self.A()).solve_right(vector(R, [-self.b()])) else: solution = [-x * self.b() / norm2 for x in self.A()] return AA(solution)
def vanishing_space(p, Fqm=None, Fq=None): """ Compute the vanishing space of the Ore polynomial p. """ if not Fqm: Fqm = p.base_ring() if not Fq: Fq = Fqm.prime_subfield() m = Fqm.degree() // Fq.degree() d = p.degree() extension, to_big_field, from_big_field = Fqm.vector_space(Fq, None, map=True) basis = extension.basis_matrix() ev = from_matrix_representation(basis, Fqm) ev = vector(p.multi_point_evaluation(ev)) ev = to_matrix_representation(ev, Fq) ev = ev.right_kernel() return ev
def matrice_systeme(systeme, variables): """ Renvoie une matrice par block représentant un programme linéaire sous forme standard. INPUT:: - ``systeme`` -- Un programme linéaire sous forme standard - ``variables`` -- La liste des variables du système EXAMPLES:: sage: x = x1,x2,x3 = var('x1,x2,x3') sage: Chvatal13 = [[2*x1 + 3*x2 + x3 <= 5, ....: 4*x1 + x2 + 2*x3 <= 11, ....: 3*x1 + 4*x2 + 2*x3 <= 8], ....: 5*x1 + 4*x2 + 3*x3] sage: m = matrice_systeme(Chvatal13, x); m [ z|s1 s2 s3|x1 x2 x3| 0] [--+--------+--------+--] [ 1| 0 0 0|-5 -4 -3| 0] [--+--------+--------+--] [ 0| 1 0 0| 2 3 1| 5] [ 0| 0 1 0| 4 1 2|11] [ 0| 0 0 1| 3 4 2| 8] """ def liste_coeffs(expression): return [expression.coeff(v) for v in variables] inequations = systeme[0] m = matrix([liste_coeffs(ineq.lhs()) for ineq in inequations]) rhs = vector(ineq.rhs() for ineq in inequations).column() slack = SR.var(",".join("s%s" % i for i in range(1, len(inequations) + 1))) z = SR.var("z") return block_matrix( [ [z, matrix([slack]), matrix([variables]), ZZ(0)], [ZZ(1), ZZ(0), -matrix([liste_coeffs(systeme[1])]), ZZ(0)], [ZZ(0), ZZ(1), m, rhs], ] )
def base_rays(self, fiber, points): """ Return the primitive lattice vectors that generate the direction given by the base projection of points. INPUT: - ``fiber`` -- a sub-lattice polytope defining the :meth:`base_projection`. - ``points`` -- the points to project to the base. OUTPUT: A tuple of primitive `\ZZ`-vectors. EXAMPLES:: sage: from sage.geometry.polyhedron.ppl_lattice_polytope import LatticePolytope_PPL sage: poly = LatticePolytope_PPL((-9,-6,-1,-1),(0,0,0,1),(0,0,1,0),(0,1,0,0),(1,0,0,0)) sage: fiber = poly.fibration_generator(2).next() sage: poly.base_rays(fiber, poly.integral_points_not_interior_to_facets()) ((-1, -1), (0, 1), (1, 0)) sage: p = LatticePolytope_PPL((1,0),(1,2),(-1,0)) sage: f = LatticePolytope_PPL((1,0),(-1,0)) sage: p.base_rays(f, p.integral_points()) ((1),) """ quo = self.base_projection(fiber) vertices = [] for p in points: v = vector(ZZ,quo(p)) if v.is_zero(): continue d = GCD_list(v.list()) if d>1: for i in range(0,v.degree()): v[i] /= d v.set_immutable() vertices.append(v) return tuple(uniq(vertices))
def decomposition(self): """ Find the decomposition of the matroid as a direct sum of indecomposable matroids. Return a partition of the groundset. Uses the algorithm of [PP19, Section 7]. """ B = self.basis() new_groundset = list(B) + list(self.groundset().difference(B)) # construct matrix with permuted columns columns = [vector(self._A[:,self._groundset_to_index[e]]) for e in new_groundset] A = matrix(ZZ, columns).transpose().echelon_form() uf = DisjointSet(self.groundset()) for i in xrange(A.nrows()): for j in xrange(i+1, A.ncols()): if A[i,j] != 0: uf.union(new_groundset[i], new_groundset[j]) return SetPartition(uf)
def _vectors_in_shell( F, bound, max = 1000): """ For an (integral) positive symmetric matrix $F$ return a list of vectors such that $x^tFx <= bound$. The method returns only nonzero vectors and for each pair $v$ and $-v$ only one. NOTE A wrapper for the pari method qfminim(). """ # assert bound >=1, 'Pari bug: bound should be >= 2' if bound < 1: return [] p = F._pari_() po = p.qfminim( bound, max, flag = 0).sage() if max == po[0]: raise ArithmeticError( 'Increase max') ves = [vector([0]*F.nrows())] for x in po[2].columns(): ves += [x,-x] # return po[2].columns() return ves
def intersection_point (a1, p1, a2, p2) : r"""Intersection point. INPUTS: * ``a1``, ``p1`` - a line defined as a1 x = a1 p1 * ``a2``, ``p2`` - a line defined as a2 x = a2 p2 NOTES: * we assume that a1 and a2 has to be 2d vectors. * if a1 and a2 are colinears, p2 is returned by convention. """ M = matrix ([a1, a2]) if (M.determinant () == 0) : return p2 else : b = vector ([a1.dot_product(p1), a2.dot_product(p2)]) return M.solve_right (b)
def matrice_systeme(systeme, variables): """ Renvoie une matrice par block représentant un programme linéaire sous forme standard. INPUT:: - ``systeme`` -- Un programme linéaire sous forme standard - ``variables`` -- La liste des variables du système EXAMPLES:: sage: x = x1,x2,x3 = var('x1,x2,x3') sage: Chvatal13 = [[2*x1 + 3*x2 + x3 <= 5, ....: 4*x1 + x2 + 2*x3 <= 11, ....: 3*x1 + 4*x2 + 2*x3 <= 8], ....: 5*x1 + 4*x2 + 3*x3] sage: m = matrice_systeme(Chvatal13, x); m [ z|s1 s2 s3|x1 x2 x3| 0] [--+--------+--------+--] [ 1| 0 0 0|-5 -4 -3| 0] [--+--------+--------+--] [ 0| 1 0 0| 2 3 1| 5] [ 0| 0 1 0| 4 1 2|11] [ 0| 0 0 1| 3 4 2| 8] """ def liste_coeffs(expression): return [expression.coeff(v) for v in variables] inequations = systeme[0] m = matrix([liste_coeffs(ineq.lhs()) for ineq in inequations]) rhs = vector(ineq.rhs() for ineq in inequations).column() slack = SR.var(','.join("s%s" % i for i in range(1, len(inequations) + 1))) z = SR.var('z') return block_matrix( [[z, matrix([slack]), matrix([variables]), ZZ(0)], [ZZ(1), ZZ(0), -matrix([liste_coeffs(systeme[1])]), ZZ(0)], [ZZ(0), ZZ(1), m, rhs]])
def __contains__(self, q): r""" Test whether the point ``q`` is in the affine space. INPUT: - ``q`` -- point as a list/tuple/iterable OUTPUT: A boolean. EXAMPLES:: sage: from sage.geometry.hyperplane_arrangement.affine_subspace import AffineSubspace sage: a = AffineSubspace([1,0,0], matrix([[1,0,0]]).right_kernel()) sage: (1,1,0) in a True sage: (0,0,0) in a False """ q = vector(self._base_ring, q) return self._point - q in self._linear_part
def cohomology(self, degree=None, weight=None, dim=False): r""" Return the bundle cohomology groups. INPUT: - ``degree`` -- ``None`` (default) or an integer. The degree of the cohomology group. - ``weight`` -- ``None`` (default) or a tuple of integers or a `M`-lattice point. A point in the dual lattice of the fan defining a torus character. The weight of the cohomology group. - ``dim`` -- Boolean (default: ``False``). Whether to return vector spaces or only their dimension. OUTPUT: The cohomology group of given cohomological ``degree`` and torus ``weight``. * If no ``weight`` is specified, the unweighted group (sum over all weights) is returned. * If no ``degree`` is specified, a dictionary whose keys are integers and whose values are the cohomology groups is returned. If, in addition, ``dim=True``, then an integral vector of the dimensions is returned. EXAMPLES:: sage: V = toric_varieties.P2().sheaves.tangent_bundle() sage: V.cohomology(degree=0, weight=(0,0)) Vector space of dimension 2 over Rational Field sage: V.cohomology(weight=(0,0), dim=True) (2, 0, 0) sage: for i,j in cartesian_product((list(range(-2,3)), list(range(-2,3)))): ....: HH = V.cohomology(weight=(i,j), dim=True) ....: if HH.is_zero(): continue ....: print('H^*i(P^2, TP^2)_M({}, {}) = {}'.format(i,j,HH)) H^*i(P^2, TP^2)_M(-1, 0) = (1, 0, 0) H^*i(P^2, TP^2)_M(-1, 1) = (1, 0, 0) H^*i(P^2, TP^2)_M(0, -1) = (1, 0, 0) H^*i(P^2, TP^2)_M(0, 0) = (2, 0, 0) H^*i(P^2, TP^2)_M(0, 1) = (1, 0, 0) H^*i(P^2, TP^2)_M(1, -1) = (1, 0, 0) H^*i(P^2, TP^2)_M(1, 0) = (1, 0, 0) """ from sage.modules.all import FreeModule if weight is None: raise NotImplementedError('sum over weights is not implemented') else: weight = self.variety().fan().dual_lattice()(weight) weight.set_immutable() if degree is not None: return self.cohomology(weight=weight, dim=dim)[degree] C = self.cohomology_complex(weight) space_dim = self._variety.dimension() C_homology = C.homology() HH = dict() for d in range(space_dim + 1): try: HH[d] = C_homology[d] except KeyError: HH[d] = FreeModule(self.base_ring(), 0) if dim: HH = vector(ZZ, [HH[i].rank() for i in range(space_dim + 1)]) return HH
def polyhedron_from_Hrep(A, b, base_ring=QQ): r"""Builds a polytope given the H-representation, in the form `Ax \leq b` INPUT: * ``A`` - matrix of size m x n, in RDF or QQ ring. Accepts generic Sage matrix, and also a Numpy arrays with a matrix shape. * ``b`` - vector of size m, in RDF or QQ ring. Accepts generic Sage matrix, and also a Numpy array. * ``base_ring`` - (default: ``QQ``). Specifies the ring (base_ring) for the Polyhedron constructor. Valid choices are: * ``QQ`` - rational. Uses ``'ppl'`` (Parma Polyhedra Library) backend * ``RDF`` - Real double field. Uses ``'cdd'`` backend. OUTPUT: * "P" - a Polyhedron object TO-DO: * accept numpy arrays. notice that we often handle numpy arrays (for instance if we load some data from matlab using the function ``scipy.io.loadmat(..)``, then the data will be loaded as a dictionary of numpy arrays) EXAMPLES:: sage: A = matrix(RDF, [[-1.0, 0.0, 0.0, 0.0, 0.0, 0.0], ....: [ 1.0, 0.0, 0.0, 0.0, 0.0, 0.0], ....: [ 0.0, 1.0, 0.0, 0.0, 0.0, 0.0], ....: [ 0.0, -1.0, 0.0, 0.0, 0.0, 0.0], ....: [ 0.0, 0.0, -1.0, 0.0, 0.0, 0.0], ....: [ 0.0, 0.0, 1.0, 0.0, 0.0, 0.0], ....: [ 0.0, 0.0, 0.0, -1.0, 0.0, 0.0], ....: [ 0.0, 0.0, 0.0, 1.0, 0.0, 0.0], ....: [ 0.0, 0.0, 0.0, 0.0, 1.0, 0.0], ....: [ 0.0, 0.0, 0.0, 0.0, -1.0, 0.0], ....: [ 0.0, 0.0, 0.0, 0.0, 0.0, 1.0], ....: [ 0.0, 0.0, 0.0, 0.0, 0.0, -1.0]]) sage: b = vector(RDF, [0.0, 10.0, 0.0, 0.0, 0.2, 0.2, 0.1, 0.1, 0.0, 0.0, 0.0, 0.0]) sage: from polyhedron_tools.misc import polyhedron_from_Hrep sage: P = polyhedron_from_Hrep(A, b, base_ring=QQ); P A 3-dimensional polyhedron in QQ^6 defined as the convex hull of 8 vertices NOTES: - This function is useful especially when the input matrices `A`, `b` are ill-defined (constraints that differ by tiny amounts making the input data to be degenerate or almost degenerate), causing problems to Polyhedron(...). - In this case it is recommended to use ``base_ring = QQ``. Each element of `A` and `b` will be converted to rational, and this will be sent to Polyhedron. Note that Polyhedron automatically removes redundant constraints. """ if (base_ring == RDF): if 'numpy.ndarray' in str(type(A)): # assuming that b is also a numpy array m = A.shape[0] n = A.shape[1] b_RDF = vector(RDF, m, [RDF(bi) for bi in b]) A_RDF = matrix(RDF, m, n) for i in range(m): A_RDF.set_row(i, [RDF(A[i][j]) for j in range(n)]) A = copy(A_RDF) b = copy(b_RDF) ambient_dim = A.ncols() # transform to real, if needed if A.base_ring() != RDF: A.change_ring(RDF) if b.base_ring() != RDF: b.change_ring(RDF) ieqs_list = [] for i in range(A.nrows()): ieqs_list.append( list(-A.row(i)) ) #change in sign, necessary since Polyhedron receives Ax+b>=0 ieqs_list[i].insert(0, b[i]) P = Polyhedron(ieqs=ieqs_list, base_ring=RDF, ambient_dim=A.ncols(), backend='cdd') elif (base_ring == QQ): if 'numpy.ndarray' in str(type(A)): # assuming that b is also a numpy array m = A.shape[0] n = A.shape[1] b_QQ = vector(QQ, m, [QQ(b[i]) for i in range(m)]) A_QQ = matrix(QQ, m, n) for i in range(m): A_QQ.set_row(i, [QQ(A[i][j]) for j in range(n)]) A = copy(A_QQ) b = copy(b_QQ) ambient_dim = A.ncols() # transform to rational, if needed if A.base_ring() != QQ: #for i in range(A.nrows()): # A.set_row(i,[QQ(A.row(i)[j]) for j in range(ambient_dim)]); A.change_ring(QQ) if b.base_ring() != QQ: #b = vector(QQ, [QQ(bi) for bi in b]); b.change_ring(QQ) ieqs_list = [] for i in range(A.nrows()): ieqs_list.append( list(-A.row(i)) ) #change in sign, necessary since Polyhedron receives Ax+b>=0 ieqs_list[i].insert(0, b[i]) P = Polyhedron(ieqs=ieqs_list, base_ring=QQ, ambient_dim=A.ncols(), backend='ppl') else: raise ValueError('Base ring not supported. Try with RDF or QQ.') return P
def poset_of_layers(self): """ Compute the poset of layers of the associated toric arrangement, using Lenz's algorithm [Len17a]. """ # TODO: implement for Q != 0 if self._Q.ncols() > 0: raise NotImplementedError A = self._A.transpose() E = range(A.nrows()) data = {} # compute Smith normal forms of all submatrices for S in powerset(E): D, U, V = A[S,:].smith_form() # D == U*A[S,:]*V diagonal = [D[i,i] if i < D.ncols() else 0 for i in xrange(len(S))] data[tuple(S)] = (diagonal, U) # generate al possible elements of the poset of layers elements = {tuple(S): list(vector(ZZ, x) for x in itertools.product(*(range(max(data[tuple(S)][0][i], 1)) for i in xrange(len(S))))) for S in powerset(E)} for l in elements.itervalues(): for v in l: v.set_immutable() possible_layers = list((S, x) for (S, l) in elements.iteritems() for x in l) uf = DisjointSet(possible_layers) cover_relations = [] for (S, l) in elements.iteritems(): diagonal_S, U_S = data[S] rk_S = A[S,:].rank() for s in S: i = S.index(s) # index where the element s appears in S T = tuple(t for t in S if t != s) diagonal_T, U_T = data[T] rk_T = A[T,:].rank() for x in l: h = (S, x) y = U_S**(-1) * x z = U_T * vector(ZZ, y[:i].list() + y[i+1:].list()) w = vector(ZZ, (a % diagonal_T[j] if diagonal_T[j] > 0 else 0 for j, a in enumerate(z))) w.set_immutable() ph = (T, w) if rk_S == rk_T: uf.union(h, ph) else: cover_relations.append((ph, h)) # find representatives for layers (we keep the representative (S,x) with maximal S) root_to_representative_dict = {} for root, subset in uf.root_to_elements_dict().iteritems(): S, x = max(subset, key=lambda (S, x): len(S)) S_labeled = tuple(self._E[i] for i in S) root_to_representative_dict[root] = (S_labeled, x) # get layers and cover relations layers = root_to_representative_dict.values() cover_relations = set( (root_to_representative_dict[uf.find(a)], root_to_representative_dict[uf.find(b)]) for (a,b) in cover_relations) return Poset(data=(layers, cover_relations), cover_relations=True)
def integral_points(self, threshold=10000): r""" Return the integral points in the polyhedron. Uses either the naive algorithm (iterate over a rectangular bounding box) or triangulation + Smith form. INPUT: - ``threshold`` -- integer (default: 10000); use the naïve algorithm as long as the bounding box is smaller than this OUTPUT: The list of integral points in the polyhedron. If the polyhedron is not compact, a ``ValueError`` is raised. EXAMPLES:: sage: Polyhedron(vertices=[(-1,-1), (1,0), (1,1), (0,1)], # optional - pynormaliz ....: backend='normaliz').integral_points() ((-1, -1), (0, 0), (0, 1), (1, 0), (1, 1)) sage: simplex = Polyhedron([(1,2,3), (2,3,7), (-2,-3,-11)], # optional - pynormaliz ....: backend='normaliz') sage: simplex.integral_points() # optional - pynormaliz ((-2, -3, -11), (0, 0, -2), (1, 2, 3), (2, 3, 7)) The polyhedron need not be full-dimensional:: sage: simplex = Polyhedron([(1,2,3,5), (2,3,7,5), (-2,-3,-11,5)], # optional - pynormaliz ....: backend='normaliz') sage: simplex.integral_points() # optional - pynormaliz ((-2, -3, -11, 5), (0, 0, -2, 5), (1, 2, 3, 5), (2, 3, 7, 5)) sage: point = Polyhedron([(2,3,7)], # optional - pynormaliz ....: backend='normaliz') sage: point.integral_points() # optional - pynormaliz ((2, 3, 7),) sage: empty = Polyhedron(backend='normaliz') # optional - pynormaliz sage: empty.integral_points() # optional - pynormaliz () Here is a simplex where the naive algorithm of running over all points in a rectangular bounding box no longer works fast enough:: sage: v = [(1,0,7,-1), (-2,-2,4,-3), (-1,-1,-1,4), (2,9,0,-5), (-2,-1,5,1)] sage: simplex = Polyhedron(v, backend='normaliz'); simplex # optional - pynormaliz A 4-dimensional polyhedron in ZZ^4 defined as the convex hull of 5 vertices sage: len(simplex.integral_points()) # optional - pynormaliz 49 A rather thin polytope for which the bounding box method would be a very bad idea (note this is a rational (non-lattice) polytope, so the other backends use the bounding box method):: sage: P = Polyhedron(vertices=((0, 0), (178933,37121))) + 1/1000*polytopes.hypercube(2) sage: P = Polyhedron(vertices=P.vertices_list(), # optional - pynormaliz ....: backend='normaliz') sage: len(P.integral_points()) # optional - pynormaliz 434 Finally, the 3-d reflexive polytope number 4078:: sage: v = [(1,0,0), (0,1,0), (0,0,1), (0,0,-1), (0,-2,1), ....: (-1,2,-1), (-1,2,-2), (-1,1,-2), (-1,-1,2), (-1,-3,2)] sage: P = Polyhedron(v, backend='normaliz') # optional - pynormaliz sage: pts1 = P.integral_points() # optional - pynormaliz sage: all(P.contains(p) for p in pts1) # optional - pynormaliz True sage: pts2 = LatticePolytope(v).points() # PALP sage: for p in pts1: p.set_immutable() # optional - pynormaliz sage: set(pts1) == set(pts2) # optional - pynormaliz True sage: timeit('Polyhedron(v, backend='normaliz').integral_points()') # not tested - random 625 loops, best of 3: 1.41 ms per loop sage: timeit('LatticePolytope(v).points()') # not tested - random 25 loops, best of 3: 17.2 ms per loop TESTS: Test some trivial cases (see :trac:`17937`): Empty polyhedron in 1 dimension:: sage: P = Polyhedron(ambient_dim=1, backend='normaliz') # optional - pynormaliz sage: P.integral_points() # optional - pynormaliz () Empty polyhedron in 0 dimensions:: sage: P = Polyhedron(ambient_dim=0, backend='normaliz') # optional - pynormaliz sage: P.integral_points() # optional - pynormaliz () Single point in 1 dimension:: sage: P = Polyhedron([[3]], backend='normaliz') # optional - pynormaliz sage: P.integral_points() # optional - pynormaliz ((3),) Single non-integral point in 1 dimension:: sage: P = Polyhedron([[1/2]], backend='normaliz') # optional - pynormaliz sage: P.integral_points() # optional - pynormaliz () Single point in 0 dimensions:: sage: P = Polyhedron([[]], backend='normaliz') # optional - pynormaliz sage: P.integral_points() # optional - pynormaliz ((),) A polytope with no integral points (:trac:`22938`):: sage: ieqs = [[1, 2, -1, 0], [0, -1, 2, -1], [0, 0, -1, 2], ....: [0, -1, 0, 0], [0, 0, -1, 0], [0, 0, 0, -1], ....: [-1, -1, -1, -1], [1, 1, 0, 0], [1, 0, 1, 0], ....: [1, 0, 0, 1]] sage: P = Polyhedron(ieqs=ieqs, backend='normaliz') # optional - pynormaliz sage: P.bounding_box() # optional - pynormaliz ((-3/4, -1/2, -1/4), (-1/2, -1/4, 0)) sage: P.bounding_box(integral_hull=True) # optional - pynormaliz (None, None) sage: P.integral_points() # optional - pynormaliz () """ import PyNormaliz if not self.is_compact(): raise ValueError( 'can only enumerate points in a compact polyhedron') # Trivial cases: polyhedron with 0 or 1 vertices if self.n_vertices() == 0: return () if self.n_vertices() == 1: v = self.vertices_list()[0] try: return (vector(ZZ, v), ) except TypeError: # vertex not integral return () # for small bounding boxes, it is faster to naively iterate over the points of the box if threshold > 1: box_min, box_max = self.bounding_box(integral_hull=True) if box_min is None: return () box_points = prod( max_coord - min_coord + 1 for min_coord, max_coord in zip(box_min, box_max)) if box_points < threshold: from sage.geometry.integral_points import rectangular_box_points return rectangular_box_points(list(box_min), list(box_max), self) # Compute with normaliz points = [] cone = self._normaliz_cone assert cone for g in PyNormaliz.NmzResult(cone, "ModuleGenerators"): assert g[-1] == 1 points.append(vector(ZZ, g[:-1])) return tuple(points)
def cohomology(self, degree=None, weight=None, dim=False): r""" Return the bundle cohomology groups. INPUT: - ``degree`` -- ``None`` (default) or an integer. The degree of the cohomology group. - ``weight`` -- ``None`` (default) or a tuple of integers or a `M`-lattice point. A point in the dual lattice of the fan defining a torus character. The weight of the cohomology group. - ``dim`` -- Boolean (default: ``False``). Whether to return vector spaces or only their dimension. OUTPUT: The cohomology group of given cohomological ``degree`` and torus ``weight``. * If no ``weight`` is specified, the unweighted group (sum over all weights) is returned. * If no ``degree`` is specified, a dictionary whose keys are integers and whose values are the cohomology groups is returned. If, in addition, ``dim=True``, then an integral vector of the dimensions is returned. EXAMPLES:: sage: V = toric_varieties.P2().sheaves.tangent_bundle() sage: V.cohomology(degree=0, weight=(0,0)) Vector space of dimension 2 over Rational Field sage: V.cohomology(weight=(0,0), dim=True) (2, 0, 0) sage: for i,j in cartesian_product((range(-2,3), range(-2,3))): ....: HH = V.cohomology(weight=(i,j), dim=True) ....: if HH.is_zero(): continue ....: print('H^*i(P^2, TP^2)_M({}, {}) = {}'.format(i,j,HH)) H^*i(P^2, TP^2)_M(-1, 0) = (1, 0, 0) H^*i(P^2, TP^2)_M(-1, 1) = (1, 0, 0) H^*i(P^2, TP^2)_M(0, -1) = (1, 0, 0) H^*i(P^2, TP^2)_M(0, 0) = (2, 0, 0) H^*i(P^2, TP^2)_M(0, 1) = (1, 0, 0) H^*i(P^2, TP^2)_M(1, -1) = (1, 0, 0) H^*i(P^2, TP^2)_M(1, 0) = (1, 0, 0) """ from sage.modules.all import FreeModule if weight is None: raise NotImplementedError('sum over weights is not implemented') else: weight = self.variety().fan().dual_lattice()(weight) weight.set_immutable() if degree is not None: return self.cohomology(weight=weight, dim=dim)[degree] C = self.cohomology_complex(weight) space_dim = self._variety.dimension() C_homology = C.homology() HH = dict() for d in range(0, space_dim+1): try: HH[d] = C_homology[d] except KeyError: HH[d] = FreeModule(self.base_ring(), 0) if dim: HH = vector(ZZ, [HH[i].rank() for i in range(0, space_dim+1) ]) return HH
def make_positive(vec): """ Returns a parallel vector to a given vector with all positive coordinates if possible. INPUT: - ``vec`` - a vector or list or tuple of numbers OUTPUT: - vector - a vector with positive coordinates if this is possible, otherwise -1 EXAMPLES: If all coordinates of the input vector are already positive, the same vector is returned:: sage: from sage.dynamics.foliations.foliation import make_positive sage: v = vector([1, 3, 5]) sage: make_positive(v) == v True If all are negative, its negative is returned:: sage: make_positive((-1, -5)) (1, 5) Even if the coordinates are complex, but have very small imaginary part as a result of an approximate eigenvector calculation for example, the coordinates are treated as real:: sage: make_positive((40.24 - 5.64e-16*I, 1.2 + 4.3e-14*I)) (40.2400000000000, 1.20000000000000) sage: make_positive((-40.24 - 5.64e-16*I, -1.2 + 4.3e-14*I)) (40.2400000000000, 1.20000000000000) If there is a complex coordinate which is not negligible, -1 is returned:: sage: make_positive((-40.24 - 5.64e-6*I, -1.2 + 4.3e-14*I)) -1 If one coordinate is zero, or very close to zero, -1 is returned:: sage: make_positive((1, 0, 2)) -1 sage: make_positive((-40.24e-15*I - 5.64e-16*I, -1.2)) -1 If there are both negative and positive coordinates, -1 is returned:: sage: make_positive((-3, 4, 5)) -1 """ if any(abs(x) < EPSILON for x in vec) or \ any(abs(x.imag()) > EPSILON for x in vec): return -1 newvec = vector([x.real() for x in vec]) if vec[0].real() < 0: newvec = -newvec if any(x < 0 for x in newvec): return -1 return newvec
def lotov_algo (A, b, v1, v2, err, rel=1) : r""" Approximate two-dimensional projection of a convex polytope by Lotov's algorithm. INPUT: * ``A`` - a matrix of M of size k x n (R) * ``b`` - a vector of Rk * ``v1``, ``v2`` - two vectors of R^n * ``err`` ( > 0) - the maximal error * ``rel`` - true if the error is relative (err in ]0, 1]), and false if absolute (err > 0) """ # init err = float (err) Mnd2d = matrix ([v1, v2]) M2dnd = Mnd2d.transpose () # 1) first approximation dn2d = vector ([0, 1]) dw2d = vector ([-1, 0]) ds2d = vector ([0, -1]) de2d = vector ([1, 0]) dnnd = M2dnd * dn2d dwnd = M2dnd * dw2d dsnd = M2dnd * ds2d dend = M2dnd * de2d # border points computation #print dnnd[0], ' ', dnnd[1], ' ', dnnd[2] n = Mnd2d * extreme_point (A, b, dnnd) w = Mnd2d * extreme_point (A, b, dwnd) s = Mnd2d * extreme_point (A, b, dsnd) e = Mnd2d * extreme_point (A, b, dend) # calculus of the bounding-box and epsilon if relative err if rel : xMin = w[0] xMax = e[0] yMin = s[1] yMax = n[1] err = max ([xMax - xMin, yMax - yMin]) * err # first underapproximation fi = [[n, w], [w, s], [s, e], [e, n]] # first overapproximation oi = [intersection_point (dn2d, n, dw2d, w), intersection_point (dw2d, w, ds2d, s), intersection_point (ds2d, s, de2d, e), intersection_point (de2d, e, dn2d, n)] # distances calculus di = [] iMax = 0 k = 4 for i in range (0, k) : di = di + [point_line_distance (fi[i], oi[i])] if (di[i] > di[iMax]) : iMax = i # 2) successive approximations while (di[iMax] > err) : #print 'k', k # refinement f = fi[iMax] p1 = f[0] p2 = f[1] d2d = normal_vector (p2 - p1) dnd = M2dnd * d2d pnd = extreme_point (A, b, dnd) p = Mnd2d * pnd q = intersection_point (normal_vector (p1 - oi[previous_i (iMax, k)]), p1, d2d, p) r = intersection_point (normal_vector (oi[next_i (iMax, k)] - p2), p2, d2d, p) # lists update fi = [fi[i] for i in range (0, iMax)] + [[p1, p], [p, p2]] + [fi[i] for i in range (iMax + 1, k)] oi = [oi[i] for i in range (0, iMax)] + [q, r] + [oi[i] for i in range (iMax + 1, k)] dq = point_line_distance ([p1, p], q) dr = point_line_distance ([p2, p], r) di = [di[i] for i in range (0, iMax)] + [dq, dr] + [di[i] for i in range (iMax + 1, k)] k = k + 1 # next iMax calculus iMax = 0 for i in range (1, k) : if (di[i] > di[iMax]) : iMax = i # result packaging and return ui = [] for i in range (0, len (fi)) : ui = ui + [fi[i][0]] return [oi, ui]
def asphericity_polytope(P, tol=1e-1, MaxIter=1e2, verbose=0, solver='GLPK'): r""" Algorithm for computing asphericity INPUT: ``x0`` - point in the interior of P ``tol`` - stop condition OUTPUT: ``psi_opt`` - `\min_{x \in P} psi(x) = R(x)/r(x)`, where `R(x)` and `r(x)` are the circumradius and the inradius at `x` respectively ``alphakList`` - sequence of `\psi(x)` which converges to `\psi_{\text{opt}}` ``xkList`` - sequence of interior points which converge to the minimum """ # check if the polytope is not full-dimensional -- in this case the notion # of asphericity still makes sense but there is yet no "affine function" (or # "relative interior") method from the Polyhedron class that we can use # see :trac:16045 if not P.is_full_dimensional(): raise NotImplementedError('The polytope is not full-dimensional') # initial data p = P.ambient_dim() [DP, d] = polyhedron_to_Hrep(P) # in the form Dy <= d D = -DP # transform to <Dj,y> + dj >= 0 l = D.nrows() mP = opposite_polyhedron(P) # unit ball, infinity norm Bn = BoxInfty(center=zero_vector(RDF, p), radius=1) [C, c] = polyhedron_to_Hrep(Bn) C = -C # transform to <Cj,y> + cj >= 0 m = len(c) # auxiliary matrices A, a, B, b A = matrix(RDF, C.nrows(), C.ncols()) a = [] for i in range(m): A.set_row(i, -C.row(i) / c[i]) a.append(support_function(mP, A.row(i), solver=solver)) B = matrix(RDF, D.nrows(), D.ncols()) b = [] for j in range(l): [nDj, ymax] = support_function(Bn, D.row(j), return_xopt=True, solver=solver) B.set_row(j, D.row(j) / nDj) b.append(d[j] / nDj) # generate an initial point x0 = chebyshev_center(DP, d) # compute asphericity at x0 R = max(A.row(i) * vector(x0) + a[i] for i in range(m)) r = min(B.row(j) * vector(x0) + b[j] for j in range(l)) alpha0 = R / r xkList = [vector(x0)] alphakList = [alpha0] xk0 = x0 psi_k0 = alpha0 alphak0 = alpha0 convergeFlag = 0 iterCount = 0 while (iterCount < MaxIter) and (not convergeFlag): [psi_k, xkDict, zkDict] = _asphericity_iteration(xk0, l, m, p, A, a, B, b, verbose=verbose, solver=solver) xk = vector(xkDict) xkList.append(xk) zk = vector(zkDict) alphakList.append(zk[0]) xk0 = xk if (abs(psi_k - psi_k0) < tol): convergeFlag = 1 psi_k0 = psi_k iterCount += 1 x_asph = xkList.pop() R_asph = max(A.row(i) * vector(x_asph) + a[i] for i in range(m)) r_asph = min(B.row(j) * vector(x_asph) + b[j] for j in range(l)) asph = R_asph / r_asph return [asph, x_asph]
def lattice_automorphism_group(self, points=None, point_labels=None): """ The integral subgroup of the restricted automorphism group. INPUT: - ``points`` -- A tuple of coordinate vectors or ``None`` (default). If specified, the points must form complete orbits under the lattice automorphism group. If ``None`` all vertices are used. - ``point_labels`` -- A tuple of labels for the ``points`` or ``None`` (default). These will be used as labels for the do permutation group. If ``None`` the ``points`` will be used themselves. OUTPUT: The integral subgroup of the restricted automorphism group acting on the given ``points``, or all vertices if not specified. EXAMPLES:: sage: from sage.geometry.polyhedron.ppl_lattice_polytope import LatticePolytope_PPL sage: Z3square = LatticePolytope_PPL((0,0), (1,2), (2,1), (3,3)) sage: Z3square.lattice_automorphism_group() Permutation Group with generators [(), ((1,2),(2,1)), ((0,0),(3,3)), ((0,0),(3,3))((1,2),(2,1))] sage: G1 = Z3square.lattice_automorphism_group(point_labels=(1,2,3,4)); G1 Permutation Group with generators [(), (2,3), (1,4), (1,4)(2,3)] sage: G1.cardinality() 4 sage: G2 = Z3square.restricted_automorphism_group(vertex_labels=(1,2,3,4)); G2 Permutation Group with generators [(2,3), (1,2)(3,4), (1,4)] sage: G2.cardinality() 8 sage: points = Z3square.integral_points(); points ((0, 0), (1, 1), (1, 2), (2, 1), (2, 2), (3, 3)) sage: Z3square.lattice_automorphism_group(points, point_labels=(1,2,3,4,5,6)) Permutation Group with generators [(), (3,4), (1,6)(2,5), (1,6)(2,5)(3,4)] """ if not self.is_full_dimensional(): return self.affine_lattice_polytope().lattice_automorphism_group() if points is None: points = self.vertices() if point_labels is None: point_labels = tuple(points) points = [ vector(ZZ, [1]+v.list()) for v in points ] map(lambda x:x.set_immutable(), points) vertices = [ vector(ZZ, [1]+v.list()) for v in self.vertices() ] pivots = matrix(ZZ, vertices).pivot_rows() basis = matrix(ZZ, [ vertices[i] for i in pivots ]) Mat_ZZ = basis.parent() basis_inverse = basis.inverse() from sage.groups.perm_gps.permgroup import PermutationGroup from sage.groups.perm_gps.permgroup_element import PermutationGroupElement lattice_gens = [] G = self.restricted_automorphism_group( vertex_labels=tuple(range(len(vertices)))) for g in G: image = matrix(ZZ, [ vertices[g(i)] for i in pivots ]) m = basis_inverse*image if m not in Mat_ZZ: continue perm_list = [ point_labels[points.index(p*m)] for p in points ] lattice_gens.append(perm_list) return PermutationGroup(lattice_gens, domain=point_labels)
def integral_points(self, threshold=10000): r""" Return the integral points in the polyhedron. Uses either the naive algorithm (iterate over a rectangular bounding box) or triangulation + Smith form. INPUT: - ``threshold`` -- integer (default: 10000); use the naïve algorithm as long as the bounding box is smaller than this OUTPUT: The list of integral points in the polyhedron. If the polyhedron is not compact, a ``ValueError`` is raised. EXAMPLES:: sage: Polyhedron(vertices=[(-1,-1), (1,0), (1,1), (0,1)], # optional - pynormaliz ....: backend='normaliz').integral_points() ((-1, -1), (0, 0), (0, 1), (1, 0), (1, 1)) sage: simplex = Polyhedron([(1,2,3), (2,3,7), (-2,-3,-11)], # optional - pynormaliz ....: backend='normaliz') sage: simplex.integral_points() # optional - pynormaliz ((-2, -3, -11), (0, 0, -2), (1, 2, 3), (2, 3, 7)) The polyhedron need not be full-dimensional:: sage: simplex = Polyhedron([(1,2,3,5), (2,3,7,5), (-2,-3,-11,5)], # optional - pynormaliz ....: backend='normaliz') sage: simplex.integral_points() # optional - pynormaliz ((-2, -3, -11, 5), (0, 0, -2, 5), (1, 2, 3, 5), (2, 3, 7, 5)) sage: point = Polyhedron([(2,3,7)], # optional - pynormaliz ....: backend='normaliz') sage: point.integral_points() # optional - pynormaliz ((2, 3, 7),) sage: empty = Polyhedron(backend='normaliz') # optional - pynormaliz sage: empty.integral_points() # optional - pynormaliz () Here is a simplex where the naive algorithm of running over all points in a rectangular bounding box no longer works fast enough:: sage: v = [(1,0,7,-1), (-2,-2,4,-3), (-1,-1,-1,4), (2,9,0,-5), (-2,-1,5,1)] sage: simplex = Polyhedron(v, backend='normaliz'); simplex # optional - pynormaliz A 4-dimensional polyhedron in ZZ^4 defined as the convex hull of 5 vertices sage: len(simplex.integral_points()) # optional - pynormaliz 49 A rather thin polytope for which the bounding box method would be a very bad idea (note this is a rational (non-lattice) polytope, so the other backends use the bounding box method):: sage: P = Polyhedron(vertices=((0, 0), (178933,37121))) + 1/1000*polytopes.hypercube(2) sage: P = Polyhedron(vertices=P.vertices_list(), # optional - pynormaliz ....: backend='normaliz') sage: len(P.integral_points()) # optional - pynormaliz 434 Finally, the 3-d reflexive polytope number 4078:: sage: v = [(1,0,0), (0,1,0), (0,0,1), (0,0,-1), (0,-2,1), ....: (-1,2,-1), (-1,2,-2), (-1,1,-2), (-1,-1,2), (-1,-3,2)] sage: P = Polyhedron(v, backend='normaliz') # optional - pynormaliz sage: pts1 = P.integral_points() # optional - pynormaliz sage: all(P.contains(p) for p in pts1) # optional - pynormaliz True sage: pts2 = LatticePolytope(v).points() # PALP sage: for p in pts1: p.set_immutable() # optional - pynormaliz sage: set(pts1) == set(pts2) # optional - pynormaliz True sage: timeit('Polyhedron(v, backend='normaliz').integral_points()') # not tested - random 625 loops, best of 3: 1.41 ms per loop sage: timeit('LatticePolytope(v).points()') # not tested - random 25 loops, best of 3: 17.2 ms per loop TESTS: Test some trivial cases (see :trac:`17937`): Empty polyhedron in 1 dimension:: sage: P = Polyhedron(ambient_dim=1, backend='normaliz') # optional - pynormaliz sage: P.integral_points() # optional - pynormaliz () Empty polyhedron in 0 dimensions:: sage: P = Polyhedron(ambient_dim=0, backend='normaliz') # optional - pynormaliz sage: P.integral_points() # optional - pynormaliz () Single point in 1 dimension:: sage: P = Polyhedron([[3]], backend='normaliz') # optional - pynormaliz sage: P.integral_points() # optional - pynormaliz ((3),) Single non-integral point in 1 dimension:: sage: P = Polyhedron([[1/2]], backend='normaliz') # optional - pynormaliz sage: P.integral_points() # optional - pynormaliz () Single point in 0 dimensions:: sage: P = Polyhedron([[]], backend='normaliz') # optional - pynormaliz sage: P.integral_points() # optional - pynormaliz ((),) A polytope with no integral points (:trac:`22938`):: sage: ieqs = [[1, 2, -1, 0], [0, -1, 2, -1], [0, 0, -1, 2], ....: [0, -1, 0, 0], [0, 0, -1, 0], [0, 0, 0, -1], ....: [-1, -1, -1, -1], [1, 1, 0, 0], [1, 0, 1, 0], ....: [1, 0, 0, 1]] sage: P = Polyhedron(ieqs=ieqs, backend='normaliz') # optional - pynormaliz sage: P.bounding_box() # optional - pynormaliz ((-3/4, -1/2, -1/4), (-1/2, -1/4, 0)) sage: P.bounding_box(integral_hull=True) # optional - pynormaliz (None, None) sage: P.integral_points() # optional - pynormaliz () Check the polytopes from :trac:`22984`:: sage: base = [[0, 2, 0, -1, 0, 0, 0, 0, 0], ....: [0, 0, 2, 0, -1, 0, 0, 0, 0], ....: [1, -1, 0, 2, -1, 0, 0, 0, 0], ....: [0, 0, -1, -1, 2, -1, 0, 0, 0], ....: [0, 0, 0, 0, -1, 2, -1, 0, 0], ....: [0, 0, 0, 0, 0, -1, 2, -1, 0], ....: [1, 0, 0, 0, 0, 0, -1, 2, -1], ....: [0, 0, 0, 0, 0, 0, 0, -1, 2], ....: [0, -1, 0, 0, 0, 0, 0, 0, 0], ....: [0, 0, -1, 0, 0, 0, 0, 0, 0], ....: [0, 0, 0, -1, 0, 0, 0, 0, 0], ....: [0, 0, 0, 0, -1, 0, 0, 0, 0], ....: [0, 0, 0, 0, 0, -1, 0, 0, 0], ....: [0, 0, 0, 0, 0, 0, -1, 0, 0], ....: [0, 0, 0, 0, 0, 0, 0, -1, 0], ....: [0, 0, 0, 0, 0, 0, 0, 0, -1], ....: [-1, -1, -1, -1, -1, -1, -1, -1, -1]] sage: ieqs = base + [ ....: [2, 1, 0, 0, 0, 0, 0, 0, 0], ....: [4, 0, 1, 0, 0, 0, 0, 0, 0], ....: [4, 0, 0, 1, 0, 0, 0, 0, 0], ....: [7, 0, 0, 0, 1, 0, 0, 0, 0], ....: [6, 0, 0, 0, 0, 1, 0, 0, 0], ....: [4, 0, 0, 0, 0, 0, 1, 0, 0], ....: [2, 0, 0, 0, 0, 0, 0, 1, 0], ....: [1, 0, 0, 0, 0, 0, 0, 0, 1]] sage: P = Polyhedron(ieqs=ieqs, backend='normaliz') # optional - pynormaliz sage: P.integral_points() # optional - pynormaliz ((-2, -2, -4, -5, -4, -3, -2, -1), (-2, -2, -4, -5, -4, -3, -2, 0), (-1, -2, -3, -4, -3, -2, -2, -1), (-1, -2, -3, -4, -3, -2, -1, 0), (-1, -1, -2, -2, -2, -2, -2, -1), (-1, -1, -2, -2, -1, -1, -1, 0), (-1, -1, -2, -2, -1, 0, 0, 0), (-1, 0, -2, -2, -2, -2, -2, -1), (0, -1, -1, -2, -2, -2, -2, -1), (0, 0, -1, -1, -1, -1, -1, 0)) sage: ieqs = base + [ ....: [3, 1, 0, 0, 0, 0, 0, 0, 0], ....: [4, 0, 1, 0, 0, 0, 0, 0, 0], ....: [6, 0, 0, 1, 0, 0, 0, 0, 0], ....: [8, 0, 0, 0, 1, 0, 0, 0, 0], ....: [6, 0, 0, 0, 0, 1, 0, 0, 0], ....: [4, 0, 0, 0, 0, 0, 1, 0, 0], ....: [2, 0, 0, 0, 0, 0, 0, 1, 0], ....: [1, 0, 0, 0, 0, 0, 0, 0, 1]] sage: P = Polyhedron(ieqs=ieqs, backend='normaliz') # optional - pynormaliz sage: P.integral_points() # optional - pynormaliz ((-3, -4, -6, -8, -6, -4, -2, -1), (-3, -4, -6, -8, -6, -4, -2, 0), (-2, -2, -4, -5, -4, -3, -2, -1), (-2, -2, -4, -5, -4, -3, -2, 0), (-1, -2, -3, -4, -3, -2, -2, -1), (-1, -2, -3, -4, -3, -2, -1, 0), (-1, -1, -2, -2, -2, -2, -2, -1), (-1, -1, -2, -2, -1, -1, -1, 0), (-1, -1, -2, -2, -1, 0, 0, 0), (-1, 0, -2, -2, -2, -2, -2, -1), (0, -1, -1, -2, -2, -2, -2, -1), (0, 0, -1, -1, -1, -1, -1, 0)) """ import PyNormaliz if not self.is_compact(): raise ValueError('can only enumerate points in a compact polyhedron') # Trivial cases: polyhedron with 0 or 1 vertices if self.n_vertices() == 0: return () if self.n_vertices() == 1: v = self.vertices_list()[0] try: return (vector(ZZ, v),) except TypeError: # vertex not integral return () # for small bounding boxes, it is faster to naively iterate over the points of the box if threshold > 1: box_min, box_max = self.bounding_box(integral_hull=True) if box_min is None: return () box_points = prod(max_coord-min_coord+1 for min_coord, max_coord in zip(box_min, box_max)) if box_points<threshold: from sage.geometry.integral_points import rectangular_box_points return rectangular_box_points(list(box_min), list(box_max), self) # Compute with normaliz points = [] cone = self._normaliz_cone assert cone for g in PyNormaliz.NmzResult(cone, "ModuleGenerators"): assert g[-1] == 1 points.append(vector(ZZ, g[:-1])) return tuple(points)
def polyhedron_to_Hrep(P, separate_equality_constraints=False): r"""Extract half-space representation of polytope. By default, returns matrices ``[A, b]`` representing `P` as `Ax \leq b`. If ``separate_equality_constraints = True``, returns matrices ``[A, b, Aeq, beq]``, with separated inequality and equality constraints. INPUT: * ``P`` - object of class polyhedron * ``separate_equality_constraints`` - (default = False) If True, returns ``Aeq``, ``beq`` containing the equality constraints, removing the corresponding lines from ``A`` and ``b``. OUTPUT: * ``[A, b]`` - dense matrix and vector respectively (dense, ``RDF``). * ``[A, b, Aeq, beq]`` - (if the flag separate_equality_constraints is True), matrices and vectors (dense, ``RDF``). NOTES: - Equality constraints are removed from A and put into Aeq. - This function is used to revert the job of ``polyhedron_from_Hrep(A, b, base_ring = RDF)``. - However, it is not the inverse generally, because of: - automatic reordering of rows (this is uncontrolled, internal to Polyhedron), and - scaling. In the example of above, with a polyhedron in RDF we see reordering of rows. EXAMPLES:: sage: A = matrix(RDF, [[-1.0, 0.0, 0.0, 0.0, 0.0, 0.0], ....: [ 1.0, 0.0, 0.0, 0.0, 0.0, 0.0], ....: [ 0.0, 1.0, 0.0, 0.0, 0.0, 0.0], ....: [ 0.0, -1.0, 0.0, 0.0, 0.0, 0.0], ....: [0.0, 0.0, -1.0, 0.0, 0.0, 0.0], ....: [0.0, 0.0, 1.0, 0.0, 0.0, 0.0], ....: [0.0, 0.0, 0.0, -1.0, 0.0, 0.0], ....: [0.0, 0.0, 0.0, 1.0, 0.0, 0.0], ....: [0.0, 0.0, 0.0, 0.0, 1.0, 0.0], ....: [0.0, 0.0, 0.0, 0.0, -1.0, 0.0], ....: [0.0, 0.0, 0.0, 0.0, 0.0, 1.0], ....: [0.0, 0.0, 0.0, 0.0, 0.0, -1.0]]) sage: b = vector(RDF, [0.0, 10.0, 0.0, 0.0, 0.2, 0.2, 0.1, 0.1, 0.0, 0.0, 0.0, 0.0]) sage: from polyhedron_tools.misc import polyhedron_from_Hrep, polyhedron_to_Hrep sage: P = polyhedron_from_Hrep(A, b, base_ring = RDF); P A 3-dimensional polyhedron in RDF^6 defined as the convex hull of 8 vertices sage: [A, b] = polyhedron_to_Hrep(P) sage: A [-0.0 1.0 -0.0 -0.0 -0.0 -0.0] [ 0.0 -1.0 0.0 0.0 0.0 0.0] [-0.0 -0.0 -0.0 -0.0 1.0 -0.0] [ 0.0 0.0 0.0 0.0 -1.0 0.0] [-0.0 -0.0 -0.0 -0.0 -0.0 1.0] [ 0.0 0.0 0.0 0.0 0.0 -1.0] [-1.0 -0.0 -0.0 -0.0 -0.0 -0.0] [ 1.0 -0.0 -0.0 -0.0 -0.0 -0.0] [-0.0 -0.0 -1.0 -0.0 -0.0 -0.0] [-0.0 -0.0 1.0 -0.0 -0.0 -0.0] [-0.0 -0.0 -0.0 -1.0 -0.0 -0.0] [-0.0 -0.0 -0.0 1.0 -0.0 -0.0] sage: b (0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 10.0, 0.2, 0.2, 0.1, 0.1) TESTS:: If we choose QQ, then we have both reordering and rescaling:: sage: P = polyhedron_from_Hrep(A, b, base_ring = QQ); P A 3-dimensional polyhedron in QQ^6 defined as the convex hull of 8 vertices sage: [A, b] = polyhedron_to_Hrep(P) sage: A [ 0.0 0.0 0.0 0.0 0.0 -1.0] [ 0.0 0.0 0.0 0.0 0.0 1.0] [ 0.0 0.0 0.0 0.0 -1.0 0.0] [ 0.0 0.0 0.0 0.0 1.0 0.0] [ 0.0 -1.0 0.0 0.0 0.0 0.0] [ 0.0 1.0 0.0 0.0 0.0 0.0] [ -1.0 0.0 0.0 0.0 0.0 0.0] [ 0.0 0.0 5.0 0.0 0.0 0.0] [ 0.0 0.0 -5.0 0.0 0.0 0.0] [ 0.0 0.0 0.0 -10.0 0.0 0.0] [ 0.0 0.0 0.0 10.0 0.0 0.0] [ 1.0 0.0 0.0 0.0 0.0 0.0] sage: b (0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 10.0) The polytope P is not full-dimensional. To extract the equality constraints we use the flag ``separate_equality_constraints``:: sage: [A, b, Aeq, beq] = polyhedron_to_Hrep(P, separate_equality_constraints = True) sage: A [ -1.0 0.0 0.0 0.0 0.0 0.0] [ 0.0 0.0 5.0 0.0 0.0 0.0] [ 0.0 0.0 -5.0 0.0 0.0 0.0] [ 0.0 0.0 0.0 -10.0 0.0 0.0] [ 0.0 0.0 0.0 10.0 0.0 0.0] [ 1.0 0.0 0.0 0.0 0.0 0.0] sage: b (0.0, 1.0, 1.0, 1.0, 1.0, 10.0) sage: Aeq [ 0.0 0.0 0.0 0.0 0.0 -1.0] [ 0.0 0.0 0.0 0.0 -1.0 0.0] [ 0.0 -1.0 0.0 0.0 0.0 0.0] sage: beq (0.0, 0.0, 0.0) """ if not separate_equality_constraints: # a priori I don't know number of equalities; actually m may be bigger than len(P.Hrepresentation()) ! # for this, we should transform equalities into inequalities, so that Ax <= b is correct. b = list() A = list() P_gen = P.Hrep_generator() for pigen in P_gen: if (pigen.is_equation()): pi_vec = pigen.vector() A.append(-pi_vec[1:len(pi_vec)]) b.append(pi_vec[0]) A.append(pi_vec[1:len(pi_vec)]) b.append(pi_vec[0]) else: pi_vec = pigen.vector() A.append(-pi_vec[1:len(pi_vec)]) b.append(pi_vec[0]) return [matrix(RDF, A), vector(RDF, b)] else: b = list() A = list() beq = list() Aeq = list() P_gen = P.Hrep_generator() for pigen in P_gen: if (pigen.is_equation()): pi_vec = pigen.vector() Aeq.append(-pi_vec[1:len(pi_vec)]) beq.append(pi_vec[0]) else: pi_vec = pigen.vector() A.append(-pi_vec[1:len(pi_vec)]) b.append(pi_vec[0]) return [matrix(RDF, A), vector(RDF, b), matrix(RDF, Aeq), vector(RDF, beq)]
def support_function(P, d, verbose=0, return_xopt=False, solver='GLPK'): r"""Compute support function of a convex polytope. It is defined as `\max_{x in P} \langle x, d \rangle` , where `d` is an input vector. INPUT: * ``P`` - an object of class Polyhedron. It can also be given as ``[A, b]`` meaning `Ax \leq b`. * ``d`` - a vector (or list) where the support function is evaluated. * ``verbose`` - (default: 0) If 1, print the status of the LP. * ``solver`` - (default: ``'GLPK'``) the LP solver used. * ``return_xopt`` - (default: False) If True, the optimal point is returned, and ``sf = [oval, opt]``. OUTPUT: * ``sf`` - the value of the support function. EXAMPLES:: sage: from polyhedron_tools.misc import BoxInfty, support_function sage: P = BoxInfty([1,2,3], 1); P A 3-dimensional polyhedron in QQ^3 defined as the convex hull of 8 vertices sage: support_function(P, [1,1,1], return_xopt=True) (9.0, {0: 2.0, 1: 3.0, 2: 4.0}) NOTES: - The possibility of giving the input polytope as `[A, b]` instead of a polyhedron is useful in cases when the dimensions are high (in practice, above or around 20, but it depends on the particular system -number of constraints- as well). - If a different solver (e.g. ``guurobi``) is given, it should be installed properly. """ from sage.numerical.mip import MixedIntegerLinearProgram if (type(P) == list): A = P[0] b = P[1] elif P.is_empty(): # avoid formulating the LP if P = [] return 0 else: #assuming some form of Polyhedra base_ring = P.base_ring() # extract the constraints from P m = len(P.Hrepresentation()) n = len(vector(P.Hrepresentation()[0])) - 1 b = vector(base_ring, m) A = matrix(base_ring, m, n) P_gen = P.Hrep_generator() i = 0 for pigen in P_gen: pi_vec = pigen.vector() A.set_row(i, -pi_vec[1:len(pi_vec)]) b[i] = pi_vec[0] i += 1 s_LP = MixedIntegerLinearProgram(maximization=True, solver=solver) x = s_LP.new_variable(integer=False, nonnegative=False) # objective function obj = sum(d[i] * x[i] for i in range(len(d))) s_LP.set_objective(obj) s_LP.add_constraint(A * x <= b) if (verbose): print '**** Solve LP ****' s_LP.show() oval = s_LP.solve() xopt = s_LP.get_values(x) if (verbose): print 'Objective Value:', oval for i, v in xopt.iteritems(): print 'x_%s = %f' % (i, v) print '\n' if (return_xopt == True): return oval, xopt else: return oval
def BoxInfty(lengths=None, center=None, radius=None, base_ring=QQ, return_HSpaceRep=False): r"""Generate a ball in the supremum norm, or more generally a hyper-rectangle in an Euclidean space. It can be constructed from its center and radius, in which case it is a box. It can also be constructed giving the lengths of the sides, in which case it is an hyper-rectangle. In all cases, it is defined as the Cartesian product of intervals. INPUT: * ``args`` - Available options are: * by center and radius: * ``center`` - a vector (or a list) containing the coordinates of the center of the ball. * ``radius`` - a number representing the radius of the ball. * by lengths: * ``lenghts`` - a list of tuples containing the length of each side with respect to the coordinate axes, in the form [(min_x1, max_x1), ..., (min_xn, max_xn)]. * ``base_ring`` - (default: ``QQ``) base ring passed to the Polyhedron constructor. Valid choices are: * ``'QQ'``: rational * ``'RDF'``: real double field * ``return_HSpaceRep`` - (default: False) If True, it does not construct the Polyhedron `P`, and returns instead the pairs ``[A, b]`` corresponding to P in half-space representation, and is understood that `Ax \leq b`. OUTPUT: * ``P`` - a Polyhedron object. If the flag ``return_HSpaceRep`` is true, it is returned as ``[A, b]`` with `A` and `b` matrices, and is understood that `Ax \leq b`. EXAMPLES:: sage: from polyhedron_tools.misc import BoxInfty sage: P = BoxInfty([1,2,3], 1); P A 3-dimensional polyhedron in QQ^3 defined as the convex hull of 8 vertices sage: P.plot(aspect_ratio=1) # not tested (plot) NOTES: - The possibility to output in matrix form [A, b] is especially interesting for more than 15 variable systems. """ # Guess input got_lengths, got_center_and_radius = False, False if (lengths is not None): if (center is None) and (radius is None): got_lengths = True elif (center is not None) and (radius is None): radius = center center = lengths lengths = [] got_center_and_radius = True else: got_center_and_radius = True if (center is not None and radius is not None) else False # Given center and radius if got_center_and_radius: # cast (optional) center = [base_ring(xi) for xi in center] radius = base_ring(radius) ndim = len(center) A = matrix(base_ring, 2 * ndim, ndim) b = vector(base_ring, 2 * ndim) count = 0 for i in range(ndim): diri = zero_vector(base_ring, ndim) diri[i] = 1 # external bound A.set_row(count, diri) b[count] = center[i] + radius count += 1 # internal bound A.set_row(count, -diri) b[count] = -(center[i] - radius) count += 1 # Given the length of each side as tuples (min, max) elif got_lengths: # clean up the argument and cast lengths = _make_listlist(lengths) lengths = [[base_ring(xi) for xi in l] for l in lengths] ndim = len(lengths) A = matrix(base_ring, 2 * ndim, ndim) b = vector(base_ring, 2 * ndim) count = 0 for i in range(ndim): diri = zero_vector(base_ring, ndim) diri[i] = 1 # external bound A.set_row(count, diri) b[count] = lengths[i][1] count += 1 # internal bound A.set_row(count, -diri) b[count] = -(lengths[i][0]) count += 1 else: raise ValueError( 'You should specify either center and radius or length of the sides.' ) if not return_HSpaceRep: P = polyhedron_from_Hrep(A, b, base_ring) return P else: return [A, b]
def compute_flowpipe(A=None, X0=None, B=None, U=None, **kwargs): r"""Implements LGG reachability algorithm for the linear continuous system dx/dx = Ax + Bu. INPUTS: * ``A`` -- coefficient matrix of the system * ``X0`` -- initial set * ``B`` -- transformation of the input * ``U`` -- input set * ``time_step`` -- (default = 1e-2) time step * ``initial_time`` -- (default = 0) the initial time * ``time_horizon`` -- (default = 1) the final time * ``number_of_time_steps`` -- (default = ceil(T/tau)) number of time steps * "directions" -- (default: random, and a box) dictionary * ``solver`` -- LP solver. Valid options are: * 'GLPK' (default). * 'Gurobi' * ``base_ring`` -- base ring where polyhedral computations are performed Valid options are: * QQ - (default) rational field * RDF - real double field OUTPUTS: * ``flowpipe`` """ # ################ # Parse input # # ################ if A is None: raise ValueError('System matrix A is missing.') else: if 'sage.matrix' in str(type(A)): n = A.ncols() elif type(A) == np.ndarray: n = A.shape[0] base_ring = kwargs['base_ring'] if 'base_ring' in kwargs else QQ if X0 is None: raise ValueError('Initial state X0 is missing.') elif 'sage.geometry.polyhedron' not in str(type(X0)) and type(X0) == list: # If X0 is not some type of polyhedron, set an initial point X0 = Polyhedron(vertices = [X0], base_ring = base_ring) elif 'sage.geometry.polyhedron' not in str(type(X0)) and X0.is_vector(): X0 = Polyhedron(vertices = [X0], base_ring = base_ring) elif 'sage.geometry.polyhedron' in str(type(X0)): # ensure that all input sets are on the same ring # not sure about this if 1==0: if X0.base_ring() != base_ring: [F, g] = polyhedron_to_Hrep(X0) X0 = polyhedron_from_Hrep(F, g, base_ring=base_ring) else: raise ValueError('Initial state X0 not understood') if B is None: # the system is homogeneous: dx/dt = Ax got_homogeneous = True else: got_homogeneous = False if U is None: raise ValueError('Input range U is missing.') tau = kwargs['time_step'] if 'time_step' in kwargs else 1e-2 t0 = kwargs['initial_time'] if 'initial_time' in kwargs else 0 T = kwargs['time_horizon'] if 'time_horizon' in kwargs else 1 global N N = kwargs['number_of_time_steps'] if 'number_of_time_steps' in kwargs else ceil(T/tau) directions = kwargs['directions'] if 'directions' in kwargs else {'select':'box'} global solver solver = kwargs['solver'] if 'solver' in kwargs else 'GLPK' global verbose verbose = kwargs['verbose'] if 'verbose' in kwargs else 0 # this involves the convex hull of X0 and a Minkowski sum #first_element_evaluation = kwargs['first_element_evaluation'] if 'first_element_evaluation' in kwargs else 'approximate' # ####################################################### # Generate template directions # # ####################################################### if directions['select'] == 'box': if n==2: theta = [0,pi/2,pi,3*pi/2] # box dList = [vector(RR,[cos(t), sin(t)]) for t in theta] else: # directions of hypercube dList = [] dList += [-identity_matrix(n).column(i) for i in range(n)] dList += [identity_matrix(n).column(i) for i in range(n)] elif directions['select'] == 'oct': if n != 2: raise NotImplementedError('Directions select octagon not implemented for n other than 2. Try box.') theta = [i*pi/4 for i in range(8)] # octagon dList = [vector(RR,[cos(t), sin(t)]) for t in theta] elif directions['select'] == 'random': order = directions['order'] if 'order' in directions else 12 if n == 2: theta = [random.uniform(0, 2*pi.n(digits=5)) for i in range(order)] dList = [vector(RR,[cos(theta[i]), sin(theta[i])]) for i in range(order)] else: raise NotImplementedError('Directions select random not implemented for n greater than 2. Try box.') elif directions['select'] == 'custom': dList = directions['dList'] else: raise TypeError('Template directions not understood.') # transform directions to numpy array, and get number of directions dArray = np.array(dList) k = len(dArray) global Phi_tau, expX0, alpha_tau_B if got_homogeneous: # dx/dx = Ax # ####################################################### # Compute first element of the approximating sequence # # ####################################################### # compute matrix exponential exp(A*tau) Phi_tau = expm(np.multiply(A, tau)) # compute exp(tau*A)X0 expX0 = Phi_tau * X0 # compute the bloating factor Ainfty = A.norm(Infinity) RX0 = radius(X0) unitBall = BoxInfty(center = zero_vector(n), radius = 1, base_ring = base_ring) alpha_tau = (exp(tau*Ainfty) - 1 - tau*Ainfty)*(RX0) alpha_tau_B = (alpha_tau*np.identity(n)) * unitBall # now we have that: # Omega0 = X0.convex_hull(expX0.Minkowski_sum(alpha_tau_B)) # compute the first element of the approximating sequence, Omega_0 #if first_element_evaluation == 'exact': # Omega0 = X0.convex_hull(expX0.Minkowski_sum(alpha_tau_B)) #elif first_element_evaluation == 'approximate': # NOT TESTED!!! #Omega0_A = dArray # Omega0_b = np.zeros(k) # for i, d in enumerate(dArray): # rho_X0_d = supp_fun_polyhedron(X0, d, solver=solver, verbose=verbose) # rho_expX0_d = supp_fun_polyhedron(expX0, d, solver=solver, verbose=verbose) # rho_alpha_tau_B_d = supp_fun_polyhedron(alpha_tau_B, d, solver=solver, verbose=verbose) # Omega0_b[i] = max(rho_X0_d, rho_expX0_d + rho_alpha_tau_B_d); # Omega0 = PolyhedronFromHSpaceRep(dArray, Omega0_b); #W_tau = Polyhedron(vertices = [], ambient_dim=n) # since W_tau = [], supp_fun_polyhedron returns 0 # ################################################ # Build the sequence of approximations Omega_i # # ################################################ Omega_i_Family_SF = [_Omega_i_supports_hom(d, X0) for d in dArray] else: # dx/dx = Ax + Bu global tau_V, beta_tau_B # compute range of the input under B, V = BU V = B * U # compute matrix exponential exp(A*tau) Phi_tau = expm(np.multiply(A, tau)) # compute exp(tau*A)X0 expX0 = Phi_tau * X0 # compute the initial over-approximation tau_V = (tau*np.identity(n)) * V # compute the bloating factor Ainfty = A.norm(Infinity) RX0 = radius(X0) RV = radius(V) unitBall = BoxInfty(center = zero_vector(n), radius = 1, base_ring = base_ring) alpha_tau = (exp(tau*Ainfty) - 1 - tau*Ainfty)*(RX0 + RV/Ainfty) alpha_tau_B = (alpha_tau*np.identity(n)) * unitBall # compute the first element of the approximating sequence, Omega_0 #aux = expX0.Minkowski_sum(tau_V) #Omega0 = X0.convex_hull(aux.Minkowski_sum(alpha_tau_B)) beta_tau = (exp(tau*Ainfty) - 1 - tau*Ainfty)*(RV/Ainfty) beta_tau_B = (beta_tau*np.identity(n)) * unitBall #W_tau = tau_V.Minkowski_sum(beta_tau_B) # ################################################ # Build the sequence of approximations Omega_i # # ################################################ Omega_i_Family_SF = [_Omega_i_supports_inhom(d, X0) for d in dArray] # ################################################ # Build the approximating polyhedra # # ################################################ # each polytope is built using the support functions over-approximation Omega_i_Poly = list() # This loop can be vectorized (?) for i in range(N): # we have N polytopes # for each one, use all directions A = matrix(base_ring, k, n); b = vector(base_ring, k) for j in range(k): #run over directions s_fun = Omega_i_Family_SF[j][i] A.set_row(j, dList[j]) b[j] = s_fun Omega_i_Poly.append( polyhedron_from_Hrep(A, b, base_ring = base_ring) ) return Omega_i_Poly
def restricted_automorphism_group(self, vertex_labels=None): r""" Return the restricted automorphism group. First, let the linear automorphism group be the subgroup of the Euclidean group `E(d) = GL(d,\RR) \ltimes \RR^d` preserving the `d`-dimensional polyhedron. The Euclidean group acts in the usual way `\vec{x}\mapsto A\vec{x}+b` on the ambient space. The restricted automorphism group is the subgroup of the linear automorphism group generated by permutations of vertices. If the polytope is full-dimensional, it is equal to the full (unrestricted) automorphism group. INPUT: - ``vertex_labels`` -- a tuple or ``None`` (default). The labels of the vertices that will be used in the output permutation group. By default, the vertices are used themselves. OUTPUT: A :class:`PermutationGroup<sage.groups.perm_gps.permgroup.PermutationGroup_generic>` acting on the vertices (or the ``vertex_labels``, if specified). REFERENCES: .. [BSS] David Bremner, Mathieu Dutour Sikiric, Achill Schuermann: Polyhedral representation conversion up to symmetries. http://arxiv.org/abs/math/0702239 EXAMPLES:: sage: from sage.geometry.polyhedron.ppl_lattice_polytope import LatticePolytope_PPL sage: Z3square = LatticePolytope_PPL((0,0), (1,2), (2,1), (3,3)) sage: Z3square.restricted_automorphism_group(vertex_labels=(1,2,3,4)) Permutation Group with generators [(2,3), (1,2)(3,4), (1,4)] sage: G = Z3square.restricted_automorphism_group(); G Permutation Group with generators [((1,2),(2,1)), ((0,0),(1,2))((2,1),(3,3)), ((0,0),(3,3))] sage: tuple(G.domain()) == Z3square.vertices() True sage: G.orbit(Z3square.vertices()[0]) ((0, 0), (1, 2), (3, 3), (2, 1)) sage: cell24 = LatticePolytope_PPL( ... (1,0,0,0),(0,1,0,0),(0,0,1,0),(0,0,0,1),(1,-1,-1,1),(0,0,-1,1), ... (0,-1,0,1),(-1,0,0,1),(1,0,0,-1),(0,1,0,-1),(0,0,1,-1),(-1,1,1,-1), ... (1,-1,-1,0),(0,0,-1,0),(0,-1,0,0),(-1,0,0,0),(1,-1,0,0),(1,0,-1,0), ... (0,1,1,-1),(-1,1,1,0),(-1,1,0,0),(-1,0,1,0),(0,-1,-1,1),(0,0,0,-1)) sage: cell24.restricted_automorphism_group().cardinality() 1152 """ if not self.is_full_dimensional(): return self.affine_lattice_polytope().\ restricted_automorphism_group(vertex_labels=vertex_labels) if vertex_labels is None: vertex_labels = self.vertices() from sage.groups.perm_gps.permgroup import PermutationGroup from sage.graphs.graph import Graph # good coordinates for the vertices v_list = [] for v in self.minimized_generators(): assert v.divisor().is_one() v_coords = (1,) + v.coefficients() v_list.append(vector(v_coords)) # Finally, construct the graph Qinv = sum( v.column() * v.row() for v in v_list ).inverse() G = Graph() for i in range(0,len(v_list)): for j in range(i+1,len(v_list)): v_i = v_list[i] v_j = v_list[j] G.add_edge(vertex_labels[i], vertex_labels[j], v_i * Qinv * v_j) return G.automorphism_group(edge_labels=True)