def test_bases(mu, case): domain, vbasis, pbasis = case.domain, case.basis('v'), case.basis('p') trfgeom = case.geometry(mu) refgeom = case.refgeom a_vbasis, a_pbasis = piola_bases(mu, case) J = trfgeom.grad(case.geometry()) detJ = fn.determinant(J) b_vbasis = fn.matmat(vbasis, J.transpose()) / detJ b_pbasis = pbasis / detJ Z = case.geometry(mu).grad(case.geometry()) detZ = fn.determinant(Z) zdiff = np.sqrt(domain.integrate((Z - J)**2 * fn.J(refgeom), ischeme='gauss9').export('dense')) np.testing.assert_almost_equal(zdiff, 0.0) c_vbasis = fn.matmat(vbasis, Z.transpose()) / detZ c_pbasis = pbasis pdiff = np.sqrt(domain.integrate((a_pbasis - b_pbasis)**2 * fn.J(refgeom), ischeme='gauss9')) np.testing.assert_almost_equal(pdiff, 0.0) pdiff = domain.integrate((a_pbasis - c_pbasis)**2 * fn.J(refgeom), ischeme='gauss9') np.testing.assert_almost_equal(pdiff, 0.0) vdiff = np.sqrt(domain.integrate((a_vbasis - b_vbasis)**2 * fn.J(refgeom), ischeme='gauss9').export('dense')) np.testing.assert_almost_equal(vdiff, 0.0) vdiff = domain.integrate((a_vbasis - c_vbasis)**2 * fn.J(refgeom), ischeme='gauss9').export('dense') np.testing.assert_almost_equal(vdiff, 0.0)
def test_nonlinear_diagonalshift(self): nelems = 10 domain, geom = mesh.rectilinear([nelems, 1]) geom *= [2 * numpy.pi / nelems, 1] ubasis = domain.basis('spline', degree=2).vector(2) u = ubasis.dot(function.Argument('dofs', [len(ubasis)])) Geom = [.5 * geom[0], geom[1] + function.cos(geom[0]) ] + u # compress by 50% and buckle cons = solver.minimize('dofs', domain.boundary['left,right'].integral( (u**2).sum(0), degree=4), droptol=1e-15).solve() strain = .5 * (function.outer(Geom.grad(geom), axis=1).sum(0) - function.eye(2)) energy = domain.integral( ((strain**2).sum([0, 1]) + 150 * (function.determinant(Geom.grad(geom)) - 1)**2) * function.J(geom), degree=6) nshift = 0 for iiter, (lhs, info) in enumerate( solver.minimize('dofs', energy, constrain=cons)): self.assertLess(iiter, 38) if info.shift: nshift += 1 if info.resnorm < self.tol: break self.assertEqual(nshift, 9)
def setUp(self): super().setUp() domain, geom = mesh.rectilinear([numpy.linspace(0, 1, 9)] * 2) ubasis = domain.basis('std', degree=2) if self.vector: u = ubasis.vector(2).dot( function.Argument('dofs', [len(ubasis) * 2])) else: u = (ubasis[:, numpy.newaxis] * function.Argument('dofs', [len(ubasis), 2])).sum(0) Geom = geom * [1.1, 1] + u self.cons = solver.optimize('dofs', domain.boundary['left,right'].integral( (u**2).sum(0), degree=4), droptol=1e-15) self.boolcons = ~numpy.isnan(self.cons) strain = .5 * (function.outer(Geom.grad(geom), axis=1).sum(0) - function.eye(2)) self.energy = domain.integral( ((strain**2).sum([0, 1]) + 20 * (function.determinant(Geom.grad(geom)) - 1)**2) * function.J(geom), degree=6) self.residual = self.energy.derivative('dofs') self.tol = 1e-10
def setUp(self): super().setUp() domain, geom = mesh.rectilinear([numpy.linspace(0,1,9)] * 2) ubasis = domain.basis('std', degree=2).vector(2) u = ubasis.dot(function.Argument('dofs', [len(ubasis)])) Geom = geom * [1.1, 1] + u self.cons = solver.minimize('dofs', domain.boundary['left,right'].integral((u**2).sum(0), degree=4), droptol=1e-15).solve() self.boolcons = ~numpy.isnan(self.cons) strain = .5 * (function.outer(Geom.grad(geom), axis=1).sum(0) - function.eye(2)) self.energy = domain.integral(((strain**2).sum([0,1]) + 20*(function.determinant(Geom.grad(geom))-1)**2)*function.J(geom), degree=6) self.residual = self.energy.derivative('dofs') self.tol = 1e-10
class PiolaScalarTransform(MuCallable): _ident_ = 'PiolaScalarTransform' def __init__(self, *deps): super().__init__((), deps) def evaluate(self, case, mu, cont): assert all(c is None for c in cont) refgeom = case.geometry() trfgeom = case.geometry(mu) J = trfgeom.grad(refgeom) return 1 / fn.determinant(J)
def piola_bases(mu, case): trfgeom = case.geometry(mu) refgeom = case.refgeom J = trfgeom.grad(refgeom) detJ = fn.determinant(J) vnbasis, vtbasis, pbasis = fn.chain([ case.domain.basis('spline', degree=(3,2))[:,_] * J[:,0] / detJ, case.domain.basis('spline', degree=(2,3))[:,_] * J[:,1] / detJ, case.domain.basis('spline', degree=2) / detJ, ]) vbasis = vnbasis + vtbasis return vbasis, pbasis
def method_library(g, method='Elliptic', degree=None): assert len(g) == 2 if degree is None: degree = g.ischeme * 4 target = function.Argument('target', [len(g.x)]) if method in ['Elliptic', 'Elliptic_unscaled']: G = metric_tensor(g, target) J = jacobian(g, target) ((g11, g12), (g21, g22)) = G s = function.stack G = s([s([g22, -g12]), s([-g12, g11])], axis=1) lapl = (J.grad(g.geom) * G[None]).sum([1, 2]) res = (g.basis.vector(2) * lapl).sum(-1) res /= (1 if method == 'Elliptic_unscaled' else (g11 + g22 + 0.0001)) res = g.domain.integral(res, geometry=g.geom, degree=degree) elif method == 'Elliptic_forward': res = function.trace(metric_tensor(g, target)) res = g.domain.integral(res, geometry=g.geom, degree=degree).derivative('target') elif method == 'Winslow': res = function.trace(metric_tensor(g, target)) / function.determinant( jacobian(g, target)) res = g.domain.integral(res, geometry=g.geom, degree=degree).derivative('target') elif method == 'Elliptic_partial': x = g.basis.vector(2).dot(target) res = (g.basis.vector(2).grad(x) * (g.geom.grad(x)[None])).sum([1, 2]) res = g.domain.integral(res, geometry=x, degree=degree) elif method == 'Liao': G = metric_tensor(g, target) ((g11, g12), (g21, g22)) = G res = g.domain.integral(g11**2 + 2 * g12**2 + g22**2, geometry=g.geom, degree=degree).derivative('target') else: raise 'Unknown method {}'.format(method) return res
def test_nonlinear_diagonalshift(self): nelems = 10 domain, geom = mesh.rectilinear([nelems,1]) geom *= [2*numpy.pi/nelems, 1] ubasis = domain.basis('spline', degree=2).vector(2) u = ubasis.dot(function.Argument('dofs', [len(ubasis)])) Geom = [.5 * geom[0], geom[1] + function.cos(geom[0])] + u # compress by 50% and buckle cons = solver.minimize('dofs', domain.boundary['left,right'].integral((u**2).sum(0), degree=4), droptol=1e-15).solve() strain = .5 * (function.outer(Geom.grad(geom), axis=1).sum(0) - function.eye(2)) energy = domain.integral(((strain**2).sum([0,1]) + 150*(function.determinant(Geom.grad(geom))-1)**2)*function.J(geom), degree=6) nshift = 0 for iiter, (lhs, info) in enumerate(solver.minimize('dofs', energy, constrain=cons)): self.assertLess(iiter, 90) if info.shift: nshift += 1 if info.resnorm < self.tol: break self.assertEqual(nshift, 60)
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
angle = np.linspace(0, 2 * np.pi, cpts.shape[0], endpoint=False) - cylrot angle = np.hstack([[angle[-1]], angle[:-1]]) upts = radius * np.vstack([np.cos(angle), np.sin(angle)]).T interp = np.linspace(0, 1, nelems + 3)**3 cc = np.vstack([(1 - i) * cpts + i * upts for i in interp]) geom = fn.asarray([basis.dot(cc[:, 0]), basis.dot(cc[:, 1])]) return domain, refgeom, geom def mk_bases(case, piola): if piola: mu = case.parameter() J = case.geometry(mu).grad(case.refgeom) detJ = fn.determinant(J) bases = [ case.domain.basis('spline', degree=(3, 2))[:, _] * J[:, 0] / detJ, case.domain.basis('spline', degree=(2, 3))[:, _] * J[:, 1] / detJ, case.domain.basis('spline', degree=2) / detJ, ] else: nr, na = case.domain.shape rkts = np.arange(nr + 1) pkts = np.arange(na + 1) rmul = [2] * len(rkts) rmul[0] = 3 rmul[-1] = 3 pmul = [2] * len(pkts) thbasis = case.domain.basis(
def elliptic_control_mapping(g, f, eps=1e-4, ltol=1e-5, degree=None, **solveargs): ''' Nutils implementation of the corresponding method from the ``fsol`` module. It's slower but accepts any control mapping function. Unline in the ``fsol`` case, all derivatives are computed automatically. ''' assert len(g) == len(f) == 2 assert eps >= 0 if degree is None: degree = 4 * g.ischeme if isinstance(f, go.TensorGridObject): if not (f.knotvector <= g.knotvector).all(): raise AssertionError(''' Error, the control-mapping knotvector needs to be a subset of the target GridObject knotvector ''') # refine, just in case f = go.refine_GridObject(f, g.knotvector) G = metric_tensor(g, f.x) else: # f is a nutils function jacT = function.transpose(f.grad(g.geom)) G = function.outer(jacT, jacT).sum(-1) target = function.Argument('target', [len(g.x)]) basis = g.basis.vector(2) x = basis.dot(target) (g11, g12), (g21, g22) = metric_tensor(g, target) lapl = g22 * x.grad(g.geom)[:, 0].grad(g.geom)[:, 0] - \ 2 * g12 * x.grad(g.geom)[:, 0].grad(g.geom)[:, 1] + \ g11 * x.grad(g.geom)[:, 1].grad(g.geom)[:, 1] G_tilde = G / function.sqrt(function.determinant(G)) (G11, G12), (G12, G22) = G_tilde P = g11 * G12.grad(g.geom)[1] - g12 * G11.grad(g.geom)[1] Q = 0.5 * (g11 * G22.grad(g.geom)[0] - g22 * G11.grad(g.geom)[0]) S = g12 * G22.grad(g.geom)[0] - g22 * G12.grad(g.geom)[0] R = 0.5 * (g11 * G22.grad(g.geom)[1] - g22 * G11.grad(g.geom)[1]) control_term = -x.grad( g.geom )[:, 0] * ( G22*(P-Q) + G12*(S-R) ) \ -x.grad( g.geom )[:, 1] * ( G11*(R-S) + G12*(Q-P) ) scale = g11 + g22 + eps res = g.domain.integral((basis * (control_term + lapl)).sum(-1) / scale, geometry=g.geom, degree=degree) init, cons = g.x, g.cons lhs = solver.newton('target', res, lhs0=init, constrain=cons.where).solve(ltol, **solveargs) g.x = lhs
def 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 jacdet(self): grd = self.mapping.grad(self.geom) return function.determinant(grd) if len(self) > 1 else function.sqrt( grd.sum(-2))
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 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: '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-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