def test_greville(self): b = BSplineBasis(4, [0, 0, 0, 0, 1, 2, 3, 3, 3, 3]) self.assertAlmostEqual(b.greville(0), 0.0) self.assertAlmostEqual(b.greville(1), 1.0 / 3.0) self.assertAlmostEqual(b.greville(2), 1.0) self.assertAlmostEqual(b.greville(), [0.0, 1.0 / 3.0, 1.0, 2.0, 8.0 / 3.0, 3.0])
def rebuild(self, p, n): """ Creates an approximation to this curve by resampling it using a uniform knot vector of order *p* with *n* control points. :param int p: Polynomial discretization order :param int n: Number of control points :return: A new approximate curve :rtype: Curve """ # establish uniform open knot vector knot = [0] * p + list(range(1, n - p + 1)) + [n - p + 1] * p basis = BSplineBasis(p, knot) # set parametric range of the new basis to be the same as the old one basis.normalize() t0 = self.bases[0].start() t1 = self.bases[0].end() basis *= (t1 - t0) basis += t0 # fetch evaluation points and solve interpolation problem t = basis.greville() N = basis.evaluate(t, sparse=True) controlpoints = splinalg.spsolve(N, self.evaluate(t)) # return new resampled curve return Curve(basis, controlpoints)
def rebuild(self, p, n): """Creates an approximation to this curve by resampling it using a uniform knot vector of order *p* with *n* control points. :param int p: Polynomial discretization order :param int n: Number of control points :return: A new approximate curve :rtype: Curve """ # establish uniform open knot vector knot = [0] * p + list(range(1, n - p + 1)) + [n - p + 1] * p basis = BSplineBasis(p, knot) # set parametric range of the new basis to be the same as the old one basis.normalize() t0 = self.bases[0].start() t1 = self.bases[0].end() basis *= t1 - t0 basis += t0 # fetch evaluation points and solve interpolation problem t = basis.greville() N = basis.evaluate(t) controlpoints = np.linalg.solve(N, self.evaluate(t)) # return new resampled curve return Curve(basis, controlpoints)
def loft(*curves): if len(curves) == 1: curves = curves[0] # clone input, so we don't change those references # make sure everything has the same dimension since we need to compute length curves = [c.clone().set_dimension(3) for c in curves] if len(curves)==2: return edge_curves(curves) elif len(curves)==3: # can't do cubic spline interpolation, so we'll do quadratic basis2 = BSplineBasis(3) dist = basis2.greville() else: x = [c.center() for c in curves] # create knot vector from the euclidian length between the curves dist = [0] for (x1,x0) in zip(x[1:],x[:-1]): dist.append(dist[-1] + np.linalg.norm(x1-x0)) # using "free" boundary condition by setting N'''(u) continuous at second to last and second knot knot = [dist[0]]*4 + dist[2:-2] + [dist[-1]]*4 basis2 = BSplineBasis(4, knot) n = len(curves) for i in range(n): for j in range(i+1,n): Curve.make_splines_identical(curves[i], curves[j]) basis1 = curves[0].bases[0] m = basis1.num_functions() u = basis1.greville() # parametric interpolation points v = dist # parametric interpolation points # compute matrices Nu = basis1(u) Nv = basis2(v) Nu_inv = np.linalg.inv(Nu) Nv_inv = np.linalg.inv(Nv) # compute interpolation points in physical space x = np.zeros((m,n, curves[0][0].size)) for i in range(n): x[:,i,:] = Nu * curves[i].controlpoints # solve interpolation problem cp = np.tensordot(Nv_inv, x, axes=(1,1)) cp = np.tensordot(Nu_inv, cp, axes=(1,1)) # re-order controlpoints so they match up with Surface constructor cp = cp.transpose((1, 0, 2)) cp = cp.reshape(n*m, cp.shape[2]) return Surface(basis1, basis2, cp, curves[0].rational)
def loft(*curves): if len(curves) == 1: curves = curves[0] # clone input, so we don't change those references # make sure everything has the same dimension since we need to compute length curves = [c.clone().set_dimension(3) for c in curves] if len(curves)==2: return edge_curves(curves) elif len(curves)==3: # can't do cubic spline interpolation, so we'll do quadratic basis2 = BSplineBasis(3) dist = basis2.greville() else: x = [c.center() for c in curves] # create knot vector from the euclidian length between the curves dist = [0] for (x1,x0) in zip(x[1:],x[:-1]): dist.append(dist[-1] + np.linalg.norm(x1-x0)) # using "free" boundary condition by setting N'''(u) continuous at second to last and second knot knot = [dist[0]]*4 + dist[2:-2] + [dist[-1]]*4 basis2 = BSplineBasis(4, knot) n = len(curves) for i in range(n): for j in range(i+1,n): Curve.make_splines_identical(curves[i], curves[j]) basis1 = curves[0].bases[0] m = basis1.num_functions() u = basis1.greville() # parametric interpolation points v = dist # parametric interpolation points # compute matrices Nu = basis1(u) Nv = basis2(v) Nu_inv = np.linalg.inv(Nu) Nv_inv = np.linalg.inv(Nv) # compute interpolation points in physical space x = np.zeros((m,n, curves[0][0].size)) for i in range(n): x[:,i,:] = Nu * curves[i].controlpoints # solve interpolation problem cp = np.tensordot(Nv_inv, x, axes=(1,1)) cp = np.tensordot(Nu_inv, cp, axes=(1,1)) # re-order controlpoints so they match up with Surface constructor cp = cp.transpose((1, 0, 2)) cp = cp.reshape(n*m, cp.shape[2]) return Surface(basis1, basis2, cp, curves[0].rational)
def test_bug44(self): # https://github.com/sintefmath/Splipy/issues/44 b = BSplineBasis(4, [ 0. , 0. , 0. , 0. , 0.01092385, 0.02200375, 0.03316167, 0.04435861, 0.05526295, 0.0663331 , 0.07748615, 0.08868065, 0.09989588, 0.11080936, 0.12188408, 0.13303942, 0.14423507, 0.15545087, 0.16636463, 0.1774395 , 0.1885949 , 0.19979059, 0.2 , 0.2 , 0.2 , 0.2 ]) grvl = b.greville() # last greville point is 0.20000000000000004, and technically outside domain [0,.2] self.assertAlmostEqual(grvl[-1], 0.2) # should snap to 0.2 and evaluate to 1 for the last function self.assertAlmostEqual(b(grvl[-1])[-1,-1], 1.0)
def test_bug44(self): # https://github.com/sintefmath/Splipy/issues/44 b = BSplineBasis(4, [ 0. , 0. , 0. , 0. , 0.01092385, 0.02200375, 0.03316167, 0.04435861, 0.05526295, 0.0663331 , 0.07748615, 0.08868065, 0.09989588, 0.11080936, 0.12188408, 0.13303942, 0.14423507, 0.15545087, 0.16636463, 0.1774395 , 0.1885949 , 0.19979059, 0.2 , 0.2 , 0.2 , 0.2 ]) grvl = b.greville() # last greville point is 0.20000000000000004, and technically outside domain [0,.2] self.assertAlmostEqual(grvl[-1], 0.2) # should snap to 0.2 and evaluate to 1 for the last function self.assertAlmostEqual(b(grvl[-1])[-1,-1], 1.0)
def test_curve_interpolation(self): basis = BSplineBasis(4, [0, 0, 0, 0, .3, .9, 1, 1, 1, 1]) t = np.array(basis.greville()) # create the mapping (x,y,z)=(t^2, 1-t, t^3+2*t) x_pts = np.zeros((len(t), 3)) x_pts[:, 0] = t * t x_pts[:, 1] = 1 - t x_pts[:, 2] = t * t * t + 2 * t crv = CurveFactory.interpolate(x_pts, basis) self.assertEqual(crv.order(0), 4) self.assertAlmostEqual(crv(.4)[0], .4**2) # x=t^2 self.assertAlmostEqual(crv(.4)[1], 1 - .4) # y=1-t self.assertAlmostEqual(crv(.4)[2], .4**3 + 2 * .4) # z=t^3+2t self.assertAlmostEqual(crv(.5)[0], .5**2) # x=t^2 self.assertAlmostEqual(crv(.5)[1], 1 - .5) # y=1-t self.assertAlmostEqual(crv(.5)[2], .5**3 + 2 * .5) # z=t^3+2t
def test_curve_interpolation(self): basis = BSplineBasis(4, [0, 0, 0, 0, .3, .9, 1, 1, 1, 1]) t = np.array(basis.greville()) # create the mapping (x,y,z)=(t^2, 1-t, t^3+2*t) x_pts = np.zeros((len(t), 3)) x_pts[:, 0] = t * t x_pts[:, 1] = 1 - t x_pts[:, 2] = t * t * t + 2 * t crv = cf.interpolate(x_pts, basis) self.assertEqual(crv.order(0), 4) self.assertAlmostEqual(crv(.4)[0], .4**2) # x=t^2 self.assertAlmostEqual(crv(.4)[1], 1 - .4) # y=1-t self.assertAlmostEqual(crv(.4)[2], .4**3 + 2 * .4) # z=t^3+2t self.assertAlmostEqual(crv(.5)[0], .5**2) # x=t^2 self.assertAlmostEqual(crv(.5)[1], 1 - .5) # y=1-t self.assertAlmostEqual(crv(.5)[2], .5**3 + 2 * .5) # z=t^3+2t
def test_lower_order(self): basis = BSplineBasis(4, [0,0,0,0,.2, .3, .3, .6, .9, 1,1,1,1]) interp_pts = basis.greville() x = [[t*(1-t), t**2] for t in interp_pts] crv = CurveFactory.interpolate(x, basis) # function in space, exact representation kept crv2 = crv.lower_order(1) # still in space, crv2 is *also* exact t = np.linspace(0,1, 13) self.assertTrue(np.allclose( crv(t), crv2(t)) ) self.assertEqual(crv.order(0), 4) self.assertEqual(crv2.order(0), 3) self.assertEqual(crv.continuity(0.3), 1) self.assertEqual(crv.continuity(0.6), 2) self.assertEqual(crv2.continuity(0.3), 1) self.assertEqual(crv2.continuity(0.6), 1)
def test_lower_order(self): b = BSplineBasis(4, [0, 0, 0, 0, .2, .3, .3, .6, .9, 1, 1, 1, 1]) t = b.greville() Y, X, Z = np.meshgrid(t, t, t) cp = np.zeros((len(t), len(t), len(t), 3)) cp[..., 0] = X * (1 - Y) cp[..., 1] = X * X cp[..., 2] = Z**2 + 2 vol = vf.interpolate(cp, [b, b, b]) vol2 = vol.lower_order(1) # still in space, vol2 is *also* exact u = np.linspace(0, 1, 5) v = np.linspace(0, 1, 6) w = np.linspace(0, 1, 7) self.assertTrue(np.allclose(vol(u, v, w), vol2(u, v, w))) self.assertTupleEqual(vol.order(), (4, 4, 4)) self.assertTupleEqual(vol2.order(), (3, 3, 3))
def test_lower_order(self): b = BSplineBasis(4, [0,0,0,0,.2, .3, .3, .6, .9, 1,1,1,1]) t = b.greville() Y, X, Z = np.meshgrid(t,t,t) cp = np.zeros((len(t), len(t), len(t), 3)) cp[...,0] = X*(1-Y) cp[...,1] = X*X cp[...,2] = Z**2 + 2 vol = VolumeFactory.interpolate(cp, [b,b,b]) vol2 = vol.lower_order(1) # still in space, vol2 is *also* exact u = np.linspace(0,1, 5) v = np.linspace(0,1, 6) w = np.linspace(0,1, 7) self.assertTrue(np.allclose( vol(u,v,w), vol2(u,v,w) )) self.assertTupleEqual(vol.order(), (4,4,4)) self.assertTupleEqual(vol2.order(), (3,3,3))
def test_lower_order(self): basis = BSplineBasis(4, [0, 0, 0, 0, .2, .3, .3, .6, .9, 1, 1, 1, 1]) interp_pts = basis.greville() x = [[t * (1 - t), t**2] for t in interp_pts] crv = cf.interpolate( x, basis) # function in space, exact representation kept crv2 = crv.lower_order(1) # still in space, crv2 is *also* exact t = np.linspace(0, 1, 13) self.assertTrue(np.allclose(crv(t), crv2(t))) self.assertEqual(crv.order(0), 4) self.assertEqual(crv2.order(0), 3) self.assertEqual(crv.continuity(0.3), 1) self.assertEqual(crv.continuity(0.6), 2) self.assertEqual(crv2.continuity(0.3), 1) self.assertEqual(crv2.continuity(0.6), 1)
def camber(M, P, order=5): """ Create the NACA centerline used for wing profiles. This is given as an exact quadratic piecewise polynomial y(x), see http://airfoiltools.com/airfoil/naca4digit. The method will produce one of two representations: For order<5 it will create x(t)=t and for order>4 it will create x(t) as qudratic in t and stretched towards the endpointspoints creating a more optimized parametrization. :param M: Max camber height (y) given as percentage 0% to 9% of length :type M: Int 0<M<10 :param P: Max camber position (x) given as percentage 0% to 90% of length :type P: Int 0<P<10 :return : Exact centerline representation :rtype : Curve """ # parametrized by x=t or x="t^2" if order>4 M = M / 100.0 P = P / 10.0 basis = BSplineBasis(order) # basis.insert_knot([P]*(order-2)) # insert a C1-knot for i in range(order - 2): basis.insert_knot(P) t = basis.greville() n = len(t) x = np.zeros((n, 2)) for i in range(n): if t[i] <= P: if order > 4: x[i, 0] = t[i]**2 / P else: x[i, 0] = t[i] x[i, 1] = M / P / P * (2 * P * x[i, 0] - x[i, 0] * x[i, 0]) else: if order > 4: x[i, 0] = (t[i]**2 - 2 * t[i] + P) / (P - 1) else: x[i, 0] = t[i] x[i, 1] = M / (1 - P) / (1 - P) * (1 - 2 * P + 2 * P * x[i, 0] - x[i, 0] * x[i, 0]) N = basis.evaluate(t) controlpoints = np.linalg.solve(N, x) return Curve(basis, controlpoints)
def loft(*surfaces): if len(surfaces) == 1: surfaces = surfaces[0] # clone input, so we don't change those references # make sure everything has the same dimension since we need to compute length surfaces = [s.clone().set_dimension(3) for s in surfaces] if len(surfaces) == 2: return SurfaceFactory.edge_curves(surfaces) elif len(surfaces) == 3: # can't do cubic spline interpolation, so we'll do quadratic basis3 = BSplineBasis(3) dist = basis3.greville() else: x = [s.center() for s in surfaces] # create knot vector from the euclidian length between the surfaces dist = [0] for (x1, x0) in zip(x[1:], x[:-1]): dist.append(dist[-1] + np.linalg.norm(x1 - x0)) # using "free" boundary condition by setting N'''(u) continuous at second to last and second knot knot = [dist[0]] * 4 + dist[2:-2] + [dist[-1]] * 4 basis3 = BSplineBasis(4, knot) n = len(surfaces) for i in range(n): for j in range(i + 1, n): Surface.make_splines_identical(surfaces[i], surfaces[j]) basis1 = surfaces[0].bases[0] basis2 = surfaces[0].bases[1] m1 = basis1.num_functions() m2 = basis2.num_functions() dim = len(surfaces[0][0]) u = basis1.greville() # parametric interpolation points v = basis2.greville() w = dist # compute matrices Nu = basis1(u) Nv = basis2(v) Nw = basis3(w) Nu_inv = np.linalg.inv(Nu) Nv_inv = np.linalg.inv(Nv) Nw_inv = np.linalg.inv(Nw) # compute interpolation points in physical space x = np.zeros((m1, m2, n, dim)) for i in range(n): tmp = np.tensordot(Nv, surfaces[i].controlpoints, axes=(1, 1)) x[:, :, i, :] = np.tensordot(Nu, tmp, axes=(1, 1)) # solve interpolation problem cp = np.tensordot(Nw_inv, x, axes=(1, 2)) cp = np.tensordot(Nv_inv, cp, axes=(1, 2)) cp = np.tensordot(Nu_inv, cp, axes=(1, 2)) # re-order controlpoints so they match up with Surface constructor cp = np.reshape(cp.transpose((2, 1, 0, 3)), (m1 * m2 * n, dim)) return Volume(basis1, basis2, basis3, cp, surfaces[0].rational)
def loft(*curves): """ Generate a surface by lofting a series of curves The resulting surface is interpolated at all input curves and a smooth transition between these curves is computed as a cubic spline interpolation in the lofting direction. In the case that insufficient curves are provided as input (less than 4 curves), then a quadratic or linear interpolation is performed. Note that the order of input curves matter as they will be interpolated in this particular order. Also note that the curves need to be parametrized in the same direction, otherwise you will encounter self-intersecting result surface as it is wrapping in on itself. :param Curve curves: A sequence of curves to be lofted :return: Lofted surface :rtype: Surface Examples: .. code:: python from splipy import curve_factory, surface_factory crv1 = curve_factory.circle(r=1) crv2 = 1.5 * crv1 + [0,0,1] crv3 = 1.0 * crv1 + [0,0,2] crv4 = 1.3 * crv1 + [0,0,4] srf = surface_factory.loft(crv1, crv2, crv3, crv4) # alternatively you can provide all input curves as a list all_my_curves = [crv1, crv2, crv3, crv4] srf = surface_factory.loft(all_my_curves) """ if len(curves) == 1: curves = curves[0] # clone input, so we don't change those references # make sure everything has the same dimension since we need to compute length curves = [c.clone().set_dimension(3) for c in curves] if len(curves) == 2: return edge_curves(curves) elif len(curves) == 3: # can't do cubic spline interpolation, so we'll do quadratic basis2 = BSplineBasis(3) dist = basis2.greville() else: x = [c.center() for c in curves] # create knot vector from the euclidian length between the curves dist = [0] for (x1, x0) in zip(x[1:], x[:-1]): dist.append(dist[-1] + np.linalg.norm(x1 - x0)) # using "free" boundary condition by setting N'''(u) continuous at second to last and second knot knot = [dist[0]] * 4 + dist[2:-2] + [dist[-1]] * 4 basis2 = BSplineBasis(4, knot) n = len(curves) for i in range(n): for j in range(i + 1, n): Curve.make_splines_identical(curves[i], curves[j]) basis1 = curves[0].bases[0] m = basis1.num_functions() u = basis1.greville() # parametric interpolation points v = dist # parametric interpolation points # compute matrices Nu = basis1(u) Nv = basis2(v) Nu_inv = np.linalg.inv(Nu) Nv_inv = np.linalg.inv(Nv) # compute interpolation points in physical space x = np.zeros((m, n, curves[0][0].size)) for i in range(n): x[:, i, :] = Nu @ curves[i].controlpoints # solve interpolation problem cp = np.tensordot(Nv_inv, x, axes=(1, 1)) cp = np.tensordot(Nu_inv, cp, axes=(1, 1)) # re-order controlpoints so they match up with Surface constructor cp = cp.transpose((1, 0, 2)) cp = cp.reshape(n * m, cp.shape[2]) return Surface(basis1, basis2, cp, curves[0].rational)
def test_greville(self): b = BSplineBasis(4, [0, 0, 0, 0, 1, 2, 3, 3, 3, 3]) self.assertAlmostEqual(b.greville(0), 0.0) self.assertAlmostEqual(b.greville(1), 1.0 / 3.0) self.assertAlmostEqual(b.greville(2), 1.0) self.assertAlmostEqual(b.greville(), [0.0, 1.0/3.0, 1.0, 2.0, 8.0/3.0, 3.0])
def loft(*surfaces): if len(surfaces) == 1: surfaces = surfaces[0] # clone input, so we don't change those references # make sure everything has the same dimension since we need to compute length surfaces = [s.clone().set_dimension(3) for s in surfaces] if len(surfaces)==2: return SurfaceFactory.edge_curves(surfaces) elif len(surfaces)==3: # can't do cubic spline interpolation, so we'll do quadratic basis3 = BSplineBasis(3) dist = basis3.greville() else: x = [s.center() for s in surfaces] # create knot vector from the euclidian length between the surfaces dist = [0] for (x1,x0) in zip(x[1:],x[:-1]): dist.append(dist[-1] + np.linalg.norm(x1-x0)) # using "free" boundary condition by setting N'''(u) continuous at second to last and second knot knot = [dist[0]]*4 + dist[2:-2] + [dist[-1]]*4 basis3 = BSplineBasis(4, knot) n = len(surfaces) for i in range(n): for j in range(i+1,n): Surface.make_splines_identical(surfaces[i], surfaces[j]) basis1 = surfaces[0].bases[0] basis2 = surfaces[0].bases[1] m1 = basis1.num_functions() m2 = basis2.num_functions() dim = len(surfaces[0][0]) u = basis1.greville() # parametric interpolation points v = basis2.greville() w = dist # compute matrices Nu = basis1(u) Nv = basis2(v) Nw = basis3(w) Nu_inv = np.linalg.inv(Nu) Nv_inv = np.linalg.inv(Nv) Nw_inv = np.linalg.inv(Nw) # compute interpolation points in physical space x = np.zeros((m1,m2,n, dim)) for i in range(n): tmp = np.tensordot(Nv, surfaces[i].controlpoints, axes=(1,1)) x[:,:,i,:] = np.tensordot(Nu, tmp , axes=(1,1)) # solve interpolation problem cp = np.tensordot(Nw_inv, x, axes=(1,2)) cp = np.tensordot(Nv_inv, cp, axes=(1,2)) cp = np.tensordot(Nu_inv, cp, axes=(1,2)) # re-order controlpoints so they match up with Surface constructor cp = np.reshape(cp.transpose((2, 1, 0, 3)), (m1*m2*n, dim)) return Volume(basis1, basis2, basis3, cp, surfaces[0].rational)
def loft(*surfaces): """ Generate a volume by lofting a series of surfaces The resulting volume is interpolated at all input surfaces and a smooth transition between these surfaces is computed as a cubic spline interpolation in the lofting direction. In the case that insufficient surfaces are provided as input (less than 4 surfaces), then a quadratic or linear interpolation is performed. Note that the order of input surfaces matter as they will be interpolated in this particular order. Also note that the surfaces need to be parametrized in the same direction, otherwise you will encounter self-intersecting result volume as it is wrapping in on itself. :param Surfaces surfaces: A sequence of surfaces to be lofted :return: Lofted volume :rtype: Volume Examples: .. code:: python from splipy import surface_factory, volume_factory srf1 = surface_factory.disc(r=1) srf2 = 1.5 * srf1 + [0,0,1] srf3 = 1.0 * srf1 + [0,0,2] srf4 = 1.3 * srf1 + [0,0,4] vol = volume_factory.loft(srf1, srf2, srf3, srf4) # alternatively you can provide all input curves as a list all_my_surfaces = [srf1, srf2, srf3, srf4] vol = volume_factory.loft(all_my_surfaces) """ if len(surfaces) == 1: surfaces = surfaces[0] # clone input, so we don't change those references # make sure everything has the same dimension since we need to compute length surfaces = [s.clone().set_dimension(3) for s in surfaces] if len(surfaces) == 2: return SurfaceFactory.edge_curves(surfaces) elif len(surfaces) == 3: # can't do cubic spline interpolation, so we'll do quadratic basis3 = BSplineBasis(3) dist = basis3.greville() else: x = [s.center() for s in surfaces] # create knot vector from the euclidian length between the surfaces dist = [0] for (x1, x0) in zip(x[1:], x[:-1]): dist.append(dist[-1] + np.linalg.norm(x1 - x0)) # using "free" boundary condition by setting N'''(u) continuous at second to last and second knot knot = [dist[0]] * 4 + dist[2:-2] + [dist[-1]] * 4 basis3 = BSplineBasis(4, knot) n = len(surfaces) for i in range(n): for j in range(i + 1, n): Surface.make_splines_identical(surfaces[i], surfaces[j]) basis1 = surfaces[0].bases[0] basis2 = surfaces[0].bases[1] m1 = basis1.num_functions() m2 = basis2.num_functions() dim = len(surfaces[0][0]) u = basis1.greville() # parametric interpolation points v = basis2.greville() w = dist # compute matrices Nu = basis1(u) Nv = basis2(v) Nw = basis3(w) Nu_inv = np.linalg.inv(Nu) Nv_inv = np.linalg.inv(Nv) Nw_inv = np.linalg.inv(Nw) # compute interpolation points in physical space x = np.zeros((m1, m2, n, dim)) for i in range(n): tmp = np.tensordot(Nv, surfaces[i].controlpoints, axes=(1, 1)) x[:, :, i, :] = np.tensordot(Nu, tmp, axes=(1, 1)) # solve interpolation problem cp = np.tensordot(Nw_inv, x, axes=(1, 2)) cp = np.tensordot(Nv_inv, cp, axes=(1, 2)) cp = np.tensordot(Nu_inv, cp, axes=(1, 2)) # re-order controlpoints so they match up with Surface constructor cp = np.reshape(cp.transpose((2, 1, 0, 3)), (m1 * m2 * n, dim)) return Volume(basis1, basis2, basis3, cp, surfaces[0].rational)
def fit(x, t0, t1, rtol=1e-4, atol=0.0): """ Computes an interpolation for a parametric curve up to a specified tolerance. The method will iteratively refine parts where needed resulting in a non-uniform knot vector with as optimized knot locations as possible. :param function x: callable function which takes as input a vector of evaluation points t and gives as output a matrix x where x[i,j] is component j evaluated at point t[i] :param float t0: start of parametric domain :param float t1: end of parametric domain :param float rtol: relative tolerance for stopping criterium. It is defined to be ||e||_L2 / D, where D is the length of the curve and ||e||_L2 is the L2-error (see Curve.error) :param float atol: absolute tolerance for stopping criterium. It is defined to be the maximal distance between the curve approximation and the exact curve :return: Curve Non-uniform cubic B-spline curve Examples: .. code:: python import numpy as np import splipy.curve_factory as curve_factory # gives a B-spline approximation to the circle with arclength parametrization # unlike curve_factory.circle which is exact, but not arclength def arclength_circle(t): return np.array( [np.cos(t), np.sin(t)] ).T crv = curve_factory.fit(arclength_circle, 0, 2*np.pi) print crv # approximates a difficult function with wild behaviour around t=0, but # this is overcome by a higher knot density around this point def one_over_t(t): eps = 1e-8 # to avoid 1/0 we add a small epsilon return np.array( [t, 1.0/(t+eps)] ).T crv = curve_factory.fit(one_over_t, 0, 1, rtol=1e-6) print crv # first knot span is ~1e-9, last knot is ~1e-1 # one can specify the target curve in terms of existing Curve objects crv = curve_factory.circle(r=1) # Curve-object, quadratic NURBS def move_along_tangent(t): return crv(t) + crv.tangent(t) # can evaluate curve or its derivatives # fit() will create a B-spline approximation using non-uniform refinement crv2 = curve_factory.fit(move_along_tangent, crv.start(0), crv.end(0)) """ b = BSplineBasis(4, [t0,t0,t0,t0, t1,t1,t1,t1]) t = np.array(b.greville()) crv = interpolate(x(t), b, t) (err2, maxerr) = crv.error(x) # polynomial input (which can be exactly represented) only use one knot span if maxerr < 1e-13: return crv # for all other curves, start with 4 knot spans knot_vector = [t0,t0,t0,t0] + [i/5.0*(t1-t0)+t0 for i in range(1,5)] + [t1,t1,t1,t1] b = BSplineBasis(4, knot_vector) t = np.array(b.greville()) crv = interpolate(x(t), b, t) (err2, maxerr) = crv.error(x) # this is technically false since we need the length of the target function *x* # and not our approximation *crv*, but we don't have the derivative of *x*, so # we can't compute it. This seems like a healthy compromise length = crv.length() while np.sqrt(np.sum(err2))/length > rtol and maxerr > atol: knot_span = crv.knots(0) # knot vector without multiplicities target_error = (rtol*length)**2 / len(err2) # equidistribute error among all knot spans refinements = [] for i in range(len(err2)): # figure out how many new knots we require in this knot interval: # if we converge with *scale* and want an error of *target_error* # |e|^2 * (1/n)^scale = target_error^2 conv_order = 4 # cubic interpolateion is order=4 square_conv_order = 2*conv_order # we are computing with square of error scale = square_conv_order + 4 # don't want to converge too quickly in case of highly non-uniform mesh refinement is required n = int(np.ceil(np.exp((np.log(err2[i]) - np.log(target_error))/scale))) # add *n* new interior knots to this knot span new_knots = np.linspace(knot_span[i], knot_span[i+1], n+1) knot_vector = knot_vector + list(new_knots[1:-1]) # build new refined knot vector knot_vector.sort() b = BSplineBasis(4, knot_vector) # do interpolation and return result t = np.array(b.greville()) crv = interpolate(x(t), b, t) (err2, maxerr) = crv.error(x) length = crv.length() return crv
def cubic_curve(x, boundary=Boundary.FREE, t=None, tangents=None): """cubic_curve(x, [boundary=Boundary.FREE], [t=None], [tangents=None]) Perform cubic spline interpolation on a provided basis. The valid boundary conditions are enumerated in :class:`Boundary`. The meaning of the `tangents` parameter depends on the specified boundary condition: - `TANGENT`: two points, - `TANGENTNATURAL`: one point, - `HERMITE`: *n* points :param matrix-like x: Matrix *X[i,j]* of interpolation points *x_i* with components *j* :param int boundary: Any value from :class:`Boundary`. :param array-like t: parametric values at interpolation points, defaults to Euclidean distance between evaluation points :param matrix-like tangents: Tangent information according to the boundary conditions. :return: Interpolated curve :rtype: Curve """ # special case periodic curves to both allow repeated start/end or not if boundary == Boundary.PERIODIC and ( np.allclose(x[0,:], x[-1,:], rtol = state.controlpoint_relative_tolerance, atol = state.controlpoint_absolute_tolerance)): # count the seam (start/end point) as only one point, otherwise the # interpolation matrix will have two identical rows corresponding to # this point x = x[:-1,:] if t is not None: t = t[:-1] n = len(x) if t is None: t = [0.0] for (x0,x1) in zip(x[:-1,:], x[1:,:]): # eucledian distance between two consecutive points dist = np.linalg.norm(np.array(x1)-np.array(x0)) t.append(t[-1]+dist) # modify knot vector for chosen boundary conditions knot = [t[0]]*3 + list(t) + [t[-1]]*3 if boundary == Boundary.FREE: del knot[-5] del knot[4] elif boundary == Boundary.HERMITE: knot = sorted(knot + t[1:-1]) # create the interpolation basis and interpolation matrix on this if boundary == Boundary.PERIODIC: knot[0] = t[-3] - t[-1] knot[1] = t[-2] - t[-1] knot[-2] = t[-1] + t[1] knot[-1] = t[-1] + t[2] basis = BSplineBasis(4, knot, 1) # since we can't interpolate at the end and start simultaneously, we # have to resample the interpolation points. We use the greville points t = basis.greville() else: basis = BSplineBasis(4, knot) N = basis(t) # left-hand-side matrix # add derivative boundary conditions if applicable if boundary in [Boundary.TANGENT, Boundary.HERMITE, Boundary.TANGENTNATURAL]: if boundary == Boundary.TANGENT: dn = basis([t[0], t[-1]], d=1) N = np.resize(N, (N.shape[0]+2, N.shape[1])) x = np.resize(x, (x.shape[0]+2, x.shape[1])) elif boundary == Boundary.TANGENTNATURAL: dn = basis(t[0], d=1) N = np.resize(N, (N.shape[0]+1, N.shape[1])) x = np.resize(x, (x.shape[0]+1, x.shape[1])) elif boundary == Boundary.HERMITE: dn = getBasis(t, d=1) N = np.resize(N, (N.shape[0]+n, N.shape[1])) x = np.resize(x, (x.shape[0]+n, x.shape[1])) x[n:,:] = tangents N[n:,:] = dn # add double derivative boundary conditions if applicable if boundary in [Boundary.NATURAL, Boundary.TANGENTNATURAL]: if boundary == Boundary.NATURAL: ddn = basis([t[0], t[-1]], d=2) new = 2 elif boundary == Boundary.TANGENTNATURAL: ddn = basis(t[-1], d=2) new = 1 N = np.resize(N, (N.shape[0]+new, N.shape[1])) x = np.resize(x, (x.shape[0]+new, x.shape[1])) N[-new:,:] = ddn x[-new:,:] = 0 # solve system to get controlpoints cp = np.linalg.solve(N,x) # wrap it all into a curve and return return Curve(basis, cp)