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) 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 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 main(nelems: int, degree: int, reynolds: float): ''' Driven cavity benchmark problem using compatible spaces. .. arguments:: nelems [12] Number of elements along edge. degree [2] Polynomial degree for velocity; the pressure space is one degree less. reynolds [1000] Reynolds number, taking the domain size as characteristic length. ''' verts = numpy.linspace(0, 1, nelems + 1) domain, geom = mesh.rectilinear([verts, verts]) ns = function.Namespace() ns.x = geom ns.Re = reynolds ns.ubasis = function.vectorize([ domain.basis('spline', degree=(degree, degree - 1), removedofs=((0, -1), None)), domain.basis('spline', degree=(degree - 1, degree), removedofs=(None, (0, -1))) ]) ns.pbasis = domain.basis('spline', degree=degree - 1) ns.u_i = 'ubasis_ni ?u_n' ns.p = 'pbasis_n ?p_n' ns.stress_ij = '(d(u_i, x_j) + d(u_j, x_i)) / Re - p δ_ij' ns.uwall = domain.boundary.indicator('top'), 0 ns.N = 5 * degree * nelems # nitsche constant based on element size = 1/nelems ns.nitsche_ni = '(N ubasis_ni - (d(ubasis_ni, x_j) + d(ubasis_nj, x_i)) n(x_j)) / Re' ures = domain.integral('d(ubasis_ni, x_j) stress_ij d:x' @ ns, degree=2 * degree) ures += domain.boundary.integral( '(nitsche_ni (u_i - uwall_i) - ubasis_ni stress_ij n(x_j)) d:x' @ ns, degree=2 * degree) pres = domain.integral('pbasis_n (d(u_k, x_k) + ?lm) d:x' @ ns, degree=2 * degree) lres = domain.integral('p d:x' @ ns, degree=2 * degree) with treelog.context('stokes'): state0 = solver.solve_linear(['u', 'p', 'lm'], [ures, pres, lres]) postprocess(domain, ns, **state0) ures += domain.integral('ubasis_ni d(u_i, x_j) u_j d:x' @ ns, degree=3 * degree) with treelog.context('navierstokes'): state1 = solver.newton(('u', 'p', 'lm'), (ures, pres, lres), arguments=state0).solve(tol=1e-10) postprocess(domain, ns, **state1) return state0, state1
def test_res(self): for name in 'direct', 'newton': with self.subTest(name): if name == 'direct': lhs = solver.solve_linear('dofs', residual=self.residual, constrain=self.cons) else: lhs = solver.newton('dofs', residual=self.residual, constrain=self.cons).solve(tol=1e-10, maxiter=0) res = self.residual.eval(arguments=dict(dofs=lhs)) resnorm = numpy.linalg.norm(res[~self.cons.where]) self.assertLess(resnorm, 1e-13)
def test_res(self): for name in 'direct', 'newton': with self.subTest(name): if name == 'direct': lhs = solver.solve_linear('dofs', residual=self.residual, constrain=self.cons) else: lhs = solver.newton('dofs', residual=self.residual, constrain=self.cons).solve(tol=1e-10, maxiter=0) res = self.residual.eval(arguments=dict(dofs=lhs)) resnorm = numpy.linalg.norm(res[~self.cons.where]) self.assertLess(resnorm, 1e-13)
def main(nelems: int, degree: int, reynolds: float): ''' Driven cavity benchmark problem using compatible spaces. .. arguments:: nelems [12] Number of elements along edge. degree [2] Polynomial degree for velocity; the pressure space is one degree less. reynolds [1000] Reynolds number, taking the domain size as characteristic length. ''' verts = numpy.linspace(0, 1, nelems + 1) domain, geom = mesh.rectilinear([verts, verts]) ns = function.Namespace() ns.x = geom ns.Re = reynolds ns.uxbasis, ns.uybasis, ns.pbasis, ns.lbasis = function.chain([ domain.basis('spline', degree=(degree, degree - 1), removedofs=((0, -1), None)), domain.basis('spline', degree=(degree - 1, degree), removedofs=(None, (0, -1))), domain.basis('spline', degree=degree - 1), [1], # lagrange multiplier ]) ns.ubasis_ni = '<uxbasis_n, uybasis_n>_i' ns.u_i = 'ubasis_ni ?lhs_n' ns.p = 'pbasis_n ?lhs_n' ns.l = 'lbasis_n ?lhs_n' ns.stress_ij = '(u_i,j + u_j,i) / Re - p δ_ij' ns.uwall = domain.boundary.indicator('top'), 0 ns.N = 5 * degree * nelems # nitsche constant based on element size = 1/nelems ns.nitsche_ni = '(N ubasis_ni - (ubasis_ni,j + ubasis_nj,i) n_j) / Re' res = domain.integral( '(ubasis_ni,j stress_ij + pbasis_n (u_k,k + l) + lbasis_n p) d:x' @ ns, degree=2 * degree) res += domain.boundary.integral( '(nitsche_ni (u_i - uwall_i) - ubasis_ni stress_ij n_j) d:x' @ ns, degree=2 * degree) with treelog.context('stokes'): lhs0 = solver.solve_linear('lhs', res) postprocess(domain, ns, lhs=lhs0) res += domain.integral('ubasis_ni u_i,j u_j d:x' @ ns, degree=3 * degree) with treelog.context('navierstokes'): lhs1 = solver.newton('lhs', res, lhs0=lhs0).solve(tol=1e-10) postprocess(domain, ns, lhs=lhs1) return lhs0, lhs1
def main(nelems: int, etype: str, degree: int, reynolds: float): ''' Driven cavity benchmark problem. .. arguments:: nelems [12] Number of elements along edge. etype [square] Element type (square/triangle/mixed). degree [2] Polynomial degree for velocity; the pressure space is one degree less. reynolds [1000] Reynolds number, taking the domain size as characteristic length. ''' domain, geom = mesh.unitsquare(nelems, etype) ns = function.Namespace() ns.Re = reynolds ns.x = geom ns.ubasis, ns.pbasis = function.chain([ domain.basis('std', degree=degree).vector(2), domain.basis('std', degree=degree - 1), ]) ns.u_i = 'ubasis_ni ?lhs_n' ns.p = 'pbasis_n ?lhs_n' ns.stress_ij = '(u_i,j + u_j,i) / Re - p δ_ij' sqr = domain.boundary.integral('u_k u_k d:x' @ ns, degree=degree * 2) wallcons = solver.optimize('lhs', sqr, droptol=1e-15) sqr = domain.boundary['top'].integral('(u_0 - 1)^2 d:x' @ ns, degree=degree * 2) lidcons = solver.optimize('lhs', sqr, droptol=1e-15) cons = numpy.choose(numpy.isnan(lidcons), [lidcons, wallcons]) cons[-1] = 0 # pressure point constraint res = domain.integral('(ubasis_ni,j stress_ij + pbasis_n u_k,k) d:x' @ ns, degree=degree * 2) with treelog.context('stokes'): lhs0 = solver.solve_linear('lhs', res, constrain=cons) postprocess(domain, ns, lhs=lhs0) res += domain.integral( '.5 (ubasis_ni u_i,j - ubasis_ni,j u_i) u_j d:x' @ ns, degree=degree * 3) with treelog.context('navierstokes'): lhs1 = solver.newton('lhs', res, lhs0=lhs0, constrain=cons).solve(tol=1e-10) postprocess(domain, ns, lhs=lhs1) return lhs0, lhs1
def main( nelems: 'number of elements' = 12, lmbda: 'first lamé constant' = 1., mu: 'second lamé constant' = 1., degree: 'polynomial degree' = 2, withplots: 'create plots' = True, solvetol: 'solver tolerance' = 1e-10, ): # construct mesh verts = numpy.linspace(0, 1, nelems + 1) domain, geom = mesh.rectilinear([verts, verts]) # create namespace ns = function.Namespace(default_geometry_name='x0') ns.x0 = geom ns.basis = domain.basis('spline', degree=degree).vector(2) ns.u_i = 'basis_ni ?lhs_n' ns.x_i = 'x0_i + u_i' ns.lmbda = lmbda ns.mu = mu ns.strain_ij = '(u_i,j + u_j,i) / 2' ns.stress_ij = 'lmbda strain_kk δ_ij + 2 mu strain_ij' # construct dirichlet boundary constraints sqr = domain.boundary['left'].integral('u_k u_k' @ ns, geometry=ns.x0, degree=2) sqr += domain.boundary['right'].integral('(u_0 - .5)^2' @ ns, geometry=ns.x0, degree=2) cons = solver.optimize('lhs', sqr, droptol=1e-15) # construct residual res = domain.integral('basis_ni,j stress_ij' @ ns, geometry=ns.x0, degree=2) # solve system and substitute the solution in the namespace lhs = solver.solve_linear('lhs', res, constrain=cons) ns |= dict(lhs=lhs) # plot solution if withplots: points, colors = domain.elem_eval([ns.x, ns.stress[0, 1]], ischeme='bezier3', separate=True) with plot.PyPlot('stress', ndigits=0) as plt: plt.mesh(points, colors, tight=False) plt.colorbar() return lhs, cons
def main(nelems:int, etype:str, degree:int, reynolds:float): ''' Driven cavity benchmark problem. .. arguments:: nelems [12] Number of elements along edge. etype [square] Element type (square/triangle/mixed). degree [2] Polynomial degree for velocity; the pressure space is one degree less. reynolds [1000] Reynolds number, taking the domain size as characteristic length. ''' domain, geom = mesh.unitsquare(nelems, etype) ns = function.Namespace() ns.Re = reynolds ns.x = geom ns.ubasis = domain.basis('std', degree=degree).vector(domain.ndims) ns.pbasis = domain.basis('std', degree=degree-1) ns.u_i = 'ubasis_ni ?u_n' ns.p = 'pbasis_n ?p_n' ns.stress_ij = '(d(u_i, x_j) + d(u_j, x_i)) / Re - p δ_ij' usqr = domain.boundary.integral('u_k u_k d:x' @ ns, degree=degree*2) wallcons = solver.optimize('u', usqr, droptol=1e-15) usqr = domain.boundary['top'].integral('(u_0 - 1)^2 d:x' @ ns, degree=degree*2) lidcons = solver.optimize('u', usqr, droptol=1e-15) ucons = numpy.choose(numpy.isnan(lidcons), [lidcons, wallcons]) pcons = numpy.zeros(len(ns.pbasis), dtype=bool) pcons[-1] = True # constrain pressure to zero in a point cons = dict(u=ucons, p=pcons) ures = domain.integral('d(ubasis_ni, x_j) stress_ij d:x' @ ns, degree=degree*2) pres = domain.integral('pbasis_n d(u_k, x_k) d:x' @ ns, degree=degree*2) with treelog.context('stokes'): state0 = solver.solve_linear(('u', 'p'), (ures, pres), constrain=cons) postprocess(domain, ns, **state0) ures += domain.integral('.5 (ubasis_ni d(u_i, x_j) - d(ubasis_ni, x_j) u_i) u_j d:x' @ ns, degree=degree*3) with treelog.context('navierstokes'): state1 = solver.newton(('u', 'p'), (ures, pres), arguments=state0, constrain=cons).solve(tol=1e-10) postprocess(domain, ns, **state1) return state0, state1
def main(nelems: int, etype: str, btype: str, degree: int, poisson: float): ''' Horizontally loaded linear elastic plate. .. arguments:: nelems [10] Number of elements along edge. etype [square] Type of elements (square/triangle/mixed). btype [std] Type of basis function (std/spline), with availability depending on the configured element type. degree [1] Polynomial degree. poisson [.25] Poisson's ratio, nonnegative and strictly smaller than 1/2. ''' domain, geom = mesh.unitsquare(nelems, etype) ns = function.Namespace() ns.x = geom ns.basis = domain.basis(btype, degree=degree).vector(2) ns.u_i = 'basis_ni ?lhs_n' ns.X_i = 'x_i + u_i' ns.lmbda = 2 * poisson ns.mu = 1 - 2 * poisson ns.strain_ij = '(d(u_i, x_j) + d(u_j, x_i)) / 2' ns.stress_ij = 'lmbda strain_kk δ_ij + 2 mu strain_ij' sqr = domain.boundary['left'].integral('u_k u_k J(x)' @ ns, degree=degree * 2) sqr += domain.boundary['right'].integral('(u_0 - .5)^2 J(x)' @ ns, degree=degree * 2) cons = solver.optimize('lhs', sqr, droptol=1e-15) res = domain.integral('d(basis_ni, x_j) stress_ij J(x)' @ ns, degree=degree * 2) lhs = solver.solve_linear('lhs', res, constrain=cons) bezier = domain.sample('bezier', 5) X, sxy = bezier.eval(['X', 'stress_01'] @ ns, lhs=lhs) export.triplot('shear.png', X, sxy, tri=bezier.tri, hull=bezier.hull) return cons, lhs
def main( nelems: 'number of elements' = 10, degree: 'polynomial degree' = 1, basistype: 'basis function' = 'spline', solvetol: 'solver tolerance' = 1e-10, figures: 'create figures' = True, ): # construct mesh verts = numpy.linspace(0, numpy.pi/2, nelems+1) domain, geom = mesh.rectilinear([verts, verts]) # create namespace ns = function.Namespace() ns.x = geom ns.basis = domain.basis(basistype, degree=degree) ns.u = 'basis_n ?lhs_n' ns.fx = 'sin(x_0)' ns.fy = 'exp(x_1)' # construct residual res = domain.integral('-basis_n,i u_,i' @ ns, geometry=ns.x, degree=degree*2) res += domain.boundary['top'].integral('basis_n fx fy' @ ns, geometry=ns.x, degree=degree*2) # construct dirichlet constraints sqr = domain.boundary['left'].integral('u^2' @ ns, geometry=ns.x, degree=degree*2) sqr += domain.boundary['bottom'].integral('(u - fx)^2' @ ns, geometry=ns.x, degree=degree*2) cons = solver.optimize('lhs', sqr, droptol=1e-15) # find lhs such that res == 0 and substitute this lhs in the namespace lhs = solver.solve_linear('lhs', res, constrain=cons) ns = ns(lhs=lhs) # plot solution if figures: points, colors = domain.elem_eval([ns.x, ns.u], ischeme='bezier9', separate=True) with plot.PyPlot('solution', index=nelems) as plt: plt.mesh(points, colors, cmap='jet') plt.colorbar() # evaluate error against exact solution fx fy err = domain.integrate('(u - fx fy)^2' @ ns, geometry=ns.x, degree=degree*2)**.5 log.user('L2 error: {:.2e}'.format(err)) return cons, lhs, err
def 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( nelems: 'number of elements' = 12, lmbda: 'first lamé constant' = 1., mu: 'second lamé constant' = 1., degree: 'polynomial degree' = 2, withplots: 'create plots' = True, solvetol: 'solver tolerance' = 1e-10, ): # construct mesh verts = numpy.linspace(0, 1, nelems+1) domain, geom = mesh.rectilinear([verts,verts]) # create namespace ns = function.Namespace(default_geometry_name='x0') ns.x0 = geom ns.basis = domain.basis('spline', degree=degree).vector(2) ns.u_i = 'basis_ni ?lhs_n' ns.x_i = 'x0_i + u_i' ns.lmbda = lmbda ns.mu = mu ns.strain_ij = '(u_i,j + u_j,i) / 2' ns.stress_ij = 'lmbda strain_kk δ_ij + 2 mu strain_ij' # construct dirichlet boundary constraints sqr = domain.boundary['left'].integral('u_k u_k' @ ns, geometry=ns.x0, degree=2) sqr += domain.boundary['right'].integral('(u_0 - .5)^2' @ ns, geometry=ns.x0, degree=2) cons = solver.optimize('lhs', sqr, droptol=1e-15) # construct residual res = domain.integral('basis_ni,j stress_ij' @ ns, geometry=ns.x0, degree=2) # solve system and substitute the solution in the namespace lhs = solver.solve_linear('lhs', res, constrain=cons) ns |= dict(lhs=lhs) # plot solution if withplots: points, colors = domain.elem_eval([ns.x, ns.stress[0,1]], ischeme='bezier3', separate=True) with plot.PyPlot('stress', ndigits=0) as plt: plt.mesh(points, colors, tight=False) plt.colorbar() return lhs, cons
def main(aquifer, degree: int, btype: str, elems: int, rw: unit['m'], rmax: unit['m'], H: unit['m'], mu: unit['Pa*s'], φ: float, ctinv: unit['Pa'], k_int: unit['m2'], Q: unit['m3/s'], timestep: unit['s'], t1endtime: unit['s']): ''' Fluid flow in porous media. .. arguments:: degree [2] Polynomial degree for pressure space. btype [spline] Type of basis function (std/spline). elems [30] Number of elements. rw [0.1m] Well radius. rmax [1000m] Far field radius of influence. H [100m] Vertical thickness of reservoir. mu [0.31mPa*s] Dynamic fluid viscosity. φ [0.2] Porosity. ctinv [10GPa] Inverse of total compressibility. k_int [0.1μm2] Intrinsic permeability. Q [0.05m3/s] Injection rate. timestep [60s] Time step. t1endtime [300s] Number of time steps per timeperiod (drawdown or buildup). ''' # define total time and steps N = round(t1endtime / timestep) timeperiod = timestep * np.linspace(0, 2 * N, 2 * N + 1) # define vertices of radial grid rverts = np.logspace(np.log2(rw), np.log2(rmax), elems + 1, base=2.0, endpoint=True) θverts = [0, 2 * np.pi] zverts = np.linspace(0, H, elems + 1) topo, geom = mesh.rectilinear([rverts, zverts, θverts], periodic=[2]) topo = topo.withboundary(inner=topo.boundary['left'], outer=topo.boundary['right']) topo = topo.withboundary(strata=topo.boundary - topo.boundary['inner,outer']) assert (topo.boundary['inner'].sample('bezier', 2).eval( geom[0]) == rw).all(), "Invalid inner boundary condition" assert (topo.boundary['outer'].sample('bezier', 2).eval( geom[0]) == rmax).all(), "Invalid outer boundary condition" ns = function.Namespace() ns.r, ns.z, ns.θ = geom ns.x_i = '<r cos(θ), z, r sin(θ)>_i' # set periodic basis function constant ns.pbasis = topo.basis('spline', degree=(1, 1, 0)) ns.Tbasis = topo.basis('spline', degree=(1, 1, 0)) # retrieve the well boundary ns.Aw = topo.boundary['inner'].integrate("d:x" @ ns, degree=1) ns.Aw2 = 2 * math.pi * rw * H assert ( np.round_(ns.Aw.eval() - ns.Aw2.eval()) == 0), "Area of inner boundary does not match" # problem variables # unit ns.Q = Q # [m^3/s] ns.H = H # [m] ns.rw = rw # [m] ns.rmax = rmax # [m] ns.φ = φ # [-] ns.ct = 1 / ctinv # ns.ctφ = 1e-9 # [1/Pa] # ns.ctf = 5e-10 # [1/Pa] ns.Jw = ns.Q / ns.H # [m^2/s] ns.mu = aquifer.mu ns.cf = aquifer.cpf # [J/kg K] ns.cs = aquifer.cps # [J/kg K] ns.ρf = aquifer.rhof # [kg/m^3] ns.ρs = aquifer.rhos # [kg/m^3] ns.λf = aquifer.labdaf # [W/mk] ns.λs = aquifer.labdas # [W/mk] ns.g = aquifer.g # [m/s^2] # ns.ge_i = '<0,-g, 0>_i' ns.k = k_int ns.uw = 'Q / Aw' ns.K = k_int / mu # ns.K = np.diag(k_int) / (ns.mu) # [m/s] *[1/rho g] # ns.q_i = '-K_ij (p_,j - ρf ge_i,j)' #[s * Pa* 1/m] = [Pa*s/m] ρf g x_1,j # ns.u_i = 'q_i / φ ' # [m/s] ns.T0 = aquifer.Tref # [K] #88.33+273 # total system (rock + fluid) variable ns.ρ = ns.φ * ns.ρf + (1 - ns.φ) * ns.ρs ns.cp = ns.φ * ns.cf + (1 - ns.φ) * ns.cs ns.λ = ns.φ * ns.λf + (1 - ns.φ) * ns.λs ########################### # Solve by implicit euler # ########################### # # # introduce temperature dependent variables # # ns.ρf = 1000 * (1 - 3.17e-4 * (ns.T - 298.15) - 2.56e-6 * (ns.T - 298.15)**2) # # ns.cf = 4187.6 * (-922.47 + 2839.5 * (ns.T / ns.Tatm) - 1800.7 * (ns.T / ns.Tatm)**2 + 525.77*(ns.T / ns.Tatm)**3 - 73.44*(ns.T / ns.Tatm)**4) # # ns.cf = 3.3774 - 1.12665e-2 * ns.T + 1.34687e-5 * ns.T**2 # if temperature above T=100 [K] # # ns.mu = PropsSI('V', 'T', ns.T.eval(), 'P', ns.p.eval(), 'IF97::Water') # note to self: temperature dependency # ns.ρf = PropsSI('D', 'T', ns.T.eval(), 'P', ns.p.eval(), 'IF97::Water') # note to self: temperature dependency # # # define initial state # sqr = topo.integral('(p - p0)^2' @ ns, degree=degree*2) # pdofs0 = solver.optimize('lhsp', sqr, droptol=1e-15) # # # define dirichlet constraints # sqrp = topo.boundary['outer'].integral('(p - p0)^2 d:x' @ ns, degree=degree*2) # # sqrp += topo.boundary['inner'].integral('(p - pw)^2 d:x' @ ns, degree=degree*2) #presdefined well pressure # consp = solver.optimize('lhsp', sqrp, droptol=1e-15) # # # formulate hydraulic process single field # # resp = topo.integral('(-pbasis_n,i q_i) d:x' @ ns, degree=degree*4) # # resp += topo.boundary['strata'].integral('(pbasis_n q_i n_i) d:x' @ ns, degree=degree*4) # # respwell = topo.boundary['inner'].integral('pbasis_n (δ_i0 n_i + uw) d:x' @ ns, degree=2) # massabalans bron # # respwell = topo.boundary['inner'].integral('pbasis_n uw d:x' @ ns, degree=2) # massabalans bron # # lhsp = solver.newton('lhsp', respwell, constrain=consp, arguments=dict(lhsp=pdofs0)).solve(tol=1e-10) # # resp = -topo.integral('pbasis_n,i q_i d:x' @ ns, degree=6) # # respd = resp - topo.boundary['inner'].integral('pbasis_n (q_i n_i - uw) d:x' @ ns, degree=degree * 2) # respd = resp + topo.boundary['inner'].integral('pbasis_n (- uw φ) d:x' @ ns, degree=degree * 2) # # # ns.Δt = timestep # # ns.p = 'pbasis_n ?lhsp_n' # # ns.p0 = 'pbasis_n ?lhsp0_n' # # ns.δp = '(p - p0) / Δt' # # # print("residue well", respwell.eval()) # # respd = resp - respwell # # lhsp = solver.solve_linear('lhsp', respd, constrain=consp, arguments=dict(lhsp=pdofs0)) # # bezier = topo.sample('bezier', 5) # # residuep, respwell = bezier.eval(['resp', 'respwell'] @ ns, lhsp=lhsp) # # print("residuep", residuep.eval()) # # # lhsp0 = solver.solve_linear('lhsp', respwell, constrain=consp, arguments={'lhsp0': pdofs0}) #los eerst massabalans over de put randvoorwaarde op # # respb = resp # pinertia = topo.integral('pbasis_n (φ ct p) d:x' @ ns, degree=6) #φ ρf ct # # # define initial condition for thermo process # sqr = topo.integral('(T - T0) (T - T0)' @ ns, degree=degree * 2) # Tdofs0 = solver.optimize('lhsT', sqr) # # # define dirichlet constraints for thermo process # sqrT = topo.boundary['outer'].integral('(T - T0) (T - T0) d:x' @ ns, degree=degree * 2) # consT = solver.optimize('lhsT', sqrT, droptol=1e-15) # # # formulate thermo process # resT = topo.integral('(ρf cf Tbasis_n (q_i T)_,i) d:x' @ ns, degree=degree * 2) #(u_k T)_,k # resT += topo.integral('(Tbasis_n,i (- λ) T_,i) d:x' @ ns, degree=degree * 2) # resT -= topo.boundary['strata'].integral('(Tbasis_n T_,i n_i) d:x' @ ns, degree=degree*4) # resT -= topo.boundary['inner'].integral('(Tbasis_n ρf uw cf T_,i n_i) d:x' @ ns, degree=degree*4) # resT2 = resT + topo.boundary['inner'].integral('(Tbasis_n ρf uw cf T_,i n_i) d:x' @ ns, degree=degree*4) # # resT -= topo.boundary['top,bottom'].integral('Tbasis_n qh d:x' @ ns, degree=degree * 2) # heat flux on boundary # Tinertia = topo.integral('(ρ cp Tbasis_n T) d:x' @ ns, degree=degree * 4) # # plottopo = topo[:, :, 0:].boundary['back'] # # # locally refine # # nref = 4 # # ns.sd = (ns.x[0]) ** 2 # # refined_topo = RefineBySDF(plottopo, ns.rw, geom[0], nref) # # plottopo = topo[:, :, 0:].boundary['back'] # # mesh # bezier = plottopo.sample('bezier', 2) # with export.mplfigure('mesh.png', dpi=800) as fig: # r, z, col = bezier.eval([ns.r, ns.z, 1]) # ax = fig.add_subplot(1, 1, 1) # ax.tripcolor(r, z, bezier.tri, col, shading='gouraud', rasterized=True) # ax.add_collection( # collections.LineCollection(np.array([r, z]).T[bezier.hull], colors='k', linewidths=0.2, # alpha=0.2)) # # bezier = plottopo.sample('bezier', 9) # solve for steady state state of pressure # lhsp = solver.solve_linear('lhsp', resp, constrain=consp) # construct empty arrays parraywell = np.empty([2 * N + 1]) parrayexact = np.empty(2 * N + 1) Tarraywell = np.empty(2 * N + 1) Tarrayexact = np.empty(2 * N + 1) Qarray = np.empty(2 * N + 1) dparraywell = np.empty([2 * N + 1]) parrayexp = np.empty(2 * N + 1) Tarrayexp = np.empty(2 * N + 1) ############################## # Linear system of equations # ############################## with treelog.iter.fraction('step', range(2 * N + 1)) as counter: for istep in counter: time = timestep * istep # define problem ns.Δt = timestep ns.p = 'pbasis_n ?plhs_n' ns.p0 = 'pbasis_n ?plhs0_n' ns.δp = '(p - p0) / Δt' ns.T = 'Tbasis_n ?Tlhs_n' ns.T0 = 'Tbasis_n ?Tlhs0_n' ns.δT = '(T - T0) / Δt' ns.k = k_int ns.mu = mu ns.q_i = '-( k / mu ) p_,i' ns.v = 'Q / Aw' ns.pref = 225e5 # [Pa] ns.Tref = 90 + 273 # [K] ns.Twell = 88 + 273 # [K] ns.epsilonjt = 2e-7 ns.constantjt = ns.epsilonjt ns.cpratio = (ns.ρf * ns.cf) / (ns.ρ * ns.cp) ns.phieff = 0 #ns.φ * ns.cpratio * (ns.epsilonjt + 1/(ns.ρf * ns.cf)) # add artificial diffusion ns.hmin = (rverts[1] - rverts[0]) strength = 0 # set strength ns.ε = strength * ns.hmin * ns.ρf * ns.cf # calculate artificial diffusion ns.qh_i = '-(λ + ε) T_,i' # set in weak formulation print("hmin", ns.hmin.eval(), "c", (ns.ρf * ns.cf).eval(), "diffusion", ns.λ.eval(), 'artificial diffusion', ns.ε.eval()) psqr = topo.boundary['outer'].integral( '( (p - pref)^2 ) d:x' @ ns, degree=2) # farfield pressure pcons = solver.optimize('plhs', psqr, droptol=1e-15) Tsqr = topo.boundary['outer'].integral( '( (T - Tref)^2 ) d:x' @ ns, degree=2) # farfield temperature Tcons = solver.optimize('Tlhs', Tsqr, droptol=1e-15) if istep == 0: psqr = topo.integral('( (p - pref)^2 ) d:x' @ ns, degree=2) Tsqr = topo.integral('( (T - Tref)^2) d:x' @ ns, degree=2) Tsqr += topo.boundary['inner'].integral( '( (T - Twell)^2 ) d:x' @ ns, degree=2) # initial well temperature plhs0 = solver.optimize('plhs', psqr, droptol=1e-15) Tlhs0 = solver.optimize('Tlhs', Tsqr, droptol=1e-15) else: psqr = topo.integral('( (p - pref)^2) d:x' @ ns, degree=2) Tsqr = topo.integral('( (T - Tref)^2) d:x' @ ns, degree=2) plhs0new = solver.optimize('plhs', psqr, droptol=1e-15) Tlhs0new = solver.optimize('Tlhs', Tsqr, droptol=1e-15) plhs0new = plhs0new.copy() Tlhs0new = Tlhs0new.copy() plhs0new[:len(plhs0)] = plhs0 Tlhs0new[:len(Tlhs0)] = Tlhs0 plhs0 = plhs0new.copy() Tlhs0 = Tlhs0new.copy() pres = -topo.integral('pbasis_n,i q_i d:x' @ ns, degree=6) # convection term darcy pres -= topo.boundary['strata, inner'].integral( '(pbasis_n q_i n_i) d:x' @ ns, degree=degree * 3) Tres = topo.integral('(Tbasis_n ρf cf q_i T_,i) d:x' @ ns, degree=degree * 2) # convection term ( totaal moet - zijn) Tres += topo.integral('(Tbasis_n,i qh_i) d:x' @ ns, degree=degree * 2) # conduction term ( totaal moet - zijn) Tres -= topo.integral( '(Tbasis_n epsilonjt ρf cf q_i p_,i) d:x' @ ns, degree=degree * 2) # J-T effect ( totaal moet + zijn) if istep > 0: pres += topo.integral('pbasis_n (φ ct) δp d:x' @ ns, degree=degree * 3) # storativity aquifer pres += topo.boundary['inner'].integral( 'pbasis_n v d:x' @ ns, degree=6) # mass conservation well Tres += topo.integral('Tbasis_n (ρ cp) δT d:x' @ ns, degree=degree * 3) # heat storage aquifer Tres += topo.boundary['inner'].integral( '(Tbasis_n ρf cf v n_i T_,i) d:x' @ ns, degree=degree * 2) # neumann bc if istep == (N + 1): ns.Td = bezier.eval(['T'] @ ns, Tlhs=Tlhs)[0][0] if istep > N: pres -= topo.boundary['inner'].integral( 'pbasis_n v d:x' @ ns, degree=6) # mass conservation well Tres -= topo.boundary['inner'].integral( '(Tbasis_n ρf cf v n_i T_,i) d:x' @ ns, degree=degree * 2) # remove neumann bc Tsqr += topo.boundary['inner'].integral( '( (T - Td)^2 ) d:x' @ ns, degree=2) Tcons = solver.optimize('Tlhs', Tsqr, droptol=1e-15) # add dirichlet bc plhs = solver.solve_linear('plhs', pres, constrain=pcons, arguments={'plhs0': plhs0}) Tlhs = solver.solve_linear('Tlhs', Tres, constrain=Tcons, arguments={ 'plhs': plhs, 'Tlhs0': Tlhs0 }) # Tresneumann = topo.boundary['inner'].integral('(Tbasis_n ρf cf v n_i T_,i) d:x' @ ns, degree=degree * 2) # neumann = Tresneumann.eval(Tlhs=Tlhs) # print("Neumann eval", neumann) q_inti = topo.boundary['inner'].integral('(q_i n_i) d:x' @ ns, degree=6) q = q_inti.eval(plhs=plhs) dT_inti = topo.boundary['inner'].integral('(T_,i n_i) d:x' @ ns, degree=6) dT = dT_inti.eval(Tlhs=Tlhs) dp_inti = topo.boundary['inner'].integral('(p_,i n_i) d:x' @ ns, degree=6) dp = dp_inti.eval(plhs=plhs) # print("the fluid flux in the FEA simulated:", q, "the flux that you want to impose:", (ns.v).eval()) # print("the temperature gradient in the FEA simulated:", dT, "the temperature that you want to impose:") plottopo = topo[:, :, 0:].boundary['back'] ########################### # Post processing # ########################### bezier = plottopo.sample('bezier', 7) x, q, p, T = bezier.eval(['x_i', 'q_i', 'p', 'T'] @ ns, plhs=plhs, Tlhs=Tlhs) # compute analytical solution if time <= t1endtime: Qarray[istep] = Q parrayexact[istep] = panalyticaldrawdown(ns, time, ns.rw) parraywell[istep] = nanjoin(p, bezier.tri)[::100][0] print("pwellFEA", parraywell[istep], "pwellEX", parrayexact[istep]) # pgrad = dpanalyticaldrawdown(ns, t1, ns.rw) # print("gradient pressure exact", pgrad) # print("gradient pressure", dp) Tarrayexact[istep] = Tanalyticaldrawdown(ns, time, ns.rw) Tarraywell[istep] = T.take(bezier.tri.T, 0)[1][0] print("TwellFEA", Tarraywell[istep], "TwellEX", Tarrayexact[istep]) plotdrawdown_1D(ns, bezier, x, p, T, time) else: Qarray[istep] = 0 parrayexact[istep] = panalyticalbuildup( ns, t1endtime, time, ns.rw) parraywell[istep] = p.take(bezier.tri.T, 0)[1][0] print("pwellFEA", parraywell[istep], "pwellEX", parrayexact[istep]) Tarrayexact[istep] = Tanalyticalbuildup( ns, t1endtime, time, ns.rw) Tarraywell[istep] = T.take(bezier.tri.T, 0)[1][0] print("TwellFEA", Tarraywell[istep], "TwellEX", Tarrayexact[istep]) plotbuildup_1D(ns, bezier, x, p, T, t1endtime, time) if time >= 2 * t1endtime: #export parrayerror = np.abs(np.subtract(parraywell, parrayexact)) # 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(x[:, 0], x[:, 1], bezier.tri, p, shading='gouraud', cmap='jet') # ax.add_collection( # collections.LineCollection(np.array([x[:, 0], x[:, 1]]).T[bezier.hull], colors='k', # linewidths=0.2, # alpha=0.2)) # fig.colorbar(im) # # with export.mplfigure('temperature.png', dpi=800) as fig: # ax = fig.add_subplot(111, title='temperature', aspect=1) # ax.autoscale(enable=True, axis='both', tight=True) # im = ax.tripcolor(x[:, 0], x[:, 1], bezier.tri, TT, shading='gouraud', cmap='jet') # ax.add_collection( # collections.LineCollection(np.array([x[:, 0], x[:, 1]]).T[bezier.hull], colors='k', linewidths=0.2, # alpha=0.2)) # fig.colorbar(im) # # with export.mplfigure('temperature1d.png', dpi=800) as plt: # ax = plt.subplots() # ax.set(xlabel='Distance [m]', ylabel='Temperature [K]') # ax.plot(nanjoin(x[:, 0], bezier.tri)[::100], nanjoin(TT, bezier.tri)[::100], label="FEM") # ax.plot(x[:, 0][::100], # np.array(Tanalyticaldrawdown(ns, t1, x[:, 0][::100]))[0][0][0], # label="analytical") # ax.legend(loc="center right") # plotovertime(timeperiod, parraywell, parrayexact, Tarraywell, Tarrayexact, Qarray) # mesh # bezier = plottopo.sample('bezier', 2) # # with export.mplfigure('mesh.png', dpi=800) as fig: # r, z, col = bezier.eval([ns.r, ns.z, 1]) # ax = fig.add_subplot(1, 1, 1) # ax.tripcolor(r, z, bezier.tri, col, shading='gouraud', rasterized=True) # ax.add_collection( # collections.LineCollection(np.array([r, z]).T[bezier.hull], colors='k', linewidths=0.2, # alpha=0.2)) # export.vtk('aquifer', bezier.tri, bezier.eval(ns.x)) #export # break plhs0 = plhs.copy() Tlhs0 = Tlhs.copy() # 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(x[:, 0], x[:, 1], bezier.tri, p, shading='gouraud', cmap='jet') # ax.add_collection( # collections.LineCollection(np.array([x[:, 0], x[:, 1]]).T[bezier.hull], colors='k', # linewidths=0.2, # alpha=0.2)) # fig.colorbar(im) return parraywell, Tarraywell # with treelog.iter.plain( # 'timestep', solver.impliciteuler(['lhsp', 'lhsT'], (respd, resT), (pinertia, Tinertia), timetarget='t', timestep=timestep, arguments=dict(lhsp=pdofs0, lhsT=Tdofs0), constrain=(dict(lhsp=consp, lhsT=consT)), newtontol=newtontol)) as steps: # # for istep, lhs in enumerate(steps): # time = istep * timestep # print("time", time) # # # define analytical solution # pex = panalyticaldrawdown(ns, time) # # # # define analytical solution # Tex = Tanalyticaldrawdown(ns, time) # # x, r, z, p, u, p0, T = bezier.eval( # [ns.x, ns.r, ns.z, ns.p, function.norm2(ns.u), ns.p0, ns.T], lhsp=lhs["lhsp"], lhsT = lhs["lhsT"]) # # parraywell[istep] = p.take(bezier.tri.T, 0)[1][0] # parrayexact[istep] = pex # Qarray[istep] = ns.Jw.eval() # print("flux in analytical", ns.Jw.eval()) # # print("flux in FEM", ns.respwell.eval()) # # print("flux in fem", sum(respwell.eval())) # # parrayexp[istep] = get_welldata("PRESSURE")[istep]/10 # # print("well pressure ", parraywell[istep]) # print("exact well pressure", pex) # # print("data well pressure", parrayexp[istep]) # # Tarraywell[istep] = T.take(bezier.tri.T, 0)[1][0] # Tarrayexact[istep] = Tex # Tarrayexp[istep] = get_welldata("TEMPERATURE")[istep]+273 # # print("well temperature ", Tarraywell[istep]) # print("exact well temperature", Tex) # # print("data well temperature", Tarrayexp[istep]) # # if time >= t1endtime: # # 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, z, bezier.tri, p, shading='gouraud', cmap='jet') # ax.add_collection( # collections.LineCollection(np.array([r, z]).T[bezier.hull], colors='k', linewidths=0.2, # alpha=0.2)) # fig.colorbar(im) # # with export.mplfigure('pressure1d.png', dpi=800) as plt: # ax = plt.subplots() # ax.set(xlabel='Distance [m]', ylabel='Pressure [MPa]') # print("pressure array", p.take(bezier.tri.T, 0)) # ax.plot(r.take(bezier.tri.T, 0), p.take(bezier.tri.T, 0)) # # with export.mplfigure('temperature.png', dpi=800) as fig: # ax = fig.add_subplot(111, title='temperature', aspect=1) # ax.autoscale(enable=True, axis='both', tight=True) # im = ax.tripcolor(r, z, bezier.tri, T, shading='gouraud', cmap='jet') # ax.add_collection( # collections.LineCollection(np.array([r, z]).T[bezier.hull], colors='k', linewidths=0.2, # alpha=0.2)) # fig.colorbar(im) # # with export.mplfigure('temperature1d.png', dpi=800) as plt: # ax = plt.subplots() # ax.set(xlabel='Distance [m]', ylabel='Temperature [K]') # ax.plot(r.take(bezier.tri.T, 0), T.take(bezier.tri.T, 0)) # # uniform = plottopo.sample('uniform', 1) # r_, z_, uv = uniform.eval( # [ns.r, ns.z, ns.u], lhsp=lhs["lhsp"]) # # 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, z, bezier.tri, u, shading='gouraud', cmap='jet') # ax.quiver(r_, z_, uv[:, 0], uv[:, 1], angles='xy', scale_units='xy') # fig.colorbar(im) # # with export.mplfigure('pressuretime.png', dpi=800) as plt: # ax1 = plt.subplots() # ax2 = ax1.twinx() # ax1.set(xlabel='Time [s]') # ax1.set_ylabel('Pressure [MPa]', color='b') # ax2.set_ylabel('Volumetric flow rate [m^3/s]', color='k') # ax1.plot(timeperiod, parraywell/1e6, 'bo', label="FEM") # ax1.plot(timeperiod, parrayexact/1e6, label="analytical") # # ax1.plot(timeperiod, parrayexp, label="NLOG") # ax1.legend(loc="center right") # ax2.plot(timeperiod, Qarray, 'k') # # with export.mplfigure('temperaturetime.png', dpi=800) as plt: # ax = plt.subplots() # ax.set(xlabel='Time [s]', ylabel='Temperature [K]') # ax.plot(timeperiod, Tarraywell, 'ro') # ax.plot(timeperiod, Tarrayexact) # # ax.plot(timeperiod, Tarrayexp) # # break # # with treelog.iter.plain( # 'timestep', solver.impliciteuler(['lhsp', 'lhsT'], (respb, resT2), (pinertia, Tinertia), timestep=timestep, # arguments=dict(lhsp=lhs["lhsp"], lhsT=lhs["lhsT"]), constrain=(dict(lhsp=consp, lhsT=consT)), # newtontol=newtontol)) as steps: # # 'timestep', solver.impliciteuler(('lhsp'), resp2, pinertia, timestep=timestep, arguments=dict(lhsp=lhsp), constrain=consp, newtontol=1e-2)) as steps: # time = 0 # istep = 0 # # for istep, lhs2 in enumerate(steps): # time = istep * timestep # # # define analytical solution # pex2 = panalyticalbuildup(ns, time, pex) # # # define analytical solution # Tex2 = Tanalyticalbuildup(ns, time, Tex) # # x, r, z, p, u, p0, T = bezier.eval( # [ns.x, ns.r, ns.z, ns.p, function.norm2(ns.u), ns.p0, ns.T], lhsp=lhs2["lhsp"], lhsT=lhs2["lhsT"]) # # parraywell[N+istep] = p.take(bezier.tri.T, 0)[1][0] # Qarray[N+istep] = 0 # parrayexact[N+istep] = pex2 # # print(get_welldata("PRESSURE")[213+istep]/10) # # parrayexp[N+istep] = get_welldata("PRESSURE")[212+istep]/10 # # print("well pressure ", parraywell[N+istep]) # print("exact well pressure", pex2) # # print("data well pressure", parrayexp[N+istep]) # # Tarraywell[N+istep] = T.take(bezier.tri.T, 0)[1][0] # Tarrayexact[N+istep] = Tex2 # # Tarrayexp[N+istep] = get_welldata("TEMPERATURE")[212+istep]+273 # # print("well temperature ", Tarraywell[N+istep]) # print("exact well temperature", Tex2) # # print("data well temperature", Tarrayexp[N+istep]) # # if time >= t1endtime: # # 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, z, bezier.tri, p, shading='gouraud', cmap='jet') # ax.add_collection( # collections.LineCollection(np.array([r, z]).T[bezier.hull], colors='k', linewidths=0.2, # alpha=0.2)) # fig.colorbar(im) # # with export.mplfigure('pressure1d.png', dpi=800) as plt: # ax = plt.subplots() # ax.set(xlabel='Distance [m]', ylabel='Pressure [MPa]') # ax.plot(r.take(bezier.tri.T, 0), p.take(bezier.tri.T, 0)) # # uniform = plottopo.sample('uniform', 1) # r_, z_, uv = uniform.eval( # [ns.r, ns.z, ns.u], lhsp=lhs2["lhsp"]) # # with export.mplfigure('temperature.png', dpi=800) as fig: # ax = fig.add_subplot(111, title='temperature', aspect=1) # ax.autoscale(enable=True, axis='both', tight=True) # im = ax.tripcolor(r, z, bezier.tri, T, shading='gouraud', cmap='jet') # ax.add_collection( # collections.LineCollection(np.array([r, z]).T[bezier.hull], colors='k', linewidths=0.2, # alpha=0.2)) # fig.colorbar(im) # # with export.mplfigure('temperature1d.png', dpi=800) as plt: # ax = plt.subplots() # ax.set(xlabel='Distance [m]', ylabel='Temperature [K]') # ax.plot(r.take(bezier.tri.T, 0), T.take(bezier.tri.T, 0)) # # 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, z, bezier.tri, u, shading='gouraud', cmap='jet') # ax.quiver(r_, z_, uv[:, 0], uv[:, 1], angles='xy', scale_units='xy') # fig.colorbar(im) # # with export.mplfigure('pressuretimebuildup.png', dpi=800) as plt: # ax1 = plt.subplots() # ax2 = ax1.twinx() # ax1.set(xlabel='Time [s]') # ax1.set_ylabel('Pressure [MPa]', color='b') # ax2.set_ylabel('Volumetric flow rate [m^3/s]', color='k') # ax1.plot(timeperiod, parraywell/1e6, 'bo', label="FEM") # ax1.plot(timeperiod, parrayexact/1e6, label="analytical") # # ax1.plot(timeperiod, parrayexp, label="NLOG") # ax1.legend(loc="center right") # ax2.plot(timeperiod, Qarray, 'k') # # with export.mplfigure('temperaturetime.png', dpi=800) as plt: # ax1 = plt.subplots() # ax2 = ax1.twinx() # ax1.set(xlabel='Time [s]') # ax1.set_ylabel('Temperature [K]', color='r') # ax2.set_ylabel('Volumetric flow rate [m^3/s]', color='k') # ax1.plot(timeperiod, Tarraywell, 'ro', label="FEM") # ax1.plot(timeperiod, Tarrayexact, 'r', label="analytical") # # ax1.plot(timeperiod, Tarrayexp, label="NLOG") # ax1.legend(loc="lower right") # ax2.plot(timeperiod, Qarray, 'k') # # # export.vtk('aquifer', bezier.tri, bezier.eval(ns.x)) # # break return
def test_direct(self): with self.assertRaises(solver.SolverError): self.assert_resnorm( solver.solve_linear('dofs', residual=self.residual, constrain=self.cons))
def main( L: 'domain size' = 4., R: 'hole radius' = 1., E: "young's modulus" = 1e5, nu: "poisson's ratio" = 0.3, T: 'far field traction' = 10, nr: 'number of h-refinements' = 2, figures: 'create figures' = True, ): ns = function.Namespace() ns.lmbda = E*nu/(1-nu**2) ns.mu = .5*E/(1+nu) # create the coarsest level parameter domain domain0, geometry = mesh.rectilinear( [1,2] ) # create the second-order B-spline basis over the coarsest domain ns.bsplinebasis = domain0.basis('spline', degree=2) ns.controlweights = 1,.5+.5/2**.5,.5+.5/2**.5,1,1,1,1,1,1,1,1,1 ns.weightfunc = 'bsplinebasis_n controlweights_n' ns.nurbsbasis = ns.bsplinebasis * ns.controlweights / ns.weightfunc # create the isogeometric map ns.controlpoints = [0,R],[R*(1-2**.5),R],[-R,R*(2**.5-1)],[-R,0],[0,.5*(R+L)],[-.15*(R+L),.5*(R+L)],[-.5*(R+L),.15*(R*L)],[-.5*(R+L),0],[0,L],[-L,L],[-L,L],[-L,0] ns.x_i = 'nurbsbasis_n controlpoints_ni' # create the computational domain by h-refinement domain = domain0.refine(nr) # create the second-order B-spline basis over the refined domain ns.bsplinebasis = domain.basis('spline', degree=2) sqr = domain.integral('(bsplinebasis_n ?lhs_n - weightfunc)^2' @ ns, geometry=ns.x, degree=9) ns.controlweights = solver.optimize('lhs', sqr) ns.nurbsbasis = ns.bsplinebasis * ns.controlweights / ns.weightfunc # prepare the displacement field ns.ubasis = ns.nurbsbasis.vector(2) ns.u_i = 'ubasis_ni ?lhs_n' ns.strain_ij = '(u_i,j + u_j,i) / 2' ns.stress_ij = 'lmbda strain_kk δ_ij + 2 mu strain_ij' # construct the exact solution ns.r2 = 'x_k x_k' ns.R2 = R**2 ns.T = T ns.k = (3-nu) / (1+nu) # plane stress parameter ns.uexact_i = '''(T / 4 mu) <x_0 ((k + 1) / 2 + (1 + k) R2 / r2 + (1 - R2 / r2) (x_0^2 - 3 x_1^2) R2 / r2^2), x_1 ((k - 3) / 2 + (1 - k) R2 / r2 + (1 - R2 / r2) (3 x_0^2 - x_1^2) R2 / r2^2)>_i''' ns.strainexact_ij = '(uexact_i,j + uexact_j,i) / 2' ns.stressexact_ij = 'lmbda strainexact_kk δ_ij + 2 mu strainexact_ij' # define the linear and bilinear forms res = domain.integral('ubasis_ni,j stress_ij' @ ns, geometry=ns.x, degree=9) res -= domain.boundary['right'].integral('ubasis_ni stressexact_ij n_j' @ ns, geometry=ns.x, degree=9) # compute the constraints vector for the symmetry conditions sqr = domain.boundary['top,bottom'].integral('(u_i n_i)^2' @ ns, degree=9) cons = solver.optimize('lhs', sqr, droptol=1e-15) # solve the system of equations lhs = solver.solve_linear('lhs', res, constrain=cons) ns = ns(lhs=lhs) # post-processing if figures: geom, stressxx = domain.simplex.elem_eval([ns.x, 'stress_00' @ ns], ischeme='bezier8', separate=True) with plot.PyPlot( 'solution', index=nr ) as plt: plt.mesh( geom, stressxx ) plt.colorbar() # compute the L2-norm of the error in the stress err = domain.integrate('(?dstress_ij ?dstress_ij)(dstress_ij = stress_ij - stressexact_ij)' @ ns, geometry=ns.x, ischeme='gauss9')**.5 # compute the mesh parameter (maximum physical distance between knots) hmax = max(numpy.linalg.norm(v[:,numpy.newaxis]-v, axis=2).max() for v in domain.elem_eval(ns.x, ischeme='bezier2', separate=True)) return err, hmax
def 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
def main(inflow: 'inflow velocity' = 10, viscosity: 'kinematic viscosity' = 1.0, density: 'density' = 1.0, theta=0.5, timestepsize=0.01): # mesh and geometry definition grid_x_1 = numpy.linspace(-3, -1, 7) grid_x_1 = grid_x_1[:-1] grid_x_2 = numpy.linspace(-1, -0.3, 8) grid_x_2 = grid_x_2[:-1] grid_x_3 = numpy.linspace(-0.3, 0.3, 13) grid_x_3 = grid_x_3[:-1] grid_x_4 = numpy.linspace(0.3, 1, 8) grid_x_4 = grid_x_4[:-1] grid_x_5 = numpy.linspace(1, 3, 7) grid_x = numpy.concatenate( (grid_x_1, grid_x_2, grid_x_3, grid_x_4, grid_x_5), axis=None) grid_y_1 = numpy.linspace(0, 1.5, 16) grid_y_1 = grid_y_1[:-1] grid_y_2 = numpy.linspace(1.5, 2, 4) grid_y_2 = grid_y_2[:-1] grid_y_3 = numpy.linspace(2, 4, 7) grid_y = numpy.concatenate((grid_y_1, grid_y_2, grid_y_3), axis=None) grid = [grid_x, grid_y] topo, geom = mesh.rectilinear(grid) domain = topo.withboundary(inflow='left', wall='top,bottom', outflow='right') - \ topo[18:20, :10].withboundary(flap='left,right,top') # Nutils namespace ns = function.Namespace() # time approximations # TR interpolation ns._functions['t'] = lambda f: theta * f + (1 - theta) * subs0(f) ns._functions_nargs['t'] = 1 # 1st order FD ns._functions['δt'] = lambda f: (f - subs0(f)) / dt ns._functions_nargs['δt'] = 1 # 2nd order FD ns._functions['tt'] = lambda f: (1.5 * f - 2 * subs0(f) + 0.5 * subs00(f) ) / dt ns._functions_nargs['tt'] = 1 # extrapolation for pressure ns._functions['tp'] = lambda f: (1.5 * f - 0.5 * subs0(f)) ns._functions_nargs['tp'] = 1 ns.nu = viscosity ns.rho = density ns.uin = inflow ns.x0 = geom # reference geometry ns.dbasis = domain.basis('std', degree=1).vector(2) ns.d_i = 'dbasis_ni ?meshdofs_n' ns.umesh_i = 'dbasis_ni (1.5 ?meshdofs_n - 2 ?oldmeshdofs_n + 0.5 ?oldoldmeshdofs_n ) / ?dt' ns.x_i = 'x0_i + d_i' # moving geometry ns.ubasis, ns.pbasis = function.chain([ domain.basis('std', degree=2).vector(2), domain.basis('std', degree=1), ]) ns.F_i = 'ubasis_ni ?F_n' # stress field ns.urel_i = 'ubasis_ni ?lhs_n' # relative velocity ns.u_i = 'umesh_i + urel_i' # total velocity ns.p = 'pbasis_n ?lhs_n' # pressure # initialization of dofs meshdofs = numpy.zeros(len(ns.dbasis)) oldmeshdofs = meshdofs oldoldmeshdofs = meshdofs oldoldoldmeshdofs = meshdofs lhs0 = numpy.zeros(len(ns.ubasis)) # for visualization bezier = domain.sample('bezier', 2) # preCICE setup configFileName = "../precice-config.xml" participantName = "Fluid" solverProcessIndex = 0 solverProcessSize = 1 interface = precice.Interface(participantName, configFileName, solverProcessIndex, solverProcessSize) # define coupling meshes meshName = "Fluid-Mesh" meshID = interface.get_mesh_id(meshName) couplinginterface = domain.boundary['flap'] couplingsample = couplinginterface.sample( 'gauss', degree=2) # mesh located at Gauss points dataIndices = interface.set_mesh_vertices(meshID, couplingsample.eval(ns.x0)) # coupling data writeData = "Force" readData = "Displacement" writedataID = interface.get_data_id(writeData, meshID) readdataID = interface.get_data_id(readData, meshID) # initialize preCICE precice_dt = interface.initialize() dt = min(precice_dt, timestepsize) # boundary conditions for fluid equations sqr = domain.boundary['wall,flap'].integral('urel_k urel_k d:x0' @ ns, degree=4) cons = solver.optimize('lhs', sqr, droptol=1e-15) sqr = domain.boundary['inflow'].integral( '((urel_0 - uin)^2 + urel_1^2) d:x0' @ ns, degree=4) cons = solver.optimize('lhs', sqr, droptol=1e-15, constrain=cons) # weak form fluid equations res = domain.integral('t(ubasis_ni,j (u_i,j + u_j,i) rho nu d:x)' @ ns, degree=4) res += domain.integral('(-ubasis_ni,j p δ_ij + pbasis_n u_k,k) d:x' @ ns, degree=4) res += domain.integral('rho ubasis_ni δt(u_i d:x)' @ ns, degree=4) res += domain.integral('rho ubasis_ni t(u_i,j urel_j d:x)' @ ns, degree=4) # weak form for force computation resF = domain.integral('(ubasis_ni,j (u_i,j + u_j,i) rho nu d:x)' @ ns, degree=4) resF += domain.integral('tp(-ubasis_ni,j p δ_ij d:x)' @ ns, degree=4) resF += domain.integral('pbasis_n u_k,k d:x' @ ns, degree=4) resF += domain.integral('rho ubasis_ni tt(u_i d:x)' @ ns, degree=4) resF += domain.integral('rho ubasis_ni (u_i,j urel_j d:x)' @ ns, degree=4) resF += couplinginterface.sample('gauss', 4).integral('ubasis_ni F_i d:x' @ ns) consF = numpy.isnan( solver.optimize('F', couplinginterface.sample('gauss', 4).integral('F_i F_i' @ ns), droptol=1e-10)) # boundary conditions mesh displacements sqr = domain.boundary['inflow,outflow,wall'].integral('d_i d_i' @ ns, degree=2) meshcons0 = solver.optimize('meshdofs', sqr, droptol=1e-15) # weak form mesh displacements meshsqr = domain.integral('d_i,x0_j d_i,x0_j d:x0' @ ns, degree=2) # better initial guess: start from Stokes solution, comment out for comparison with other solvers #res_stokes = domain.integral('(ubasis_ni,j ((u_i,j + u_j,i) rho nu - p δ_ij) + pbasis_n u_k,k) d:x' @ ns, degree=4) #lhs0 = solver.solve_linear('lhs', res_stokes, constrain=cons, arguments=dict(meshdofs=meshdofs, oldmeshdofs=oldmeshdofs, oldoldmeshdofs=oldoldmeshdofs, oldoldoldmeshdofs=oldoldoldmeshdofs, dt=dt)) lhs00 = lhs0 timestep = 0 t = 0 while interface.is_coupling_ongoing(): # read displacements from interface if interface.is_read_data_available(): readdata = interface.read_block_vector_data( readdataID, dataIndices) coupledata = couplingsample.asfunction(readdata) sqr = couplingsample.integral(((ns.d - coupledata)**2).sum(0)) meshcons = solver.optimize('meshdofs', sqr, droptol=1e-15, constrain=meshcons0) meshdofs = solver.optimize('meshdofs', meshsqr, constrain=meshcons) # save checkpoint if interface.is_action_required( precice.action_write_iteration_checkpoint()): lhs_checkpoint = lhs0 lhs00_checkpoint = lhs00 t_checkpoint = t timestep_checkpoint = timestep oldmeshdofs_checkpoint = oldmeshdofs oldoldmeshdofs_checkpoint = oldoldmeshdofs oldoldoldmeshdofs_checkpoint = oldoldoldmeshdofs interface.mark_action_fulfilled( precice.action_write_iteration_checkpoint()) # solve fluid equations lhs1 = solver.newton( 'lhs', res, lhs0=lhs0, constrain=cons, arguments=dict( lhs0=lhs0, dt=dt, meshdofs=meshdofs, oldmeshdofs=oldmeshdofs, oldoldmeshdofs=oldoldmeshdofs, oldoldoldmeshdofs=oldoldoldmeshdofs)).solve(tol=1e-6) # write forces to interface if interface.is_write_data_required(dt): F = solver.solve_linear('F', resF, constrain=consF, arguments=dict( lhs00=lhs00, lhs0=lhs0, lhs=lhs1, dt=dt, meshdofs=meshdofs, oldmeshdofs=oldmeshdofs, oldoldmeshdofs=oldoldmeshdofs, oldoldoldmeshdofs=oldoldoldmeshdofs)) # writedata = couplingsample.eval(ns.F, F=F) # for stresses writedata = couplingsample.eval('F_i d:x' @ ns, F=F, meshdofs=meshdofs) * \ numpy.concatenate([p.weights for p in couplingsample.points])[:, numpy.newaxis] interface.write_block_vector_data(writedataID, dataIndices, writedata) # do the coupling precice_dt = interface.advance(dt) dt = min(precice_dt, timestepsize) # advance variables timestep += 1 t += dt lhs00 = lhs0 lhs0 = lhs1 oldoldoldmeshdofs = oldoldmeshdofs oldoldmeshdofs = oldmeshdofs oldmeshdofs = meshdofs # read checkpoint if required if interface.is_action_required( precice.action_read_iteration_checkpoint()): lhs0 = lhs_checkpoint lhs00 = lhs00_checkpoint t = t_checkpoint timestep = timestep_checkpoint oldmeshdofs = oldmeshdofs_checkpoint oldoldmeshdofs = oldoldmeshdofs_checkpoint oldoldoldmeshdofs = oldoldoldmeshdofs_checkpoint interface.mark_action_fulfilled( precice.action_read_iteration_checkpoint()) if interface.is_time_window_complete(): x, u, p = bezier.eval(['x_i', 'u_i', 'p'] @ ns, lhs=lhs1, meshdofs=meshdofs, oldmeshdofs=oldmeshdofs, oldoldmeshdofs=oldoldmeshdofs, oldoldoldmeshdofs=oldoldoldmeshdofs, dt=dt) with treelog.add(treelog.DataLog()): export.vtk('Fluid_' + str(timestep), bezier.tri, x, u=u, p=p) interface.finalize()
def main(fname: str, degree: int, Δuload: unit['mm'], nsteps: int, E: unit['GPa'], nu: float, σyield: unit['MPa'], hardening: unit['GPa'], referencedata: typing.Optional[str], testing: bool): ''' Plastic deformation of a perforated strip. .. arguments:: fname [strip.msh] Mesh file with units in [mm] degree [1] Finite element interpolation order Δuload [5μm] Load boundary displacement steps nsteps [11] Number of load steps E [70GPa] Young's modulus nu [0.2] Poisson ratio σyield [243MPa] Yield strength hardening [2.25GPa] Hardening parameter referencedata [zienkiewicz.csv] Reference data file name testing [False] Use a 1 element mesh for testing .. presets:: testing nsteps=30 referencedata= testing=True ''' # We commence with reading the mesh from the specified GMSH file, or, alternatively, # we continue with a single-element mesh for testing purposes. domain, geom = mesh.gmsh(pathlib.Path(__file__).parent/fname) Wdomain, Hdomain = domain.boundary['load'].integrate([function.J(geom),geom[1]*function.J(geom)], degree=1) Hdomain /= Wdomain if testing: domain, geom = mesh.rectilinear([numpy.linspace(0,Wdomain/2,2),numpy.linspace(0,Hdomain,2)]) domain = domain.withboundary(hsymmetry='left', vsymmetry='bottom', load='top') # We next initiate the point set in which the constitutive bahvior will be evaluated. # Note that this is also the point set in which the history variable will be stored. gauss = domain.sample('gauss', 2) # Elasto-plastic formulation # ========================== # The weak formulation is constructed using a `Namespace`, which is initialized and # populated with the necessary problem parameters and coordinate system. Note that the # coefficients `mu` and `λ` have been defined such that 'C_{ijkl}' is the fourth-order # **plane stress** elasticity tensor. ns = function.Namespace(fallback_length=domain.ndims) ns.x = geom ns.Δuload = Δuload ns.mu = E/(1-nu**2)*((1-nu)/2) ns.λ = E/(1-nu**2)*nu ns.delta = function.eye(domain.ndims) ns.C_ijkl = 'mu ( delta_ik delta_jl + delta_il delta_jk ) + λ delta_ij delta_kl' # We make use of a Lagrange finite element basis of arbitrary `degree`. Since we # approximate the displacement field, the basis functions are vector-valued. Both # the displacement field at the current load step `u` and displacement field of the # previous load step `u0` are approximated using this basis: ns.basis = domain.basis('std',degree=degree).vector(domain.ndims) ns.u0_i = 'basis_ni ?lhs0_n' ns.u_i = 'basis_ni ?lhs_n' # This simulation is based on a standard elasto-plasticity model. In this # model the *total strain*, ε_kl = ½ (∂u_k/∂x_l + ∂u_l/∂x_k)', is comprised of an # elastic and a plastic part: # # ε_kl = εe_kl + εp_kl # # The stress is related to the *elastic strain*, εe_kl, through Hooke's law: # # σ_ij = C_ijkl εe_kl = C_ijkl (ε_kl - εp_kl) ns.ε_kl = '(u_k,l + u_l,k) / 2' ns.ε0_kl = '(u0_k,l + u0_l,k) / 2' ns.gbasis = gauss.basis() ns.εp0_ij = 'gbasis_n ?εp0_nij' ns.κ0 = 'gbasis_n ?κ0_n' ns.εp = PlasticStrain(ns.ε, ns.ε0, ns.εp0, ns.κ0, E, nu, σyield, hardening) ns.εe_ij = 'ε_ij - εp_ij' ns.σ_ij = 'C_ijkl εe_kl' # Note that the plasticity model is implemented through the user-defined function `PlasticStrain`, # which implements the actual yielding model including a standard return mapping algorithm. This # function is discussed in detail below. # # The components of the residual vector are then defined as: # # r_n = ∫_Ω (∂N_ni/∂x_j) σ_ij dΩ res = domain.integral('basis_ni,j σ_ij d:x' @ ns, degree=2) # The problem formulation is completed by supplementing prescribed displacement boundary conditions # (for a load step), which are computed in the standard manner: sqr = domain.boundary['hsymmetry,vsymmetry'].integral('(u_k n_k)^2 d:x' @ ns, degree=2) sqr += domain.boundary['load'].integral('(u_k n_k - Δuload)^2 d:x' @ ns, degree=2) cons = solver.optimize('lhs', sqr, droptol=1e-15) # Incremental-iterative solution procedure # ======================================== # We initialize the solution vector for the first load step `lhs` and solution vector # of the previous load step `lhs0`. Note that, in order to construct a predictor step # for the first load step, we define the previous load step state as the solution # vector corresponding to a negative elastic loading step. lhs0 = -solver.solve_linear('lhs', domain.integral('basis_ni,j C_ijkl ε_kl d:x' @ ns, degree=2), constrain=cons) lhs = numpy.zeros_like(lhs0) εp0 = numpy.zeros((gauss.npoints,)+ns.εp0.shape) κ0 = numpy.zeros((gauss.npoints,)+ns.κ0.shape) # To store the force-dispalcement data we initialize an empty data array with the # inital state solution substituted in the first row. fddata = numpy.empty(shape=(nsteps+1,2)) fddata[:] = numpy.nan fddata[0,:] = 0 # Load step incrementation # ------------------------ with treelog.iter.fraction('step', range(nsteps)) as counter: for step in counter: # The solution of the previous load step is set to `lhs0`, and the Newton solution # procedure is initialized by extrapolation of the state vector: lhs_init = lhs + (lhs-lhs0) lhs0 = lhs # The non-linear system of equations is solved for `lhs` using Newton iterations, # where the `step` variable is used to scale the incremental constraints. lhs = solver.newton(target='lhs', residual=res, constrain=cons*step, lhs0=lhs_init, arguments={'lhs0':lhs0,'εp0':εp0,'κ0':κ0}).solve(tol=1e-6) # The computed solution is post-processed in the form of a loading curve - which # plots the normalized mean stress versus the maximum 'ε_{yy}' strain # component - and a contour plot showing the 'σ_{yy}' stress component on a # deformed mesh. Note that since the stresses are defined in the integration points # only, a post-processing step is involved that transfers the stress information to # the nodal points. εyymax = gauss.eval(ns.ε[1,1], arguments=dict(lhs=lhs)).max() basis = domain.basis('std', degree=1) bw, b = domain.integrate([basis * ns.σ[1,1] * function.J(geom), basis * function.J(geom)], degree=2, arguments=dict(lhs=lhs,lhs0=lhs0,εp0=εp0,κ0=κ0)) σyy = basis.dot(bw / b) uyload, σyyload = domain.boundary['load'].integrate(['u_1 d:x'@ns,σyy * function.J(geom)], degree=2, arguments=dict(lhs=lhs,εp0=εp0,κ0=κ0)) uyload /= Wdomain σyyload /= Wdomain fddata[step,0] = (E*εyymax)/σyield fddata[step,1] = (σyyload*2)/σyield with export.mplfigure('forcedisp.png') as fig: ax = fig.add_subplot(111, xlabel=r'${E \cdot {\rm max}(\varepsilon_{yy})}/{\sigma_{\rm yield}}$', ylabel=r'${\sigma_{\rm mean}}/{\sigma_{\rm yield}}$') if referencedata: data = numpy.genfromtxt(pathlib.Path(__file__).parent/referencedata, delimiter=',', skip_header=1) ax.plot(data[:,0], data[:,1], 'r:', label='Reference') ax.legend() ax.plot(fddata[:,0], fddata[:,1], 'o-', label='Nutils') ax.grid() bezier = domain.sample('bezier', 3) points, uvals, σyyvals = bezier.eval(['(x_i + 25 u_i)' @ ns, ns.u, σyy], arguments=dict(lhs=lhs)) with export.mplfigure('stress.png') as fig: ax = fig.add_subplot(111, aspect='equal', xlabel=r'$x$ [mm]', ylabel=r'$y$ [mm]') im = ax.tripcolor(points[:,0]/unit('mm'), points[:,1]/unit('mm'), bezier.tri, σyyvals/unit('MPa'), shading='gouraud', cmap='jet') ax.add_collection(collections.LineCollection(points.take(bezier.hull, axis=0), colors='k', linewidths=.1)) ax.autoscale(enable=True, axis='both', tight=True) cb = fig.colorbar(im) im.set_clim(0, 1.2*σyield) cb.set_label(r'$σ_{yy}$ [MPa]') # Load step convergence # --------------------- # At the end of the loading step, the plastic strain state and history parameter are updated, # where use if made of the strain hardening relation for the history variable: # # Δκ = √(Δεp_ij Δεp_ij) Δεp = ns.εp-ns.εp0 Δκ = function.sqrt((Δεp*Δεp).sum((0,1))) κ0 = gauss.eval(ns.κ0+Δκ, arguments=dict(lhs0=lhs0,lhs=lhs,εp0=εp0,κ0=κ0)) εp0 = gauss.eval(ns.εp, arguments=dict(lhs0=lhs0,lhs=lhs,εp0=εp0,κ0=κ0))
def main(X: unit['m'], Y: unit['m'], l0: unit['m'], degree: int, du: unit['m']): ''' Mechanical test case .. arguments:: X [0.5mm] Domain size in x direction. Y [0.04mm] Domain size in y direction. l0 [0.015mm] Charateristic length scale. degree [1] Polynomial degree of the approximation. du [0.01mm] Applied displacement. ''' assert degree > 0 # create the mesh topo, geom = mesh.rectilinear([ numpy.linspace(0.001, 0.001 + X, 31), numpy.linspace(0.001, 0.001 + Y, 11) ]) # prepare the integration and post processing samples ipoints = topo.sample('gauss', 2 * degree) bezier = topo.sample('bezier', 2 * degree) # initialize the namespace ns = function.Namespace() ns.x = geom ns.ubasis = topo.basis('th-spline', degree=degree).vector(topo.ndims) ns.dbasis = topo.basis('th-spline', degree=degree) ns.Hbasis = ipoints.basis() ns.u_i = 'ubasis_ni ?solu_n' ns.d = 'dbasis_n ?sold_n' ns.H0 = 'Hbasis_n ?solH0_n' ns.l0 = l0 ns.du = du # volume coupling fields ns.Gc = 'dbasis_n ?gcdofs_n' ns.lmbda = 'dbasis_n ?lmbdadofs_n' ns.mu = 'dbasis_n ?mudofs_n' # formulation ns.strain_ij = '( u_i,j + u_j,i ) / 2' ns.stress_ij = 'lmbda strain_kk δ_ij + 2 mu strain_ij' ns.psi = 'stress_ij strain_ij / 2' ns.H = function.max(ns.psi, ns.H0) ns.gamma = '( d^2 + l0^2 d_,i d_,i ) / (2 l0)' # boundary condition for displacement field sqru = topo.boundary['top'].integral('( u_i n_i - du )^2 d:x' @ ns, degree=degree * 2) sqru += topo.boundary['bottom'].integral('( u_i n_i )^2 d:x' @ ns, degree=degree * 2) sqru += topo.boundary['bottom'].boundary['left'].integral( 'u_i u_i d:x' @ ns, degree=degree * 2) consu = solver.optimize('solu', sqru, droptol=1e-12) # initialize the solution vectors solu = numpy.zeros(ns.ubasis.shape[0]) sold = numpy.zeros(ns.dbasis.shape[0]) solH0 = ipoints.eval(0.) # preCICE setup configFileName = "precice-config.xml" participantName = "BrittleFracture" solverProcessIndex = 0 solverProcessSize = 1 interface = precice.Interface(participantName, configFileName, solverProcessIndex, solverProcessSize) # define coupling mesh meshName = "BrittleFracture-Mesh" meshID = interface.get_mesh_id(meshName) couplingsample = topo.sample('gauss', degree=degree * 2) vertices = couplingsample.eval(ns.x) dataIndices = interface.set_mesh_vertices(meshID, vertices) lmbda = 121153.8e6 # First Lamé parameter in Pa mu = 80769.2e6 # Second Lamé parameter in Pa sqrl = couplingsample.integral((ns.lmbda - lmbda)**2) lmbdadofs = solver.optimize('lmbdadofs', sqrl, droptol=1e-12) sqrm = couplingsample.integral((ns.mu - mu)**2) mudofs = solver.optimize('mudofs', sqrm, droptol=1e-12) # coupling data gcID = interface.get_data_id("Gc", meshID) precice_dt = interface.initialize( ) # pseudo timestep size handled by preCICE nstep = 10000 # very high number of steps, end of simulation is steered by preCICE instead # time loop with treelog.iter.fraction('step', range(nstep)) as counter: for istep in counter: if not interface.is_coupling_ongoing(): break if interface.is_read_data_available(): gc = interface.read_block_scalar_data(gcID, dataIndices) gc_function = couplingsample.asfunction(gc) sqrg = couplingsample.integral((ns.Gc - gc_function)**2) gcdofs = solver.optimize('gcdofs', sqrg, droptol=1e-12) ############################ # Phase field problem # ############################ resd = ipoints.integral( '( Gc / l0 ) ( d dbasis_n + l0^2 d_,i dbasis_n,i ) d:x' @ ns) resd += ipoints.integral('2 H ( d - 1 ) dbasis_n d:x' @ ns) sold = solver.solve_linear('sold', resd, arguments={ 'solu': solu, 'solH0': solH0, 'lmbdadofs': lmbdadofs, 'mudofs': mudofs, 'gcdofs': gcdofs }) ############################ # Elasticity problem # ############################ resu = topo.integral('( 1 - d )^2 ubasis_ni,j stress_ij d:x' @ ns, degree=2 * degree) solu = solver.solve_linear('solu', resu, arguments={ 'sold': sold, 'lmbdadofs': lmbdadofs, 'mudofs': mudofs }, constrain=consu) # Update zero state and history field solH0 = ipoints.eval(ns.H, arguments={ 'solu': solu, 'solH0': solH0, 'lmbdadofs': lmbdadofs, 'mudofs': mudofs }) # do the coupling precice_dt = interface.advance(precice_dt) ############################ # Output # ############################ # element-averaged history field transforms = ipoints.transforms[0] indicator = function.kronecker( 1., axis=0, length=len(transforms), pos=function.TransformsIndexWithTail(transforms, function.TRANS).index) areas, integrals = ipoints.integrate( [indicator, indicator * ns.H], arguments={ 'solu': solu, 'solH0': solH0, 'lmbdadofs': lmbdadofs, 'mudofs': mudofs, 'gcdofs': gcdofs }) H = indicator.dot(integrals / areas) # evaluate fields points, dvals, uvals, lvals, mvals, gcvals = bezier.eval( ['x_i', 'd', 'u_i', 'lmbda', 'mu', 'Gc'] @ ns, arguments={ 'solu': solu, 'sold': sold, 'solH0': solH0, 'lmbdadofs': lmbdadofs, 'mudofs': mudofs, 'gcdofs': gcdofs }) Hvals = bezier.eval(H, arguments={'solu': solu, 'solH0': solH0}) with treelog.add(treelog.DataLog()): export.vtk('Solid_' + str(istep), bezier.tri, points, Gc=gcvals, D=dvals, U=uvals, H=Hvals) interface.finalize()
def main(nelems: int, etype: str, btype: str, degree: int): ''' Laplace problem on a unit square. .. arguments:: nelems [10] Number of elements along edge. etype [square] Type of elements (square/triangle/mixed). btype [std] Type of basis function (std/spline), availability depending on the selected element type. degree [1] Polynomial degree. ''' # A unit square domain is created by calling the # :func:`nutils.mesh.unitsquare` mesh generator, with the number of elements # along an edge as the first argument, and the type of elements ("square", # "triangle", or "mixed") as the second. The result is a topology object # ``domain`` and a vectored valued geometry function ``geom``. domain, geom = mesh.unitsquare(nelems, etype) # To be able to write index based tensor contractions, we need to bundle all # relevant functions together in a namespace. Here we add the geometry ``x``, # a scalar ``basis``, and the solution ``u``. The latter is formed by # contracting the basis with a to-be-determined solution vector ``?lhs``. ns = function.Namespace() ns.x = geom ns.basis = domain.basis(btype, degree=degree) ns.u = 'basis_n ?lhs_n' # We are now ready to implement the Laplace equation. In weak form, the # solution is a scalar field :math:`u` for which: # # .. math:: ∀ v: ∫_Ω \frac{dv}{dx_i} \frac{du}{dx_i} - ∫_{Γ_n} v f = 0. # # By linearity the test function :math:`v` can be replaced by the basis that # spans its space. The result is an integral ``res`` that evaluates to a # vector matching the size of the function space. res = domain.integral('d(basis_n, x_i) d(u, x_i) J(x)' @ ns, degree=degree * 2) res -= domain.boundary['right'].integral( 'basis_n cos(1) cosh(x_1) J(x)' @ ns, degree=degree * 2) # The Dirichlet constraints are set by finding the coefficients that minimize # the error: # # .. math:: \min_u ∫_{\Gamma_d} (u - u_d)^2 # # The resulting ``cons`` array holds numerical values for all the entries of # ``?lhs`` that contribute (up to ``droptol``) to the minimization problem. # All remaining entries are set to ``NaN``, signifying that these degrees of # freedom are unconstrained. sqr = domain.boundary['left'].integral('u^2 J(x)' @ ns, degree=degree * 2) sqr += domain.boundary['top'].integral( '(u - cosh(1) sin(x_0))^2 J(x)' @ ns, degree=degree * 2) cons = solver.optimize('lhs', sqr, droptol=1e-15) # The unconstrained entries of ``?lhs`` are to be determined such that the # residual vector evaluates to zero in the corresponding entries. This step # involves a linearization of ``res``, resulting in a jacobian matrix and # right hand side vector that are subsequently assembled and solved. The # resulting ``lhs`` array matches ``cons`` in the constrained entries. lhs = solver.solve_linear('lhs', res, constrain=cons) # Once all entries of ``?lhs`` are establised, the corresponding solution can # be vizualised by sampling values of ``ns.u`` along with physical # coordinates ``ns.x``, with the solution vector provided via the # ``arguments`` dictionary. The sample members ``tri`` and ``hull`` provide # additional inter-point information required for drawing the mesh and # element outlines. bezier = domain.sample('bezier', 9) x, u = bezier.eval(['x', 'u'] @ ns, lhs=lhs) export.triplot('solution.png', x, u, tri=bezier.tri, hull=bezier.hull) # To confirm that our computation is correct, we use our knowledge of the # analytical solution to evaluate the L2-error of the discrete result. err = domain.integral('(u - sin(x_0) cosh(x_1))^2 J(x)' @ ns, degree=degree * 2).eval(lhs=lhs)**.5 treelog.user('L2 error: {:.2e}'.format(err)) return cons, lhs, err
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-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( L: 'domain size' = 4., R: 'hole radius' = 1., E: "young's modulus" = 1e5, nu: "poisson's ratio" = 0.3, T: 'far field traction' = 10, nr: 'number of h-refinements' = 2, withplots: 'create plots' = True, ): ns = function.Namespace() ns.lmbda = E * nu / (1 - nu**2) ns.mu = .5 * E / (1 + nu) # create the coarsest level parameter domain domain0, geometry = mesh.rectilinear([1, 2]) # create the second-order B-spline basis over the coarsest domain ns.bsplinebasis = domain0.basis('spline', degree=2) ns.controlweights = 1, .5 + .5 / 2**.5, .5 + .5 / 2**.5, 1, 1, 1, 1, 1, 1, 1, 1, 1 ns.weightfunc = 'bsplinebasis_n controlweights_n' ns.nurbsbasis = ns.bsplinebasis * ns.controlweights / ns.weightfunc # create the isogeometric map ns.controlpoints = [0, R], [R * (1 - 2**.5), R], [-R, R * (2**.5 - 1)], [ -R, 0 ], [0, .5 * (R + L)], [-.15 * (R + L), .5 * (R + L)], [-.5 * (R + L), .15 * (R * L) ], [-.5 * (R + L), 0], [0, L], [-L, L], [-L, L], [-L, 0] ns.x_i = 'nurbsbasis_n controlpoints_ni' # create the computational domain by h-refinement domain = domain0.refine(nr) # create the second-order B-spline basis over the refined domain ns.bsplinebasis = domain.basis('spline', degree=2) sqr = domain.integral('(bsplinebasis_n ?lhs_n - weightfunc)^2' @ ns, geometry=ns.x, degree=9) ns.controlweights = solver.optimize('lhs', sqr) ns.nurbsbasis = ns.bsplinebasis * ns.controlweights / ns.weightfunc # prepare the displacement field ns.ubasis = ns.nurbsbasis.vector(2) ns.u_i = 'ubasis_ni ?lhs_n' ns.strain_ij = '(u_i,j + u_j,i) / 2' ns.stress_ij = 'lmbda strain_kk δ_ij + 2 mu strain_ij' # construct the exact solution ns.r2 = 'x_k x_k' ns.R2 = R**2 ns.T = T ns.k = (3 - nu) / (1 + nu) # plane stress parameter ns.uexact_i = '''(T / 4 mu) <x_0 ((k + 1) / 2 + (1 + k) R2 / r2 + (1 - R2 / r2) (x_0^2 - 3 x_1^2) R2 / r2^2), x_1 ((k - 3) / 2 + (1 - k) R2 / r2 + (1 - R2 / r2) (3 x_0^2 - x_1^2) R2 / r2^2)>_i''' ns.strainexact_ij = '(uexact_i,j + uexact_j,i) / 2' ns.stressexact_ij = 'lmbda strainexact_kk δ_ij + 2 mu strainexact_ij' # define the linear and bilinear forms res = domain.integral('ubasis_ni,j stress_ij' @ ns, geometry=ns.x, degree=9) res -= domain.boundary['right'].integral( 'ubasis_ni stressexact_ij n_j' @ ns, geometry=ns.x, degree=9) # compute the constraints vector for the symmetry conditions sqr = domain.boundary['top,bottom'].integral('(u_i n_i)^2' @ ns, degree=9) cons = solver.optimize('lhs', sqr, droptol=1e-15) # solve the system of equations lhs = solver.solve_linear('lhs', res, constrain=cons) ns |= dict(lhs=lhs) # post-processing if withplots: geom, stressxx = domain.simplex.elem_eval([ns.x, 'stress_00' @ ns], ischeme='bezier8', separate=True) with plot.PyPlot('solution', index=nr) as plt: plt.mesh(geom, stressxx) plt.colorbar() # compute the L2-norm of the error in the stress err = domain.integrate( '?dstress_ij ?dstress_ij | ?dstress_ij = stress_ij - stressexact_ij' @ ns, geometry=ns.x, ischeme='gauss9')**.5 # compute the mesh parameter (maximum physical distance between knots) hmax = max( numpy.linalg.norm(v[:, numpy.newaxis] - v, axis=2).max() for v in domain.elem_eval(ns.x, ischeme='bezier2', separate=True)) return err, hmax
def test_direct(self): with self.assertRaises(solver.ModelError): self.assert_resnorm(solver.solve_linear('dofs', residual=self.residual, constrain=self.cons))
def main(nrefine: int, traction: float, radius: float, poisson: float): ''' Horizontally loaded linear elastic plate with IGA hole. .. arguments:: nrefine [2] Number of uniform refinements starting from 1x2 base mesh. traction [.1] Far field traction (relative to Young's modulus). radius [.5] Cut-out radius. poisson [.3] Poisson's ratio, nonnegative and strictly smaller than 1/2. ''' # create the coarsest level parameter domain domain, geom0 = mesh.rectilinear([1, 2]) bsplinebasis = domain.basis('spline', degree=2) controlweights = numpy.ones(12) controlweights[1:3] = .5 + .25 * numpy.sqrt(2) weightfunc = bsplinebasis.dot(controlweights) nurbsbasis = bsplinebasis * controlweights / weightfunc # create geometry function indices = [0, 2], [1, 2], [2, 1], [2, 0] controlpoints = numpy.concatenate([ numpy.take([0, 2**.5 - 1, 1], indices) * radius, numpy.take([0, .3, 1], indices) * (radius + 1) / 2, numpy.take([0, 1, 1], indices) ]) geom = (nurbsbasis[:, numpy.newaxis] * controlpoints).sum(0) radiuserr = domain.boundary['left'].integral( (function.norm2(geom) - radius)**2 * function.J(geom0), degree=9).eval()**.5 treelog.info('hole radius exact up to L2 error {:.2e}'.format(radiuserr)) # refine domain if nrefine: domain = domain.refine(nrefine) bsplinebasis = domain.basis('spline', degree=2) controlweights = domain.project(weightfunc, onto=bsplinebasis, geometry=geom0, ischeme='gauss9') nurbsbasis = bsplinebasis * controlweights / weightfunc ns = function.Namespace() ns.x = geom ns.lmbda = 2 * poisson ns.mu = 1 - poisson ns.ubasis = nurbsbasis.vector(2) ns.u_i = 'ubasis_ni ?lhs_n' ns.X_i = 'x_i + u_i' ns.strain_ij = '(d(u_i, x_j) + d(u_j, x_i)) / 2' ns.stress_ij = 'lmbda strain_kk δ_ij + 2 mu strain_ij' ns.r2 = 'x_k x_k' ns.R2 = radius**2 / ns.r2 ns.k = (3 - poisson) / (1 + poisson) # plane stress parameter ns.scale = traction * (1 + poisson) / 2 ns.uexact_i = 'scale (x_i ((k + 1) (0.5 + R2) + (1 - R2) R2 (x_0^2 - 3 x_1^2) / r2) - 2 δ_i1 x_1 (1 + (k - 1 + R2) R2))' ns.du_i = 'u_i - uexact_i' sqr = domain.boundary['top,bottom'].integral('(u_i n_i)^2 J(x)' @ ns, degree=9) cons = solver.optimize('lhs', sqr, droptol=1e-15) sqr = domain.boundary['right'].integral('du_k du_k J(x)' @ ns, degree=20) cons = solver.optimize('lhs', sqr, droptol=1e-15, constrain=cons) # construct residual res = domain.integral('d(ubasis_ni, x_j) stress_ij J(x)' @ ns, degree=9) # solve system lhs = solver.solve_linear('lhs', res, constrain=cons) # vizualize result bezier = domain.sample('bezier', 9) X, stressxx = bezier.eval(['X', 'stress_00'] @ ns, lhs=lhs) export.triplot('stressxx.png', X, stressxx, tri=bezier.tri, hull=bezier.hull, clim=(numpy.nanmin(stressxx), numpy.nanmax(stressxx))) # evaluate error err = domain.integral('<du_k du_k, sum:ij(d(du_i, x_j)^2)>_n J(x)' @ ns, degree=9).eval(lhs=lhs)**.5 treelog.user('errors: L2={:.2e}, H1={:.2e}'.format(*err)) return err, cons, 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(etype: str, btype: str, degree: int, nrefine: int): ''' Adaptively refined Laplace problem on an L-shaped domain. .. arguments:: etype [square] Type of elements (square/triangle/mixed). btype [h-std] Type of basis function (h/th-std/spline), with availability depending on the configured element type. degree [2] Polynomial degree nrefine [5] Number of refinement steps to perform. ''' domain, geom = mesh.unitsquare(2, etype) x, y = geom - .5 exact = (x**2 + y**2)**(1 / 3) * function.cos( function.arctan2(y + x, y - x) * (2 / 3)) domain = domain.trim(exact - 1e-15, maxrefine=0) linreg = util.linear_regressor() with treelog.iter.fraction('level', range(nrefine + 1)) as lrange: for irefine in lrange: if irefine: refdom = domain.refined ns.refbasis = refdom.basis(btype, degree=degree) indicator = refdom.integral( 'd(refbasis_n, x_k) d(u, x_k) J(x)' @ ns, degree=degree * 2).eval(lhs=lhs) indicator -= refdom.boundary.integral( 'refbasis_n d(u, x_k) n(x_k) J(x)' @ ns, degree=degree * 2).eval(lhs=lhs) supp = ns.refbasis.get_support( indicator**2 > numpy.mean(indicator**2)) domain = domain.refined_by(refdom.transforms[supp]) ns = function.Namespace() ns.x = geom ns.basis = domain.basis(btype, degree=degree) ns.u = 'basis_n ?lhs_n' ns.du = ns.u - exact sqr = domain.boundary['trimmed'].integral('u^2 J(x)' @ ns, degree=degree * 2) cons = solver.optimize('lhs', sqr, droptol=1e-15) sqr = domain.boundary.integral('du^2 J(x)' @ ns, degree=7) cons = solver.optimize('lhs', sqr, droptol=1e-15, constrain=cons) res = domain.integral('d(basis_n, x_k) d(u, x_k) J(x)' @ ns, degree=degree * 2) lhs = solver.solve_linear('lhs', res, constrain=cons) ndofs = len(ns.basis) error = domain.integral('<du^2, sum:k(d(du, x_k)^2)>_i J(x)' @ ns, degree=7).eval(lhs=lhs)**.5 rate, offset = linreg.add(numpy.log(len(ns.basis)), numpy.log(error)) treelog.user( 'ndofs: {ndofs}, L2 error: {error[0]:.2e} ({rate[0]:.2f}), H1 error: {error[1]:.2e} ({rate[1]:.2f})' .format(ndofs=len(ns.basis), error=error, rate=rate)) bezier = domain.sample('bezier', 9) x, u, du = bezier.eval(['x', 'u', 'du'] @ ns, lhs=lhs) export.triplot('sol.png', x, u, tri=bezier.tri, hull=bezier.hull) export.triplot('err.png', x, du, tri=bezier.tri, hull=bezier.hull) return ndofs, error, lhs
def main(nelems: int, etype: str, btype: str, degree: int, traction: float, maxrefine: int, radius: float, poisson: float): ''' Horizontally loaded linear elastic plate with FCM hole. .. arguments:: nelems [9] Number of elements along edge. etype [square] Type of elements (square/triangle/mixed). btype [std] Type of basis function (std/spline), with availability depending on the selected element type. degree [2] Polynomial degree. traction [.1] Far field traction (relative to Young's modulus). maxrefine [2] Number or refinement levels used for the finite cell method. radius [.5] Cut-out radius. poisson [.3] Poisson's ratio, nonnegative and strictly smaller than 1/2. ''' domain0, geom = mesh.unitsquare(nelems, etype) domain = domain0.trim(function.norm2(geom) - radius, maxrefine=maxrefine) ns = function.Namespace() ns.x = geom ns.lmbda = 2 * poisson ns.mu = 1 - poisson ns.ubasis = domain.basis(btype, degree=degree).vector(2) ns.u_i = 'ubasis_ni ?lhs_n' ns.X_i = 'x_i + u_i' ns.strain_ij = '(d(u_i, x_j) + d(u_j, x_i)) / 2' ns.stress_ij = 'lmbda strain_kk δ_ij + 2 mu strain_ij' ns.r2 = 'x_k x_k' ns.R2 = radius**2 / ns.r2 ns.k = (3 - poisson) / (1 + poisson) # plane stress parameter ns.scale = traction * (1 + poisson) / 2 ns.uexact_i = 'scale (x_i ((k + 1) (0.5 + R2) + (1 - R2) R2 (x_0^2 - 3 x_1^2) / r2) - 2 δ_i1 x_1 (1 + (k - 1 + R2) R2))' ns.du_i = 'u_i - uexact_i' sqr = domain.boundary['left,bottom'].integral('(u_i n_i)^2 J(x)' @ ns, degree=degree * 2) cons = solver.optimize('lhs', sqr, droptol=1e-15) sqr = domain.boundary['top,right'].integral('du_k du_k J(x)' @ ns, degree=20) cons = solver.optimize('lhs', sqr, droptol=1e-15, constrain=cons) res = domain.integral('d(ubasis_ni, x_j) stress_ij J(x)' @ ns, degree=degree * 2) lhs = solver.solve_linear('lhs', res, constrain=cons) bezier = domain.sample('bezier', 5) X, stressxx = bezier.eval(['X', 'stress_00'] @ ns, lhs=lhs) export.triplot('stressxx.png', X, stressxx, tri=bezier.tri, hull=bezier.hull) err = domain.integral('<du_k du_k, sum:ij(d(du_i, x_j)^2)>_n J(x)' @ ns, degree=max(degree, 3) * 2).eval(lhs=lhs)**.5 treelog.user('errors: L2={:.2e}, H1={:.2e}'.format(*err)) return err, cons, lhs
def neutrondiffusion(mat_flag='scatterer', BC_flag='vacuum', bigsquare=1, smallsquare=.1, degree=1, basis='lagrange'): W = bigsquare #width of outer box Wi = smallsquare #width of inner box nelems = int((2 * 1 * W / Wi)) if mat_flag == 'scatterer': sigt = 2 sigs = 1.99 elif mat_flag == 'reflector': sigt = 2 sigs = 1.8 elif mat_flag == 'absorber': sigt = 10 sigs = 2 elif mat_flag == 'air': sigt = .01 sigs = .006 topo, geom = mesh.unitsquare(nelems, 'square') #unit square centred at (0.5, 0.5) ns = function.Namespace() ns.basis = topo.basis(basis, degree=degree) ns.x = W * geom #scales the unit square to our physical square ns.f = function.max( function.abs(ns.x[0] - W / 2), function.abs(ns.x[1] - W / 2)) #level set function for inner square inner, outer = function.partition( ns.f, Wi / 2) #indicator function for inner square and outer square ns.phi = 'basis_A ?dofs_A' ns.SIGs = sigs * outer #scattering cross-section ns.SIGt = 0.1 * inner + sigt * outer #total cross-section ns.SIGa = 'SIGt - SIGs' #absorption cross-section ns.D = '1 / (3 SIGt)' #diffusion co-efficient ns.Q = inner #source term if BC_flag == 'vacuum': sqr = topo.boundary.integral('(phi - 0)^2 J(x)' @ ns, degree=degree * 2) cons = solver.optimize( 'dofs', sqr, droptol=1e-14) #this applies the boundary condition to u #residual res = topo.integral( '(D basis_i,j phi_,j + SIGa basis_i phi - basis_i Q) J(x)' @ ns, degree=degree * 2) #solve for degrees of freedom dofs = solver.solve_linear('dofs', res, constrain=cons) elif BC_flag == 'reflecting': #residual res = topo.integral( '(D basis_i,j phi_,j + SIGa basis_i phi - basis_i Q) J(x)' @ ns, degree=degree * 2) #solve for degrees of freedom dofs = solver.solve_linear('dofs', res) #select lower triangular half of square domain. Diagonal is one of its boundaries triang = topo.trim('x_0 - x_1' @ ns, maxrefine=5) triangbnd = triang.boundary #select boundary of lower triangle # eval the vertices of the boundary elements of lower triangle: verts = triangbnd.sample(*element.parse_legacy_ischeme("vertex")).eval( 'x_i' @ ns) # now select the verts of the boundary diag = topology.SubsetTopology(triangbnd, [ triangbnd.references[i] if (verts[2 * i][0] == verts[2 * i][1] and verts[2 * i + 1][0] == verts[2 * i + 1][1]) else triangbnd.references[i].empty for i in range(len(triangbnd.references)) ]) bezier_diag = diag.sample('bezier', degree + 1) x_diag = bezier_diag.eval('(x_0^2 + x_1^2)^(1 / 2)' @ ns) phi_diag = bezier_diag.eval('phi' @ ns, dofs=dofs) bezier_bottom = topo.boundary['bottom'].sample('bezier', degree + 1) x_bottom = bezier_bottom.eval('x_0' @ ns) phi_bottom = bezier_bottom.eval('phi' @ ns, dofs=dofs) fig_bottom = plt.figure(0) ax_bottom = fig_bottom.add_subplot(111) plt.plot(x_bottom, phi_bottom) ax_bottom.set_title('Scalar flux along bottom for %s with %s BCs' % (mat_flag, BC_flag)) ax_bottom.set_xlabel('x') ax_bottom.set_ylabel('$\\phi(x)$') fig_diag = plt.figure(1) ax_diag = fig_diag.add_subplot(111) plt.plot(x_diag, phi_diag) ax_diag.set_title('Scalar flux along diagonal for %s with %s BCs' % (mat_flag, BC_flag)) ax_diag.set_xlabel('$\\sqrt{x^2 + y^2}$') ax_diag.set_ylabel('$\\phi(x = y)$') return fig_bottom, fig_diag