def navierstokes(): domain, geom = mesh.rectilinear( [numpy.linspace(0,1,9)] * 2 ) ubasis, pbasis = function.chain([ domain.basis( 'std', degree=2 ).vector(2), domain.basis( 'std', degree=1 ), ]) dofs = function.DerivativeTarget( [len(ubasis)] ) u = ubasis.dot( dofs ) p = pbasis.dot( dofs ) viscosity = 1 inertia = model.Integral( (ubasis * u).sum(-1), domain=domain, geometry=geom, degree=5 ) stokesres = model.Integral( viscosity * ubasis['ni,j'] * (u['i,j']+u['j,i']) - ubasis['nk,k'] * p + pbasis['n'] * u['k,k'], domain=domain, geometry=geom, degree=5 ) residual = stokesres + model.Integral( ubasis['ni'] * u['i,j'] * u['j'], domain=domain, geometry=geom, degree=5 ) cons = domain.boundary['top,bottom'].project( [0,0], onto=ubasis, geometry=geom, ischeme='gauss2' ) \ | domain.boundary['left'].project( [geom[1]*(1-geom[1]),0], onto=ubasis, geometry=geom, ischeme='gauss2' ) lhs0 = model.solve_linear( dofs, residual=stokesres, constrain=cons ) for name in 'direct', 'newton', 'pseudotime': @unittest( name=name, raises=name=='direct' and model.ModelError) def res(): tol = 1e-10 if name == 'direct': lhs = model.solve_linear( dofs, residual=residual, constrain=cons ) elif name == 'newton': lhs = model.newton( dofs, residual=residual, lhs0=lhs0, freezedofs=cons.where ).solve( tol=tol, maxiter=2 ) else: lhs = model.pseudotime( dofs, residual=residual, lhs0=lhs0, freezedofs=cons.where, inertia=inertia, timestep=1 ).solve( tol=tol, maxiter=3 ) res = residual.replace( dofs, lhs ).eval() resnorm = numpy.linalg.norm( res[~cons.where] ) assert resnorm < tol
def setUp(self): super().setUp() domain, geom = mesh.rectilinear([numpy.linspace(0, 1, 9)] * 2) ubasis, pbasis = function.chain([ domain.basis('std', degree=2).vector(2), domain.basis('std', degree=1), ]) dofs = function.Argument('dofs', [len(ubasis)]) u = ubasis.dot(dofs) p = pbasis.dot(dofs) viscosity = 1e-3 self.inertia = domain.integral((ubasis * u).sum(-1) * function.J(geom), degree=5) stokesres = domain.integral( (viscosity * (ubasis.grad(geom) * (u.grad(geom) + u.grad(geom).T)).sum([-1, -2]) - ubasis.div(geom) * p + pbasis * u.div(geom)) * function.J(geom), degree=5) self.residual = stokesres + domain.integral( (ubasis * (u.grad(geom) * u).sum(-1) * u).sum(-1) * function.J(geom), degree=5) self.cons = domain.boundary['top,bottom'].project([0,0], onto=ubasis, geometry=geom, ischeme='gauss4') \ | domain.boundary['left'].project([geom[1]*(1-geom[1]),0], onto=ubasis, geometry=geom, ischeme='gauss4') self.lhs0 = solver.solve_linear('dofs', residual=stokesres, constrain=self.cons) self.tol = 1e-10
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 setUp(self): super().setUp() domain, geom = mesh.rectilinear([numpy.linspace(0,1,9)] * 2) gauss = domain.sample('gauss', 5) uin = geom[1] * (1-geom[1]) dx = function.J(geom, 2) ubasis = domain.basis('std', degree=2) pbasis = domain.basis('std', degree=1) if self.single: ubasis, pbasis = function.chain([ubasis.vector(2), pbasis]) dofs = function.Argument('dofs', [len(ubasis)]) u = ubasis.dot(dofs) p = pbasis.dot(dofs) dofs = 'dofs' ures = gauss.integral((self.viscosity * (ubasis.grad(geom) * (u.grad(geom) + u.grad(geom).T)).sum([-1,-2]) - ubasis.div(geom) * p) * dx) dres = gauss.integral((ubasis * (u.grad(geom) * u).sum(-1)).sum(-1) * dx) else: u = (ubasis[:,numpy.newaxis] * function.Argument('dofs', [len(ubasis), 2])).sum(0) p = (pbasis * function.Argument('pdofs', [len(pbasis)])).sum(0) dofs = 'dofs', 'pdofs' ures = gauss.integral((self.viscosity * (ubasis[:,numpy.newaxis].grad(geom) * (u.grad(geom) + u.grad(geom).T)).sum(-1) - ubasis.grad(geom) * p) * dx) dres = gauss.integral(ubasis[:,numpy.newaxis] * (u.grad(geom) * u).sum(-1) * dx) pres = gauss.integral((pbasis * u.div(geom)) * dx) cons = solver.optimize('dofs', domain.boundary['top,bottom'].integral((u**2).sum(), degree=4), droptol=1e-10) cons = solver.optimize('dofs', domain.boundary['left'].integral((u[0]-uin)**2 + u[1]**2, degree=4), droptol=1e-10, constrain=cons) self.cons = cons if self.single else {'dofs': cons} stokes = solver.solve_linear(dofs, residual=ures + pres if self.single else [ures, pres], constrain=self.cons) self.arguments = dict(dofs=stokes) if self.single else stokes self.residual = ures + dres + pres if self.single else [ures + dres, pres] inertia = gauss.integral(.5 * (u**2).sum(-1) * dx).derivative('dofs') self.inertia = inertia if self.single else [inertia, None] self.tol = 1e-10 self.dofs = dofs self.frozen = types.frozenarray if self.single else solver.argdict
def __init__(self, refine=1, degree=3, nel=None): if nel is None: nel = int(10 * refine) xpts = np.linspace(0, 2, 2 * nel + 1) ypts = np.linspace(0, 1, nel + 1) domain, geom = mesh.rectilinear([xpts, ypts]) NutilsCase.__init__(self, 'Channel flow', domain, geom, geom) bases = [ domain.basis('spline', degree=(degree, degree - 1)), # vx domain.basis('spline', degree=(degree - 1, degree)), # vy domain.basis('spline', degree=degree - 1), # pressure [0] * 2, # stabilization terms ] basis_lens = [len(b) for b in bases] vxbasis, vybasis, pbasis, __ = fn.chain(bases) vbasis = vxbasis[:, _] * (1, 0) + vybasis[:, _] * (0, 1) self.bases.add('v', vbasis, length=sum(basis_lens[:2])) self.bases.add('p', pbasis, length=basis_lens[2]) self.extra_dofs = 2 self.constrain('v', 'left', 'top', 'bottom') x, y = geom profile = (y * (1 - y))[_] * (1, 0) self.integrals['lift'] = Affine(1, self.project_lift(profile, 'v')) self.integrals['geometry'] = Affine(1, geom) self._exact_solutions = {'v': profile, 'p': 4 - 2 * x} self.integrals['divergence'] = AffineIntegral( -1, fn.outer(vbasis.div(geom), pbasis)) self.integrals['laplacian'] = AffineIntegral( 1, fn.outer(vbasis.grad(geom)).sum((-1, -2))) self.integrals['v-h1s'] = AffineIntegral( 1, fn.outer(vbasis.grad(geom)).sum([-1, -2])) self.integrals['p-l2'] = AffineIntegral(1, fn.outer(pbasis)) self.integrals['convection'] = AffineIntegral( 1, NutilsDelayedIntegrand('w_ia u_jb v_ka,b', 'ijk', 'wuv', x=geom, w=vbasis, u=vbasis, v=vbasis)) points = [(0, (0, 0)), (nel - 1, (0, 1))] eqn = (vbasis.laplace(geom) - pbasis.grad(geom))[:, 0, _] self.integrals['stab-lhs'] = AffineIntegral( 1, collocate(domain, eqn, points, self.ndofs - self.extra_dofs, self.ndofs))
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 navierstokes(): domain, geom = mesh.rectilinear([numpy.linspace(0, 1, 9)] * 2) ubasis, pbasis = function.chain([ domain.basis('std', degree=2).vector(2), domain.basis('std', degree=1), ]) dofs = function.DerivativeTarget([len(ubasis)]) u = ubasis.dot(dofs) p = pbasis.dot(dofs) viscosity = 1 inertia = model.Integral((ubasis * u).sum(-1), domain=domain, geometry=geom, degree=5) stokesres = model.Integral(viscosity * ubasis['ni,j'] * (u['i,j'] + u['j,i']) - ubasis['nk,k'] * p + pbasis['n'] * u['k,k'], domain=domain, geometry=geom, degree=5) residual = stokesres + model.Integral(ubasis['ni'] * u['i,j'] * u['j'], domain=domain, geometry=geom, degree=5) cons = domain.boundary['top,bottom'].project( [0,0], onto=ubasis, geometry=geom, ischeme='gauss2' ) \ | domain.boundary['left'].project( [geom[1]*(1-geom[1]),0], onto=ubasis, geometry=geom, ischeme='gauss2' ) lhs0 = model.solve_linear(dofs, residual=stokesres, constrain=cons) for name in 'direct', 'newton', 'pseudotime': @unittest(name=name, raises=name == 'direct' and model.ModelError) def res(): tol = 1e-10 if name == 'direct': lhs = model.solve_linear(dofs, residual=residual, constrain=cons) elif name == 'newton': lhs = model.newton(dofs, residual=residual, lhs0=lhs0, freezedofs=cons.where).solve(tol=tol, maxiter=2) else: lhs = model.pseudotime(dofs, residual=residual, lhs0=lhs0, freezedofs=cons.where, inertia=inertia, timestep=1).solve(tol=tol, maxiter=3) res = residual.replace(dofs, lhs).eval() resnorm = numpy.linalg.norm(res[~cons.where]) assert resnorm < tol
def setUp(self): super().setUp() domain, geom = mesh.rectilinear([numpy.linspace(0,1,9)] * 2) ubasis, pbasis = function.chain([ domain.basis('std', degree=2).vector(2), domain.basis('std', degree=1), ]) dofs = function.Argument('dofs', [len(ubasis)]) u = ubasis.dot(dofs) p = pbasis.dot(dofs) viscosity = 1 self.inertia = domain.integral((ubasis * u).sum(-1), geometry=geom, degree=5) stokesres = domain.integral(viscosity * (ubasis.grad(geom) * (u.grad(geom)+u.grad(geom).T)).sum([-1,-2]) - ubasis.div(geom) * p + pbasis * u.div(geom), geometry=geom, degree=5) self.residual = stokesres + domain.integral((ubasis * (u.grad(geom) * u).sum(-1) * u).sum(-1), geometry=geom, degree=5) self.cons = domain.boundary['top,bottom'].project([0,0], onto=ubasis, geometry=geom, ischeme='gauss2') \ | domain.boundary['left'].project([geom[1]*(1-geom[1]),0], onto=ubasis, geometry=geom, ischeme='gauss2') self.lhs0 = solver.solve_linear('dofs', residual=stokesres, constrain=self.cons) self.tol = 1e-10
def main( uinf = 2.0, L = 2.0, R = 0.5, nelems = 7 , degree = 2 , maxrefine = 3 , withplots = True ): """`main` functions with different parameters. Parameters ---------- uinf : float free stream velocity L : float domain size R : float cylinder radius nelems : int number of elements degree : int b-spline degree maxrefine : int bisectioning steps withplots : bool create plots Returns ------- lhs : float solution φ err : float L2 norm, H1 norm and energy errors """ # construct mesh verts = numpy.linspace(-L/2, L/2, nelems+1) domain, geom = mesh.rectilinear([verts, verts]) # trim out a circle domain = domain.trim(function.norm2(geom)-R, maxrefine=maxrefine) # initialize namespace ns = function.Namespace() ns.R = R ns.x = geom ns.uinf = uinf # construct function space and lagrange multiplier ns.phibasis, ns.lbasis = function.chain([domain.basis('spline', degree=degree),[1.]]) ns.phi = ' phibasis_n ?lhs_n' ns.u = 'sqrt( (phibasis_n,i ?lhs_n) (phibasis_m,i ?lhs_m) )' ns.l = 'lbasis_n ?lhs_n' # set the exact solution ns.phiexact = 'uinf x_1 ( 1 - R^2 / (x_0^2 + x_1^2) )' # average is zero ns.phierror = 'phi - phiexact' # construct residual res = domain.integral('-phibasis_n,i phi_,i' @ ns, geometry=ns.x, degree=degree*2) res += domain.boundary.integral('phibasis_n phiexact_,i n_i' @ ns, geometry=ns.x, degree=degree*2) res += domain.integral('lbasis_n phi + l phibasis_n' @ ns, geometry=ns.x, degree=degree*2) # find lhs such that res == 0 and substitute this lhs in the namespace lhs = solver.solve_linear('lhs', res) ns = ns(lhs=lhs) # evaluate error err1 = numpy.sqrt(domain.integrate(['phierror phierror' @ns,'phierror phierror + phierror_,i phierror_,i' @ns, '0.5 phi_,i phi_,i' @ns], geometry=ns.x, degree=degree*4)) err2 = abs(err1[2] - 2.7710377946088443) err = numpy.array([err1[0],err1[1],err2]) log.info('errors: L2={:.2e}, H1={:.2e}, eh={:.2e}'.format(*err)) if withplots: makeplots(domain, ns) return 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, tol: 'solver tolerance' = 1e-5, maxiter: 'maximum number if iterations, 0 for unlimited' = 0, withplots: 'create plots' = True, ): Re = density / viscosity # based on unit length and velocity log.user( 'reynolds number: {:.1f}'.format(Re) ) # construct mesh verts = numpy.linspace( 0, 1, nelems+1 ) domain, geom = mesh.rectilinear( [verts,verts] ) # construct bases vxbasis, vybasis, pbasis, 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 ]) if not warp: vbasis = function.stack( [ vxbasis, vybasis ], axis=1 ) else: gridgeom = geom xi, eta = gridgeom geom = (eta+2) * function.rotmat(xi*.4)[:,1] - (0,2) # slight downward bend J = geom.grad( gridgeom ) detJ = function.determinant( J ) vbasis = ( vxbasis[:,_] * J[:,0] + vybasis[:,_] * J[:,1] ) / detJ # piola transform pbasis /= detJ stressbasis = (2*viscosity) * vbasis.symgrad(geom) - (pbasis)[:,_,_] * function.eye( domain.ndims ) # construct matrices A = function.outer( vbasis.grad(geom), stressbasis ).sum([2,3]) \ + function.outer( pbasis, vbasis.div(geom)+lbasis ) \ + function.outer( lbasis, pbasis ) Ad = function.outer( vbasis.div(geom) ) stokesmat, divmat = domain.integrate( [ A, Ad ], geometry=geom, ischeme='gauss9' ) # define boundary conditions normal = geom.normal() utop = function.asarray([ normal[1], -normal[0] ]) h = domain.boundary.elem_eval( 1, geometry=geom, ischeme='gauss9', asfunction=True ) nietzsche = (2*viscosity) * ( ((degree+1)*2.5/h) * vbasis - vbasis.symgrad(geom).dotnorm(geom) ) stokesmat += domain.boundary.integrate( function.outer( nietzsche, vbasis ).sum(-1), geometry=geom, ischeme='gauss9' ) rhs = domain.boundary['top'].integrate( ( nietzsche * utop ).sum(-1), geometry=geom, ischeme='gauss9' ) # prepare plotting makeplots = MakePlots( domain, geom ) if withplots else lambda *args: None # start picard iterations lhs = stokesmat.solve( rhs, tol=tol, solver='cg', precon='spilu' ) for iiter in log.count( 'picard' ): log.info( 'velocity divergence:', divmat.matvec(lhs).dot(lhs) ) makeplots( vbasis.dot(lhs), pbasis.dot(lhs) ) ugradu = ( vbasis.grad(geom) * vbasis.dot(lhs) ).sum(-1) convection = density * function.outer( vbasis, ugradu ).sum(-1) matrix = stokesmat + domain.integrate( convection, ischeme='gauss9', geometry=geom ) lhs, info = matrix.solve( rhs, lhs0=lhs, tol=tol, info=True, precon='spilu', restart=999 ) if iiter == maxiter-1 or info.niter == 0: break return rhs, lhs
def __init__(self, refine=1, degree=3, nel=None, power=3): if nel is None: nel = int(10 * refine) pts = np.linspace(0, 1, nel + 1) domain, geom = mesh.rectilinear([pts, pts]) x, y = geom NutilsCase.__init__(self, 'Exact divergence-conforming flow', domain, geom, geom) w = self.parameters.add('w', 1, 2) h = self.parameters.add('h', 1, 2) bases = [ domain.basis('spline', degree=(degree, degree-1)), # vx domain.basis('spline', degree=(degree-1, degree)), # vy domain.basis('spline', degree=degree-1), # pressure [1], # lagrange multiplier [0] * 4, # stabilization terms ] basis_lens = [len(b) for b in bases] vxbasis, vybasis, pbasis, lbasis, __ = fn.chain(bases) vbasis = vxbasis[:,_] * (1,0) + vybasis[:,_] * (0,1) self.bases.add('v', vbasis, length=sum(basis_lens[:2])) self.bases.add('p', pbasis, length=basis_lens[2]) self.extra_dofs = 5 self.integrals['geometry'] = Affine( 1, geom, w-1, fn.asarray((x,0)), h-1, fn.asarray((0,y)), ) self.constrain('v', 'left', 'top', 'bottom', 'right') r = power self.power = power # Exact solution f = x**r g = y**r f1 = r * x**(r-1) g1 = r * y**(r-1) g2 = r*(r-1) * y**(r-2) f3 = r*(r-1)*(r-2) * x**(r-3) g3 = r*(r-1)*(r-2) * y**(r-3) self._exact_solutions = {'v': fn.asarray((f*g1, -f1*g)), 'p': f1*g1 - 1} # Awkward way of computing a solenoidal lift mdom, t = mesh.rectilinear([pts]) hbasis = mdom.basis('spline', degree=degree) hcoeffs = mdom.project(t[0]**r, onto=hbasis, geometry=t, ischeme='gauss9') projtderiv = hbasis.dot(hcoeffs).grad(t)[0] zbasis = mdom.basis('spline', degree=degree-1) zcoeffs = mdom.project(projtderiv, onto=zbasis, geometry=t, ischeme='gauss9') q = np.hstack([ np.outer(hcoeffs, zcoeffs).flatten(), - np.outer(zcoeffs, hcoeffs).flatten(), np.zeros((sum(basis_lens) - len(hcoeffs) * len(zcoeffs) * 2)) ]) self.integrals['lift'] = Affine(w**(r-1) * h**(r-1), q) self.integrals['forcing'] = AffineIntegral( w**(r-2) * h**(r+2), vybasis * (f3 * g)[_], w**r * h**r, 2*vybasis * (f1*g2)[_], w**(r+2) * h**(r-2), -vxbasis * (f*g3)[_], ) vx_x = vxbasis.grad(geom)[:,0] vx_xx = vx_x.grad(geom)[:,0] vx_y = vxbasis.grad(geom)[:,1] vx_yy = vx_y.grad(geom)[:,1] vy_x = vybasis.grad(geom)[:,0] vy_y = vybasis.grad(geom)[:,1] p_x = pbasis.grad(geom)[:,0] self.integrals['laplacian'] = AffineIntegral( h * w, fn.outer(vx_x, vx_x), h**3 / w, fn.outer(vy_x, vy_x), w**3 / h, fn.outer(vx_y, vx_y), w * h, fn.outer(vy_y, vy_y), ) self.integrals['divergence'] = AffineIntegral( h * w, (fn.outer(vx_x, pbasis) + fn.outer(vy_y, pbasis)) ) self['v-h1s'] = AffineIntegral(self.integrals['laplacian']) self['p-l2'] = AffineIntegral(h * w, fn.outer(pbasis, pbasis)) root = self.ndofs - self.extra_dofs points = [(0, (0, 0)), (nel-1, (0, 1)), (nel*(nel-1), (1, 0)), (nel**2-1, (1, 1))] ca, cb, cc = [ collocate(domain, eqn[:,_], points, root+1, self.ndofs) for eqn in [p_x, -vx_xx, -vx_yy] ] self.integrals['stab-lhs'] = AffineIntegral( 1/w, ca, 1/w, cb, w/h**2, cc, 1, fn.outer(lbasis, pbasis), w**3 * h**(r-3), collocate(domain, -f*g3[_], points, root+1, self.ndofs), ) self.integrals['v-trf'] = Affine( w, fn.asarray([[1,0], [0,0]]), h, fn.asarray([[0,0], [0,1]]), )
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
rmul[0] = 3 rmul[-1] = 3 pmul = [2] * len(pkts) thbasis = case.domain.basis( 'spline', degree=(2, 2), knotvalues=[rkts, pkts], knotmultiplicities=[rmul, pmul], ) bases = [ thbasis[:, _] * (1, 0), thbasis[:, _] * (0, 1), case.domain.basis('spline', degree=1) ] vnbasis, vtbasis, pbasis = fn.chain(bases) vbasis = vnbasis + vtbasis case.bases.add('v', vbasis, length=len(bases[0]) + len(bases[1])) case.bases.add('p', pbasis, length=len(bases[2])) return vbasis, pbasis, len(pbasis) def mk_lift(case): mu = case.parameter() vbasis = case.basis('v', mu) geom = case['geometry'](mu) x, __ = geom cons = case.domain.boundary['left'].project((0, 0),
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, figures: 'create figures' = 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 figures 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
def __init__(self, refine=1, degree=3, nel_up=None, nel_length=None, nel_up_mid=None, nel_length_out=None, stabilize=True, override=True): if nel_up is None: nel_up = int(50 * refine) if nel_length is None: nel_length = int(50 * refine) if nel_up_mid is None: nel_up_mid = nel_up // 5 if nel_length_out is None: nel_length_out = 2 * nel_length // 5 domain, geom = mesh.multipatch( patches=[[[0,1],[4,5]], [[1,2],[5,6]], [[2,3],[6,7]], [[5,6],[8,9]]], nelems={ (0,1): nel_up, (4,5): nel_up, (2,3): nel_up, (6,7): nel_up, (1,2): nel_up_mid, (5,6): nel_up_mid, (8,9): nel_up_mid, (0,4): nel_length, (1,5): nel_length, (2,6): nel_length, (3,7): nel_length, (5,8): nel_length_out, (6,9): nel_length_out, }, patchverts=[ [-5,0], [-5,1], [-5,2], [-5,3], [0,0], [0,1], [0,2], [0,3], [2,1], [2,2], ] ) NutilsCase.__init__(self, 'T-shape channel', domain, geom, geom) NU = 1 / self.parameters.add('viscosity', 20, 50) H = self.parameters.add('height', 1, 5) V = self.parameters.add('velocity', 1, 5) bases = [ domain.basis('spline', degree=(degree, degree-1)), # vx domain.basis('spline', degree=(degree-1, degree)), # vy domain.basis('spline', degree=degree-1) # pressure ] if stabilize: bases.append([0] * 4) basis_lens = [len(b) for b in bases] vxbasis, vybasis, pbasis, *__ = fn.chain(bases) vbasis = vxbasis[:,_] * (1,0) + vybasis[:,_] * (0,1) self.bases.add('v', vbasis, length=sum(basis_lens[:2])) self.bases.add('p', pbasis, length=basis_lens[2]) self.extra_dofs = 4 if stabilize else 0 x, y = geom hy = fn.piecewise(y, (1,2), y-1, 0, y-2) self.integrals['geometry'] = Affine(1, geom, H - 1, fn.asarray((0, hy))) self.constrain( 'v', 'patch0-bottom', 'patch0-left', 'patch0-right', 'patch1-left', 'patch2-left', 'patch2-top', 'patch2-right', 'patch3-bottom', 'patch3-top', ) vgrad = vbasis.grad(geom) # Lifting function profile = fn.max(0, y/3 * (1-y/3) * (1-x))[_] * (1, 0) self.integrals['lift'] = Affine(V, self.project_lift(profile, 'v')) # Characteristic functions cp0, cp1, cp2, cp3 = [characteristic(domain, (i,)) for i in range(4)] cp02 = cp0 + cp2 cp13 = cp1 + cp3 # Stokes divergence term self.integrals['divergence'] = AffineIntegral( -(H-1), fn.outer(vgrad[:,0,0], pbasis) * cp02, -1, fn.outer(vbasis.div(geom), pbasis), ) # Stokes laplacian term self.integrals['laplacian'] = AffineIntegral( NU, fn.outer(vgrad).sum([-1,-2]) * cp13, NU*H, fn.outer(vgrad[:,:,0]).sum(-1) * cp02, NU/H, fn.outer(vgrad[:,:,1]).sum(-1) * cp02, ) # Navier-Stokes convective term args = ('ijk', 'wuv') kwargs = {'x': geom, 'w': vbasis, 'u': vbasis, 'v': vbasis} self.integrals['convection'] = AffineIntegral( H, NutilsDelayedIntegrand('c w_ia u_j0 v_ka,0', *args, **kwargs, c=cp02), 1, NutilsDelayedIntegrand('c w_ia u_j1 v_ka,1', *args, **kwargs, c=cp02), 1, NutilsDelayedIntegrand('c w_ia u_jb v_ka,b', *args, **kwargs, c=cp13), ) # Norms self.integrals['v-h1s'] = self.integrals['laplacian'] / NU self.integrals['v-l2'] = AffineIntegral( H, fn.outer(vbasis).sum(-1) * cp02, 1, fn.outer(vbasis).sum(-1) * cp13, ) self.integrals['p-l2'] = AffineIntegral( H, fn.outer(pbasis) * cp02, 1, fn.outer(pbasis) * cp13, ) if not stabilize: self.verify() return root = self.ndofs - self.extra_dofs points = [ (0, (0, 0)), (nel_up*(nel_length-1), (1, 0)), (nel_up*nel_length + nel_up_mid*nel_length + nel_up - 1, (0, 1)), (nel_up*nel_length*2 + nel_up_mid*nel_length - 1, (1, 1)) ] terms = [] eqn = vbasis[:,0].grad(geom).grad(geom) colloc = collocate(domain, eqn[:,0,0,_], points, root, self.ndofs) terms.extend([NU, (colloc + colloc.T)]) colloc = collocate(domain, eqn[:,1,1,_], points, root, self.ndofs) terms.extend([NU/H**2, (colloc + colloc.T)]) eqn = -pbasis.grad(geom)[:,0,_] colloc = collocate(domain, eqn, points, root, self.ndofs) terms.extend([1, colloc + colloc.T]) self.integrals['stab-lhs'] = AffineIntegral(*terms) self.verify()
def __init__(self, nel=10, L=15, penalty=1e10, ratio=1000, override=False, finalize=True): L /= 5 hnel = int(L * 5 * nel // 2) xpts = np.linspace(0, L, 2 * hnel + 1) yzpts = np.linspace(0, 0.2, nel + 1) domain, geom = mesh.rectilinear([xpts, yzpts]) dom1 = domain[:hnel, :] dom2 = domain[hnel:, :] NutilsCase.__init__(self, 'Elastic split beam', domain, geom) E1 = self.parameters.add('ymod1', 1e10, 9e10) E2 = self.parameters.add('ymod2', 1e10 / ratio, 9e10 / ratio) NU = self.parameters.add('prat', 0.25, 0.42) F1 = self.parameters.add('force1', -1e8, 1e8) mults = [[2] + [1] * (hnel - 1) + [2] + [1] * (hnel - 1) + [2], [2] + [1] * (nel - 1) + [2]] basis = domain.basis('spline', degree=1, knotmultiplicities=mults).vector(2) blen = len(basis) basis, *__ = fn.chain([basis, [0] * (2 * (nel + 1))]) self.bases.add('u', basis, length=blen) self.extra_dofs = (nel + 1) * 2 self.lift += 1, np.zeros((len(basis), )) self.constrain('u', 'left') self.constrain('u', 'right') MU1 = E1 / (1 + NU) MU2 = E2 / (1 + NU) LAMBDA1 = E1 * NU / (1 + NU) / (1 - 2 * NU) LAMBDA2 = E2 * NU / (1 + NU) / (1 - 2 * NU) itg_mu = fn.outer(basis.symgrad(geom)).sum([-1, -2]) itg_la = fn.outer(basis.div(geom)) self['stiffness'] += MU1, NutilsArrayIntegrand(itg_mu).prop( domain=dom1) self['stiffness'] += MU2, NutilsArrayIntegrand(itg_mu).prop( domain=dom2) self['stiffness'] += LAMBDA1, NutilsArrayIntegrand(itg_la).prop( domain=dom1) self['stiffness'] += LAMBDA2, NutilsArrayIntegrand(itg_la).prop( domain=dom2) # Penalty term ... quite hacky K = blen // 2 ldofs = list(range(hnel * (nel + 1), (hnel + 1) * (nel + 1))) rdofs = list(range((hnel + 1) * (nel + 1), (hnel + 2) * (nel + 1))) ldofs += [k + K for k in ldofs] rdofs += [k + K for k in rdofs] L = len(ldofs) Linds = list(range(self.ndofs - L, self.ndofs)) pen = sp.coo_matrix(((np.ones(L), (Linds, ldofs))), shape=(self.ndofs, self.ndofs)) pen += sp.coo_matrix(((-np.ones(L), (Linds, rdofs))), shape=(self.ndofs, self.ndofs)) self['penalty'] += 1, ScipyArrayIntegrand(sp.csc_matrix(pen + pen.T)) # Even more hacky for the ROM part bnd_lft = domain[:hnel, :].boundary['right'] bnd_rgt = domain[hnel:, :].boundary['left'] pts, wts = bnd_lft.elements[0].reference.getischeme('gauss5') penalty_mx = np.zeros((self.ndofs, self.ndofs)) for el_lft, el_rgt in zip(bnd_lft.elements, bnd_rgt.elements): udiff = ( basis.eval(_points=pts, _transforms=(el_lft.transform, el_lft.opposite)) - basis.eval(_points=pts, _transforms=(el_rgt.transform, el_rgt.opposite))) penalty_mx += (udiff[:, :, _, :] * udiff[:, _, :, :] * wts[:, _, _, _]).sum((0, -1)) self['penalty-rom'] += 1e20, sp.csc_matrix(penalty_mx) normdot = fn.matmat(basis, geom.normal()) force_bnd = domain[hnel - 11:hnel - 1, :].boundary['top'] self['forcing'] += F1, NutilsArrayIntegrand(normdot).prop( domain=force_bnd) self['u-h1s'] = self['stiffness'] self['u-h1s-l'] = self['stiffness'] self['u-h1s-r'] = self['stiffness'] self['u-h1s-e'] = self['stiffness'] self.verify()
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 __init__(self, refine=1, degree=4, nel=None): if nel is None: nel = int(10 * refine) pts = np.linspace(0, 1, nel + 1) domain, geom = mesh.rectilinear([pts, pts]) NutilsCase.__init__(self, 'Cavity flow', domain, geom, geom) bases = [ domain.basis('spline', degree=(degree, degree - 1)), # vx domain.basis('spline', degree=(degree - 1, degree)), # vy domain.basis('spline', degree=degree - 1), # pressure [1], # lagrange multiplier [0] * 4, # stabilization terms ] basis_lens = [len(b) for b in bases] vxbasis, vybasis, pbasis, lbasis, __ = fn.chain(bases) vbasis = vxbasis[:, _] * (1, 0) + vybasis[:, _] * (0, 1) self.bases.add('v', vbasis, length=sum(basis_lens[:2])) self.bases.add('p', pbasis, length=basis_lens[2]) self.extra_dofs = 5 self.constrain('v', 'left', 'top', 'bottom', 'right') self.integrals['lift'] = Affine(1, np.zeros(vbasis.shape[0])) self.integrals['geometry'] = Affine(1, geom) x, y = geom f = 4 * (x - x**2)**2 g = 4 * (y - y**2)**2 d1f = f.grad(geom)[0] d1g = g.grad(geom)[1] velocity = fn.asarray((f * d1g, -d1f * g)) pressure = d1f * d1g total = domain.integrate(pressure * fn.J(geom), ischeme='gauss9') pressure -= total / domain.volume(geometry=geom) force = pressure.grad(geom) - velocity.laplace(geom) self._exact_solutions = {'v': velocity, 'p': pressure} self.integrals['forcing'] = AffineIntegral(1, (vbasis * force[_, :]).sum(-1)) self.integrals['divergence'] = AffineIntegral( -1, fn.outer(vbasis.div(geom), pbasis)) self.integrals['laplacian'] = AffineIntegral( 1, fn.outer(vbasis.grad(geom)).sum((-1, -2))) self.integrals['v-h1s'] = AffineIntegral( 1, fn.outer(vbasis.grad(geom)).sum([-1, -2])) self.integrals['p-l2'] = AffineIntegral(1, fn.outer(pbasis)) root = self.ndofs - self.extra_dofs points = [(0, (0, 0)), (nel - 1, (0, 1)), (nel * (nel - 1), (1, 0)), (nel**2 - 1, (1, 1))] eqn = (pbasis.grad(geom) - vbasis.laplace(geom))[:, 0, _] colloc = collocate(domain, eqn, points, root + 1, self.ndofs) self.integrals['stab-lhs'] = AffineIntegral(1, colloc, 1, fn.outer(lbasis, pbasis)) self.integrals['stab-rhs'] = AffineIntegral( 1, collocate(domain, force[0, _], points, root + 1, self.ndofs))
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( 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: '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, withplots: 'create plots' = True, ): uinf = numpy.array([ 1, 0 ]) log.user( 'reynolds number:', density/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, gridgeom = mesh.rectilinear( [range(melems+1),numpy.linspace(0,2*numpy.pi,2*nelems+1)], periodic=(1,) ) rho, phi = gridgeom phi += 1e-3 # tiny nudge (0.057 deg) to break element symmetry cylvelo = -.5 * rotation * function.trignormal(phi) radius = .5 * function.exp( rscale * rho ) geom = radius * function.trigtangent(phi) # prepare bases J = geom.grad( gridgeom ) detJ = function.determinant( J ) vnbasis, vtbasis, pbasis = function.chain([ # compatible spaces using piola transformation domain.basis( 'spline', degree=(3,2), removedofs=((0,),None) )[:,_] * J[:,0] / detJ, domain.basis( 'spline', degree=(2,3) )[:,_] * J[:,1] / detJ, domain.basis( 'spline', degree=2 ) / detJ, ]) vbasis = vnbasis + vtbasis stressbasis = (2*viscosity) * vbasis.symgrad(geom) - pbasis[:,_,_] * function.eye( domain.ndims ) # prepare matrices A = function.outer( vbasis.grad(geom), stressbasis ).sum([2,3]) + function.outer( pbasis, vbasis.div(geom) ) Ao = function.outer( vbasis, density * ( vbasis.grad(geom) * uinf ).sum(-1) ).sum(-1) At = (density/timestep) * function.outer( vbasis ).sum(-1) Ad = function.outer( vbasis.div(geom) ) stokesmat, uniconvec, inertmat, divmat = domain.integrate( [ A, Ao, At, Ad ], geometry=geom, ischeme='gauss9' ) # interior boundary condition (weak imposition of shear verlocity component) inner = domain.boundary['left'] h = inner.elem_eval( 1, geometry=geom, ischeme='gauss9', asfunction=True ) nietzsche = (2*viscosity) * ( (7.5/h) * vbasis - vbasis.symgrad(geom).dotnorm(geom) ) B = function.outer( nietzsche, vbasis ).sum(-1) b = ( nietzsche * cylvelo ).sum(-1) bcondmat, rhs = inner.integrate( [ B, b ], geometry=geom, ischeme='gauss9' ) stokesmat += bcondmat # exterior boundary condition (full velocity vector imposed at inflow) inflow = domain.boundary['right'].select( -( uinf * geom.normal() ).sum(-1), ischeme='gauss1' ) cons = inflow.project( uinf, onto=vbasis, geometry=geom, ischeme='gauss9', tol=1e-12 ) # initial condition (stationary oseen flow) lhs = (stokesmat+uniconvec).solve( rhs, constrain=cons, tol=0 ) # prepare plotting if not withplots: makeplots = lambda *args: None else: makeplots = MakePlots( domain, geom, video=withplots=='video', timestep=timestep ) mesh_ = domain.elem_eval( geom, ischeme='bezier5', separate=True ) inflow_ = inflow.elem_eval( geom, ischeme='bezier5', separate=True ) with plot.PyPlot( 'mesh', ndigits=0 ) as plt: plt.mesh( mesh_ ) plt.rectangle( makeplots.bbox[:,0], *( makeplots.bbox[:,1] - makeplots.bbox[:,0] ), ec='green' ) plt.segments( inflow_, colors='red' ) # start time stepping stokesmat += inertmat precon = (stokesmat+uniconvec).getprecon( 'splu', constrain=cons ) for istep in log.count( 'timestep', start=1 ): log.info( 'velocity divergence:', divmat.matvec(lhs).dot(lhs) ) convection = density * ( vbasis.grad(geom) * vbasis.dot(lhs) ).sum(-1) matrix = stokesmat + domain.integrate( function.outer( vbasis, convection ).sum(-1), ischeme='gauss9', geometry=geom ) lhs = matrix.solve( rhs + inertmat.matvec(lhs), lhs0=lhs, constrain=cons, tol=tol, restart=9999, precon=precon ) makeplots( vbasis.dot(lhs), pbasis.dot(lhs), istep*timestep*rotation ) if istep*timestep > tmax: break return lhs
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', withplots: 'create plots' = True, ): mineps = 1. / nelems if not epsilon: log.info('setting epsilon=%f' % mineps) epsilon = mineps elif epsilon < mineps: log.warning('epsilon under crititical threshold: %f < %f' % (epsilon, mineps)) ewall = .5 * numpy.cos(theta * numpy.pi / 180) # construct mesh xnodes = ynodes = numpy.linspace(0, 1, nelems + 1) domain, geom = mesh.rectilinear([xnodes, ynodes]) # prepare bases cbasis, mubasis = function.chain( [domain.basis('spline', degree=2), domain.basis('spline', degree=2)]) # define mixing energy and splitting: F' = f_p - f_n F = lambda c_: (.5 / epsilon**2) * (c_**2 - 1)**2 f_p = lambda c_: (1. / epsilon**2) * 4 * c_ f_n = lambda c_: (1. / epsilon**2) * (6 * c_ - 2 * c_**3) # prepare matrix A = function.outer( cbasis ) \ + (timestep*epsilon**2) * function.outer( cbasis.grad(geom), mubasis.grad(geom) ).sum(-1) \ + function.outer( mubasis, mubasis - f_p(cbasis) ) \ - function.outer( mubasis.grad(geom), cbasis.grad(geom) ).sum(-1) matrix = domain.integrate(A, geometry=geom, ischeme='gauss4') # prepare wall energy right hand side rhs0 = domain.boundary.integrate(mubasis * ewall, geometry=geom, ischeme='gauss4') # construct initial condition if init == 'random': numpy.random.seed(0) c = cbasis.dot(numpy.random.normal(0, .5, cbasis.shape)) elif init == 'bubbles': R1 = .25 R2 = numpy.sqrt(.5) * R1 # area2 = .5 * area1 c = 1 + function.tanh( (R1-function.norm2(geom-(.5+R2/numpy.sqrt(2)+.8*epsilon)))/epsilon ) \ + function.tanh( (R2-function.norm2(geom-(.5-R1/numpy.sqrt(2)-.8*epsilon)))/epsilon ) else: raise Exception('unknown init %r' % init) # prepare plotting nsteps = numeric.round(maxtime / timestep) makeplots = MakePlots(domain, geom, nsteps, video=withplots == 'video') if withplots else lambda *args: None # start time stepping for istep in log.range('timestep', nsteps): Emix = F(c) Eiface = .5 * (c.grad(geom)**2).sum(-1) Ewall = (abs(ewall) + ewall * c) b = cbasis * c - mubasis * f_n(c) rhs, total, energy_mix, energy_iface = domain.integrate( [b, c, Emix, Eiface], geometry=geom, ischeme='gauss4') energy_wall = domain.boundary.integrate(Ewall, geometry=geom, ischeme='gauss4') log.user('concentration {}, energy {}'.format( total, energy_mix + energy_iface + energy_wall)) lhs = matrix.solve(rhs0 + rhs, tol=1e-12, restart=999) c = cbasis.dot(lhs) mu = mubasis.dot(lhs) makeplots(c, mu, energy_mix, energy_iface, energy_wall) return lhs, energy_mix, energy_iface, energy_wall
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 get_case(refine: int, degree: int): nel_up = int(10 * refine) nel_length = int(100 * refine) up_edges = [(0, 1), (3, 4), (6, 7), (0, 3), (1, 4), (2, 3), (5, 6)] length_edges = [(2, 5), (3, 6), (4, 7)] all_edges = [*up_edges, *length_edges] domain, refgeom = mesh.multipatch( patches=[[(0, 1), (3, 4)], [(3, 4), (6, 7)], [(2, 3), (5, 6)]], nelems={ **{e: nel_up for e in up_edges}, **{e: nel_length for e in length_edges} }, patchverts=[(-1, 0), (-1, 1), (0, -1), (0, 0), (0, 1), (1, -1), (1, 0), (1, 1)]) case = NutilsCase('Backward-facing step channel', domain, refgeom, refgeom) NU = 1 / case.parameters.add('viscosity', 20, 50) L = case.parameters.add('length', 9, 12, 10) H = case.parameters.add('height', 0.3, 2, 1) V = case.parameters.add('velocity', 0.5, 1.2, 1) vxbasis = domain.basis('spline', degree=degree) vybasis = domain.basis('spline', degree=degree) pbasis = domain.basis('spline', degree=degree - 1) vdofs = len(vxbasis) + len(vybasis) pdofs = len(pbasis) ndofs = vdofs + pdofs vxbasis, vybasis, pbasis = fn.chain([vxbasis, vybasis, pbasis]) vbasis = vxbasis[:, _] * (1, 0) + vybasis[:, _] * (0, 1) case.bases.add('v', vbasis, length=vdofs) case.bases.add('p', pbasis, length=pdofs) case['geometry'] = MuLambda( partial(geometry, L=L, H=H, refgeom=refgeom), (2, ), ('length', 'height'), ) case.constrain( 'v', 'patch0-bottom', 'patch0-top', 'patch0-left', 'patch1-top', 'patch2-bottom', 'patch2-left', ) case['divergence'] = ntl.NSDivergence(ndofs, 'length', 'height') case['convection'] = ntl.NSConvection(ndofs, 'length', 'height') case['laplacian'] = ntl.Laplacian(ndofs, 'v', 'length', 'height', scale=NU) case['v-h1s'] = ntl.Laplacian(ndofs, 'v', 'length', 'height') case['p-l2'] = ntl.Mass(ndofs, 'p', 'length', 'height') with matrix.Scipy(): __, y = refgeom profile = fn.max(0, y * (1 - y))[_] * (1, 0) case['lift'] = MuConstant(case.project_lift(profile, 'v'), scale=V) mu = case.parameter() lhs = solvers.stokes(case, mu) case['lift'] = MuConstant(case.solution_vector(lhs, mu, lift=True), scale=V) return case
def __init__(self, refine=1, degree=3, nel_up=None, nel_length=None, stabilize=True): if nel_up is None: nel_up = int(10 * refine) if nel_length is None: nel_length = int(100 * refine) domain, geom = mesh.multipatch( patches=[[[0, 1], [3, 4]], [[3, 4], [6, 7]], [[2, 3], [5, 6]]], nelems={ (0, 1): nel_up, (3, 4): nel_up, (6, 7): nel_up, (2, 5): nel_length, (3, 6): nel_length, (4, 7): nel_length, (0, 3): nel_up, (1, 4): nel_up, (2, 3): nel_up, (5, 6): nel_up, }, patchverts=[[-1, 0], [-1, 1], [0, -1], [0, 0], [0, 1], [1, -1], [1, 0], [1, 1]], ) NutilsCase.__init__(self, 'Backward-facing step channel', domain, geom, geom) NU = 1 / self.parameters.add('viscosity', 20, 50) L = self.parameters.add('length', 9, 12, 10) H = self.parameters.add('height', 0.3, 2, 1) V = self.parameters.add('velocity', 0.5, 1.2, 1) # Bases bases = [ domain.basis('spline', degree=(degree, degree - 1)), # vx domain.basis('spline', degree=(degree - 1, degree)), # vy domain.basis('spline', degree=degree - 1), # pressure ] if stabilize: bases.append([0] * 4) basis_lens = [len(b) for b in bases] vxbasis, vybasis, pbasis, *__ = fn.chain(bases) vbasis = vxbasis[:, _] * (1, 0) + vybasis[:, _] * (0, 1) self.bases.add('v', vbasis, length=sum(basis_lens[:2])) self.bases.add('p', pbasis, length=basis_lens[2]) self.extra_dofs = 4 if stabilize else 0 x, y = geom hx = fn.piecewise(x, (0, ), 0, x) hy = fn.piecewise(y, (0, ), y, 0) self.integrals['geometry'] = Affine( 1.0, geom, (L - 1), fn.asarray((hx, 0)), (H - 1), fn.asarray((0, hy)), ) self.constrain('v', 'patch0-bottom', 'patch0-top', 'patch0-left', 'patch1-top', 'patch2-bottom', 'patch2-left') vgrad = vbasis.grad(geom) # Lifting function profile = fn.max(0, y * (1 - y) * 4)[_] * (1, 0) self.integrals['lift'] = Affine(V, self.project_lift(profile, 'v')) # Characteristic functions cp0, cp1, cp2 = [characteristic(domain, (i, )) for i in range(3)] cp12 = cp1 + cp2 # Stokes divergence term self.integrals['divergence'] = AffineIntegral( -(H - 1), fn.outer(vgrad[:, 0, 0], pbasis) * cp2, -(L - 1), fn.outer(vgrad[:, 1, 1], pbasis) * cp12, -1, fn.outer(vbasis.div(geom), pbasis), ) # Stokes laplacian term self.integrals['laplacian'] = AffineIntegral( NU, fn.outer(vgrad).sum([-1, -2]) * cp0, NU / L, fn.outer(vgrad[:, :, 0]).sum(-1) * cp1, NU * L, fn.outer(vgrad[:, :, 1]).sum(-1) * cp1, NU * H / L, fn.outer(vgrad[:, :, 0]).sum(-1) * cp2, NU * L / H, fn.outer(vgrad[:, :, 1]).sum(-1) * cp2, ) # Navier-stokes convective term args = ('ijk', 'wuv') kwargs = {'x': geom, 'w': vbasis, 'u': vbasis, 'v': vbasis} self.integrals['convection'] = AffineIntegral( H, NutilsDelayedIntegrand('c w_ia u_j0 v_ka,0', *args, **kwargs, c=cp2), L, NutilsDelayedIntegrand('c w_ia u_j1 v_ka,1', *args, **kwargs, c=cp12), 1, NutilsDelayedIntegrand('c w_ia u_jb v_ka,b', *args, **kwargs, c=cp0), 1, NutilsDelayedIntegrand('c w_ia u_j0 v_ka,0', *args, **kwargs, c=cp1), ) # Norms self.integrals['v-h1s'] = self.integrals['laplacian'] / NU self.integrals['v-l2'] = AffineIntegral( 1, fn.outer(vbasis).sum(-1) * cp0, L, fn.outer(vbasis).sum(-1) * cp1, L * H, fn.outer(vbasis).sum(-1) * cp2, ) self.integrals['p-l2'] = AffineIntegral( 1, fn.outer(pbasis) * cp0, L, fn.outer(pbasis) * cp1, L * H, fn.outer(pbasis) * cp2, ) if not stabilize: self.verify return root = self.ndofs - self.extra_dofs terms = [] points = [(0, (0, 0)), (nel_up - 1, (0, 1))] eqn = vbasis.laplace(geom)[:, 0, _] colloc = collocate(domain, eqn, points, root, self.ndofs) terms.extend([NU, (colloc + colloc.T)]) eqn = -pbasis.grad(geom)[:, 0, _] colloc = collocate(domain, eqn, points, root, self.ndofs) terms.extend([1, colloc + colloc.T]) points = [(nel_up**2 + nel_up * nel_length, (0, 0))] eqn = vbasis[:, 0].grad(geom).grad(geom) colloc = collocate(domain, eqn[:, 0, 0, _], points, root + 2, self.ndofs) terms.extend([NU / L**2, colloc.T]) colloc = collocate(domain, eqn[:, 1, 1, _], points, root + 2, self.ndofs) terms.extend([NU / H**2, colloc]) eqn = -pbasis.grad(geom)[:, 0, _] colloc = collocate(domain, -pbasis.grad(geom)[:, 0, _], points, root + 2, self.ndofs) terms.extend([1 / L, colloc]) points = [(nel_up * (nel_up - 1), (1, 0))] colloc = collocate(domain, vbasis.laplace(geom)[:, 0, _], points, root + 3, self.ndofs) terms.extend([NU, colloc]) colloc = collocate(domain, -pbasis.grad(geom)[:, 0, _], points, root + 3, self.ndofs) terms.extend([1, colloc]) self.integrals['stab-lhs'] = AffineIntegral(*terms) self.verify()
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', withplots: 'create plots' = 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 withplots 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