예제 #1
0
def radius(P):
    r"""Maximum norm of any element in a polyhedron, with respect to the supremum norm.

    It is defined as `\max\limits_{x in P} \Vert x \Vert_\inf`. It can be computed with support functions as 
    `\max\limits_{i=1,\ldots,n} \max \{|\rho_P(e_i)|, |\rho_P(-e_i)|\}`, 
    where `\rho_P(e_i)` is the support function of `P` evaluated at the i-th canonical vector `e_i`.

    INPUT:

    * ``P`` - an object of class Polyhedron. It can also be given as ``[A, b]`` with A and b matrices, assuming `Ax \leq b`.

    OUTPUT:

    * ``snorm`` - the value of the max norm of any element of P, in the sup-norm.

    TO-DO:

    - To use '*args' so that it works both with ``polyhedron_sup_norm(A, b)`` and with ``polyhedron_sup_norm(P)`` 
    (depending on the number of the arguments). 
    
    - The difference with mult-methods is that they check also for the type of the arguments.
    
    - v = P.bounding_box() # returns the coordinates of a rectangular box containing the polytope
    polyInfNorm = max( vecpnorm(v[i],p='inf') for i in range(len(v)))
    return polyInfNorm
    """
    from polyhedron_tools.misc import support_function

    if (type(P) == list):

        A = P[0]
        b = P[1]
        # obtain dimension of the ambient space
        n = A.ncols()

        r = 0
        for i in range(n):
            # generate canonical direction
            d = zero_vector(RDF, n)
            d[i] = 1
            aux_sf = abs(support_function([A, b], d))
            if (aux_sf >= r):
                r = aux_sf

            # change sign
            d[i] = -1
            aux_sf = abs(support_function([A, b], d))
            if (aux_sf >= r):
                r = aux_sf
        snorm = r
        return snorm
예제 #2
0
	def zero(self):
		"""returns a distribution with all zero moments (and the same prime and weight)"""
		return dist(self.p,self.weight,zero_vector(QQ,self.num_moments()))
예제 #3
0
 def zero(self,S):
     ''' encoding of 0 at index S '''
     return self.encode(zero_vector(self.n), S)
예제 #4
0
    def _find_isomorphism_degenerate(self, polytope):
        """
        Helper to pick an isomorphism of degenerate polygons

        INPUT:

        - ``polytope`` -- a :class:`LatticePolytope_PPL_class`. The
          polytope to compare with.

        EXAMPLES::

            sage: from sage.geometry.polyhedron.ppl_lattice_polytope import LatticePolytope_PPL, C_Polyhedron
            sage: L1 = LatticePolytope_PPL(C_Polyhedron(2, 'empty'))
            sage: L2 = LatticePolytope_PPL(C_Polyhedron(3, 'empty'))
            sage: iso = L1.find_isomorphism(L2)   # indirect doctest
            sage: iso(L1) == L2
            True
            sage: iso = L1._find_isomorphism_degenerate(L2)
            sage: iso(L1) == L2
            True

            sage: L1 = LatticePolytope_PPL((-1,4))
            sage: L2 = LatticePolytope_PPL((2,1,5))
            sage: iso = L1.find_isomorphism(L2)
            sage: iso(L1) == L2
            True

            sage: L1 = LatticePolytope_PPL((-1,), (3,))
            sage: L2 = LatticePolytope_PPL((2,1,5), (2,-3,5))
            sage: iso = L1.find_isomorphism(L2)
            sage: iso(L1) == L2
            True

            sage: L1 = LatticePolytope_PPL((-1,-1), (3,-1))
            sage: L2 = LatticePolytope_PPL((2,1,5), (2,-3,5))
            sage: iso = L1.find_isomorphism(L2)
            sage: iso(L1) == L2
            True

            sage: L1 = LatticePolytope_PPL((-1,2), (3,1))
            sage: L2 = LatticePolytope_PPL((1,2,3),(1,2,4))
            sage: iso = L1.find_isomorphism(L2)
            sage: iso(L1) == L2
            True

            sage: L1 = LatticePolytope_PPL((-1,2), (3,2))
            sage: L2 = LatticePolytope_PPL((1,2,3),(1,2,4))
            sage: L1.find_isomorphism(L2)
            Traceback (most recent call last):
            ...
            LatticePolytopesNotIsomorphicError: different number of integral points

            sage: L1 = LatticePolytope_PPL((-1,2), (3,1))
            sage: L2 = LatticePolytope_PPL((1,2,3),(1,2,5))
            sage: L1.find_isomorphism(L2)
            Traceback (most recent call last):
            ...
            LatticePolytopesNotIsomorphicError: different number of integral points
        """
        from sage.geometry.polyhedron.lattice_euclidean_group_element import \
            LatticePolytopesNotIsomorphicError
        polytope_vertices = polytope.vertices()
        self_vertices = self.ordered_vertices()
        # handle degenerate cases
        if self.n_vertices() == 0:
            A = zero_matrix(ZZ, polytope.space_dimension(),
                            self.space_dimension())
            b = zero_vector(ZZ, polytope.space_dimension())
            return LatticeEuclideanGroupElement(A, b)
        if self.n_vertices() == 1:
            A = zero_matrix(ZZ, polytope.space_dimension(),
                            self.space_dimension())
            b = polytope_vertices[0]
            return LatticeEuclideanGroupElement(A, b)
        if self.n_vertices() == 2:
            self_origin = self_vertices[0]
            self_ray = self_vertices[1] - self_origin
            polytope_origin = polytope_vertices[0]
            polytope_ray = polytope_vertices[1] - polytope_origin
            Ds, Us, Vs = self_ray.column().smith_form()
            Dp, Up, Vp = polytope_ray.column().smith_form()
            assert Vs.nrows() == Vs.ncols() == Vp.nrows() == Vp.ncols() == 1
            assert abs(Vs[0, 0]) == abs(Vp[0, 0]) == 1
            A = zero_matrix(ZZ, Dp.nrows(), Ds.nrows())
            A[0, 0] = 1
            A = Up.inverse() * A * Us * (Vs[0, 0] * Vp[0, 0])
            b = polytope_origin - A * self_origin
            try:
                A = matrix(ZZ, A)
                b = vector(ZZ, b)
            except TypeError:
                raise LatticePolytopesNotIsomorphicError('different lattice')
            hom = LatticeEuclideanGroupElement(A, b)
            if hom(self) == polytope:
                return hom
            raise LatticePolytopesNotIsomorphicError('different polygons')
예제 #5
0
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]
예제 #6
0
    def _plot(self, geosub, color=None):
        r"""
        EXAMPLES::

            sage: from EkEkstar import kFace, GeoSub
            sage: sub = {1:[1,2], 2:[1,3], 3:[1]}
            sage: geosub = GeoSub(sub,2, dual=True)
            sage: _ = kFace((10,21,33), (1,))._plot(geosub)              # case A
            sage: _ = kFace((10,21,33), (1,2), dual=True)._plot(geosub)  # case C
            sage: _ = kFace((10,21,33), (1,), dual=True)._plot(geosub)   # case E

        ::

            sage: sub = {1: [1, 3, 2, 3], 2: [2, 3], 3: [3, 2, 3, 1, 3, 2, 3]}
            sage: geosub = GeoSub(sub, 2, dual=True)
            sage: _ = kFace((10,21,33), (1,2), dual=True)._plot(geosub)  # case B

        ::

            sage: sub = {1:[1,2,3,3,3,3], 2:[1,3], 3:[1]}
            sage: geosub = GeoSub(sub,2, dual=True)
            sage: _ = kFace((0,0,0),(1,2), dual=True)._plot(geosub)      # case A
        """
        v = self.vector()
        t = self.type()
        if color != None:
            col = color
        else:
            col = self._color
        G = Graphics()

        K = geosub.field()
        b = K.gen()

        num = geosub._sigma_dict.keys()

        if self.is_dual():
            h = list(set(num) - set(t))
            B = b
            vec = geosub.dominant_left_eigenvector()
            emb = geosub.contracting_eigenvalues_indices()
        else:
            h = list(t)
            B = b**(-1)  # TODO: this seems useless (why?)
            vec = -geosub.dominant_left_eigenvector()
            emb = geosub.dilating_eigenvalues_indices()

        el = v * vec
        iter = 0

        conjugates = geosub.complex_embeddings()

        if len(h) == 1:
            if conjugates[emb[0]].is_real() == True:
                bp = zero_vector(CC, len(emb))
                for i in range(len(emb)):
                    bp[i] = K(el).complex_embeddings()[emb[i]]
                bp1 = zero_vector(CC, len(emb))
                for i in range(len(emb)):
                    bp1[i] = K(
                        (el + vec[h[0] - 1])).complex_embeddings()[emb[i]]
                if len(emb) == 1:
                    return line([bp[0], bp1[0]], color=col, thickness=3)
                else:
                    return line([bp, bp1], color=col, thickness=3)
            else:
                bp = K(el).complex_embeddings()[emb[0]]
                bp1 = K((el + vec[h[0] - 1])).complex_embeddings()[emb[0]]
                return line([bp, bp1], color=col, thickness=3)
        elif len(h) == 2:
            if conjugates[emb[0]].is_real() == True:
                bp = (K(el).complex_embeddings()[emb[0]],
                      K(el).complex_embeddings()[emb[1]])
                bp1 = (K(el + vec[h[0] - 1]).complex_embeddings()[emb[0]],
                       K(el + vec[h[0] - 1]).complex_embeddings()[emb[1]])
                bp2 = (K(el + vec[h[0] - 1] +
                         vec[h[1] - 1]).complex_embeddings()[emb[0]],
                       K(el + vec[h[0] - 1] +
                         vec[h[1] - 1]).complex_embeddings()[emb[1]])
                bp3 = (K(el + vec[h[1] - 1]).complex_embeddings()[emb[0]],
                       K(el + vec[h[1] - 1]).complex_embeddings()[emb[1]])
                return polygon2d([bp, bp1, bp2, bp3],
                                 color=col,
                                 thickness=.1,
                                 alpha=0.8)
            else:
                bp = K(el).complex_embeddings()[emb[0]]
                bp1 = K(el + vec[h[0] - 1]).complex_embeddings()[emb[0]]
                bp2 = K(el + vec[h[0] - 1] +
                        vec[h[1] - 1]).complex_embeddings()[emb[0]]
                bp3 = K(el + vec[h[1] - 1]).complex_embeddings()[emb[0]]
                return polygon2d([[bp[0], bp[1]], [bp1[0], bp1[1]],
                                  [bp2[0], bp2[1]], [bp3[0], bp3[1]]],
                                 color=col,
                                 thickness=.1,
                                 alpha=0.8)

        else:
            raise NotImplementedError(
                "Plotting is implemented only for patches in two or three dimensions."
            )
        return G
예제 #7
0
    def milton_proj(self, geosub):
        r"""
        EXAMPLES::

            sage: from EkEkstar import kFace, GeoSub
            sage: sub = {1:[1,2], 2:[1,3], 3:[1]}
            sage: geosub = GeoSub(sub,2, dual=True)
            sage: kFace((10,21,33), (1,2), dual=True).milton_proj(geosub)  # case C
            [-45.2833796391680 + 24.0675974519667*I,
             -46.0552241455140 + 25.1827399600067*I]
            sage: kFace((10,21,33), (1,), dual=True).milton_proj(geosub)   # case E
            [[-45.2833796391680, 24.0675974519667],
             [-46.7030230167750, 23.4613067227595],
             [-47.4748675231211, 24.5764492307995],
             [-46.0552241455140, 25.1827399600067]]

        Brun substitutions ``[123,132,213,231]`` gives a incidence matrix with
        totally real eigenvalues::

            sage: from slabbe.mult_cont_frac import Brun
            sage: algo = Brun()
            sage: S = algo.substitutions()
            sage: sub = prod([S[a] for a in [123,132,213,231]])
            sage: sub
            WordMorphism: 1->1323, 2->23, 3->3231323

        Case B::

            sage: sub = {1: [1,3,2,3], 2: [2,3], 3: [3,2,3,1,3,2,3]}
            sage: geosub = GeoSub(sub, 2, dual=True)
            sage: kFace((10,21,33), (1,2), dual=True).milton_proj(geosub)  # case B
            [(6.69036552922519, -1.50019003695006),
             (5.44338592550772, -1.05514816903743)]

        Case A::

            sage: sub = {1:[1,2,3,3,3,3], 2:[1,3], 3:[1]}
            sage: geosub = GeoSub(sub,2, dual=True)
            sage: kFace((0,0,0),(1,2), dual=True).milton_proj(geosub)      # case A
            [0.000000000000000, -4.74482607768192]

        Case D::

            sage: #kFace((10,21,33), (1,2)).milton_proj(geosub)            # case D (broken)
        """
        v = self.vector()
        t = self.type()

        K = geosub.field()
        b = K.gen()

        num = geosub._sigma_dict.keys()

        if self.is_dual():
            h = list(set(num) - set(t))
            B = b
            vec = geosub.dominant_left_eigenvector()
            emb = geosub.contracting_eigenvalues_indices()
        else:
            h = list(t)
            B = b**(-1)  # TODO: this seems useless (why?)
            vec = -geosub.dominant_left_eigenvector()
            emb = geosub.dilating_eigenvalues_indices()

        el = v * vec
        iter = 0

        conjugates = geosub.complex_embeddings()

        if len(h) == 1:
            if conjugates[emb[0]].is_real() == True:
                bp = zero_vector(CC, len(emb))
                for i in range(len(emb)):
                    bp[i] = K(el).complex_embeddings()[emb[i]]
                bp1 = zero_vector(CC, len(emb))
                for i in range(len(emb)):
                    bp1[i] = K(
                        (el + vec[h[0] - 1])).complex_embeddings()[emb[i]]
                if len(emb) == 1:
                    #print "case A"
                    return [bp[0], bp1[0]]
                else:
                    #print "case B"
                    return [bp, bp1]
            else:
                bp = K(el).complex_embeddings()[emb[0]]
                bp1 = K((el + vec[h[0] - 1])).complex_embeddings()[emb[0]]
                #print "case C"
                return [bp, bp1]
        elif len(h) == 2:
            if conjugates[emb[0]].is_real() == True:
                bp = (K(el).complex_embeddings()[emb[0]],
                      K(el).complex_embeddings()[emb[1]])
                bp1 = (K(el + vec[h[0] - 1]).complex_embeddings()[emb[0]],
                       K(el + vec[h[0] - 1]).complex_embeddings()[emb[1]])
                bp2 = (K(el + vec[h[0] - 1] +
                         vec[h[1] - 1]).complex_embeddings()[emb[0]],
                       K(el + vec[h[0] - 1] +
                         vec[h[1] - 1]).complex_embeddings()[emb[1]])
                bp3 = (K(el + vec[h[1] - 1]).complex_embeddings()[emb[0]],
                       K(el + vec[h[1] - 1]).complex_embeddings()[emb[1]])
                #print "case D"
                return [bp, bp1, bp2, bp3]
            else:
                bp = K(el).complex_embeddings()[emb[0]]
                bp1 = K(el + vec[h[0] - 1]).complex_embeddings()[emb[0]]
                bp2 = K(el + vec[h[0] - 1] +
                        vec[h[1] - 1]).complex_embeddings()[emb[0]]
                bp3 = K(el + vec[h[1] - 1]).complex_embeddings()[emb[0]]
                #print "case E"
                return [[bp[0], bp[1]], [bp1[0], bp1[1]], [bp2[0], bp2[1]],
                        [bp3[0], bp3[1]]]

        else:
            raise NotImplementedError(
                "Projection is implemented only for patches in two or three dimensions."
            )
예제 #8
0
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]
예제 #9
0
    def __init__(self, N):
        r"""
        
        INPUT:
           ``N`` -- a positive integer

        OUTPUT:
            none

        EXAMPLES:

        ::
        
        
        """
        ## Store the level
        self.__N = N

        ## Creates and stores the Sage representation of P^1(Z/NZ)
        P = P1List(N)
        self.__P = P

        ## Creates a fundamental domain for Gamma_0(N) whose boundary is a union
        ## of unimodular paths (except in the case of 3-torsion).  
        ## We will call the intersection of this domain with the real axis the
        ## collection of cusps (even if some are Gamma_0(N) equivalent to one another).
        cusps = self.form_list_of_cusps()

        ## Takes the boundary of this fundamental domain and finds SL_2(Z) matrices whose
        ## associated unimodular path gives this boundary.  These matrices form the
        ## beginning of our collection of coset reps for Gamma_0(N) / SL_2(Z).
        coset_reps = self.fd_boundary(cusps)

        ## Make the identity matrix
        Id = Matrix(2,2,[1,0,0,1])

        ## Gives names to matrices of order 2 and 3 (in PSL_2)
        sig = Matrix(2,2,[0,1,-1,0])
        tau = Matrix(2,2,[0,-1,1,-1])

        ## Takes the bottom row of each of our current coset reps,
        ## thinking of them as distinct elements of P^1(Z/NZ)
        p1s = [(coset_reps[j])[1] for j in range(len(coset_reps))]  

        ## Initializes relevant Manin data
        gens = []
        twotor = []
        twotorrels = []
        threetor = []
        threetorrels = [] 
        rels = [0 for i in range(0,len(coset_reps))]  

        ## the list rels (above) will give Z[Gamma_0(N)] relations between
        ## the associated divisor of each coset representatives in terms
        ## of our chosen set of generators.
        ## entries of rel will be lists of elements of the form (c,A,r) 
        ## with c a constant, A a Gamma_0(N) matrix, and r the index of a 
        ## generator.  The meaning is that the divisor associated to the
        ## j-th coset rep will equal the sum of:
        ##
        ##   c * A^(-1) * (divisor associated to r-th coset rep) 
        ##
        ## as one varies over all (c,A,r) in rel[j].  
        ## (Here r must be in self.generator_indices().)
        ##
        ## This will be used for modular symbols as then the value of a
        ## modular symbol phi on the (associated divisor) of the j-th 
        ## element of coset_reps will be the sum of c * phi (r-th genetator) | A 
        ## as one varies over the tuples in rel[j]

        boundary_checked = [False for i in range(0,len(coset_reps))]  
        
        ## The list boundary_checked keeps track of which boundary pieces of the 
        ## fundamental domain have been already used as we are picking 
        ## our generators
 
#        glue_data = [0 for i in range(0,len(coset_reps))]   ## ????


        ## The following loop will choose our generators by picking one edge 
        ## out of each pair of edges that are glued to each other and picking 
        ## each edge glued to itself (arising from two-torsion)
        ## ------------------------------------------------------------------        
        for r in range(0,len(coset_reps)):    
            if boundary_checked[r] == False:  

                ## We now check if this boundary edge is glued to itself by 
                ## Gamma_0(N)

                if P.index(p1s[r][0],p1s[r][1]) == P.index(-p1s[r][1],p1s[r][0]):
                    ## This edge is glued to itself and so coset_reps[r] 
                    ## needs to be added to our generator list.
                    rels[r] = [(1,Id,r)] ## this relation expresses the fact
                                              ## that coset_reps[r] is one of our 
                                                   ## basic generators

                    gens = gens + [r]    ## the index r is adding to our list
                                                  ## of indexes of generators
                    twotor = twotor + [r]  ## the index r is adding to our 
                                                         ## list of indexes of generators 
                                          ## which satisfy a 2-torsion relation
                    
                    gam = coset_reps[r] * sig * coset_reps[r]**(-1)        
                    ## gam is 2-torsion matrix and in Gamma_0(N).
                    ## if D is the divisor associated to coset_reps[r] 
                    ## then gam * D = - D and so (1+gam)D=0.
                    
                    ## This gives a restriction to the possible values of 
                    ## modular symbols on D

                    twotorrels = twotorrels + [gam]    

                    ## The 2-torsion matrix gam is recorded in our list of 
                    ## 2-torsion relations.

#                    glue_data[r]=(r,gam)  

                    ## this records that the edge associated to coset_reps[r] 
                    ##is glued to itself by gam
                    
                    boundary_checked[r] = True  
                    
                    ## We have now finished with this edge.
                
                else:
                    c = coset_reps[r][1,0]
                    d = coset_reps[r][1,1]
                    if (c**2+d**2+c*d)%N == 0:  
                    ## If true, then the ideal triangle below the unimodular 
                    ## path described by coset_reps[r] contains a point
                    ## fixed by a 3-torsion element.

                        gens = gens + [r]   
                        ## the index r is adding to our list of indexes 
                        ## of generators
                        
                        rels[r] = [(1,Id,r)]  
                        ## this relation expresses the fact that coset_reps[r]
                        ## is one of our basic generators

                        threetor = threetor + [r]                            
                        ## the index r is adding to our list of indexes of 
                        ##generators which satisfy a 3-torsion relation
                        
                        gam = coset_reps[r] * tau * coset_reps[r]**(-1)    
                        ## gam is 3-torsion matrix and in Gamma_0(N).
                        ## if D is the divisor associated to coset_reps[r] 
                        ## then (1+gam+gam^2)D=0.
                        ## This gives a restriction to the possible values of 
                        ## modular symbols on D
                        
                        threetorrels = threetorrels + [gam]  

                        ## The 3-torsion matrix gam is recorded in our list of
                        ## 2-torsion relations.
                        ###  DID I EVER ADD THE GLUE DATA HERE?  DO I NEED IT?

                        ## The reverse of the unimodular path associated to 
                        ## coset_reps[r] is not Gamma_0(N) equivalent to it, so
                        ## we need to include it in our list of coset 
                        ## representatives and record the relevant relations.
                        
                        a = coset_reps[r][0][0]
                        b = coset_reps[r][0][1]
                        A = Matrix(2,2,[-b,a,-d,c])
                        coset_reps = coset_reps + [A]  
                        ## A (representing the reversed edge) is included in 
                        ## our list of coset reps

                        rels = rels + [[(-1,Id,r)]]    
                        ## This relation means that phi on the reversed edge 
                        ## equals -phi on original edge

                        boundary_checked[r] = True     
                        ## We have now finished with this edge.

                    else:

                        ## This is the generic case where neither 2 or 
                        ## 3-torsion intervenes.
                        ## The below loop searches through the remaining edges 
                        ## and finds which one is equivalent to the reverse of
                        ##  coset_reps[r]
                        ## ---------------------------------------------------
                        found = False

                        s = r + 1    
                        ## s is the index we use to run through the 
                        ## remaining edges
                        
                        while (not found):
                            if P.index(p1s[s][0],p1s[s][1]) == P.index(-p1s[r][1],p1s[r][0]):
                                ## the reverse of coset_reps[r] is 
                                ## Gamma_0(N)-equivalent to coset_reps[s]
                                ## coset_reps[r] will now be made a generator 
                                ## and we need to express phi(coset_reps[s]) 
                                ## in terms of phi(coset_reps[r])
                                
                                gens = gens + [r]         
                                ## the index r is adding to our list of 
                                ## indexes of generators
                                
                                rels[r] = [(1,Id,r)]    
                                ## this relation expresses the fact that 
                                ## coset_reps[r] is one of our basic generators
                                
                                A = coset_reps[s] * sig   
                                ## A corresponds to reversing the orientation 
                                ## of the edge corr. to coset_reps[r]
                                
                                gam = coset_reps[r] * A**(-1)  
                                ## gam is in Gamma_0(N) (by assumption of 
                                ## ending up here in this if statement)
                                
                                rels[s] = [(-1,gam,r)]  
                                ## this relation means that phi evaluated on 
                                ## coset_reps[s] equals -phi(coset_reps[r])|gam
                                ## To see this, let D_r be the divisor 
                                ## associated to coset_reps[r] and D_s to 
                                ## coset_reps[s]. Then gam D_s = -D_r and so 
                                ## phi(gam D_s) = - phi(D_r) and thus 
                                ## phi(D_s) = -phi(D_r)|gam 
                                ## since gam is in Gamma_0(N) 
                               
#                                glue_data[r] = (s,gam)  ## ????
                                found = True
                                boundary_checked[r] = 1
                                boundary_checked[s] = 1
                            
                            else:
                                s = s + 1  ## moves on to the next edge
                
        ## We now need to complete our list of coset representatives by        
        ## finding all unimodular paths in the interior of the fundamental 
        ## domain, as well as express these paths in terms of our chosen set 
        ## of generators.
        ## -------------------------------------------------------------------
        
        for r in range(len(cusps)-2):    

        ## r is the index of the cusp on the left of the path.  We only run 
        ## thru to the number of cusps - 2 since you can't start an interior 
        ## path on either of the last two cusps
            
            for s in range(r+2,len(cusps)):  
            ## s is in the index of the cusp on the the right of the path
                cusp1 = cusps[r]
                cusp2 = cusps[s]
                if self.is_unimodular_path(cusp1,cusp2):
                    A,B = self.unimod_to_matrices(cusp1,cusp2)  
                    ## A and B are the matrices whose associated paths 
                    ## connect cusp1 to cusp2 and cusp2 to cusp1 (respectively)
                    coset_reps = coset_reps + [A,B]   
                    ## A and B are added to our coset reps
                    vA = []             
                    vB = []
                    
                    ## This loop now encodes the relation between the 
                    ## unimodular path A and our generators.  This is done 
                    ## simply by accounting for all of the edges that lie 
                    ## below the path attached to A (as they form a triangle)
                    ## Similarly, this is also done for B.
                    
                    for j in range(s-r):  
                    ## Running between the cusps between cusp1 and cusp2
                        vA = vA + rels[r+j+2]  ## Edge relation added
                        t = (-rels[r+j+2][0][0],rels[r+j+2][0][1],rels[r+j+2][0][2]) ## This is simply the negative of the above edge relation.
                        vB = vB + [t]  ## Negative of edge relation added
                    rels = rels + [vA,vB]  ## Relations for A and B adding to relations list
####        return [coset_reps,gens,twotor,twotorrels,threetor,threetorrels,rels,glue_data]


        ## Store the data coming from solving the Manin Relations
        ## ======================================================
        
        self.__mats = coset_reps              
        
        ## Coset representatives of Gamma_0(N) coming from the geometric 
        ## fundamental domain algorithm

        ## Make the translation table between the Sage and Geometric 
        ## descriptions of P^1
        
        v = zero_vector(len(coset_reps))
        for r in range(len(coset_reps)):  
            v[P.index(coset_reps[r][1,0], coset_reps[r][1,1])] = r
        self.__P1_to_mats = v  

        self.__gens = gens            
        ## This is a list of indices of the (geometric) coset representatives 
        ## whose values (on the associated degree zero divisors) determine the
        ## modular symbol.

        self.__twotor = twotor              
        ## A list of indices of the (geometric) coset representatives whose 
        ## paths are identified by some 2-torsion element (which switches the 
        ## path orientation)

        self.__twotorrels = twotorrels      
        ## A list of (2-torsion in PSL_2(Z)) matrices in Gamma_0(N) that give 
        ## the orientation identification in the paths listed in twotor above!

        self.__threetor = threetor            
        ## A list of indices of the (geometric) coset representatives that 
        ## form one side of an ideal triangle with an interior fixed point of 
        ## a 3-torsion element of Gamma_0(N)        

        self.__threetorrels = threetorrels    
        ## A list of (3-torsion in PSL_2(Z)) matrices in Gamma_0(N) that give 
        ## the interior fixed point described in threetor above!

        self.__rels = rels      
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