def global_curve_approx_errbnd(r, Q, p, E, ps=1): ''' Approximate (fit) a set of points {Qk}, k=0,...,r, to within a specified tolerance E. The algorithm is a Type 2 approximation method, i.e. it starts with many control points, fit, check deviation, and discard control points if possible. While the least-squares based algorithm global_curve_approx_fixedn is appropriate for the fitting step, it can sometimes fail, either because degree elevation requires more control points than there are data points, or because the many multiple knots make the system of equations singular. A simple fix is to restart the process with a higher degree curve. The deviation checking is measured by the max norm deviation and, finally, control points are discarded by knot removal. Parameters ---------- r + 1 = the number of data points to fit Q = the point set in object matrix form p = the degree of the fit E = the max norm deviation of the fit ps = the starting degree for the interpolating curve Returns ------- Uh, Pwh = the knot vector and the object matrix of the fitted curve Source ------ The NURBS Book (2nd Ed.), Pg. 431. ''' if p < ps: raise ImproperInput(p, ps) Q = nurbs.obj_mat_to_3D(Q) uk = knot.chord_length_param(r, Q) U, Pw = global_curve_interp(r, Q, ps, uk) n = Pw.shape[0] - 1 ek = np.zeros(r + 1) try: for deg in xrange(ps, p + 1): nh, Uh, dummy = remove_knots_bounds_curve(n, deg, U, Pw, uk, ek, E) if deg == p: break n, U = curve.mult_degree_elevate(nh, deg, Uh, 1) U, Pw = global_curve_approx_fixedn(r, Q, deg + 1, n, uk, U) update_errors(n, deg + 1, U, Pw, r, Q, uk, ek) if n == nh: return U, Pw U, n = Uh, nh U, Pw = global_curve_approx_fixedn(r, Q, p, n, uk, U) update_errors(n, p, U, Pw, r, Q, uk, ek) dummy, Uh, Pwh = remove_knots_bounds_curve(n, p, U, Pw, uk, ek, E) return Uh, Pwh except (RuntimeError, NotEnoughDataPoints): return global_curve_approx_errbnd(r, Q, p, E, ps + 1)
def global_curve_interp_ders(n, Q, p, k, Ds, l, De, uk=None): ''' Idem to global_curve_interp, but, in addition to the data points, end derivatives are also given Ds^1,...,Ds^k De^1,...,De^l k,l < p where Ds^i denotes the ith derivative at the start point and De^j the jth derivative at the end. Parameters ---------- n + 1 = the number of data points to interpolate Q = the point set in object matrix form p = the degree of the interpolant k, l = the number of start and end point derivatives Ds, De = the start and end point derivatives uk = the parameter values associated to each point (if available) Returns ------- U, Pw = the knot vector and the object matrix of the interpolant Source ------ Piegl & Tiller, Curve Interpolation with Arbitrary End Derivatives, Engineering with Computers, 2000. ''' if p < 1 or not k < p or not l < p or n + k + l < p: raise ImproperInput(p, n, k, l) Q = nurbs.obj_mat_to_3D(Q) Ds, De = [np.asfarray(V) for V in Ds, De] nk = n + k nkl = nk + l P = np.zeros((nkl + 1, 3)) if uk is None: uk = knot.chord_length_param(n, Q) U = knot.end_derivs_knot_vec(n, p, k, l, uk) A = scipy.sparse.lil_matrix((nkl + 1, nkl + 1)) A[:k+1,:p+1] = \ basis.ders_basis_funs(p, uk[0], p, k, U) for i in xrange(1, n): span = basis.find_span(nkl, p, U, uk[i]) A[i + k, span - p:span + 1] = basis.basis_funs(span, uk[i], p, U) A[nkl+1:nk-1:-1,nkl-p:nkl+1] = \ basis.ders_basis_funs(nkl, uk[-1], p, l, U) lu = scipy.sparse.linalg.splu(A.tocsc()) for i in xrange(3): rhs = Q[:, i] ind = k * [1] + l * [n] der = np.hstack((Ds[:, i] if k else [], De[::-1, i] if l else [])) rhs = np.insert(rhs, ind, der) P[:, i] = lu.solve(rhs) Pw = nurbs.obj_mat_to_4D(P) return U, Pw
def global_curve_interp(n, Q, p, uk=None, U=None): ''' Suppose a pth-degree nonrational B-spline curve interpolant to the set of points {Qk}, k = 0,...,n, is seeked. If a parameter value ubk is assigned to each Qk, and an appropriate knot vector U = {u0,...,um} is selected, it is possible to set up a (n + 1) x (n + 1) system of linear equations Qk = C(ubk) = sum_(i=0)^n (Nip(ubk) * Pi) The control points, Pi, are the (n + 1) unknowns. Let r be the number of coordinates in the Qk (r < 4). Note that this method is independent of r; there is only one coefficient matrix, with r right-hand sides and, correspondingly, r solution sets for the r coordinates. Parameters ---------- n + 1 = the number of data points to interpolate Q = the point set in object matrix form p = the degree of the interpolant uk = the parameter values associated to each point (if available) U = the knot vector to use for the interpolation (if available) Returns ------- U, Pw = the knot vector and the object matrix of the interpolant Source ------ The NURBS Book (2nd Ed.), Pg. 369. ''' if p < 1 or n < p: raise ImproperInput(p, n) Q = nurbs.obj_mat_to_3D(Q) P = np.zeros((n + 1, 3)) if uk is None: uk = knot.chord_length_param(n, Q) if U is None: U = knot.averaging_knot_vec(n, p, uk) A = scipy.sparse.lil_matrix((n + 1, n + 1)) for i in xrange(n + 1): span = basis.find_span(n, p, U, uk[i]) A[i, span - p:span + 1] = basis.basis_funs(span, uk[i], p, U) lu = scipy.sparse.linalg.splu(A.tocsc()) for i in xrange(3): rhs = Q[:, i] P[:, i] = lu.solve(rhs) Pw = nurbs.obj_mat_to_4D(P) return U, Pw
def global_curve_approx_fixedn_fair(r, Q, p, n, B=60): ''' Idem to global_curve_approx_fixedn, but, in addition to fitting the data points, the curve energy functional int_u ( B * || C''(u) ||^2 ) du is also minimized. B is some user-defined coefficient; if set to 0 then the problem reverts back to the normal least-squares fit. Note that given the polynomial nature of B-splines the above integral is carried out exactly using Gauss-Legendre quadrature. Parameters ---------- r + 1 = the number of data points to fit Q = the point set in object matrix form p = the degree of the fit n + 1 = the number of control points to use in the fit B = the bending coefficient Returns ------- U, Pw = the knot vector and the object matrix of the fitted curve Source ------ Park et al., A method for approximate NURBS curve compatibility based on multiple curve refitting, Computer-aided design, 2000. ''' if p < 1 or n < p: raise ImproperInput(p, n) if not r > n: raise NotEnoughDataPoints(r, n) Q = nurbs.obj_mat_to_3D(Q) uk = knot.chord_length_param(r, Q) U = knot.approximating_knot_vec(n, p, r, uk) Uu = np.unique(U) ni = len(Uu) - 1 # get the Gauss-Legendre roots and weights gn = (p + 2) // 2 gx, gw = scipy.special.orthogonal.p_roots(gn) # precompute the N^(2) vector for each knot span u = [] for i in xrange(ni): a, b = Uu[i], Uu[i + 1] uu = (b - a) * (gx + 1) / 2.0 + a u.append(uu) u = np.hstack(u) N2 = np.zeros((n + 1, gn * ni)) for i in xrange(n + 1): N2[i] = basis.ders_one_basis_fun_v(p, U, i, u, 2, gn * ni)[2] # build the (normalized) stiffness matrix K K = np.zeros((n + 1, n + 1)) for i in xrange(n + 1): for j in xrange(i, min(i + p + 1, n + 1)): kij = 0.0 for k in xrange(ni): a, b = Uu[k], Uu[k + 1] kgn = k * gn NN = N2[i, kgn:kgn + gn] * N2[j, kgn:kgn + gn] kij += (b - a) / 2.0 * np.dot(gw, NN) K[i, j] = kij K += K.T K[np.diag_indices_from(K)] /= 2 K /= np.max(K) # build the system matrix NTN N = np.zeros((r + 1, n + 1)) spans = basis.find_span_v(n, p, U, uk, r + 1) bfuns = basis.basis_funs_v(spans, uk, p, U, r + 1) spans0, spans1 = spans - p, spans + 1 for s in xrange(r + 1): N[s, spans0[s]:spans1[s]] = bfuns[:, s] NTN = np.dot(N.T, N) # build the matrix of constraints C C = np.zeros((2, n + 1)) for i in (0, -1): span = basis.find_span(n, p, U, uk[i]) bfun = basis.basis_funs(span, uk[i], p, U) C[i, span - p:span + 1] = bfun # build the rhs vector D D = np.zeros((2, 3)) D[0], D[1] = Q[0], Q[r] # build and solve Eq. (12) A = scipy.sparse.lil_matrix((n + 3, n + 3)) A[:n + 1, :n + 1] = B * K + NTN A[:n + 1, n + 1:] = C.T A[n + 1:, :n + 1] = C lu = scipy.sparse.linalg.splu(A.tocsc()) P = np.zeros((n + 1, 3)) rhs = np.zeros(n + 3) for i in xrange(3): rhs[:n + 1] = np.dot(N.T, Q[:, i]) rhs[n + 1:] = D[:, i] sol = lu.solve(rhs) P[:, i] = sol[:n + 1] Pw = nurbs.obj_mat_to_4D(P) return U, Pw
def global_curve_approx_fixedn_ders(r, Q, p, n, k, Ds, l, De, uk=None): ''' Idem to global_curve_approx_fixedn, but, in addition to the data points, end derivatives are also given Ds^1,...,Ds^k De^1,...,De^l k,l < p + 1 where Ds^i denotes the ith derivative at the start point and De^j the jth derivative at the end. Parameters ---------- r + 1 = the number of data points to fit Q = the point set in object matrix form p = the degree of the fit n + 1 = the number of control points to use in the fit k, l = the number of start and end point derivatives Ds, De = the start and end point derivatives uk = the parameter values associated to each data point (if available) Returns ------- U, Pw = the knot vector and the object matrix of the fitted curve Source ------ Piegl & Tiller, Least-Squares B-spline Curve Approximation with Arbitrary End Derivatives, Engineering with Computers, 2000. ''' if p < 1 or n < p or p < k or p < l or n < k + l + 1: raise ImproperInput(p, n, k, l) if not r > n: raise NotEnoughDataPoints(r, n) Q = nurbs.obj_mat_to_3D(Q) Ds, De = [np.asfarray(V) for V in Ds, De] if uk is None: uk = knot.chord_length_param(r, Q) U = knot.approximating_knot_vec_end(n, p, r, k, l, uk) P = np.zeros((n + 1, 3)) P[0], P[n] = Q[0], Q[r] if k > 0: ders = basis.ders_basis_funs(p, uk[0], p, k, U) for i in xrange(1, k + 1): Pi = Ds[i - 1] for h in xrange(i): Pi -= ders[i, h] * P[h] P[i] = Pi / ders[i, i] if l > 0: ders = basis.ders_basis_funs(n, uk[-1], p, l, U) for j in xrange(1, l + 1): Pj = De[j - 1] for h in xrange(j): Pj -= ders[j, -h - 1] * P[-h - 1] P[-j - 1] = Pj / ders[j, -j - 1] if n > k + l + 1: Ps = [P[i][:, np.newaxis] for i in xrange(k + 1)] Pe = [P[-j - 1][:, np.newaxis] for j in xrange(l + 1)] lu, NT, Os, Oe = build_decompose_NTN(r, p, n, uk, U, k, l) R = Q.copy() for i in xrange(k + 1): R -= (Os[i] * Ps[i]).T for j in xrange(l + 1): R -= (Oe[j] * Pe[j]).T for i in xrange(3): rhs = np.dot(NT, R[1:-1, i]) P[k + 1:-l - 1, i] = lu.solve(rhs) Pw = nurbs.obj_mat_to_4D(P) return U, Pw
def global_curve_approx_fixedn(r, Q, p, n, uk=None, U=None, NTN=None): ''' Assume the degree, number of control points (minus one) and data points are given. A pth-degree nonrational curve C(u) = sum_(i=0)^n (Nip(u) * Pi) u in [0, 1] is seeked, satisfying that: - Q0 = C(0) and Qm = C(1); - the remaining Qk are approximated in the least-squares sense, i.e. sum_(k=1)^(m-1) |Qk - C(ubk)|^2 is a minimum with respect to the (n + 1) variables, Pi; the {ubk} are the precomputed parameter values. The resulting curve generally does not pass precisely through Qk, and C(ubk) is not the closest points on C(u) to Qk. Parameters ---------- r + 1 = the number of data points to fit Q = the point set in object matrix form p = the degree of the fit n + 1 = the number of control points to use in the fit uk = the parameter values associated to each data point (if available) U = the knot vector to use in the fit (if available) NTN = the decomposed matrix of scalars (if available) Returns ------- U, Pw = the knot vector and the object matrix of the fitted curve Source ------ The NURBS Book (2nd Ed.), Pg. 410. ''' if p < 1 or n < p: raise ImproperInput(p, n) if not r > n: raise NotEnoughDataPoints(r, n) Q = nurbs.obj_mat_to_3D(Q) if uk is None: uk = knot.chord_length_param(r, Q) if U is None: U = knot.approximating_knot_vec(n, p, r, uk) if NTN is None: lu, NT, Os, Oe = build_decompose_NTN(r, p, n, uk, U) else: lu, NT, Os, Oe = NTN R = Q - (Os[0] * Q[0][:,np.newaxis]).T - \ (Oe[0] * Q[r][:,np.newaxis]).T P = np.zeros((n + 1, 3)) P[0], P[n] = Q[0], Q[r] for i in xrange(3): rhs = np.dot(NT, R[1:-1, i]) P[1:-1, i] = lu.solve(rhs) Pw = nurbs.obj_mat_to_4D(P) return U, Pw