def test_insert_knot(self): # more or less random 2D surface with p=[3,2] and n=[4,3] controlpoints = [[0, 0], [-1, 1], [0, 2], [1, -1], [1, 0], [1, 1], [2, 1], [2, 2], [2, 3], [3, 0], [4, 1], [3, 2]] basis1 = BSplineBasis(4, [0, 0, 0, 0, 2, 2, 2, 2]) basis2 = BSplineBasis(3, [0, 0, 0, 1, 1, 1]) surf = Surface(basis1, basis2, controlpoints) # pick some evaluation point (could be anything) evaluation_point1 = surf(0.23, 0.37) surf.insert_knot(.22, 0) surf.insert_knot(.5, 0) surf.insert_knot(.7, 0) surf.insert_knot(.1, 1) surf.insert_knot(1.0 / 3, 1) knot1, knot2 = surf.knots(with_multiplicities=True) self.assertEqual(len(knot1), 11) # 8 to start with, 3 new ones self.assertEqual(len(knot2), 8) # 6 to start with, 2 new ones evaluation_point2 = surf(0.23, 0.37) # evaluation before and after InsertKnot should remain unchanged self.assertAlmostEqual(evaluation_point1[0], evaluation_point2[0]) self.assertAlmostEqual(evaluation_point1[1], evaluation_point2[1]) # test a rational 2D surface controlpoints = [[0, 0, 1], [-1, 1, .96], [0, 2, 1], [1, -1, 1], [1, 0, .8], [1, 1, 1], [2, 1, .89], [2, 2, .9], [2, 3, 1], [3, 0, 1], [4, 1, 1], [3, 2, 1]] basis1 = BSplineBasis(3, [0, 0, 0, .4, 1, 1, 1]) basis2 = BSplineBasis(3, [0, 0, 0, 1, 1, 1]) surf = Surface(basis1, basis2, controlpoints, True) evaluation_point1 = surf(0.23, 0.37) surf.insert_knot(.22, 0) surf.insert_knot(.5, 0) surf.insert_knot(.7, 0) surf.insert_knot(.1, 1) surf.insert_knot(1.0 / 3, 1) knot1, knot2 = surf.knots(with_multiplicities=True) self.assertEqual(len(knot1), 10) # 7 to start with, 3 new ones self.assertEqual(len(knot2), 8) # 6 to start with, 2 new ones evaluation_point2 = surf(0.23, 0.37) # evaluation before and after InsertKnot should remain unchanged self.assertAlmostEqual(evaluation_point1[0], evaluation_point2[0]) self.assertAlmostEqual(evaluation_point1[1], evaluation_point2[1]) # test errors and exceptions with self.assertRaises(TypeError): surf.insert_knot(1, 2, 3) # too many arguments with self.assertRaises(ValueError): surf.insert_knot("tree-fiddy", .5) # wrong argument type with self.assertRaises(ValueError): surf.insert_knot(0, -0.2) # Outside-domain error with self.assertRaises(ValueError): surf.insert_knot(1, 1.4) # Outside-domain error
def test_constructor(self): # test 3D constructor cp = [[0, 0, 0], [1, 0, 0], [0, 1, 0], [1, 1, 0]] surf = Surface(controlpoints=cp) val = surf(0.5, 0.5) self.assertEqual(val[0], 0.5) self.assertEqual(len(surf[0]), 3) # test 2D constructor cp = [[0, 0], [1, 0], [0, 1], [1, 1]] surf2 = Surface(controlpoints=cp) val = surf2(0.5, 0.5) self.assertEqual(val[0], 0.5) self.assertEqual(len(surf2[0]), 2) # test rational 2D constructor cp = [[0, 0, 1], [1, 0, 1], [0, 1, 1], [1, 1, 1]] surf3 = Surface(controlpoints=cp, rational=True) val = surf3(0.5, 0.5) self.assertEqual(val[0], 0.5) self.assertEqual(len(surf3[0]), 3) # test rational 3D constructor cp = [[0, 0, 0, 1], [1, 0, 0, 1], [0, 1, 0, 1], [1, 1, 0, 1]] surf4 = Surface(controlpoints=cp, rational=True) val = surf4(0.5, 0.5) self.assertEqual(val[0], 0.5) self.assertEqual(len(surf4[0]), 4) # test constructor with single basis b = BSplineBasis(4) surf = Surface(b, b) surf.insert_knot(.3, 'u') # change one, but not the other self.assertEqual(len(surf.knots('u')), 3) self.assertEqual(len(surf.knots('v')), 2) # TODO: Include a default constructor specifying nothing, or just polynomial degrees, or just knot vectors. # This should create identity mappings # test errors and exceptions controlpoints = [[0, 0, 1], [1, 0, 1], [0, 1, 1], [1, 1, 1]] with self.assertRaises(ValueError): basis1 = BSplineBasis(2, [1, 1, 0, 0]) basis2 = BSplineBasis(2, [0, 0, 1, 1]) surf = Surface(basis1, basis2, controlpoints) # illegal knot vector with self.assertRaises(ValueError): basis1 = BSplineBasis(2, [0, 0, .5, 1, 1]) basis2 = BSplineBasis(2, [0, 0, 1, 1]) surf = Surface(basis1, basis2, controlpoints) # too few controlpoints
def finitestrain_patch(bottom, right, top, left): from nutils import version if int(version[0]) != 4: raise ImportError( 'Mismatching nutils version detected, only version 4 supported. Upgrade by \"pip install --upgrade nutils\"' ) from nutils import mesh, function from nutils import _, log, solver # error test input if not (left.dimension == right.dimension == top.dimension == bottom.dimension == 2): raise RuntimeError( 'finitestrain_patch only supported for planar (2D) geometries') if left.rational or right.rational or top.rational or bottom.rational: raise RuntimeError( 'finitestrain_patch not supported for rational splines') # these are given as a oriented loop, so make all run in positive parametric direction top.reverse() left.reverse() # in order to add spline surfaces, they need identical parametrization Curve.make_splines_identical(top, bottom) Curve.make_splines_identical(left, right) # create an initial mesh (correct corners) which we will morph into the right one p1 = bottom.order(0) p2 = left.order(0) p = max(p1, p2) linear = BSplineBasis(2) srf = Surface(linear, linear, [bottom[0], bottom[-1], top[0], top[-1]]) srf.raise_order(p1 - 2, p2 - 2) for k in bottom.knots(0, True)[p1:-p1]: srf.insert_knot(k, 0) for k in left.knots(0, True)[p2:-p2]: srf.insert_knot(k, 1) # create computational mesh n1 = len(bottom) n2 = len(left) dim = left.dimension domain, geom = mesh.rectilinear(srf.knots()) ns = function.Namespace() ns.basis = domain.basis('spline', degree(srf), knotmultiplicities=multiplicities(srf)).vector(2) ns.phi = domain.basis('spline', degree(srf), knotmultiplicities=multiplicities(srf)) ns.eye = np.array([[1, 0], [0, 1]]) ns.cp = controlpoints(srf) ns.x_i = 'cp_ni phi_n' ns.lmbda = 1 ns.mu = 1 # add total boundary conditions # for hard problems these will be taken in steps and multiplied by dt every # time (quasi-static iterations) constraints = np.array([[[np.nan] * n2] * n1] * dim) for d in range(dim): constraints[d, 0, :] = (left[:, d] - srf[0, :, d]) constraints[d, -1, :] = (right[:, d] - srf[-1, :, d]) constraints[d, :, 0] = (bottom[:, d] - srf[:, 0, d]) constraints[d, :, -1] = (top[:, d] - srf[:, -1, d]) # TODO: Take a close look at the logic below # in order to iterate, we let t0=0 be current configuration and t1=1 our target configuration # if solver divergeces (too large deformation), we will try with dt=0.5. If this still # fails we will resort to dt=0.25 until a suitable small iterations size have been found # dt = 1 # t0 = 0 # t1 = 1 # while t0 < 1: # dt = t1-t0 n = 10 dt = 1 / n for i in range(n): # print(' ==== Quasi-static '+str(t0*100)+'-'+str(t1*100)+' % ====') print(' ==== Quasi-static ' + str(i / (n - 1) * 100) + ' % ====') # define the non-linear finite strain problem formulation ns.cp = np.reshape(srf[:, :, :].swapaxes(0, 1), (n1 * n2, dim), order='F') ns.x_i = 'cp_ni phi_n' # geometric mapping (reference geometry) ns.u_i = 'basis_ki ?w_k' # displacement (unknown coefficients w_k) ns.X_i = 'x_i + u_i' # displaced geometry ns.strain_ij = '.5 (u_i,j + u_j,i + u_k,i u_k,j)' ns.stress_ij = 'lmbda strain_kk eye_ij + 2 mu strain_ij' # try: residual = domain.integral(ns.eval_n('stress_ij basis_ni,j d:X'), degree=2 * p) cons = np.ndarray.flatten(constraints * dt, order='C') lhs = solver.newton('w', residual, constrain=cons).solve( tol=state.controlpoint_absolute_tolerance, maxiter=8) # store the results on a splipy object and continue geom = lhs.reshape((n2, n1, dim), order='F') srf[:, :, :] += geom.swapaxes(0, 1) # t0 += dt # t1 = 1 # except solver.SolverError: # newton method fail to converge, try a smaller step length 'dt' # t1 = (t1+t0)/2 return srf