示例#1
0
def face_of_span(S):
    """

    Returns the face matrix S^F of the span matrix S,
    that is, a matrix such that

        {x = S z, z >= 0} if and only if {S^F x <= 0}.

    """
    V = hstack([zeros((S.shape[1], 1)), S.T])
    # V-representation: first column is 0 for rays
    V_cdd = Matrix(V, number_type="float")
    V_cdd.rep_type = RepType.GENERATOR
    P = Polyhedron(V_cdd)
    ineq = P.get_inequalities()
    H = array(ineq)
    if H.shape == (0,):  # H = []
        return H
    # b, A = H[:, 0], -H[:, 1:]  # H matrix is [b, -A]
    A = []
    for i in xrange(H.shape[0]):
        if H[i, 0] != 0:  # b should be zero
            raise NotConeSpan(S)
        elif i not in ineq.lin_set:
            A.append(-H[i, 1:])
    return array(A)
def cone_span_to_face(S, eliminate_redundancies=False):
    """

    Returns the face matrix S^F of the span matrix S,
    that is, a matrix such that

        {x = S z, z >= 0} if and only if {S^F x <= 0}.

    """
    V = hstack([zeros((S.shape[1], 1)), S.T])
    # V-representation: first column is 0 for rays
    V_cdd = Matrix(V, number_type=NUMBER_TYPE)
    V_cdd.rep_type = RepType.GENERATOR
    P = Polyhedron(V_cdd)
    H_matrix = P.get_inequalities();
    if(eliminate_redundancies):
        H_matrix.canonicalize();
    H = array(H_matrix);
    if(H.shape[1]>1):
        b = H[:, 0]
        A = H[:, 1:]
    else:
        b, A = H[:, 0], zeros((H.shape[0],S.shape[0]));
    for i in xrange(H.shape[0]):
        if b[i] != 0:
            raise NotConeSpan(S)
    return -A
示例#3
0
    def discard_redundant(self):
        """Remove redundant elements from the credal set

        Redundant elements are those that are not vertices of the credal set's
        convex hull.

        >>> K = CredalSet('abc')
        >>> K.add({'a': 1, 'b': 1, 'c': 1})
        >>> assert (
        ...     K ==
        ...     CredalSet(
        ...         {PMFunc({'a': '1/3', 'c': '1/3', 'b': '1/3'}),
        ...          PMFunc({'a': 1}), PMFunc({'b': 1}), PMFunc({'c': 1})}
        ...     )
        ... )
        >>> K.discard_redundant()
        >>> assert (
        ...     K ==
        ...     CredalSet(
        ...         {PMFunc({'a': 1}), PMFunc({'b': 1}), PMFunc({'c': 1})})
        ... )

        """
        pspace = list(self.pspace())
        K = list(self)
        mat = Matrix(list([1] + list(p[x] for x in pspace) for p in K),
                     number_type='fraction')
        mat.rep_type = RepType.GENERATOR
        lin, red = mat.canonicalize()
        for i in red:
            self.discard(K[i])
def cone_span_to_face(S, eliminate_redundancies=False):
    """

    Returns the face matrix S^F of the span matrix S,
    that is, a matrix such that

        {x = S z, z >= 0} if and only if {S^F x <= 0}.

    """
    V = hstack([ones((S.shape[1], 1)), S.T])
    # V-representation: first column is 0 for rays
    V_cdd = Matrix(V, number_type=NUMBER_TYPE)
    V_cdd.rep_type = RepType.GENERATOR
    P = Polyhedron(V_cdd)
    H_matrix = P.get_inequalities()
    if (eliminate_redundancies):
        H_matrix.canonicalize()
    H = array(H_matrix)
    if (H.shape[1] > 1):
        b = H[:, 0]
        A = H[:, 1:]
    else:
        b, A = H[:, 0], zeros((H.shape[0], S.shape[0]))
    #~ for i in xrange(H.shape[0]):
    #~ if b[i] != 0:
    #~ raise NotConeSpan(S)
    return -A, b
示例#5
0
def vf_enumeration(data=[]):
    """Perform vertex/facet enumeration

      :type `data`: an argument accepted by the
        :class:`~murasyp.vectors.Polytope` constructor.

    :returns: the vertex/facet enumeration of the polytope (assumed to be in
      facet/vertex-representation)
    :rtype: a :class:`~murasyp.vectors.Polytope`

    """
    vf_poly = Polytope(data)
    coordinates = list(vf_poly.domain())
    mat = Matrix(list([0] + [vector[x] for x in coordinates]
                      for vector in vf_poly),
                 number_type='fraction')
    mat.rep_type = RepType.INEQUALITY
    poly = Polyhedron(mat)
    ext = poly.get_generators()
    fv_poly = Polytope([{coordinates[j-1]: ext[i][j]
                         for j in range(1, ext.col_size)}
                        for i in range(0, ext.row_size)] +
                       [{coordinates[j-1]: -ext[i][j]
                         for j in range(1, ext.col_size)}
                        for i in ext.lin_set])
    return fv_poly
示例#6
0
def span_of_face(F):
    """

    Compute the span matrix F^S of the face matrix F,
    that is, a matrix such that

        {F x <= 0} if and only if {x = F^S z, z >= 0}.

    """
    b, A = zeros((F.shape[0], 1)), F
    # H-representation: b - A x >= 0
    # ftp://ftp.ifor.math.ethz.ch/pub/fukuda/cdd/cddlibman/node3.html
    # the input to pycddlib is [b, -A]
    F_cdd = Matrix(hstack([b, -A]), number_type="float")
    F_cdd.rep_type = RepType.INEQUALITY
    P = Polyhedron(F_cdd)
    g = P.get_generators()
    V = array(g)
    rays = []
    for i in xrange(V.shape[0]):
        if V[i, 0] != 0:  # 1 = vertex, 0 = ray
            raise NotConeFace(F)
        elif i not in g.lin_set:
            rays.append(V[i, 1:])
    return array(rays).T
示例#7
0
def eliminate_redundant_inequalities(A,b):
    
    # H-representation: A x + b >= 0
    A_plot = Matrix(hstack([b.reshape((A.shape[0],1)), -A]), number_type=NUMBER_TYPE)
    A_plot.rep_type = RepType.INEQUALITY
    A_plot.canonicalize()
    A_plot = np.array(A_plot)
    b_plot, A_plot = A_plot[:, 0], -A_plot[:, 1:]
    return (A_plot, b_plot);
def eliminate_redundant_inequalities(A,b):
    ''' Input format is  A x + b <= 0'''
    # H-representation: A x + b >= 0
    A_plot = Matrix(hstack([-b.reshape((A.shape[0],1)), -A]), number_type=NUMBER_TYPE)
    A_plot.rep_type = RepType.INEQUALITY
    A_plot.canonicalize()
    A_plot = np.array(A_plot)
    b_plot, A_plot = -A_plot[:, 0], -A_plot[:, 1:]
    return (A_plot, b_plot);
示例#9
0
def make_hrep(lhs, rhs, num_type='fraction'):
    """Create an H-representation cdd.Matrix

    The `lhs` input list of lists should have element lists of equal length
    and integer values; the `rhs` list should have length equal to `lhs`
    and integer values.

    """
    mat = Matrix(lhs_rhs2constraints(lhs, rhs), number_type=num_type)
    mat.rep_type = RepType.INEQUALITY
    return mat
示例#10
0
def make_vrep(points, rays, num_type='fraction'):
    """Create a V-representation cdd.Matrix

    The suggestively named input lists of lists should have element lists
    of equal length and integer values.

    """
    for pointlist in points:
      pointlist.insert(0, 1)
    for raylist in rays:
      raylist.insert(0, 0)
    mat = Matrix(points + rays, number_type=num_type)
    mat.rep_type = RepType.GENERATOR
    return mat
def poly_span_to_face(S):
    """

    Returns the face matrix S^F of the span matrix S,
    that is, a matrix such that

        {x = S z, z >= 0, sum(z)=1} if and only if {S^F x <= s}.

    """
    V = hstack([ones((S.shape[1], 1)), S.T])
    # V-representation: first column is 0 for rays, 1 for vertices
    V_cdd = Matrix(V, number_type=NUMBER_TYPE)
    V_cdd.rep_type = RepType.GENERATOR
    P = Polyhedron(V_cdd)
    H = array(P.get_inequalities()) # H-representation: A x + b >= 0
    b, A = H[:, 0], H[:, 1:]
    return (-A,b)
def poly_span_to_face(S):
    """

    Returns the face matrix S^F of the span matrix S,
    that is, a matrix such that

        {x = S z, z >= 0, sum(z)=1} if and only if {S^F x <= s}.

    """
    V = hstack([ones((S.shape[1], 1)), S.T])
    # V-representation: first column is 0 for rays, 1 for vertices
    V_cdd = Matrix(V, number_type=NUMBER_TYPE)
    V_cdd.rep_type = RepType.GENERATOR
    P = Polyhedron(V_cdd)
    H = array(P.get_inequalities())  # H-representation: A x + b >= 0
    b, A = H[:, 0], H[:, 1:]
    return (-A, b)
示例#13
0
def get_coh_hrep_via_vreps(K, num_type='fraction'):
    """Compute a minimal H-representation for coherence

    See Procedure C.1 in my ISIPTA '13 paper “Characterizing coherence,
    correcting incoherence”.

    """
    vrep = generate_asl_vrep(K, num_type)
    hreplist = [Polyhedron(vrep).get_inequalities()]
    for i in xrange(len(K)):
        vrep = generate_Sasl_vrep(K, i, num_type)
        hreplist.append(Polyhedron(vrep).get_inequalities())
    constraints = ([list(t) for t in hrep[:]] for hrep in hreplist)
    constraintslist = list(chain.from_iterable(constraints))
    hrep = Matrix(constraintslist, number_type=num_type)
    hrep.rep_type = RepType.INEQUALITY
    hrep.canonicalize()
    return hrep
def arbitrary_face_to_span(F, f):
    """

    Compute the span matrix F^S of the face matrix F,
    that is, a matrix such that

        {F x <= f} if and only if {x = F^S z, z >= 0, sum(z)=1}.
        
    Works for both polytopes and cones.

    """
    b, A = f.reshape((F.shape[0], 1)), -F
    # H-representation: A x + b >= 0
    F_cdd = Matrix(hstack([b, A]), number_type=NUMBER_TYPE)
    F_cdd.rep_type = RepType.INEQUALITY
    P = Polyhedron(F_cdd)
    V = array(P.get_generators())
    return (V[:, 1:].T, V[:, 0])
def arbitrary_face_to_span(F,f):
    """

    Compute the span matrix F^S of the face matrix F,
    that is, a matrix such that

        {F x <= f} if and only if {x = F^S z, z >= 0, sum(z)=1}.
        
    Works for both polytopes and cones.

    """
    b, A = f.reshape((F.shape[0],1)), -F
    # H-representation: A x + b >= 0
    F_cdd = Matrix(hstack([b, A]), number_type=NUMBER_TYPE)
    F_cdd.rep_type = RepType.INEQUALITY
    P = Polyhedron(F_cdd)
    V = array(P.get_generators())
    return (V[:, 1:].T, V[:, 0]);
示例#16
0
def theta_k(circumbody, k=0, i=0):
    """
    This is from the Section 4 of the paper [Sadraddini and Tedrake, 2020].
    
    Inputs:
    
        * AH-polytope: ``circumbody``
        * int ``k``: the number of columns added to the subspace of ``U``. 
        For more information, look at the paper.
        *Default* is 0.
    
    Outputs:
    
        * 2D numpy array ``Theta``, which is defined in the paper
    """
    circumbody_AH = pp.to_AH_polytope(circumbody)
    H_y = circumbody_AH.P.H
    q_y = H_y.shape[0]
    if k < 0:
        return np.eye(q_y)
    Y = circumbody_AH.T
    # First identify K=[H_y'^Y'+  ker(H_y')]
    S_1 = np.dot(np.linalg.pinv(H_y.T), Y.T)
    S_2 = spa.null_space(H_y.T)
    if S_2.shape[1] > 0:
        S = np.hstack((S_1, S_2))
    else:
        S = S_1
    # phiw>=0. Now with the augmentation
    S_complement = spa.null_space(S.T)
    circumbody.dim_complement = S_complement.shape[1]
    number_of_columns = min(k, S_complement.shape[1])
    if k == 0:
        psi = S
    else:
        psi = np.hstack((S, S_complement[:, i:number_of_columns + i]))
    p_mat = Matrix(np.hstack((np.zeros((psi.shape[0], 1)), psi)))
    p_mat.rep_type = RepType.INEQUALITY
    poly = Polyhedron(p_mat)
    R = np.array(poly.get_generators())
    r = R[:, 1:].T
    Theta = np.dot(psi, r)
    assert np.all(Theta > -1e-5)
    return Theta
def cone_span_to_face(S, eliminate_redundancies=False):
    """

    Returns the face matrix S^F of the span matrix S,
    that is, a matrix such that

        {x = S z, z >= 0} if and only if {S^F x <= 0}.

    """
    S = np.asarray(S).squeeze()
    V = hstack([zeros((S.shape[1], 1)), S.T])
    # V-representation: first column is 0 for rays
    V_cdd = Matrix(V, number_type=NUMBER_TYPE)
    V_cdd.rep_type = RepType.GENERATOR
    P = Polyhedron(V_cdd)
    H_matrix = P.get_inequalities()
    if (eliminate_redundancies):
        try:
            H_matrix.canonicalize()
        except:
            print "RuntimeError: failed to canonicalize matrix"
    H = array(H_matrix)
    if (len(H.shape) < 2):
        #        warnings.warn("[cone_span_to_face] H is a vector rather than a matrix. S:\n"+str(S)+"\nH:\n"+str(H));
        #        S += 1e-6*np.random.rand(S.shape[0], S.shape[1]);
        #        V = hstack([zeros((S.shape[1], 1)), S.T])
        #        V_cdd = Matrix(V, number_type=NUMBER_TYPE)
        #        V_cdd.rep_type = RepType.GENERATOR
        #        P = Polyhedron(V_cdd)
        #        H_matrix = P.get_inequalities();
        #        H = array(H_matrix);
        #        if(len(H.shape)<2):
        raise ValueError(
            "[cone_span_to_face] Cddlib failed to convert cone span to face: H is a vector rather than a matrix. H: "
            + str(H))
    if (H.shape[1] > 1):
        b = H[:, 0]
        A = H[:, 1:]
    else:
        b, A = H[:, 0], zeros((H.shape[0], S.shape[0]))
    for i in xrange(H.shape[0]):
        if b[i] != 0:
            raise NotConeSpan(S)
    return -A
示例#18
0
def span_of_face(F):
    """

    Compute the span matrix F^S of the face matrix F,
    that is, a matrix such that

        {F x <= 0} if and only if {x = F^S z, z >= 0}.

    """
    b, A = zeros((F.shape[0], 1)), -F
    # H-representation: A x + b >= 0
    F_cdd = Matrix(hstack([b, A]), number_type=NUMBER_TYPE)
    F_cdd.rep_type = RepType.INEQUALITY
    P = Polyhedron(F_cdd)
    V = array(P.get_generators())
    for i in xrange(V.shape[0]):
        if V[i, 0] != 0:  # 1 = vertex, 0 = ray
            raise NotConeFace(F)
    return V[:, 1:]
def arbitrary_span_to_face(S, rv):
    """

    Returns the face matrix S^F of the span matrix S,
    that is, a matrix such that

        {x = S z, z >= 0} if and only if {S^F x <= f}.

    The vector rv specifies whether the corresponding column in S
    is a vertex (1) or a ray (0).
    """
    V = hstack([rv.reshape((S.shape[1], 1)), S.T])
    # V-representation: first column is 0 for rays
    V_cdd = Matrix(V, number_type=NUMBER_TYPE)
    V_cdd.rep_type = RepType.GENERATOR
    P = Polyhedron(V_cdd)
    H = array(P.get_inequalities())
    b, A = H[:, 0], H[:, 1:]
    return (-A, b)
def arbitrary_span_to_face(S, rv):
    """

    Returns the face matrix S^F of the span matrix S,
    that is, a matrix such that

        {x = S z, z >= 0} if and only if {S^F x <= f}.

    The vector rv specifies whether the corresponding column in S
    is a vertex (1) or a ray (0).
    """
    V = hstack([rv.reshape((S.shape[1], 1)), S.T])
    # V-representation: first column is 0 for rays
    V_cdd = Matrix(V, number_type=NUMBER_TYPE)
    V_cdd.rep_type = RepType.GENERATOR
    P = Polyhedron(V_cdd)
    H = array(P.get_inequalities())
    b, A = H[:, 0], H[:, 1:]
    return (-A,b)
def cone_face_to_span(F):
    """

    Compute the span matrix F^S of the face matrix F,
    that is, a matrix such that

        {F x <= 0} if and only if {x = F^S z, z >= 0}.

    """
    b, A = zeros((F.shape[0], 1)), -F
    # H-representation: A x + b >= 0
    F_cdd = Matrix(hstack([b, A]), number_type=NUMBER_TYPE)
    F_cdd.rep_type = RepType.INEQUALITY
    P = Polyhedron(F_cdd)
    V = array(P.get_generators())
    for i in xrange(V.shape[0]):
        if V[i, 0] != 0:  # 1 = vertex, 0 = ray
            raise NotConeFace(F)
    return V[:, 1:].T
def face_of_span(S):
    """

    Returns the face matrix S^F of the span matrix S,
    that is, a matrix such that

        {x = S z, z >= 0} if and only if {S^F x <= 0}.

    """
    V = hstack([zeros((S.shape[1], 1)), S.T])
    # V-representation: first column is 0 for rays
    V_cdd = Matrix(V, number_type=NUMBER_TYPE)
    V_cdd.rep_type = RepType.GENERATOR
    P = Polyhedron(V_cdd)
    H = array(P.get_inequalities())
    b, A = H[:, 0], H[:, 1:]
    for i in xrange(H.shape[0]):
        if b[i] != 0:
            raise NotConeSpan(S)
    return -A
示例#23
0
def face_of_span(S):
    """

    Returns the face matrix S^F of the span matrix S,
    that is, a matrix such that

        {x = S z, z >= 0} if and only if {S^F x <= 0}.

    """
    V = hstack([zeros((S.shape[1], 1)), S.T])
    # V-representation: first column is 0 for rays
    V_cdd = Matrix(V, number_type=NUMBER_TYPE)
    V_cdd.rep_type = RepType.GENERATOR
    P = Polyhedron(V_cdd)
    H = array(P.get_inequalities())
    b, A = H[:, 0], H[:, 1:]
    for i in xrange(H.shape[0]):
        if b[i] != 0:
            raise NotConeSpan(S)
    return -A
def poly_face_to_span(F, f):
    """

    Compute the span matrix F^S of the face matrix F,
    that is, a matrix such that

        {F x <= f} if and only if {x = F^S z, z >= 0, sum(z)=1}.

    """
    b, A = f.reshape((F.shape[0], 1)), -F
    # H-representation: A x + b >= 0
    F_cdd = Matrix(hstack([b, A]), number_type=NUMBER_TYPE)
    F_cdd.rep_type = RepType.INEQUALITY
    P = Polyhedron(F_cdd)
    V = array(P.get_generators())
    if (V.shape[0] == 0):
        print "V.shape", V.shape, "F.shape", F.shape, "f.shape", f.shape
        raise ValueError("This polytope seems to have no vertices")

    for i in xrange(V.shape[0]):
        if V[i, 0] != 1:  # 1 = vertex, 0 = ray
            raise NotPolyFace(F)
    return V[:, 1:].T
示例#25
0
def visualize_2D(list_of_polytopes, a=1.5):
    """
    Given a polytope in its H-representation, plot it
    """
    p_list = []
    x_all = np.empty((0, 2))
    for polytope in list_of_polytopes:
        p_mat = Matrix(np.hstack((polytope.H, polytope.h)))
        poly = Polyhedron(p_mat)
        x = np.array(poly.get_generators())[:, 0:2]
        x = x[ConvexHull(x).vertices, :]
        x_all = np.vstack((x_all, x))
        p = Polygon(x)
        p_list.append(p)
    p_patch = PatchCollection(p_list, color=[(np.random.random(),np.random.random(),np.tanh(np.random.random())) \
        for polytope in list_of_polytopes],alpha=0.6)
    fig, ax = plt.subplots()
    ax.add_collection(p_patch)
    ax.set_xlim([np.min(x_all[:, 0]) * a, a * np.max(x_all[:, 0])])
    ax.set_ylim([np.min(x_all[:, 1]) * a, a * np.max(x_all[:, 1])])
    ax.grid(color=(0, 0, 0), linestyle='--', linewidth=0.3)
示例#26
0
def maximize(data, mapping={}, objective=(0, {})):
    """Maximization using the CONEstrip algorithm

      .. todo::

        document, test more and clean up

    """
    E = feasible(data, mapping)
    if E == set():
        raise ValueError("The linear program is infeasible.")
        #print("The linear program is infeasible.")
        #return 0
    l = sum(len(A) for A in E)
    h = Vector(mapping)
    goal = (objective[0], Vector(objective[1]))
    #print(goal)
    coordinates = list(frozenset.union(*(A.domain() for A in E)))
    E = [[vector for vector in A] for A in E]
    mat = Matrix([[0] + l * [0]], number_type='fraction')
    mat.extend([[-h[x]] + [v[x] for A in E for v in A]
                for x in coordinates], linear=True) # cone-constraints
    mat.extend([[0] + [int(B == A and w == v) for B in E for w in B]
                for A in E for v in A]) # mu >= 0
    mat.obj_type = LPObjType.MAX
    mat.obj_func = tuple([goal[0]] + [goal[1][v] for A in E for v in A])
                      # (constant, mu)
    #print(mat)
    lp = LinProg(mat)
    lp.solve()
    if lp.status == LPStatusType.OPTIMAL:
        #print(lp.primal_solution)
        return lp.obj_value
    elif lp.status == LPStatusType.UNDECIDED:
        status = "undecided"
    elif lp.status == LPStatusType.INCONSISTENT:
        status = "inconsistent"
    elif lp.status == LPStatusType.UNBOUNDED:
        status = "unbounded"
    else:
        status = "of unknown status"
    raise ValueError("The linear program is " + str(status) + '.')
示例#27
0
def feasible(data, mapping=None):
    """Check feasibility using the CONEstrip algorithm

      .. todo::

        document, test more and clean up

    """
    D = set(Polytope(A) for A in data)
    if (mapping == None) or all(mapping[x] != 0 for x in mapping):
        h = None
    else:
        h = Vector(mapping)
        D.add(Polytope({-h}))
    coordinates = list(frozenset.union(*(A.domain() for A in D)))
    E = [[vector for vector in A] for A in D]
    #print(E)
    while (E != []):
        k = len(E)
        L = [len(A) for A in E]
        l = sum(L)
        mat = Matrix([[0] + l * [0] + k * [0]], number_type='fraction')
        mat.extend([[0] + [v[x] for A in E for v in A] + k * [0]
                    for x in coordinates],
                   linear=True) # cone-constraints
        mat.extend([[0] + [int(B == A and w == v) for B in E for w in B]
                        + k * [0]
                    for A in E for v in A]) # mu >= 0
        mat.extend([[1] + l * [0] + [-int(B == A) for B in E]
                    for A in E]) # tau <= 1
        mat.extend([[0] + l * [0] + [int(B == A) for B in E]
                    for A in E]) # tau >= 0
        mat.extend([[-1] + l * [0] + k * [1]]) # (sum of tau_A) >= 1
        mat.extend([[0] + [int(B == A and w == v) for B in E for w in B]
                        + [-int(B == A) for B in E]
                    for A in E for v in A]) # tau_A <= mu_A for all A
        if h != None: # mu_{-h} >= 1
            mat.extend([[-1] + [int(A == [-h]) for A in E for w in A]
                             + k * [0]])
        mat.obj_type = LPObjType.MAX
        mat.obj_func = tuple([0] + l * [0] + k * [1]) # (constant, mu, tau)
        #print(mat)
        lp = LinProg(mat)
        lp.solve()
        if lp.status == LPStatusType.OPTIMAL:
            sol = lp.primal_solution # (constant, mu, tau)
            tau = sol[l:]
            #print(tau)
            mu = [sol[sum(L[0:n]):sum(L[0:n]) + L[n]] for n in range(0, k)]
            #print(mu)
            E = [E[n] for n in range(0, k) if tau[n] == 1]
            #print(E)
            if all(all(mu[n][m] == 0 for m in range(0, L[n]))
                   for n in range(0, k) if tau[n] == 0):
                E = {Polytope(A) for A in E}
                #print(E)
                if h != None:
                    E = E - {Polytope([-h])}
                    #print(E)
                return E
            else:
                continue
        else:
            return set()
    else:
        return set()
示例#28
0
def sample_knots(num_intervals: int,
                 knot_bounds: Union[np.ndarray, None] = None,
                 interval_sizes: Union[np.ndarray, None] = None,
                 num_samples: int = 1) -> Union[np.ndarray, None]:
    """Sample knots given a set of rules.

    Args:
        num_intervals
            Number of intervals (number of knots minus 1).
        knot_bounds
            Bounds for the interior knots. Here we assume the domain span 0 to 1,
            bound for a knot should be between 0 and 1, e.g. ``[0.1, 0.2]``.
            ``knot_bounds`` should have number of interior knots of rows, and each row
            is a bound for corresponding knot, e.g.
            ``knot_bounds=np.array([[0.0, 0.2], [0.3, 0.4], [0.3, 1.0]])``,
            for when we have three interior knots.
        interval_sizes
            Bounds for the distances between knots. For the same reason, we assume
            elements in `interval_sizes` to be between 0 and 1. For example,
            ``interval_distances=np.array([[0.1, 0.2], [0.1, 0.3], [0.1, 0.5], [0.1, 0.5]])``
            means that the distance between first (0) and second knot has to be between 0.1 and 0.2, etc.
            And the number of rows for ``interval_sizes`` has to be same with ``num_intervals``.
        num_samples
            Number of knots samples.

    Returns:
        np.ndarray: Return knots sample as array, with `num_samples` rows and number of knots columns.
    """
    # rename variables
    k = num_intervals
    b = knot_bounds
    d = interval_sizes
    N = num_samples

    t0 = 0.0
    tk = 1.0
    # check input
    assert t0 <= tk
    assert k >= 2

    if d is not None:
        assert d.shape == (k, 2) and sum(d[:, 0]) <= 1.0 and\
            np.all(d >= 0.0) and np.all(d <= 1.0)
    else:
        d = np.repeat(np.array([[0.0, 1.0]]), k, axis=0)

    if b is not None:
        assert b.shape == (k - 1, 2) and\
            np.all(b[:, 0] <= b[:, 1]) and\
            np.all(b[:-1, 1] <= b[1:, 1]) and\
            np.all(b >= 0.0) and np.all(b <= 1.0)
    else:
        b = np.repeat(np.array([[0.0, 1.0]]), k - 1, axis=0)

    d = d * (tk - t0)
    b = b * (tk - t0) + t0
    d[0] += t0
    d[-1] -= tk

    # find vertices of the polyhedron
    D = -col_diff_mat(k - 1)
    I = np.identity(k - 1)

    A1 = np.vstack((-D, D))
    A2 = np.vstack((-I, I))

    b1 = np.hstack((-d[:, 0], d[:, 1]))
    b2 = np.hstack((-b[:, 0], b[:, 1]))

    A = np.vstack((A1, A2))
    b = np.hstack((b1, b2))

    mat = np.insert(-A, 0, b, axis=1)
    mat = Matrix(mat)
    mat.rep_type = RepType.INEQUALITY
    poly = Polyhedron(mat)
    ext = poly.get_generators()
    vertices_and_rays = np.array(ext)

    if vertices_and_rays.size == 0:
        print('there is no feasible knots')
        return None

    if np.any(vertices_and_rays[:, 0] == 0.0):
        print('polyhedron is not closed, something is wrong.')
        return None
    else:
        vertices = vertices_and_rays[:, 1:]

    # sample from the convex combination of the vertices
    n = vertices.shape[0]
    s_simplex = sample_simplex(n, N=N)
    s = s_simplex.dot(vertices)

    s = np.insert(s, 0, t0, axis=1)
    s = np.insert(s, k, tk, axis=1)

    return s