def main(viscosity=1.3e-3, density=1e3, pout=223e5, uw=-0.01, nelems=10): # viscosity = 1.3e-3 # density = 1e3 # pout = 223e5 # nelems = 10 # uw = -0.01 domain, geom = mesh.rectilinear([numpy.linspace(0, 1, nelems), numpy.linspace(1, 2, nelems), [0, 2 * numpy.pi]], periodic=[2]) domain = domain.withboundary(inner='bottom', outer='top') ns = function.Namespace() ns.y, ns.r, ns.θ = geom ns.x_i = '<r cos(θ), y, r sin(θ)>_i' ns.uybasis, ns.urbasis, ns.pbasis = function.chain([ domain.basis('std', degree=3, removedofs=((0,-1), None, None)), # remove normal component at y=0 and y=1 domain.basis('std', degree=3, removedofs=((0,-1), None, None)), # remove tangential component at y=0 (no slip) domain.basis('std', degree=2)]) ns.ubasis_ni = '<urbasis_n cos(θ), uybasis_n, urbasis_n sin(θ)>_i' ns.viscosity = viscosity ns.density = density ns.u_i = 'ubasis_ni ?lhs_n' ns.p = 'pbasis_n ?lhs_n' ns.sigma_ij = 'viscosity (u_i,j + u_j,i) - p δ_ij' ns.pout = pout ns.uw = uw ns.uw_i = 'uw <cos(phi), 0, sin(phi)>_i' ns.tout_i = '-pout n_i' ns.uw_i = 'uw n_i' # uniform outflow res = domain.integral('(viscosity ubasis_ni,j u_i,j - p ubasis_ni,i + pbasis_n u_k,k) d:x' @ ns, degree=6) # res -= domain[1].boundary['inner'].integral('ubasis_ni tout_i d:x' @ ns, degree=6) sqr = domain.boundary['inner'].integral('(u_i - uw_i) (u_i - uw_i) d:x' @ ns, degree=6) # sqr = domain.boundary['outer'].integral('(u_i - uin_i) (u_i - uin_i) d:x' @ ns, degree=6) sqr -= domain.boundary['outer'].integral('(p - pout) (p - pout) d:x' @ ns, degree=6) cons = solver.optimize('lhs', sqr, droptol=1e-15) lhs = solver.solve_linear('lhs', res, constrain=cons) plottopo = domain[:, :, 0:].boundary['back'] bezier = plottopo.sample('bezier', 10) r, y, p, u = bezier.eval([ns.r, ns.y, ns.p, function.norm2(ns.u)], lhs=lhs) with export.mplfigure('pressure.png', dpi=800) as fig: ax = fig.add_subplot(111, title='pressure', aspect=1) ax.autoscale(enable=True, axis='both', tight=True) im = ax.tripcolor(r, y, bezier.tri, p, shading='gouraud', cmap='jet') ax.add_collection( collections.LineCollection(numpy.array([y, r]).T[bezier.hull], colors='k', linewidths=0.2, alpha=0.2)) fig.colorbar(im) uniform = plottopo.sample('uniform', 1) r_, y_, uv = uniform.eval([ns.r, ns.y, ns.u], lhs=lhs) with export.mplfigure('velocity.png', dpi=800) as fig: ax = fig.add_subplot(111, title='Velocity', aspect=1) ax.autoscale(enable=True, axis='both', tight=True) im = ax.tripcolor(r, y, bezier.tri, u, shading='gouraud', cmap='jet') ax.quiver(r_, y_, uv[:, 0], uv[:, 1], angles='xy', scale_units='xy') fig.colorbar(im)
def test_sparsity(self): topo, geom = mesh.rectilinear([6] * self.ndim) topo = topo.refined_by( set( map( topo.transforms.index, itertools.chain(topo[1:3].transforms, topo[-2:].transforms)))) ns = function.Namespace() ns.x = geom ns.tbasis = topo.basis('th-spline', degree=2, truncation_tolerance=1e-14) ns.tnotol = topo.basis('th-spline', degree=2, truncation_tolerance=0) ns.hbasis = topo.basis('h-spline', degree=2) tA, tA_tol, hA = topo.sample('gauss', 5).integrate_sparse([ ns.eval_ij('tbasis_i,k tbasis_j,k'), ns.eval_ij('tnotol_i,k tnotol_j,k'), ns.eval_ij('hbasis_i,k hbasis_j,k') ]) tA_nnz, tA_tol_nnz, hA_nnz = self.vals[self.ndim] self.assertEqual(len(sparse.prune(sparse.dedup(tA))), tA_nnz) self.assertEqual(len(sparse.prune(sparse.dedup(tA_tol))), tA_tol_nnz) self.assertEqual(len(sparse.prune(sparse.dedup(hA))), hA_nnz)
def setUp(self): super().setUp() self.ns = function.Namespace() self.domain, self.ns.geom = mesh.rectilinear([2,2]) self.ns.ubasis = self.domain.basis('std', degree=1) self.ns.u = 'ubasis_n ?dofs_n' self.optimize = solver.optimize if not self.minimize else lambda *args, newtontol=0, **kwargs: solver.minimize(*args, **kwargs).solve(newtontol)
def main(nelems:int, etype:str, btype:str, degree:int, poisson:float, angle:float, restol:float, trim:bool): ''' Deformed hyperelastic plate. .. arguments:: nelems [10] Number of elements along edge. etype [square] Type of elements (square/triangle/mixed). btype [std] Type of basis function (std/spline). degree [1] Polynomial degree. poisson [.25] Poisson's ratio, nonnegative and stricly smaller than 1/2. angle [20] Rotation angle for right clamp (degrees). restol [1e-10] Newton tolerance. trim [no] Create circular-shaped hole. ''' domain, geom = mesh.unitsquare(nelems, etype) if trim: domain = domain.trim(function.norm2(geom-.5)-.2, maxrefine=2) bezier = domain.sample('bezier', 5) ns = function.Namespace(fallback_length=domain.ndims) ns.x = geom ns.angle = angle * numpy.pi / 180 ns.lmbda = 2 * poisson ns.mu = 1 - 2 * poisson ns.ubasis = domain.basis(btype, degree=degree) ns.u_i = 'ubasis_k ?lhs_ki' ns.X_i = 'x_i + u_i' ns.strain_ij = '.5 (d(u_i, x_j) + d(u_j, x_i))' ns.energy = 'lmbda strain_ii strain_jj + 2 mu strain_ij strain_ij' sqr = domain.boundary['left'].integral('u_k u_k J(x)' @ ns, degree=degree*2) sqr += domain.boundary['right'].integral('((u_0 - x_1 sin(2 angle) - cos(angle) + 1)^2 + (u_1 - x_1 (cos(2 angle) - 1) + sin(angle))^2) J(x)' @ ns, degree=degree*2) cons = solver.optimize('lhs', sqr, droptol=1e-15) energy = domain.integral('energy J(x)' @ ns, degree=degree*2) lhs0 = solver.optimize('lhs', energy, constrain=cons) X, energy = bezier.eval(['X', 'energy'] @ ns, lhs=lhs0) export.triplot('linear.png', X, energy, tri=bezier.tri, hull=bezier.hull) ns.strain_ij = '.5 (d(u_i, x_j) + d(u_j, x_i) + d(u_k, x_i) d(u_k, x_j))' ns.energy = 'lmbda strain_ii strain_jj + 2 mu strain_ij strain_ij' energy = domain.integral('energy J(x)' @ ns, degree=degree*2) lhs1 = solver.minimize('lhs', energy, arguments=dict(lhs=lhs0), constrain=cons).solve(restol) X, energy = bezier.eval(['X', 'energy'] @ ns, lhs=lhs1) export.triplot('nonlinear.png', X, energy, tri=bezier.tri, hull=bezier.hull) return lhs0, lhs1
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 setUp(self): ns = function.Namespace() domain, ns.x = mesh.rectilinear([10], periodic=(0,)) ns.basis = domain.basis('discont', degree=1) ns.u = 'basis_n ?dofs_n' ns.f = '.5 u^2' self.residual = domain.integral('-basis_n,0 f d:x' @ ns, degree=2) self.residual += domain.interfaces.integral('-[basis_n] n_0 ({f} - .5 [u] n_0) d:x' @ ns, degree=4) self.inertia = domain.integral('basis_n u d:x' @ ns, degree=5) self.lhs0 = numpy.sin(numpy.arange(len(ns.basis))) # "random" initial vector
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: 'number of elements' = 12, lmbda: 'first lamé constant' = 1., mu: 'second lamé constant' = 1., degree: 'polynomial degree' = 2, withplots: 'create plots' = True, solvetol: 'solver tolerance' = 1e-10, ): # construct mesh verts = numpy.linspace(0, 1, nelems + 1) domain, geom = mesh.rectilinear([verts, verts]) # create namespace ns = function.Namespace(default_geometry_name='x0') ns.x0 = geom ns.basis = domain.basis('spline', degree=degree).vector(2) ns.u_i = 'basis_ni ?lhs_n' ns.x_i = 'x0_i + u_i' ns.lmbda = lmbda ns.mu = mu ns.strain_ij = '(u_i,j + u_j,i) / 2' ns.stress_ij = 'lmbda strain_kk δ_ij + 2 mu strain_ij' # construct dirichlet boundary constraints sqr = domain.boundary['left'].integral('u_k u_k' @ ns, geometry=ns.x0, degree=2) sqr += domain.boundary['right'].integral('(u_0 - .5)^2' @ ns, geometry=ns.x0, degree=2) cons = solver.optimize('lhs', sqr, droptol=1e-15) # construct residual res = domain.integral('basis_ni,j stress_ij' @ ns, geometry=ns.x0, degree=2) # solve system and substitute the solution in the namespace lhs = solver.solve_linear('lhs', res, constrain=cons) ns |= dict(lhs=lhs) # plot solution if withplots: points, colors = domain.elem_eval([ns.x, ns.stress[0, 1]], ischeme='bezier3', separate=True) with plot.PyPlot('stress', ndigits=0) as plt: plt.mesh(points, colors, tight=False) plt.colorbar() return lhs, cons
def project(self, projection): ns = fn.Namespace() for name, func in self._kwargs.items(): setattr(ns, name, func) for p, (name, func) in zip(projection, self._defaults.items()): if p is not None: func = fn.matmat(p, func) setattr(ns, name, func) integrand = getattr(ns, self._evaluator)(self._code) domain, geom, ischeme = self.prop('domain', 'geometry', 'ischeme') with matrix.Numpy(): retval = domain.integrate(integrand * fn.J(geom), ischeme=ischeme) return NumpyArrayIntegrand(retval)
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 main( nelems: 'number of elements' = 20, degree: 'polynomial degree' = 1, timescale: 'time scale (timestep=timescale/nelems)' = .5, tol: 'solver tolerance' = 1e-5, ndims: 'spatial dimension' = 1, endtime: 'end time, 0 for no end time' = 0, withplots: 'create plots' = True, ): # construct mesh, basis ns = function.Namespace() domain, ns.x = mesh.rectilinear([numpy.linspace(0, 1, nelems + 1)] * ndims, periodic=range(ndims)) ns.basis = domain.basis('discont', degree=degree) ns.u = 'basis_n ?lhs_n' # construct initial condition (centered gaussian) lhs0 = domain.project('exp(-?y_i ?y_i) | ?y_i = 5 (x_i - 0.5_i)' @ ns, onto=ns.basis, geometry=ns.x, degree=5) # prepare residual ns.f = '.5 u^2' ns.C = 1 res = domain.integral('-basis_n,0 f' @ ns, geometry=ns.x, degree=5) res += domain.interfaces.integral( '-[basis_n] n_0 ({f} - .5 C [u] n_0)' @ ns, geometry=ns.x, degree=5) inertia = domain.integral('basis_n u' @ ns, geometry=ns.x, degree=5) # prepare plotting makeplots = MakePlots(domain, video=withplots == 'video') if withplots else lambda ns: None # start time stepping timestep = timescale / nelems for itime, lhs in log.enumerate( 'timestep', solver.impliciteuler('lhs', res, inertia, timestep, lhs0, newtontol=tol)): makeplots(ns | dict(lhs=lhs)) if endtime and itime * timestep >= endtime: break return res.eval(arguments=dict(lhs=lhs)), lhs
def check(self, method, theta): ns = function.Namespace() topo, ns.x = mesh.rectilinear([1]) ns.u_n = '?u_n + <0>_n' inertia = topo.integral('?u_n d:x' @ ns, degree=0) residual = topo.integral('-<1>_n sin(?t) d:x' @ ns, degree=0) timestep = 0.1 udesired = numpy.array([0.]) uactualiter = iter(method(target='u', residual=residual, inertia=inertia, timestep=timestep, lhs0=udesired, timetarget='t')) for i in range(5): with self.subTest(i=i): uactual = next(uactualiter) self.assertAllAlmostEqual(uactual, udesired) udesired += timestep*(theta*numpy.sin((i+1)*timestep)+(1-theta)*numpy.sin(i*timestep))
def main(nelems: int, etype: str, btype: str, degree: int, poisson: float): ''' Horizontally loaded linear elastic plate. .. arguments:: nelems [10] Number of elements along edge. etype [square] Type of elements (square/triangle/mixed). btype [std] Type of basis function (std/spline), with availability depending on the configured element type. degree [1] Polynomial degree. poisson [.25] Poisson's ratio, nonnegative and strictly smaller than 1/2. ''' domain, geom = mesh.unitsquare(nelems, etype) ns = function.Namespace() ns.x = geom ns.basis = domain.basis(btype, degree=degree).vector(2) ns.u_i = 'basis_ni ?lhs_n' ns.X_i = 'x_i + u_i' ns.lmbda = 2 * poisson ns.mu = 1 - 2 * poisson ns.strain_ij = '(d(u_i, x_j) + d(u_j, x_i)) / 2' ns.stress_ij = 'lmbda strain_kk δ_ij + 2 mu strain_ij' sqr = domain.boundary['left'].integral('u_k u_k J(x)' @ ns, degree=degree * 2) sqr += domain.boundary['right'].integral('(u_0 - .5)^2 J(x)' @ ns, degree=degree * 2) cons = solver.optimize('lhs', sqr, droptol=1e-15) res = domain.integral('d(basis_ni, x_j) stress_ij J(x)' @ ns, degree=degree * 2) lhs = solver.solve_linear('lhs', res, constrain=cons) bezier = domain.sample('bezier', 5) X, sxy = bezier.eval(['X', 'stress_01'] @ ns, lhs=lhs) export.triplot('shear.png', X, sxy, tri=bezier.tri, hull=bezier.hull) return cons, lhs
def main( nelems: 'number of elements' = 10, degree: 'polynomial degree' = 1, basistype: 'basis function' = 'spline', solvetol: 'solver tolerance' = 1e-10, figures: 'create figures' = True, ): # construct mesh verts = numpy.linspace(0, numpy.pi/2, nelems+1) domain, geom = mesh.rectilinear([verts, verts]) # create namespace ns = function.Namespace() ns.x = geom ns.basis = domain.basis(basistype, degree=degree) ns.u = 'basis_n ?lhs_n' ns.fx = 'sin(x_0)' ns.fy = 'exp(x_1)' # construct residual res = domain.integral('-basis_n,i u_,i' @ ns, geometry=ns.x, degree=degree*2) res += domain.boundary['top'].integral('basis_n fx fy' @ ns, geometry=ns.x, degree=degree*2) # construct dirichlet constraints sqr = domain.boundary['left'].integral('u^2' @ ns, geometry=ns.x, degree=degree*2) sqr += domain.boundary['bottom'].integral('(u - fx)^2' @ ns, geometry=ns.x, degree=degree*2) cons = solver.optimize('lhs', sqr, droptol=1e-15) # find lhs such that res == 0 and substitute this lhs in the namespace lhs = solver.solve_linear('lhs', res, constrain=cons) ns = ns(lhs=lhs) # plot solution if figures: points, colors = domain.elem_eval([ns.x, ns.u], ischeme='bezier9', separate=True) with plot.PyPlot('solution', index=nelems) as plt: plt.mesh(points, colors, cmap='jet') plt.colorbar() # evaluate error against exact solution fx fy err = domain.integrate('(u - fx fy)^2' @ ns, geometry=ns.x, degree=degree*2)**.5 log.user('L2 error: {:.2e}'.format(err)) return cons, lhs, err
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 main(nelems: int, degree: int, reynolds: float, rotation: float, timestep: float, maxradius: float, seed: int, endtime: float): ''' Flow around a cylinder. .. arguments:: nelems [24] Element size expressed in number of elements along the cylinder wall. All elements have similar shape with approximately unit aspect ratio, with elements away from the cylinder wall growing exponentially. degree [3] Polynomial degree for velocity space; the pressure space is one degree less. reynolds [1000] Reynolds number, taking the cylinder radius as characteristic length. rotation [0] Cylinder rotation speed. timestep [.04] Time step maxradius [25] Target exterior radius; the actual domain size is subject to integer multiples of the configured element size. seed [0] Random seed for small velocity noise in the intial condition. endtime [inf] Stopping time. ''' elemangle = 2 * numpy.pi / nelems melems = int(numpy.log(2 * maxradius) / elemangle + .5) treelog.info('creating {}x{} mesh, outer radius {:.2f}'.format( melems, nelems, .5 * numpy.exp(elemangle * melems))) domain, geom = mesh.rectilinear([melems, nelems], periodic=(1, )) domain = domain.withboundary(inner='left', outer='right') ns = function.Namespace() ns.uinf = 1, 0 ns.r = .5 * function.exp(elemangle * geom[0]) ns.Re = reynolds ns.phi = geom[1] * elemangle # add small angle to break element symmetry ns.x_i = 'r <cos(phi), sin(phi)>_i' ns.J = ns.x.grad(geom) ns.unbasis, ns.utbasis, ns.pbasis = function.chain([ # compatible spaces domain.basis( 'spline', degree=(degree, degree - 1), removedofs=((0, ), None)), domain.basis('spline', degree=(degree - 1, degree)), domain.basis('spline', degree=degree - 1), ]) / function.determinant(ns.J) ns.ubasis_ni = 'unbasis_n J_i0 + utbasis_n J_i1' # piola transformation ns.u_i = 'ubasis_ni ?lhs_n' ns.p = 'pbasis_n ?lhs_n' ns.sigma_ij = '(u_i,j + u_j,i) / Re - p δ_ij' ns.h = .5 * elemangle ns.N = 5 * degree / ns.h ns.rotation = rotation ns.uwall_i = '0.5 rotation <-sin(phi), cos(phi)>_i' inflow = domain.boundary['outer'].select( -ns.uinf.dotnorm(ns.x), ischeme='gauss1') # upstream half of the exterior boundary sqr = inflow.integral('(u_i - uinf_i) (u_i - uinf_i)' @ ns, degree=degree * 2) cons = solver.optimize( 'lhs', sqr, droptol=1e-15) # constrain inflow semicircle to uinf sqr = domain.integral('(u_i - uinf_i) (u_i - uinf_i) + p^2' @ ns, degree=degree * 2) lhs0 = solver.optimize('lhs', sqr) # set initial condition to u=uinf, p=0 numpy.random.seed(seed) lhs0 *= numpy.random.normal(1, .1, lhs0.shape) # add small velocity noise res = domain.integral( '(ubasis_ni u_i,j u_j + ubasis_ni,j sigma_ij + pbasis_n u_k,k) d:x' @ ns, degree=9) res += domain.boundary['inner'].integral( '(N ubasis_ni - (ubasis_ni,j + ubasis_nj,i) n_j) (u_i - uwall_i) d:x / Re' @ ns, degree=9) inertia = domain.integral('ubasis_ni u_i d:x' @ ns, degree=9) bbox = numpy.array( [[-2, 46 / 9], [-2, 2]]) # bounding box for figure based on 16x9 aspect ratio bezier0 = domain.sample('bezier', 5) bezier = bezier0.subset((bezier0.eval( (ns.x - bbox[:, 0]) * (bbox[:, 1] - ns.x)) > 0).all(axis=1)) interpolate = util.tri_interpolator( bezier.tri, bezier.eval(ns.x), mergetol=1e-5) # interpolator for quivers spacing = .05 # initial quiver spacing xgrd = util.regularize(bbox, spacing) with treelog.iter.plain( 'timestep', solver.impliciteuler('lhs', residual=res, inertia=inertia, lhs0=lhs0, timestep=timestep, constrain=cons, newtontol=1e-10)) as steps: for istep, lhs in enumerate(steps): t = istep * timestep x, u, normu, p = bezier.eval( ['x_i', 'u_i', 'sqrt(u_k u_k)', 'p'] @ ns, lhs=lhs) ugrd = interpolate[xgrd](u) with export.mplfigure('flow.png', figsize=(12.8, 7.2)) as fig: ax = fig.add_axes([0, 0, 1, 1], yticks=[], xticks=[], frame_on=False, xlim=bbox[0], ylim=bbox[1]) im = ax.tripcolor(x[:, 0], x[:, 1], bezier.tri, p, shading='gouraud', cmap='jet') import matplotlib.collections ax.add_collection( matplotlib.collections.LineCollection(x[bezier.hull], colors='k', linewidths=.1, alpha=.5)) ax.quiver(xgrd[:, 0], xgrd[:, 1], ugrd[:, 0], ugrd[:, 1], angles='xy', width=1e-3, headwidth=3e3, headlength=5e3, headaxislength=2e3, zorder=9, alpha=.5) ax.plot(0, 0, 'k', marker=(3, 2, t * rotation * 180 / numpy.pi - 90), markersize=20) cax = fig.add_axes([0.9, 0.1, 0.01, 0.8]) cax.tick_params(labelsize='large') fig.colorbar(im, cax=cax) if t >= endtime: break xgrd = util.regularize(bbox, spacing, xgrd + ugrd * timestep) return lhs0, lhs
def main(nelems: int, ndims: int, degree: int, timescale: float, newtontol: float, endtime: float): ''' Burgers equation on a 1D or 2D periodic domain. .. arguments:: nelems [20] Number of elements along a single dimension. ndims [1] Number of spatial dimensions. degree [1] Polynomial degree for discontinuous basis functions. timescale [.5] Fraction of timestep and element size: timestep=timescale/nelems. newtontol [1e-5] Newton tolerance. endtime [inf] Stopping time. ''' domain, geom = mesh.rectilinear([numpy.linspace(0, 1, nelems + 1)] * ndims, periodic=range(ndims)) ns = function.Namespace() ns.x = geom ns.basis = domain.basis('discont', degree=degree) ns.u = 'basis_n ?lhs_n' ns.f = '.5 u^2' ns.C = 1 res = domain.integral('-basis_n,0 f d:x' @ ns, degree=5) res += domain.interfaces.integral( '-[basis_n] n_0 ({f} - .5 C [u] n_0) d:x' @ ns, degree=degree * 2) inertia = domain.integral('basis_n u d:x' @ ns, degree=5) sqr = domain.integral( '(u - exp(-?y_i ?y_i)(y_i = 5 (x_i - 0.5_i)))^2 d:x' @ ns, degree=5) lhs0 = solver.optimize('lhs', sqr) timestep = timescale / nelems bezier = domain.sample('bezier', 7) with treelog.iter.plain( 'timestep', solver.impliciteuler('lhs', res, inertia, timestep=timestep, lhs0=lhs0, newtontol=newtontol)) as steps: for itime, lhs in enumerate(steps): x, u = bezier.eval(['x_i', 'u'] @ ns, lhs=lhs) export.triplot('solution.png', x, u, tri=bezier.tri, hull=bezier.hull, clim=(0, 1)) if itime * timestep >= endtime: break return lhs
def setUp(self): super().setUp() self.ns = function.Namespace() self.domain, self.ns.geom = mesh.rectilinear([2, 2]) self.ns.ubasis = self.domain.basis('std', degree=1) self.ns.u = 'ubasis_n ?dofs_n'
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( nelems: 'number of elements' = 20, epsilon: 'epsilon, 0 for automatic (based on nelems)' = 0, timestep: 'time step' = .01, maxtime: 'end time' = 1., theta: 'contact angle (degrees)' = 90, init: 'initial condition (random/bubbles)' = 'random', figures: 'create figures' = True, ): mineps = 1. / nelems if not epsilon: log.info('setting epsilon={}'.format(mineps)) epsilon = mineps elif epsilon < mineps: log.warning('epsilon under crititical threshold: {} < {}'.format( epsilon, mineps)) # create namespace ns = function.Namespace() ns.epsilon = epsilon ns.ewall = .5 * numpy.cos(theta * numpy.pi / 180) # construct mesh xnodes = ynodes = numpy.linspace(0, 1, nelems + 1) domain, ns.x = mesh.rectilinear([xnodes, ynodes]) # prepare bases ns.cbasis, ns.mubasis = function.chain( [domain.basis('spline', degree=2), domain.basis('spline', degree=2)]) # polulate namespace ns.c = 'cbasis_n ?lhs_n' ns.c0 = 'cbasis_n ?lhs0_n' ns.mu = 'mubasis_n ?lhs_n' ns.f = '(6 c0 - 2 c0^3 - 4 c) / epsilon^2' # construct initial condition if init == 'random': numpy.random.seed(0) lhs0 = numpy.random.normal(0, .5, ns.cbasis.shape) elif init == 'bubbles': R1 = .25 R2 = numpy.sqrt(.5) * R1 # area2 = .5 * area1 ns.cbubble1 = function.tanh( (R1 - function.norm2(ns.x - (.5 + R2 / numpy.sqrt(2) + .8 * ns.epsilon))) / ns.epsilon) ns.cbubble2 = function.tanh( (R2 - function.norm2(ns.x - (.5 - R1 / numpy.sqrt(2) - .8 * ns.epsilon))) / ns.epsilon) sqr = domain.integral('(c - cbubble1 - cbubble2 - 1)^2 + mu^2' @ ns, geometry=ns.x, degree=4) lhs0 = solver.optimize('lhs', sqr) else: raise Exception('unknown init %r' % init) # construct residual res = domain.integral( 'epsilon^2 cbasis_n,k mu_,k + mubasis_n (mu + f) - mubasis_n,k c_,k' @ ns, geometry=ns.x, degree=4) res -= domain.boundary.integral('mubasis_n ewall' @ ns, geometry=ns.x, degree=4) inertia = domain.integral('cbasis_n c' @ ns, geometry=ns.x, degree=4) # solve time dependent problem nsteps = numeric.round(maxtime / timestep) makeplots = MakePlots(domain, nsteps, ns) if figures else lambda *args: None for istep, lhs in log.enumerate( 'timestep', solver.impliciteuler('lhs', target0='lhs0', residual=res, inertia=inertia, timestep=timestep, lhs0=lhs0)): makeplots(lhs) if istep == nsteps: break return lhs0, lhs
def main( nelems: 'number of elements' = 12, viscosity: 'fluid viscosity' = 1e-2, density: 'fluid density' = 1, tol: 'solver tolerance' = 1e-12, rotation: 'cylinder rotation speed' = 0, timestep: 'time step' = 1/24, maxradius: 'approximate domain size' = 25, tmax: 'end time' = numpy.inf, degree: 'polynomial degree' = 2, withplots: 'create plots' = True, ): log.user('reynolds number: {:.1f}'.format(density / viscosity)) # based on unit length and velocity # create namespace ns = function.Namespace() ns.uinf = 1, 0 ns.density = density ns.viscosity = viscosity # construct mesh rscale = numpy.pi / nelems melems = numpy.ceil(numpy.log(2*maxradius) / rscale).astype(int) log.info('creating {}x{} mesh, outer radius {:.2f}'.format(melems, 2*nelems, .5*numpy.exp(rscale*melems))) domain, x0 = mesh.rectilinear([range(melems+1),numpy.linspace(0,2*numpy.pi,2*nelems+1)], periodic=(1,)) rho, phi = x0 phi += 1e-3 # tiny nudge (0.057 deg) to break element symmetry radius = .5 * function.exp(rscale * rho) ns.x = radius * function.trigtangent(phi) domain = domain.withboundary(inner='left', inflow=domain.boundary['right'].select(-ns.uinf.dotnorm(ns.x), ischeme='gauss1')) # prepare bases (using piola transformation to maintain u/p compatibility) J = ns.x.grad(x0) detJ = function.determinant(J) ns.unbasis, ns.utbasis, ns.pbasis = function.chain([ # compatible spaces using piola transformation domain.basis('spline', degree=(degree+1,degree), removedofs=((0,),None))[:,_] * J[:,0] / detJ, domain.basis('spline', degree=(degree,degree+1))[:,_] * J[:,1] / detJ, domain.basis('spline', degree=degree) / detJ, ]) ns.ubasis_ni = 'unbasis_ni + utbasis_ni' # populate namespace ns.u_i = 'ubasis_ni ?lhs_n' ns.p = 'pbasis_n ?lhs_n' ns.sigma_ij = 'viscosity (u_i,j + u_j,i) - p δ_ij' ns.hinner = 2 * numpy.pi / nelems ns.c = 5 * (degree+1) / ns.hinner ns.nietzsche_ni = 'viscosity (c ubasis_ni - (ubasis_ni,j + ubasis_nj,i) n_j)' ns.ucyl = -.5 * rotation * function.trignormal(phi) # create residual vector components res = domain.integral('ubasis_ni,j sigma_ij + pbasis_n u_k,k' @ ns, geometry=ns.x, degree=2*(degree+1)) res += domain.boundary['inner'].integral('nietzsche_ni (u_i - ucyl_i)' @ ns, geometry=ns.x, degree=2*(degree+1)) oseen = domain.integral('density ubasis_ni u_i,j uinf_j' @ ns, geometry=ns.x, degree=2*(degree+1)) convec = domain.integral('density ubasis_ni u_i,j u_j' @ ns, geometry=ns.x, degree=3*(degree+1)) inertia = domain.integral('density ubasis_ni u_i' @ ns, geometry=ns.x, degree=2*(degree+1)) # constrain full velocity vector at inflow sqr = domain.boundary['inflow'].integral('(u_i - uinf_i) (u_i - uinf_i)' @ ns, geometry=ns.x, degree=9) cons = solver.optimize('lhs', sqr, droptol=1e-15) # solve unsteady navier-stokes equations, starting from stationary oseen flow lhs0 = solver.solve_linear('lhs', res+oseen, constrain=cons) makeplots = MakePlots(domain, ns, timestep=timestep, rotation=rotation) if withplots else lambda *args: None for istep, lhs in log.enumerate('timestep', solver.impliciteuler('lhs', residual=res+convec, inertia=inertia, lhs0=lhs0, timestep=timestep, constrain=cons, newtontol=1e-10)): makeplots(lhs) if istep * timestep >= tmax: break return lhs0, lhs
class NutilsDelayedIntegrand(Integrand): optimized = False def __init__(self, code, indices, variables, **kwargs): super().__init__() self._code = code self._defaults = OrderedDict([(name, kwargs[name]) for name in variables]) self._kwargs = {name: func for name, func in kwargs.items() if name not in variables} self._evaluator = 'eval_' + indices self._kwargs, self._arg_shapes = {}, {} self.update_kwargs({k: v for k, v in kwargs.items() if k not in variables}) if code is not None: self.shape = self._integrand().shape self.ndim = len(self.shape) def update_kwargs(self, kwargs): for name, func in kwargs.items(): if isinstance(func, tuple): func, shape = func self._arg_shapes[name] = shape self._kwargs[name] = func def add(self, code, **kwargs): self.update_kwargs(kwargs) if self._code is None: self._code = f'({code})' self.shape = self._integrand().shape self.ndim = len(self.shape) else: self._code = f'{self._code} + ({code})' def write(self, group, name): sub = group.require_group(name) data = {key: getattr(self, key) for key in ['_code', '_defaults', '_kwargs', '_evaluator', 'shape']} util.to_dataset(data, sub, 'data') sub.attrs['type'] = 'NutilsDelayedIntegrand' self.write_props(sub) return sub @staticmethod def read(group): retval = NutilsDelayedIntegrand.__new__(NutilsDelayedIntegrand) data = util.from_dataset(group['data']) retval.__dict__.update(data) retval.ndim = len(retval.shape) retval.read_props(group) return retval def _integrand(self, contraction=None, mu=None, case=None): if contraction is None: contraction = (None,) * len(self._defaults) ns = fn.Namespace() for name, func in self._kwargs.items(): if isinstance(func, fn.Array): setattr(ns, name, func) elif mu is not None and case is not None: assert callable(getattr(case, func)) setattr(ns, name, getattr(case, func)(mu)) else: # This code path should ONLY be used for inferring the shape of the integrand assert not hasattr(self, 'shape') setattr(ns, name, fn.zeros(self._arg_shapes[name]))
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 neutrondiffusion(mat_flag='scatterer', BC_flag='vacuum', bigsquare=1, smallsquare=.1, degree=1, basis='lagrange'): W = bigsquare #width of outer box Wi = smallsquare #width of inner box nelems = int((2 * 1 * W / Wi)) if mat_flag == 'scatterer': sigt = 2 sigs = 1.99 elif mat_flag == 'reflector': sigt = 2 sigs = 1.8 elif mat_flag == 'absorber': sigt = 10 sigs = 2 elif mat_flag == 'air': sigt = .01 sigs = .006 topo, geom = mesh.unitsquare(nelems, 'square') #unit square centred at (0.5, 0.5) ns = function.Namespace() ns.basis = topo.basis(basis, degree=degree) ns.x = W * geom #scales the unit square to our physical square ns.f = function.max( function.abs(ns.x[0] - W / 2), function.abs(ns.x[1] - W / 2)) #level set function for inner square inner, outer = function.partition( ns.f, Wi / 2) #indicator function for inner square and outer square ns.phi = 'basis_A ?dofs_A' ns.SIGs = sigs * outer #scattering cross-section ns.SIGt = 0.1 * inner + sigt * outer #total cross-section ns.SIGa = 'SIGt - SIGs' #absorption cross-section ns.D = '1 / (3 SIGt)' #diffusion co-efficient ns.Q = inner #source term if BC_flag == 'vacuum': sqr = topo.boundary.integral('(phi - 0)^2 J(x)' @ ns, degree=degree * 2) cons = solver.optimize( 'dofs', sqr, droptol=1e-14) #this applies the boundary condition to u #residual res = topo.integral( '(D basis_i,j phi_,j + SIGa basis_i phi - basis_i Q) J(x)' @ ns, degree=degree * 2) #solve for degrees of freedom dofs = solver.solve_linear('dofs', res, constrain=cons) elif BC_flag == 'reflecting': #residual res = topo.integral( '(D basis_i,j phi_,j + SIGa basis_i phi - basis_i Q) J(x)' @ ns, degree=degree * 2) #solve for degrees of freedom dofs = solver.solve_linear('dofs', res) #select lower triangular half of square domain. Diagonal is one of its boundaries triang = topo.trim('x_0 - x_1' @ ns, maxrefine=5) triangbnd = triang.boundary #select boundary of lower triangle # eval the vertices of the boundary elements of lower triangle: verts = triangbnd.sample(*element.parse_legacy_ischeme("vertex")).eval( 'x_i' @ ns) # now select the verts of the boundary diag = topology.SubsetTopology(triangbnd, [ triangbnd.references[i] if (verts[2 * i][0] == verts[2 * i][1] and verts[2 * i + 1][0] == verts[2 * i + 1][1]) else triangbnd.references[i].empty for i in range(len(triangbnd.references)) ]) bezier_diag = diag.sample('bezier', degree + 1) x_diag = bezier_diag.eval('(x_0^2 + x_1^2)^(1 / 2)' @ ns) phi_diag = bezier_diag.eval('phi' @ ns, dofs=dofs) bezier_bottom = topo.boundary['bottom'].sample('bezier', degree + 1) x_bottom = bezier_bottom.eval('x_0' @ ns) phi_bottom = bezier_bottom.eval('phi' @ ns, dofs=dofs) fig_bottom = plt.figure(0) ax_bottom = fig_bottom.add_subplot(111) plt.plot(x_bottom, phi_bottom) ax_bottom.set_title('Scalar flux along bottom for %s with %s BCs' % (mat_flag, BC_flag)) ax_bottom.set_xlabel('x') ax_bottom.set_ylabel('$\\phi(x)$') fig_diag = plt.figure(1) ax_diag = fig_diag.add_subplot(111) plt.plot(x_diag, phi_diag) ax_diag.set_title('Scalar flux along diagonal for %s with %s BCs' % (mat_flag, BC_flag)) ax_diag.set_xlabel('$\\sqrt{x^2 + y^2}$') ax_diag.set_ylabel('$\\phi(x = y)$') return fig_bottom, fig_diag
def main( L: 'domain size' = 4., R: 'hole radius' = 1., E: "young's modulus" = 1e5, nu: "poisson's ratio" = 0.3, T: 'far field traction' = 10, nr: 'number of h-refinements' = 2, withplots: 'create plots' = True, ): ns = function.Namespace() ns.lmbda = E * nu / (1 - nu**2) ns.mu = .5 * E / (1 + nu) # create the coarsest level parameter domain domain0, geometry = mesh.rectilinear([1, 2]) # create the second-order B-spline basis over the coarsest domain ns.bsplinebasis = domain0.basis('spline', degree=2) ns.controlweights = 1, .5 + .5 / 2**.5, .5 + .5 / 2**.5, 1, 1, 1, 1, 1, 1, 1, 1, 1 ns.weightfunc = 'bsplinebasis_n controlweights_n' ns.nurbsbasis = ns.bsplinebasis * ns.controlweights / ns.weightfunc # create the isogeometric map ns.controlpoints = [0, R], [R * (1 - 2**.5), R], [-R, R * (2**.5 - 1)], [ -R, 0 ], [0, .5 * (R + L)], [-.15 * (R + L), .5 * (R + L)], [-.5 * (R + L), .15 * (R * L) ], [-.5 * (R + L), 0], [0, L], [-L, L], [-L, L], [-L, 0] ns.x_i = 'nurbsbasis_n controlpoints_ni' # create the computational domain by h-refinement domain = domain0.refine(nr) # create the second-order B-spline basis over the refined domain ns.bsplinebasis = domain.basis('spline', degree=2) sqr = domain.integral('(bsplinebasis_n ?lhs_n - weightfunc)^2' @ ns, geometry=ns.x, degree=9) ns.controlweights = solver.optimize('lhs', sqr) ns.nurbsbasis = ns.bsplinebasis * ns.controlweights / ns.weightfunc # prepare the displacement field ns.ubasis = ns.nurbsbasis.vector(2) ns.u_i = 'ubasis_ni ?lhs_n' ns.strain_ij = '(u_i,j + u_j,i) / 2' ns.stress_ij = 'lmbda strain_kk δ_ij + 2 mu strain_ij' # construct the exact solution ns.r2 = 'x_k x_k' ns.R2 = R**2 ns.T = T ns.k = (3 - nu) / (1 + nu) # plane stress parameter ns.uexact_i = '''(T / 4 mu) <x_0 ((k + 1) / 2 + (1 + k) R2 / r2 + (1 - R2 / r2) (x_0^2 - 3 x_1^2) R2 / r2^2), x_1 ((k - 3) / 2 + (1 - k) R2 / r2 + (1 - R2 / r2) (3 x_0^2 - x_1^2) R2 / r2^2)>_i''' ns.strainexact_ij = '(uexact_i,j + uexact_j,i) / 2' ns.stressexact_ij = 'lmbda strainexact_kk δ_ij + 2 mu strainexact_ij' # define the linear and bilinear forms res = domain.integral('ubasis_ni,j stress_ij' @ ns, geometry=ns.x, degree=9) res -= domain.boundary['right'].integral( 'ubasis_ni stressexact_ij n_j' @ ns, geometry=ns.x, degree=9) # compute the constraints vector for the symmetry conditions sqr = domain.boundary['top,bottom'].integral('(u_i n_i)^2' @ ns, degree=9) cons = solver.optimize('lhs', sqr, droptol=1e-15) # solve the system of equations lhs = solver.solve_linear('lhs', res, constrain=cons) ns |= dict(lhs=lhs) # post-processing if withplots: geom, stressxx = domain.simplex.elem_eval([ns.x, 'stress_00' @ ns], ischeme='bezier8', separate=True) with plot.PyPlot('solution', index=nr) as plt: plt.mesh(geom, stressxx) plt.colorbar() # compute the L2-norm of the error in the stress err = domain.integrate( '?dstress_ij ?dstress_ij | ?dstress_ij = stress_ij - stressexact_ij' @ ns, geometry=ns.x, ischeme='gauss9')**.5 # compute the mesh parameter (maximum physical distance between knots) hmax = max( numpy.linalg.norm(v[:, numpy.newaxis] - v, axis=2).max() for v in domain.elem_eval(ns.x, ischeme='bezier2', separate=True)) return err, hmax
def elasticity_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, solver from nutils import _, log # error test input if not (left.dimension == right.dimension == top.dimension == bottom.dimension == 2): raise RuntimeError( 'elasticity_patch only supported for planar (2D) geometries') if left.rational or right.rational or top.rational or bottom.rational: raise RuntimeError( 'elasticity_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 computational (nutils) mesh p1 = bottom.order(0) p2 = left.order(0) n1 = len(bottom) n2 = len(left) dim = left.dimension k1 = bottom.knots(0) k2 = left.knots(0) m1 = [bottom.order(0) - bottom.continuity(k) - 1 for k in k1] m2 = [left.order(0) - left.continuity(k) - 1 for k in k2] domain, geom = mesh.rectilinear([k1, k2]) # assemble system matrix ns = function.Namespace() ns.x = geom ns.basis = domain.basis('spline', degree=[p1 - 1, p2 - 1], knotmultiplicities=[m1, m2]).vector(2) ns.eye = np.array([[1, 0], [0, 1]]) ns.lmbda = 1 ns.mu = .3 # ns.u_i = 'basis_ni ?lhs_n' # ns.strain_ij = '(u_i,j + u_j,i) / 2' # ns.stress_ij = 'lmbda strain_kk eye_ij + 2 mu strain_ij' ns.strain_nij = '(basis_ni,j + basis_nj,i) / 2' ns.stress_nij = 'lmbda strain_nkk eye_ij + 2 mu strain_nij' # construct matrix and right hand-side matrix = domain.integrate(ns.eval_nm('strain_nij stress_mij d:x'), ischeme='gauss' + str(max(p1, p2) + 1)) rhs = np.zeros((n1 * n2 * dim)) # add boundary conditions constraints = np.array([[[np.nan] * n2] * n1] * dim) for d in range(dim): constraints[d, 0, :] = left[:, d] constraints[d, -1, :] = right[:, d] constraints[d, :, 0] = bottom[:, d] constraints[d, :, -1] = top[:, d] # solve system lhs = matrix.solve(rhs, constrain=np.ndarray.flatten(constraints, order='C')) # rewrap results into splipy datastructures controlpoints = np.reshape(lhs, (dim, n1, n2), order='C') controlpoints = controlpoints.swapaxes(0, 2) controlpoints = controlpoints.swapaxes(0, 1) return Surface(bottom.bases[0], left.bases[0], controlpoints, bottom.rational, raw=True)
def main(nelems: int, etype: str, btype: str, degree: int, traction: float, maxrefine: int, radius: float, poisson: float): ''' Horizontally loaded linear elastic plate with FCM hole. .. arguments:: nelems [9] Number of elements along edge. etype [square] Type of elements (square/triangle/mixed). btype [std] Type of basis function (std/spline), with availability depending on the selected element type. degree [2] Polynomial degree. traction [.1] Far field traction (relative to Young's modulus). maxrefine [2] Number or refinement levels used for the finite cell method. radius [.5] Cut-out radius. poisson [.3] Poisson's ratio, nonnegative and strictly smaller than 1/2. ''' domain0, geom = mesh.unitsquare(nelems, etype) domain = domain0.trim(function.norm2(geom) - radius, maxrefine=maxrefine) ns = function.Namespace() ns.x = geom ns.lmbda = 2 * poisson ns.mu = 1 - poisson ns.ubasis = domain.basis(btype, degree=degree).vector(2) ns.u_i = 'ubasis_ni ?lhs_n' ns.X_i = 'x_i + u_i' ns.strain_ij = '(d(u_i, x_j) + d(u_j, x_i)) / 2' ns.stress_ij = 'lmbda strain_kk δ_ij + 2 mu strain_ij' ns.r2 = 'x_k x_k' ns.R2 = radius**2 / ns.r2 ns.k = (3 - poisson) / (1 + poisson) # plane stress parameter ns.scale = traction * (1 + poisson) / 2 ns.uexact_i = 'scale (x_i ((k + 1) (0.5 + R2) + (1 - R2) R2 (x_0^2 - 3 x_1^2) / r2) - 2 δ_i1 x_1 (1 + (k - 1 + R2) R2))' ns.du_i = 'u_i - uexact_i' sqr = domain.boundary['left,bottom'].integral('(u_i n_i)^2 J(x)' @ ns, degree=degree * 2) cons = solver.optimize('lhs', sqr, droptol=1e-15) sqr = domain.boundary['top,right'].integral('du_k du_k J(x)' @ ns, degree=20) cons = solver.optimize('lhs', sqr, droptol=1e-15, constrain=cons) res = domain.integral('d(ubasis_ni, x_j) stress_ij J(x)' @ ns, degree=degree * 2) lhs = solver.solve_linear('lhs', res, constrain=cons) bezier = domain.sample('bezier', 5) X, stressxx = bezier.eval(['X', 'stress_00'] @ ns, lhs=lhs) export.triplot('stressxx.png', X, stressxx, tri=bezier.tri, hull=bezier.hull) err = domain.integral('<du_k du_k, sum:ij(d(du_i, x_j)^2)>_n J(x)' @ ns, degree=max(degree, 3) * 2).eval(lhs=lhs)**.5 treelog.user('errors: L2={:.2e}, H1={:.2e}'.format(*err)) return err, cons, lhs
def main(nelems: int, etype: str, btype: str, degree: int): ''' Laplace problem on a unit square. .. arguments:: nelems [10] Number of elements along edge. etype [square] Type of elements (square/triangle/mixed). btype [std] Type of basis function (std/spline), availability depending on the selected element type. degree [1] Polynomial degree. ''' # A unit square domain is created by calling the # :func:`nutils.mesh.unitsquare` mesh generator, with the number of elements # along an edge as the first argument, and the type of elements ("square", # "triangle", or "mixed") as the second. The result is a topology object # ``domain`` and a vectored valued geometry function ``geom``. domain, geom = mesh.unitsquare(nelems, etype) # To be able to write index based tensor contractions, we need to bundle all # relevant functions together in a namespace. Here we add the geometry ``x``, # a scalar ``basis``, and the solution ``u``. The latter is formed by # contracting the basis with a to-be-determined solution vector ``?lhs``. ns = function.Namespace() ns.x = geom ns.basis = domain.basis(btype, degree=degree) ns.u = 'basis_n ?lhs_n' # We are now ready to implement the Laplace equation. In weak form, the # solution is a scalar field :math:`u` for which: # # .. math:: ∀ v: ∫_Ω \frac{dv}{dx_i} \frac{du}{dx_i} - ∫_{Γ_n} v f = 0. # # By linearity the test function :math:`v` can be replaced by the basis that # spans its space. The result is an integral ``res`` that evaluates to a # vector matching the size of the function space. res = domain.integral('d(basis_n, x_i) d(u, x_i) J(x)' @ ns, degree=degree * 2) res -= domain.boundary['right'].integral( 'basis_n cos(1) cosh(x_1) J(x)' @ ns, degree=degree * 2) # The Dirichlet constraints are set by finding the coefficients that minimize # the error: # # .. math:: \min_u ∫_{\Gamma_d} (u - u_d)^2 # # The resulting ``cons`` array holds numerical values for all the entries of # ``?lhs`` that contribute (up to ``droptol``) to the minimization problem. # All remaining entries are set to ``NaN``, signifying that these degrees of # freedom are unconstrained. sqr = domain.boundary['left'].integral('u^2 J(x)' @ ns, degree=degree * 2) sqr += domain.boundary['top'].integral( '(u - cosh(1) sin(x_0))^2 J(x)' @ ns, degree=degree * 2) cons = solver.optimize('lhs', sqr, droptol=1e-15) # The unconstrained entries of ``?lhs`` are to be determined such that the # residual vector evaluates to zero in the corresponding entries. This step # involves a linearization of ``res``, resulting in a jacobian matrix and # right hand side vector that are subsequently assembled and solved. The # resulting ``lhs`` array matches ``cons`` in the constrained entries. lhs = solver.solve_linear('lhs', res, constrain=cons) # Once all entries of ``?lhs`` are establised, the corresponding solution can # be vizualised by sampling values of ``ns.u`` along with physical # coordinates ``ns.x``, with the solution vector provided via the # ``arguments`` dictionary. The sample members ``tri`` and ``hull`` provide # additional inter-point information required for drawing the mesh and # element outlines. bezier = domain.sample('bezier', 9) x, u = bezier.eval(['x', 'u'] @ ns, lhs=lhs) export.triplot('solution.png', x, u, tri=bezier.tri, hull=bezier.hull) # To confirm that our computation is correct, we use our knowledge of the # analytical solution to evaluate the L2-error of the discrete result. err = domain.integral('(u - sin(x_0) cosh(x_1))^2 J(x)' @ ns, degree=degree * 2).eval(lhs=lhs)**.5 treelog.user('L2 error: {:.2e}'.format(err)) return cons, lhs, err