def test_newton_relax0(self): self.assert_resnorm( solver.newton('dofs', residual=self.residual, lhs0=self.lhs0, constrain=self.cons, relax0=.1).solve(tol=self.tol, maxiter=5))
def test_newton_relax0(self): self.assert_resnorm( solver.newton(self.dofs, residual=self.residual, arguments=self.arguments, constrain=self.cons, relax0=.1).solve(tol=self.tol, maxiter=5))
def test_newton_medianbased(self): self.assert_resnorm( solver.newton('dofs', residual=self.residual, lhs0=self.lhs0, constrain=self.cons, linesearch=solver.MedianBased()).solve(tol=self.tol, maxiter=2))
def test_newton_medianbased(self): self.assert_resnorm( solver.newton(self.dofs, residual=self.residual, arguments=self.arguments, constrain=self.cons, linesearch=solver.MedianBased()).solve(tol=self.tol, maxiter=2))
def test_newton_iter(self): _test_recursion_cache( self, lambda: ((types.frozenarray(lhs), resnorm) for lhs, resnorm in solver.newton('dofs', residual=self.residual, lhs0=self.lhs0, constrain=self.cons)))
def main(nelems: int, degree: int, reynolds: float): ''' Driven cavity benchmark problem using compatible spaces. .. arguments:: nelems [12] Number of elements along edge. degree [2] Polynomial degree for velocity; the pressure space is one degree less. reynolds [1000] Reynolds number, taking the domain size as characteristic length. ''' verts = numpy.linspace(0, 1, nelems + 1) domain, geom = mesh.rectilinear([verts, verts]) ns = function.Namespace() ns.x = geom ns.Re = reynolds ns.ubasis = function.vectorize([ domain.basis('spline', degree=(degree, degree - 1), removedofs=((0, -1), None)), domain.basis('spline', degree=(degree - 1, degree), removedofs=(None, (0, -1))) ]) ns.pbasis = domain.basis('spline', degree=degree - 1) ns.u_i = 'ubasis_ni ?u_n' ns.p = 'pbasis_n ?p_n' ns.stress_ij = '(d(u_i, x_j) + d(u_j, x_i)) / Re - p δ_ij' ns.uwall = domain.boundary.indicator('top'), 0 ns.N = 5 * degree * nelems # nitsche constant based on element size = 1/nelems ns.nitsche_ni = '(N ubasis_ni - (d(ubasis_ni, x_j) + d(ubasis_nj, x_i)) n(x_j)) / Re' ures = domain.integral('d(ubasis_ni, x_j) stress_ij d:x' @ ns, degree=2 * degree) ures += domain.boundary.integral( '(nitsche_ni (u_i - uwall_i) - ubasis_ni stress_ij n(x_j)) d:x' @ ns, degree=2 * degree) pres = domain.integral('pbasis_n (d(u_k, x_k) + ?lm) d:x' @ ns, degree=2 * degree) lres = domain.integral('p d:x' @ ns, degree=2 * degree) with treelog.context('stokes'): state0 = solver.solve_linear(['u', 'p', 'lm'], [ures, pres, lres]) postprocess(domain, ns, **state0) ures += domain.integral('ubasis_ni d(u_i, x_j) u_j d:x' @ ns, degree=3 * degree) with treelog.context('navierstokes'): state1 = solver.newton(('u', 'p', 'lm'), (ures, pres, lres), arguments=state0).solve(tol=1e-10) postprocess(domain, ns, **state1) return state0, state1
def solve_nutils(g, method='Elliptic', ltol=1e-5, **solveargs): assert len(g) == 2 res = method_library(g, method=method) init, cons = g.x, g.cons lhs = solver.newton('target', res, lhs0=init, constrain=cons.where).solve(ltol, **solveargs) g.x = lhs
def test_res(self): for name in 'direct', 'newton': with self.subTest(name): if name == 'direct': lhs = solver.solve_linear('dofs', residual=self.residual, constrain=self.cons) else: lhs = solver.newton('dofs', residual=self.residual, constrain=self.cons).solve(tol=1e-10, maxiter=0) res = self.residual.eval(arguments=dict(dofs=lhs)) resnorm = numpy.linalg.norm(res[~self.cons.where]) self.assertLess(resnorm, 1e-13)
def test_newton_tolnotreached(self): with self.assertLogs('nutils', logging.WARNING) as cm: self.assert_resnorm( solver.newton('dofs', residual=self.residual, lhs0=self.lhs0, constrain=self.cons, linrtol=1e-99).solve(tol=self.tol, maxiter=2)) for msg in cm.output: self.assertIn('solver failed to reach tolerance', msg)
def main(nelems: int, degree: int, reynolds: float): ''' Driven cavity benchmark problem using compatible spaces. .. arguments:: nelems [12] Number of elements along edge. degree [2] Polynomial degree for velocity; the pressure space is one degree less. reynolds [1000] Reynolds number, taking the domain size as characteristic length. ''' verts = numpy.linspace(0, 1, nelems + 1) domain, geom = mesh.rectilinear([verts, verts]) ns = function.Namespace() ns.x = geom ns.Re = reynolds ns.uxbasis, ns.uybasis, ns.pbasis, ns.lbasis = function.chain([ domain.basis('spline', degree=(degree, degree - 1), removedofs=((0, -1), None)), domain.basis('spline', degree=(degree - 1, degree), removedofs=(None, (0, -1))), domain.basis('spline', degree=degree - 1), [1], # lagrange multiplier ]) ns.ubasis_ni = '<uxbasis_n, uybasis_n>_i' ns.u_i = 'ubasis_ni ?lhs_n' ns.p = 'pbasis_n ?lhs_n' ns.l = 'lbasis_n ?lhs_n' ns.stress_ij = '(u_i,j + u_j,i) / Re - p δ_ij' ns.uwall = domain.boundary.indicator('top'), 0 ns.N = 5 * degree * nelems # nitsche constant based on element size = 1/nelems ns.nitsche_ni = '(N ubasis_ni - (ubasis_ni,j + ubasis_nj,i) n_j) / Re' res = domain.integral( '(ubasis_ni,j stress_ij + pbasis_n (u_k,k + l) + lbasis_n p) d:x' @ ns, degree=2 * degree) res += domain.boundary.integral( '(nitsche_ni (u_i - uwall_i) - ubasis_ni stress_ij n_j) d:x' @ ns, degree=2 * degree) with treelog.context('stokes'): lhs0 = solver.solve_linear('lhs', res) postprocess(domain, ns, lhs=lhs0) res += domain.integral('ubasis_ni u_i,j u_j d:x' @ ns, degree=3 * degree) with treelog.context('navierstokes'): lhs1 = solver.newton('lhs', res, lhs0=lhs0).solve(tol=1e-10) postprocess(domain, ns, lhs=lhs1) return lhs0, lhs1
def main(nelems: int, etype: str, degree: int, reynolds: float): ''' Driven cavity benchmark problem. .. arguments:: nelems [12] Number of elements along edge. etype [square] Element type (square/triangle/mixed). degree [2] Polynomial degree for velocity; the pressure space is one degree less. reynolds [1000] Reynolds number, taking the domain size as characteristic length. ''' domain, geom = mesh.unitsquare(nelems, etype) ns = function.Namespace() ns.Re = reynolds ns.x = geom ns.ubasis, ns.pbasis = function.chain([ domain.basis('std', degree=degree).vector(2), domain.basis('std', degree=degree - 1), ]) ns.u_i = 'ubasis_ni ?lhs_n' ns.p = 'pbasis_n ?lhs_n' ns.stress_ij = '(u_i,j + u_j,i) / Re - p δ_ij' sqr = domain.boundary.integral('u_k u_k d:x' @ ns, degree=degree * 2) wallcons = solver.optimize('lhs', sqr, droptol=1e-15) sqr = domain.boundary['top'].integral('(u_0 - 1)^2 d:x' @ ns, degree=degree * 2) lidcons = solver.optimize('lhs', sqr, droptol=1e-15) cons = numpy.choose(numpy.isnan(lidcons), [lidcons, wallcons]) cons[-1] = 0 # pressure point constraint res = domain.integral('(ubasis_ni,j stress_ij + pbasis_n u_k,k) d:x' @ ns, degree=degree * 2) with treelog.context('stokes'): lhs0 = solver.solve_linear('lhs', res, constrain=cons) postprocess(domain, ns, lhs=lhs0) res += domain.integral( '.5 (ubasis_ni u_i,j - ubasis_ni,j u_i) u_j d:x' @ ns, degree=degree * 3) with treelog.context('navierstokes'): lhs1 = solver.newton('lhs', res, lhs0=lhs0, constrain=cons).solve(tol=1e-10) postprocess(domain, ns, lhs=lhs1) return lhs0, lhs1
def main(nelems:int, etype:str, degree:int, reynolds:float): ''' Driven cavity benchmark problem. .. arguments:: nelems [12] Number of elements along edge. etype [square] Element type (square/triangle/mixed). degree [2] Polynomial degree for velocity; the pressure space is one degree less. reynolds [1000] Reynolds number, taking the domain size as characteristic length. ''' domain, geom = mesh.unitsquare(nelems, etype) ns = function.Namespace() ns.Re = reynolds ns.x = geom ns.ubasis = domain.basis('std', degree=degree).vector(domain.ndims) ns.pbasis = domain.basis('std', degree=degree-1) ns.u_i = 'ubasis_ni ?u_n' ns.p = 'pbasis_n ?p_n' ns.stress_ij = '(d(u_i, x_j) + d(u_j, x_i)) / Re - p δ_ij' usqr = domain.boundary.integral('u_k u_k d:x' @ ns, degree=degree*2) wallcons = solver.optimize('u', usqr, droptol=1e-15) usqr = domain.boundary['top'].integral('(u_0 - 1)^2 d:x' @ ns, degree=degree*2) lidcons = solver.optimize('u', usqr, droptol=1e-15) ucons = numpy.choose(numpy.isnan(lidcons), [lidcons, wallcons]) pcons = numpy.zeros(len(ns.pbasis), dtype=bool) pcons[-1] = True # constrain pressure to zero in a point cons = dict(u=ucons, p=pcons) ures = domain.integral('d(ubasis_ni, x_j) stress_ij d:x' @ ns, degree=degree*2) pres = domain.integral('pbasis_n d(u_k, x_k) d:x' @ ns, degree=degree*2) with treelog.context('stokes'): state0 = solver.solve_linear(('u', 'p'), (ures, pres), constrain=cons) postprocess(domain, ns, **state0) ures += domain.integral('.5 (ubasis_ni d(u_i, x_j) - d(ubasis_ni, x_j) u_i) u_j d:x' @ ns, degree=degree*3) with treelog.context('navierstokes'): state1 = solver.newton(('u', 'p'), (ures, pres), arguments=state0, constrain=cons).solve(tol=1e-10) postprocess(domain, ns, **state1) return state0, state1
def test_newton_boolcons(self): self.assert_resnorm( solver.newton('dofs', residual=self.residual, constrain=self.boolcons).solve(tol=self.tol, maxiter=7))
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
def main(inflow: 'inflow velocity' = 10, viscosity: 'kinematic viscosity' = 1.0, density: 'density' = 1.0, theta=0.5, timestepsize=0.01): # mesh and geometry definition grid_x_1 = numpy.linspace(-3, -1, 7) grid_x_1 = grid_x_1[:-1] grid_x_2 = numpy.linspace(-1, -0.3, 8) grid_x_2 = grid_x_2[:-1] grid_x_3 = numpy.linspace(-0.3, 0.3, 13) grid_x_3 = grid_x_3[:-1] grid_x_4 = numpy.linspace(0.3, 1, 8) grid_x_4 = grid_x_4[:-1] grid_x_5 = numpy.linspace(1, 3, 7) grid_x = numpy.concatenate( (grid_x_1, grid_x_2, grid_x_3, grid_x_4, grid_x_5), axis=None) grid_y_1 = numpy.linspace(0, 1.5, 16) grid_y_1 = grid_y_1[:-1] grid_y_2 = numpy.linspace(1.5, 2, 4) grid_y_2 = grid_y_2[:-1] grid_y_3 = numpy.linspace(2, 4, 7) grid_y = numpy.concatenate((grid_y_1, grid_y_2, grid_y_3), axis=None) grid = [grid_x, grid_y] topo, geom = mesh.rectilinear(grid) domain = topo.withboundary(inflow='left', wall='top,bottom', outflow='right') - \ topo[18:20, :10].withboundary(flap='left,right,top') # Nutils namespace ns = function.Namespace() # time approximations # TR interpolation ns._functions['t'] = lambda f: theta * f + (1 - theta) * subs0(f) ns._functions_nargs['t'] = 1 # 1st order FD ns._functions['δt'] = lambda f: (f - subs0(f)) / dt ns._functions_nargs['δt'] = 1 # 2nd order FD ns._functions['tt'] = lambda f: (1.5 * f - 2 * subs0(f) + 0.5 * subs00(f) ) / dt ns._functions_nargs['tt'] = 1 # extrapolation for pressure ns._functions['tp'] = lambda f: (1.5 * f - 0.5 * subs0(f)) ns._functions_nargs['tp'] = 1 ns.nu = viscosity ns.rho = density ns.uin = inflow ns.x0 = geom # reference geometry ns.dbasis = domain.basis('std', degree=1).vector(2) ns.d_i = 'dbasis_ni ?meshdofs_n' ns.umesh_i = 'dbasis_ni (1.5 ?meshdofs_n - 2 ?oldmeshdofs_n + 0.5 ?oldoldmeshdofs_n ) / ?dt' ns.x_i = 'x0_i + d_i' # moving geometry ns.ubasis, ns.pbasis = function.chain([ domain.basis('std', degree=2).vector(2), domain.basis('std', degree=1), ]) ns.F_i = 'ubasis_ni ?F_n' # stress field ns.urel_i = 'ubasis_ni ?lhs_n' # relative velocity ns.u_i = 'umesh_i + urel_i' # total velocity ns.p = 'pbasis_n ?lhs_n' # pressure # initialization of dofs meshdofs = numpy.zeros(len(ns.dbasis)) oldmeshdofs = meshdofs oldoldmeshdofs = meshdofs oldoldoldmeshdofs = meshdofs lhs0 = numpy.zeros(len(ns.ubasis)) # for visualization bezier = domain.sample('bezier', 2) # preCICE setup configFileName = "../precice-config.xml" participantName = "Fluid" solverProcessIndex = 0 solverProcessSize = 1 interface = precice.Interface(participantName, configFileName, solverProcessIndex, solverProcessSize) # define coupling meshes meshName = "Fluid-Mesh" meshID = interface.get_mesh_id(meshName) couplinginterface = domain.boundary['flap'] couplingsample = couplinginterface.sample( 'gauss', degree=2) # mesh located at Gauss points dataIndices = interface.set_mesh_vertices(meshID, couplingsample.eval(ns.x0)) # coupling data writeData = "Force" readData = "Displacement" writedataID = interface.get_data_id(writeData, meshID) readdataID = interface.get_data_id(readData, meshID) # initialize preCICE precice_dt = interface.initialize() dt = min(precice_dt, timestepsize) # boundary conditions for fluid equations sqr = domain.boundary['wall,flap'].integral('urel_k urel_k d:x0' @ ns, degree=4) cons = solver.optimize('lhs', sqr, droptol=1e-15) sqr = domain.boundary['inflow'].integral( '((urel_0 - uin)^2 + urel_1^2) d:x0' @ ns, degree=4) cons = solver.optimize('lhs', sqr, droptol=1e-15, constrain=cons) # weak form fluid equations res = domain.integral('t(ubasis_ni,j (u_i,j + u_j,i) rho nu d:x)' @ ns, degree=4) res += domain.integral('(-ubasis_ni,j p δ_ij + pbasis_n u_k,k) d:x' @ ns, degree=4) res += domain.integral('rho ubasis_ni δt(u_i d:x)' @ ns, degree=4) res += domain.integral('rho ubasis_ni t(u_i,j urel_j d:x)' @ ns, degree=4) # weak form for force computation resF = domain.integral('(ubasis_ni,j (u_i,j + u_j,i) rho nu d:x)' @ ns, degree=4) resF += domain.integral('tp(-ubasis_ni,j p δ_ij d:x)' @ ns, degree=4) resF += domain.integral('pbasis_n u_k,k d:x' @ ns, degree=4) resF += domain.integral('rho ubasis_ni tt(u_i d:x)' @ ns, degree=4) resF += domain.integral('rho ubasis_ni (u_i,j urel_j d:x)' @ ns, degree=4) resF += couplinginterface.sample('gauss', 4).integral('ubasis_ni F_i d:x' @ ns) consF = numpy.isnan( solver.optimize('F', couplinginterface.sample('gauss', 4).integral('F_i F_i' @ ns), droptol=1e-10)) # boundary conditions mesh displacements sqr = domain.boundary['inflow,outflow,wall'].integral('d_i d_i' @ ns, degree=2) meshcons0 = solver.optimize('meshdofs', sqr, droptol=1e-15) # weak form mesh displacements meshsqr = domain.integral('d_i,x0_j d_i,x0_j d:x0' @ ns, degree=2) # better initial guess: start from Stokes solution, comment out for comparison with other solvers #res_stokes = domain.integral('(ubasis_ni,j ((u_i,j + u_j,i) rho nu - p δ_ij) + pbasis_n u_k,k) d:x' @ ns, degree=4) #lhs0 = solver.solve_linear('lhs', res_stokes, constrain=cons, arguments=dict(meshdofs=meshdofs, oldmeshdofs=oldmeshdofs, oldoldmeshdofs=oldoldmeshdofs, oldoldoldmeshdofs=oldoldoldmeshdofs, dt=dt)) lhs00 = lhs0 timestep = 0 t = 0 while interface.is_coupling_ongoing(): # read displacements from interface if interface.is_read_data_available(): readdata = interface.read_block_vector_data( readdataID, dataIndices) coupledata = couplingsample.asfunction(readdata) sqr = couplingsample.integral(((ns.d - coupledata)**2).sum(0)) meshcons = solver.optimize('meshdofs', sqr, droptol=1e-15, constrain=meshcons0) meshdofs = solver.optimize('meshdofs', meshsqr, constrain=meshcons) # save checkpoint if interface.is_action_required( precice.action_write_iteration_checkpoint()): lhs_checkpoint = lhs0 lhs00_checkpoint = lhs00 t_checkpoint = t timestep_checkpoint = timestep oldmeshdofs_checkpoint = oldmeshdofs oldoldmeshdofs_checkpoint = oldoldmeshdofs oldoldoldmeshdofs_checkpoint = oldoldoldmeshdofs interface.mark_action_fulfilled( precice.action_write_iteration_checkpoint()) # solve fluid equations lhs1 = solver.newton( 'lhs', res, lhs0=lhs0, constrain=cons, arguments=dict( lhs0=lhs0, dt=dt, meshdofs=meshdofs, oldmeshdofs=oldmeshdofs, oldoldmeshdofs=oldoldmeshdofs, oldoldoldmeshdofs=oldoldoldmeshdofs)).solve(tol=1e-6) # write forces to interface if interface.is_write_data_required(dt): F = solver.solve_linear('F', resF, constrain=consF, arguments=dict( lhs00=lhs00, lhs0=lhs0, lhs=lhs1, dt=dt, meshdofs=meshdofs, oldmeshdofs=oldmeshdofs, oldoldmeshdofs=oldoldmeshdofs, oldoldoldmeshdofs=oldoldoldmeshdofs)) # writedata = couplingsample.eval(ns.F, F=F) # for stresses writedata = couplingsample.eval('F_i d:x' @ ns, F=F, meshdofs=meshdofs) * \ numpy.concatenate([p.weights for p in couplingsample.points])[:, numpy.newaxis] interface.write_block_vector_data(writedataID, dataIndices, writedata) # do the coupling precice_dt = interface.advance(dt) dt = min(precice_dt, timestepsize) # advance variables timestep += 1 t += dt lhs00 = lhs0 lhs0 = lhs1 oldoldoldmeshdofs = oldoldmeshdofs oldoldmeshdofs = oldmeshdofs oldmeshdofs = meshdofs # read checkpoint if required if interface.is_action_required( precice.action_read_iteration_checkpoint()): lhs0 = lhs_checkpoint lhs00 = lhs00_checkpoint t = t_checkpoint timestep = timestep_checkpoint oldmeshdofs = oldmeshdofs_checkpoint oldoldmeshdofs = oldoldmeshdofs_checkpoint oldoldoldmeshdofs = oldoldoldmeshdofs_checkpoint interface.mark_action_fulfilled( precice.action_read_iteration_checkpoint()) if interface.is_time_window_complete(): x, u, p = bezier.eval(['x_i', 'u_i', 'p'] @ ns, lhs=lhs1, meshdofs=meshdofs, oldmeshdofs=oldmeshdofs, oldoldmeshdofs=oldoldmeshdofs, oldoldoldmeshdofs=oldoldoldmeshdofs, dt=dt) with treelog.add(treelog.DataLog()): export.vtk('Fluid_' + str(timestep), bezier.tri, x, u=u, p=p) interface.finalize()
def main(fname: str, degree: int, Δuload: unit['mm'], nsteps: int, E: unit['GPa'], nu: float, σyield: unit['MPa'], hardening: unit['GPa'], referencedata: typing.Optional[str], testing: bool): ''' Plastic deformation of a perforated strip. .. arguments:: fname [strip.msh] Mesh file with units in [mm] degree [1] Finite element interpolation order Δuload [5μm] Load boundary displacement steps nsteps [11] Number of load steps E [70GPa] Young's modulus nu [0.2] Poisson ratio σyield [243MPa] Yield strength hardening [2.25GPa] Hardening parameter referencedata [zienkiewicz.csv] Reference data file name testing [False] Use a 1 element mesh for testing .. presets:: testing nsteps=30 referencedata= testing=True ''' # We commence with reading the mesh from the specified GMSH file, or, alternatively, # we continue with a single-element mesh for testing purposes. domain, geom = mesh.gmsh(pathlib.Path(__file__).parent/fname) Wdomain, Hdomain = domain.boundary['load'].integrate([function.J(geom),geom[1]*function.J(geom)], degree=1) Hdomain /= Wdomain if testing: domain, geom = mesh.rectilinear([numpy.linspace(0,Wdomain/2,2),numpy.linspace(0,Hdomain,2)]) domain = domain.withboundary(hsymmetry='left', vsymmetry='bottom', load='top') # We next initiate the point set in which the constitutive bahvior will be evaluated. # Note that this is also the point set in which the history variable will be stored. gauss = domain.sample('gauss', 2) # Elasto-plastic formulation # ========================== # The weak formulation is constructed using a `Namespace`, which is initialized and # populated with the necessary problem parameters and coordinate system. Note that the # coefficients `mu` and `λ` have been defined such that 'C_{ijkl}' is the fourth-order # **plane stress** elasticity tensor. ns = function.Namespace(fallback_length=domain.ndims) ns.x = geom ns.Δuload = Δuload ns.mu = E/(1-nu**2)*((1-nu)/2) ns.λ = E/(1-nu**2)*nu ns.delta = function.eye(domain.ndims) ns.C_ijkl = 'mu ( delta_ik delta_jl + delta_il delta_jk ) + λ delta_ij delta_kl' # We make use of a Lagrange finite element basis of arbitrary `degree`. Since we # approximate the displacement field, the basis functions are vector-valued. Both # the displacement field at the current load step `u` and displacement field of the # previous load step `u0` are approximated using this basis: ns.basis = domain.basis('std',degree=degree).vector(domain.ndims) ns.u0_i = 'basis_ni ?lhs0_n' ns.u_i = 'basis_ni ?lhs_n' # This simulation is based on a standard elasto-plasticity model. In this # model the *total strain*, ε_kl = ½ (∂u_k/∂x_l + ∂u_l/∂x_k)', is comprised of an # elastic and a plastic part: # # ε_kl = εe_kl + εp_kl # # The stress is related to the *elastic strain*, εe_kl, through Hooke's law: # # σ_ij = C_ijkl εe_kl = C_ijkl (ε_kl - εp_kl) ns.ε_kl = '(u_k,l + u_l,k) / 2' ns.ε0_kl = '(u0_k,l + u0_l,k) / 2' ns.gbasis = gauss.basis() ns.εp0_ij = 'gbasis_n ?εp0_nij' ns.κ0 = 'gbasis_n ?κ0_n' ns.εp = PlasticStrain(ns.ε, ns.ε0, ns.εp0, ns.κ0, E, nu, σyield, hardening) ns.εe_ij = 'ε_ij - εp_ij' ns.σ_ij = 'C_ijkl εe_kl' # Note that the plasticity model is implemented through the user-defined function `PlasticStrain`, # which implements the actual yielding model including a standard return mapping algorithm. This # function is discussed in detail below. # # The components of the residual vector are then defined as: # # r_n = ∫_Ω (∂N_ni/∂x_j) σ_ij dΩ res = domain.integral('basis_ni,j σ_ij d:x' @ ns, degree=2) # The problem formulation is completed by supplementing prescribed displacement boundary conditions # (for a load step), which are computed in the standard manner: sqr = domain.boundary['hsymmetry,vsymmetry'].integral('(u_k n_k)^2 d:x' @ ns, degree=2) sqr += domain.boundary['load'].integral('(u_k n_k - Δuload)^2 d:x' @ ns, degree=2) cons = solver.optimize('lhs', sqr, droptol=1e-15) # Incremental-iterative solution procedure # ======================================== # We initialize the solution vector for the first load step `lhs` and solution vector # of the previous load step `lhs0`. Note that, in order to construct a predictor step # for the first load step, we define the previous load step state as the solution # vector corresponding to a negative elastic loading step. lhs0 = -solver.solve_linear('lhs', domain.integral('basis_ni,j C_ijkl ε_kl d:x' @ ns, degree=2), constrain=cons) lhs = numpy.zeros_like(lhs0) εp0 = numpy.zeros((gauss.npoints,)+ns.εp0.shape) κ0 = numpy.zeros((gauss.npoints,)+ns.κ0.shape) # To store the force-dispalcement data we initialize an empty data array with the # inital state solution substituted in the first row. fddata = numpy.empty(shape=(nsteps+1,2)) fddata[:] = numpy.nan fddata[0,:] = 0 # Load step incrementation # ------------------------ with treelog.iter.fraction('step', range(nsteps)) as counter: for step in counter: # The solution of the previous load step is set to `lhs0`, and the Newton solution # procedure is initialized by extrapolation of the state vector: lhs_init = lhs + (lhs-lhs0) lhs0 = lhs # The non-linear system of equations is solved for `lhs` using Newton iterations, # where the `step` variable is used to scale the incremental constraints. lhs = solver.newton(target='lhs', residual=res, constrain=cons*step, lhs0=lhs_init, arguments={'lhs0':lhs0,'εp0':εp0,'κ0':κ0}).solve(tol=1e-6) # The computed solution is post-processed in the form of a loading curve - which # plots the normalized mean stress versus the maximum 'ε_{yy}' strain # component - and a contour plot showing the 'σ_{yy}' stress component on a # deformed mesh. Note that since the stresses are defined in the integration points # only, a post-processing step is involved that transfers the stress information to # the nodal points. εyymax = gauss.eval(ns.ε[1,1], arguments=dict(lhs=lhs)).max() basis = domain.basis('std', degree=1) bw, b = domain.integrate([basis * ns.σ[1,1] * function.J(geom), basis * function.J(geom)], degree=2, arguments=dict(lhs=lhs,lhs0=lhs0,εp0=εp0,κ0=κ0)) σyy = basis.dot(bw / b) uyload, σyyload = domain.boundary['load'].integrate(['u_1 d:x'@ns,σyy * function.J(geom)], degree=2, arguments=dict(lhs=lhs,εp0=εp0,κ0=κ0)) uyload /= Wdomain σyyload /= Wdomain fddata[step,0] = (E*εyymax)/σyield fddata[step,1] = (σyyload*2)/σyield with export.mplfigure('forcedisp.png') as fig: ax = fig.add_subplot(111, xlabel=r'${E \cdot {\rm max}(\varepsilon_{yy})}/{\sigma_{\rm yield}}$', ylabel=r'${\sigma_{\rm mean}}/{\sigma_{\rm yield}}$') if referencedata: data = numpy.genfromtxt(pathlib.Path(__file__).parent/referencedata, delimiter=',', skip_header=1) ax.plot(data[:,0], data[:,1], 'r:', label='Reference') ax.legend() ax.plot(fddata[:,0], fddata[:,1], 'o-', label='Nutils') ax.grid() bezier = domain.sample('bezier', 3) points, uvals, σyyvals = bezier.eval(['(x_i + 25 u_i)' @ ns, ns.u, σyy], arguments=dict(lhs=lhs)) with export.mplfigure('stress.png') as fig: ax = fig.add_subplot(111, aspect='equal', xlabel=r'$x$ [mm]', ylabel=r'$y$ [mm]') im = ax.tripcolor(points[:,0]/unit('mm'), points[:,1]/unit('mm'), bezier.tri, σyyvals/unit('MPa'), shading='gouraud', cmap='jet') ax.add_collection(collections.LineCollection(points.take(bezier.hull, axis=0), colors='k', linewidths=.1)) ax.autoscale(enable=True, axis='both', tight=True) cb = fig.colorbar(im) im.set_clim(0, 1.2*σyield) cb.set_label(r'$σ_{yy}$ [MPa]') # Load step convergence # --------------------- # At the end of the loading step, the plastic strain state and history parameter are updated, # where use if made of the strain hardening relation for the history variable: # # Δκ = √(Δεp_ij Δεp_ij) Δεp = ns.εp-ns.εp0 Δκ = function.sqrt((Δεp*Δεp).sum((0,1))) κ0 = gauss.eval(ns.κ0+Δκ, arguments=dict(lhs0=lhs0,lhs=lhs,εp0=εp0,κ0=κ0)) εp0 = gauss.eval(ns.εp, arguments=dict(lhs0=lhs0,lhs=lhs,εp0=εp0,κ0=κ0))
def test_newton_iter(self): _test_recursion_cache(self, lambda: ((self.frozen(lhs), info.resnorm) for lhs, info in solver.newton(self.dofs, residual=self.residual, constrain=self.cons)))
def mixed_fem(g, ltol=1e-5, coordinate_directions=None, **solveargs): assert len(g) == 2 n = len(g.x) basis = g.basis def c0(g): return tuple( [i for i in range(2) if g.degree[i] in g.knotmultiplicities[i]]) if coordinate_directions is None: coordinate_directions = c0(g) assert len(coordinate_directions) in (1, 2) and all( [i in (0, 1) for i in coordinate_directions]) s = function.stack veclength = {1: 4, 2: 6}[len(coordinate_directions)] target = function.Argument('target', [veclength * len(basis)]) U = g.basis.vector(veclength).dot(target) G = metric_tensor(g, target[-n:]) (g11, g12), (g21, g22) = G scale = g11 + g22 if len(coordinate_directions) == 2: u, v, x_ = U[:2], U[2:4], U[4:] expr = function.concatenate(x_.grad(g.geom) - s([u, v], axis=1)) res1 = (basis.vector(4) * expr).sum(-1) res2 = g22 * u.grad(g.geom)[:, 0] - g12 * u.grad( g.geom)[:, 1] - g12 * v.grad(g.geom)[:, 0] + g11 * v.grad( g.geom)[:, 1] if len(coordinate_directions) == 1: index = coordinate_directions[0] u, x_ = U[:2], U[2:4] expr = x_.grad(g.geom)[:, index] - u res1 = (basis.vector(2) * expr).sum(-1) if index == 0: x_eta = x_.grad(g.geom)[:, 1] res2 = g22 * u.grad(g.geom)[:, 0] - g12 * u.grad( g.geom)[:, 1] - g12 * x_eta.grad( g.geom)[:, 0] + g11 * x_eta.grad(g.geom)[:, 1] if index == 1: x_xi = x_.grad(g.geom)[:, 0] res2 = g22 * x_xi.grad(g.geom)[:, 0] - g12 * u.grad( g.geom)[:, 0] - g12 * x_xi.grad(g.geom)[:, 1] + g11 * u.grad( g.geom)[:, 1] res = function.concatenate( [res1, (basis.vector(2) * res2).sum(-1) / scale]) res = g.domain.integral(res, geometry=g.geom, degree=g.ischeme * 4) mapping0 = g.basis.vector(2).dot(g.x) cons = util.NanVec(n * (len(coordinate_directions) + 1)) cons[-n:] = g.cons init = np.concatenate([ g.domain.project(mapping0.grad(g.geom)[:, i], geometry=g.geom, onto=g.basis.vector(2), ischeme='gauss12') for i in coordinate_directions ] + [g.x]) lhs = solver.newton('target', res, lhs0=init, constrain=cons.where).solve(ltol, **solveargs) g.x = lhs[-n:]
def elliptic_control_mapping(g, f, eps=1e-4, ltol=1e-5, degree=None, **solveargs): ''' Nutils implementation of the corresponding method from the ``fsol`` module. It's slower but accepts any control mapping function. Unline in the ``fsol`` case, all derivatives are computed automatically. ''' assert len(g) == len(f) == 2 assert eps >= 0 if degree is None: degree = 4 * g.ischeme if isinstance(f, go.TensorGridObject): if not (f.knotvector <= g.knotvector).all(): raise AssertionError(''' Error, the control-mapping knotvector needs to be a subset of the target GridObject knotvector ''') # refine, just in case f = go.refine_GridObject(f, g.knotvector) G = metric_tensor(g, f.x) else: # f is a nutils function jacT = function.transpose(f.grad(g.geom)) G = function.outer(jacT, jacT).sum(-1) target = function.Argument('target', [len(g.x)]) basis = g.basis.vector(2) x = basis.dot(target) (g11, g12), (g21, g22) = metric_tensor(g, target) lapl = g22 * x.grad(g.geom)[:, 0].grad(g.geom)[:, 0] - \ 2 * g12 * x.grad(g.geom)[:, 0].grad(g.geom)[:, 1] + \ g11 * x.grad(g.geom)[:, 1].grad(g.geom)[:, 1] G_tilde = G / function.sqrt(function.determinant(G)) (G11, G12), (G12, G22) = G_tilde P = g11 * G12.grad(g.geom)[1] - g12 * G11.grad(g.geom)[1] Q = 0.5 * (g11 * G22.grad(g.geom)[0] - g22 * G11.grad(g.geom)[0]) S = g12 * G22.grad(g.geom)[0] - g22 * G12.grad(g.geom)[0] R = 0.5 * (g11 * G22.grad(g.geom)[1] - g22 * G11.grad(g.geom)[1]) control_term = -x.grad( g.geom )[:, 0] * ( G22*(P-Q) + G12*(S-R) ) \ -x.grad( g.geom )[:, 1] * ( G11*(R-S) + G12*(Q-P) ) scale = g11 + g22 + eps res = g.domain.integral((basis * (control_term + lapl)).sum(-1) / scale, geometry=g.geom, degree=degree) init, cons = g.x, g.cons lhs = solver.newton('target', res, lhs0=init, constrain=cons.where).solve(ltol, **solveargs) g.x = lhs
def test_newton(self): self.assert_resnorm(solver.newton('dofs', residual=self.residual, lhs0=self.lhs0, constrain=self.cons).solve(tol=self.tol, maxiter=2))
def test_newton_boolcons(self): self.assert_resnorm(solver.newton('dofs', residual=self.residual, constrain=self.boolcons).solve(tol=self.tol, maxiter=7))
def main( nelems: 'number of elements' = 12, viscosity: 'fluid viscosity' = 1e-3, density: 'fluid density' = 1, degree: 'polynomial degree' = 2, warp: 'warp domain (downward bend)' = False, figures: 'create figures' = True, ): log.user('reynolds number: {:.1f}'.format( density / viscosity)) # based on unit length and velocity # create namespace ns = function.Namespace() ns.viscosity = viscosity ns.density = density # construct mesh verts = numpy.linspace(0, 1, nelems + 1) domain, ns.x0 = mesh.rectilinear([verts, verts]) # construct bases ns.uxbasis, ns.uybasis, ns.pbasis, ns.lbasis = function.chain([ domain.basis('spline', degree=(degree + 1, degree), removedofs=((0, -1), None)), domain.basis('spline', degree=(degree, degree + 1), removedofs=(None, (0, -1))), domain.basis('spline', degree=degree), [1], # lagrange multiplier ]) ns.ubasis_ni = '<uxbasis_n, uybasis_n>_i' # construct geometry if not warp: ns.x = ns.x0 else: xi, eta = ns.x0 ns.x = (eta + 2) * function.rotmat(xi * .4)[:, 1] - ( 0, 2) # slight downward bend ns.J_ij = 'x_i,x0_j' ns.detJ = function.determinant(ns.J) ns.ubasis_ni = 'ubasis_nj J_ij / detJ' # piola transform ns.pbasis_n = 'pbasis_n / detJ' # populate namespace ns.u_i = 'ubasis_ni ?lhs_n' ns.p = 'pbasis_n ?lhs_n' ns.l = 'lbasis_n ?lhs_n' ns.sigma_ij = 'viscosity (u_i,j + u_j,i) - p δ_ij' ns.c = 5 * (degree + 1) / domain.boundary.elem_eval( 1, geometry=ns.x, ischeme='gauss2', asfunction=True) ns.nietzsche_ni = 'viscosity (c ubasis_ni - (ubasis_ni,j + ubasis_nj,i) n_j)' ns.top = domain.boundary.indicator('top') ns.utop_i = 'top <n_1, -n_0>_i' # solve stokes flow res = domain.integral( 'ubasis_ni,j sigma_ij + pbasis_n (u_k,k + l) + lbasis_n p' @ ns, geometry=ns.x, degree=2 * (degree + 1)) res += domain.boundary.integral('nietzsche_ni (u_i - utop_i)' @ ns, geometry=ns.x, degree=2 * (degree + 1)) lhs0 = solver.solve_linear('lhs', res) if figures: postprocess(domain, ns(lhs=lhs0)) # solve navier-stokes flow res += domain.integral('density ubasis_ni u_i,j u_j' @ ns, geometry=ns.x, degree=3 * (degree + 1)) lhs1 = solver.newton('lhs', res, lhs0=lhs0).solve(tol=1e-10) if figures: postprocess(domain, ns(lhs=lhs1)) return lhs0, lhs1
def test_newton_iter(self): _test_recursion_cache(self, lambda: ((types.frozenarray(lhs), info.resnorm) for lhs, info in solver.newton('dofs', residual=self.residual, constrain=self.cons)))