def mass_matrix_V0_1d(p, Nbase, T, bc):
    """
    Computes the 1d mass matrix of the space V0.
    
    Parameters
    ----------
    p : int
        spline degree
    
    Nbase : int
        number of spline functions
        
    T : np.array
        knot vector
        
    bc : boolean
        boundary conditions (True = periodic, False = homogeneous Dirichlet)
        
    Returns
    -------
    mass : 2d np.array
        mass matrix in V0
    """

    if bc == True:
        bcon = 1
        Nbase_0 = Nbase - p
    else:
        bcon = 0
        Nbase_0 = Nbase

    el_b = inter.construct_grid_from_knots(p, Nbase, T)
    ne = len(el_b) - 1

    pts_loc, wts_loc = np.polynomial.legendre.leggauss(p + 1)
    pts, wts = inter.construct_quadrature_grid(ne, p + 1, pts_loc, wts_loc,
                                               el_b)

    d = 0
    basis = inter.eval_on_grid_splines_ders(p, Nbase, p + 1, d, T, pts)

    mass = np.zeros((Nbase_0, Nbase_0))

    for ie in range(ne):
        for il in range(p + 1):
            for jl in range(p + 1):
                i = ie + il
                j = ie + jl

                value = 0.

                for g in range(p + 1):
                    value += wts[g, ie] * basis[il, 0, g, ie] * basis[jl, 0, g,
                                                                      ie]

                mass[i % Nbase_0, j % Nbase_0] += value

    return mass[(1 - bcon):Nbase_0 - (1 - bcon),
                (1 - bcon):Nbase_0 - (1 - bcon)]
def PI_1_1d(fun, p, Nbase, T, bc):
    """
    Computes the FEM coefficient of the function 'fun' projected on the space V1.
    
    Parameters
    ----------
    fun : callable
        the function to be projected
        
    p : int
        spline degree
        
    Nbase : int
        number of spline functions
        
    T : np.array
        knot vector
        
    Returns
    -------
    
    vec : np.array
        the FEM coefficients
    """

    # compute greville points
    grev = inter.compute_greville(p, Nbase, T)

    # compute quadrature grid
    pts_loc, wts_loc = np.polynomial.legendre.leggauss(p)
    pts, wts = inter.construct_quadrature_grid(Nbase - 1, p, pts_loc, wts_loc,
                                               grev)

    # compute element boundaries to get length of domain for periodic boundary conditions
    el_b = inter.construct_grid_from_knots(p, Nbase, T)

    # assemble vector of histopolation problem at greville points
    rhs = integrate_1d(pts % el_b[-1], wts, fun)

    # assemble histopolation matrix
    D = histopolation_matrix_1d(p, Nbase, T, grev, bc)

    # apply boundary conditions
    if bc == True:
        lower = int(np.ceil(p / 2) - 1)
        upper = -int(np.floor(p / 2))

    else:
        lower = 0
        upper = Nbase - 1

    rhs = rhs[lower:upper]

    # solve histopolation problem
    vec = np.linalg.solve(D, rhs)

    return vec
def L2_prod_V1_1d(fun, p, Nbase, T):
    """
    Computes the L2 scalar product of the function 'fun' with the B-splines of the space V1
    using a quadrature rule of order p.
    
    Parameters
    ----------
    fun : callable
        function for scalar product
    
    p : int
        spline degree
    
    Nbase : int
        number of spline functions
        
    T : np.array
        knot vector
        
    Returns
    -------
    f_int : np.array
        the result of the integration with each basis function
    """

    el_b = inter.construct_grid_from_knots(p, Nbase, T)
    ne = len(el_b) - 1

    pts_loc, wts_loc = np.polynomial.legendre.leggauss(p)
    pts, wts = inter.construct_quadrature_grid(ne, p, pts_loc, wts_loc, el_b)

    t = T[1:-1]

    d = 0
    basis = inter.eval_on_grid_splines_ders(p - 1, Nbase - 1, p, d, t, pts)

    f_int = np.zeros(Nbase - 1)

    for ie in range(ne):
        for il in range(p):
            i = ie + il

            value = 0.
            for g in range(p):
                value += wts[g, ie] * fun(pts[g, ie]) * basis[il, 0, g, ie]

            f_int[i] += value * p / (t[i + p] - t[i])

    return f_int
def normalization_V1_1d(p, Nbase, T):
    """
    Computes the normalization of all basis functions of the space V1.
    
    Parameters
    ---------- 
    p : int
        spline degree
    
    Nbase : int
        number of spline functions
        
    T : np.array
        knot vector
        
    Returns
    ----------
    norm: np.array
        the integral of each basis function over the entire computational domain
    
    """

    el_b = inter.construct_grid_from_knots(p, Nbase, T)
    Nel = len(el_b) - 1
    t = T[1:-1]

    pts_loc, wts_loc = np.polynomial.legendre.leggauss(p - 1)
    pts, wts = inter.construct_quadrature_grid(Nel, p - 1, pts_loc, wts_loc,
                                               el_b)

    basis = inter.eval_on_grid_splines_ders(p - 1, Nbase - 1, p - 1, 0, t, pts)

    norm = np.zeros(Nbase - 1)

    for ie in range(Nel):
        for il in range(p):
            i = ie + il

            value = 0.
            for g in range(p - 1):

                value += wts[g, ie] * basis[il, 0, g, ie]

            norm[i] += value * p / (t[i + p] - t[i])

    return norm
def histopolation_matrix_1d(p, Nbase, T, grev, bc):
    """
    Computest the 1d histopolation matrix.
    
    Parameters
    ----------
    p : int
        spline degree
        
    Nbase : int
        number of spline functions
    
    T : np.array 
        knot vector
    
    grev : np.array
        greville points
        
    bc : boolean
        boundary conditions (True = periodic, False = homogeneous Dirichlet)
        
    Returns
    -------
    D : 2d np.array
        histopolation matrix
    """

    el_b = inter.construct_grid_from_knots(p, Nbase, T)
    Nel = len(el_b) - 1

    if bc == False:
        return inter.histopolation_matrix(p, Nbase, T, grev)

    else:
        if p % 2 != 0:
            dx = el_b[-1] / Nel
            ne = Nbase - 1

            pts_loc, wts_loc = np.polynomial.legendre.leggauss(p - 1)
            pts, wts = inter.construct_quadrature_grid(ne, p - 1, pts_loc,
                                                       wts_loc, grev)

            col_quad = inter.collocation_matrix(
                p - 1, ne, T[1:-1], (pts % el_b[-1]).flatten()) / dx

            D = np.zeros((ne, ne))

            for i in range(ne):
                for j in range(ne):
                    for k in range(p - 1):
                        D[i, j] += wts[k, i] * col_quad[i + ne * k, j]

            lower = int(np.ceil(p / 2) - 1)
            upper = -int(np.floor(p / 2))

            D[:, :(p - 1)] += D[:, -(p - 1):]
            D = D[lower:upper, :D.shape[1] - (p - 1)]

            return D

        else:
            dx = el_b[-1] / Nel
            ne = Nbase - 1

            a = grev
            b = grev + dx / 2
            c = np.vstack((a, b)).reshape((-1, ), order='F')[:-1]

            pts_loc, wts_loc = np.polynomial.legendre.leggauss(p - 1)
            pts, wts = inter.construct_quadrature_grid(2 * ne, p - 1, pts_loc,
                                                       wts_loc, c)

            col_quad = inter.collocation_matrix(
                p - 1, ne, T[1:-1], (pts % el_b[-1]).flatten()) / dx

            D = np.zeros((ne, ne))

            for il in range(2 * ne):
                i = int(np.floor(il / 2))
                for j in range(ne):
                    for k in range(p - 1):
                        D[i, j] += wts[k, il] * col_quad[il + 2 * ne * k, j]

            lower = int(np.ceil(p / 2) - 1)
            upper = -int(np.floor(p / 2))

            D[:, :(p - 1)] += D[:, -(p - 1):]
            D = D[lower:upper, :D.shape[1] - (p - 1)]

            return D