def _get_roughness(self):
        """Calculate the "R" matrix analytically if the B-spline degree is 3,
        taking advantage of integration by parts and the fact that the 3rd
        derivative of a 3rd-degree B-spline is a constant. Further description
        in roughness.tex."""
        p = self._degree
        if p != 3:
            return BSplineBasicSmoother._get_roughness(self)
        k = self._get_num_coefficients()
        r = np.zeros((k, k))
        bases = [sp.bsplinebasis(self._knots, i, p) for i in range(k)]
        d1 = [sp.bsplinebasis_deriv(self._knots, i, p, 1) for i in range(k)]
        d2 = [sp.bsplinebasis_deriv(self._knots, i, p, 2) for i in range(k)]
        t = self._knots
        t_min = t[0]
        t_max = t[-1]
        for i in range(k):
            if t[i+1] > t[i]:
                c_0 = 6./((t[i+3] - t[i]) * (t[i+2] - t[i]) * (t[i+1] - t[i]))
            else:
                c_0 = 0

            div = ((t[i+3] - t[i])*(t[i+2] - t[i])) 
            l1 = 1. / div if div > 0 else 0
            div = ((t[i+3] - t[i])*(t[i+3] - t[i+1]))
            l2 = 1. / div if div > 0 else 0
            div = ((t[i+4] - t[i+1])*(t[i+3] - t[i+1]))
            l3 = 1. / div if div > 0 else 0
            c_1 = - 6./(t[i+2] - t[i+1]) * (l1 + l2 + l3) if t[i+2] > t[i+1] else 0

            div = ((t[i+3] - t[i])*(t[i+3] - t[i+1]))
            l1 = 1. / div if div > 0 else 0
            div = ((t[i+4] - t[i+1])*(t[i+3] - t[i+1]))
            l2 = 1. / div if div > 0 else 0
            div = ((t[i+4] - t[i+1])*(t[i+4] - t[i+2]))
            l3 = 1. / div if div > 0 else 0
            c_2 = 6./(t[i+3] - t[i+2]) * (l1 + l2 + l3) if t[i+3] > t[i+2] else 0

            if t[i+4] > t[i+3]:
                c_3 = - 6./((t[i+4] - t[i+1]) * (t[i+4] - t[i+2]) * (t[i+4] - t[i+3]))
            else:
                c_3 = 0
                
            jmin = max(0, i - self._degree)
            jmax = min(k, i + self._degree + 1)
            for j in range(jmin, jmax):
                phi = bases[j]
                r[i, j] = d2[i](t_max) * d1[j](t_max) - d2[i](t_min) * d1[j](t_min) \
                  - c_0 * (phi(t[i+1]) - phi(t[i])) \
                  - c_1 * (phi(t[i+2]) - phi(t[i+1])) \
                  - c_2 * (phi(t[i+3]) - phi(t[i+2])) \
                  - c_3 * (phi(t[i+4]) - phi(t[i+3]))
        return r
 def _get_roughness(self):
     """Calculate the "R" matrix as the product of the second derivative of
     the basis functions with its transpose, integrated over time. Returns a
     k*k matrix, where k is the number of basis functions (i.e. the number
     of coefficients). Implements eq. 10 of Chen et al."""
     k = self._get_num_coefficients()
     r = np.zeros((k, k))
     p = self._degree
     d2 = [sp.bsplinebasis_deriv(self._knots, i, p, 2) for i in range(k)]
     t_min = self._knots[0]
     t_max = self._knots[-1]
     for i in range(k):
         jmin = max(0, i - self._degree)
         jmax = min(k, i + self._degree + 1)
         for j in range(jmin, jmax):
             discont = self._knots[min(i, j):max(i, j) + self._degree + 1]
             y, err = scipy.integrate.quad(lambda t: d2[i](t) * d2[j](t),
                                           t_min, t_max, points=discont)
             r[i, j] = y
     return r