def test_bases(mu, case): domain, vbasis, pbasis = case.domain, case.basis('v'), case.basis('p') trfgeom = case.geometry(mu) refgeom = case.refgeom a_vbasis, a_pbasis = piola_bases(mu, case) J = trfgeom.grad(case.geometry()) detJ = fn.determinant(J) b_vbasis = fn.matmat(vbasis, J.transpose()) / detJ b_pbasis = pbasis / detJ Z = case.geometry(mu).grad(case.geometry()) detZ = fn.determinant(Z) zdiff = np.sqrt(domain.integrate((Z - J)**2 * fn.J(refgeom), ischeme='gauss9').export('dense')) np.testing.assert_almost_equal(zdiff, 0.0) c_vbasis = fn.matmat(vbasis, Z.transpose()) / detZ c_pbasis = pbasis pdiff = np.sqrt(domain.integrate((a_pbasis - b_pbasis)**2 * fn.J(refgeom), ischeme='gauss9')) np.testing.assert_almost_equal(pdiff, 0.0) pdiff = domain.integrate((a_pbasis - c_pbasis)**2 * fn.J(refgeom), ischeme='gauss9') np.testing.assert_almost_equal(pdiff, 0.0) vdiff = np.sqrt(domain.integrate((a_vbasis - b_vbasis)**2 * fn.J(refgeom), ischeme='gauss9').export('dense')) np.testing.assert_almost_equal(vdiff, 0.0) vdiff = domain.integrate((a_vbasis - c_vbasis)**2 * fn.J(refgeom), ischeme='gauss9').export('dense') np.testing.assert_almost_equal(vdiff, 0.0)
def 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([8,8]) basis = domain.basis('std', degree=1) self.cons = domain.boundary['left'].project(0, onto=basis, geometry=geom, ischeme='gauss2') dofs = function.Argument('dofs', [len(basis)]) u = basis.dot(dofs) self.residual = domain.integral((basis.grad(geom) * u.grad(geom)).sum(-1)*function.J(geom), degree=2) \ + domain.boundary['top'].integral(basis*function.J(geom), degree=2)
def setUp(self): super().setUp() domain, geom = mesh.rectilinear([numpy.linspace(0, 1, 9)] * 2) ubasis = domain.basis('std', degree=2) if self.vector: u = ubasis.vector(2).dot( function.Argument('dofs', [len(ubasis) * 2])) else: u = (ubasis[:, numpy.newaxis] * function.Argument('dofs', [len(ubasis), 2])).sum(0) Geom = geom * [1.1, 1] + u self.cons = solver.optimize('dofs', domain.boundary['left,right'].integral( (u**2).sum(0), degree=4), droptol=1e-15) self.boolcons = ~numpy.isnan(self.cons) strain = .5 * (function.outer(Geom.grad(geom), axis=1).sum(0) - function.eye(2)) self.energy = domain.integral( ((strain**2).sum([0, 1]) + 20 * (function.determinant(Geom.grad(geom)) - 1)**2) * function.J(geom), degree=6) self.residual = self.energy.derivative('dofs') self.tol = 1e-10
def _divs(fast: bool = False, piola: bool = False, num=8): __, solutions, supremizers = get_ensemble(fast, piola, num) case = get_case(fast, piola) angles = np.linspace(-35, 35, 15) * np.pi / 180 eig_sol = reduction.eigen(case, solutions, fields=['v', 'p']) rb_sol = reduction.reduced_bases(case, solutions, eig_sol, (20, 20)) results = [] for angle in angles: log.user(f'angle = {angle}') mu = case.parameter(angle=angle) geom = case.physical_geometry(mu) divs = [] for i in range(10): vsol, = case.solution(rb_sol['v'][i], mu, ['v'], lift=True) div = np.sqrt( case.domain.integrate(vsol.div(geom)**2 * fn.J(geom), ischeme='gauss9')) log.user(f'{i}: {div:.2e}') divs.append(div) results.append([angle * 180 / np.pi, max(divs), np.mean(divs)]) results = np.array(results) np.savetxt( util.make_filename(_divs, 'airfoil-divs-{piola}.csv', piola=piola), results)
def test_poly(self): target = self.geom.sum(-1) if self.btype == 'bubble' \ else (self.geom**self.degree).sum(-1) + self.domain.f_index if self.btype == 'discont' \ else (self.geom**self.degree).sum(-1) projection = self.domain.projection(target, onto=self.basis, geometry=self.geom, ischeme='gauss', degree=2*self.degree, droptol=0) error2 = self.domain.integrate((target-projection)**2*function.J(self.geom), ischeme='gauss', degree=2*self.degree) numpy.testing.assert_almost_equal(error2, 0, decimal=24)
class Mass(MuCallable): _ident_ = 'Mass' def __init__(self, n, basisname, *deps, scale=1): super().__init__((n, n), deps, scale=scale) self.basisname = basisname def write(self, group): super().write(group) util.to_dataset(self.basisname, group, 'basisname') def _read(self, group): super()._read(group) self.basisname = util.from_dataset(group['basisname']) def evaluate(self, case, mu, cont): geom = case['geometry'](mu) basis = case.basis(self.basisname, mu) itg = fn.outer(basis) if basis.ndim > 1: itg = itg.sum([-1]) itg = util.contract(itg, cont) with matrix.Scipy(): return unwrap(case.domain.integrate(itg * fn.J(geom), ischeme='gauss9'))
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 mk_lift(case): mu = case.parameter() vbasis = case.basis('v', mu) geom = case['geometry'](mu) x, __ = geom cons = case.domain.boundary['left'].project((0, 0), onto=vbasis, geometry=geom, ischeme='gauss1') cons = case.domain.boundary['right'].select(-x).project( (1, 0), onto=vbasis, geometry=geom, ischeme='gauss9', constrain=cons, ) mx = case['laplacian'](mu) + case['divergence'](mu, sym=True) lhs = matrix.ScipyMatrix(mx).solve(np.zeros(case.ndofs), constrain=cons) vsol = vbasis.dot(lhs) vdiv = vsol.div(geom)**2 vdiv = np.sqrt(case.domain.integrate(vdiv * fn.J(geom), ischeme='gauss9')) log.user('Lift divergence (ref coord):', vdiv) lhs[case.bases['p'].indices] = 0.0 return lhs
def test_nonlinear_diagonalshift(self): nelems = 10 domain, geom = mesh.rectilinear([nelems, 1]) geom *= [2 * numpy.pi / nelems, 1] ubasis = domain.basis('spline', degree=2).vector(2) u = ubasis.dot(function.Argument('dofs', [len(ubasis)])) Geom = [.5 * geom[0], geom[1] + function.cos(geom[0]) ] + u # compress by 50% and buckle cons = solver.minimize('dofs', domain.boundary['left,right'].integral( (u**2).sum(0), degree=4), droptol=1e-15).solve() strain = .5 * (function.outer(Geom.grad(geom), axis=1).sum(0) - function.eye(2)) energy = domain.integral( ((strain**2).sum([0, 1]) + 150 * (function.determinant(Geom.grad(geom)) - 1)**2) * function.J(geom), degree=6) nshift = 0 for iiter, (lhs, info) in enumerate( solver.minimize('dofs', energy, constrain=cons)): self.assertLess(iiter, 38) if info.shift: nshift += 1 if info.resnorm < self.tol: break self.assertEqual(nshift, 9)
def integrate(*args): domain, geom, ischeme = args[0]._domain, args[0]._geometry, args[0]._ischeme assert all(arg._domain is domain for arg in args[1:]) assert all(arg._geometry is geom for arg in args[1:]) assert all(arg._ischeme == ischeme for arg in args[1:]) with MaybeScipyBackend(): retval = domain.integrate([arg._obj*fn.J(geom) for arg in args], ischeme=ischeme) return [r.core if isinstance(r, matrix.Matrix) else r for r in retval]
def test_pum_sum(self): # Note that this test holds for btype 'lagrange' as well, although the # basis functions are not confined to range [0,1]. error2 = self.domain.integrate( (1 - self.basis.sum(0))**2 * function.J(self.geom), ischeme='gauss', degree=2 * self.degree) numpy.testing.assert_almost_equal(error2, 0, decimal=22)
def test_mass_matrix(mu, case): p_vbasis, p_pbasis = piola_bases(mu, case) trfgeom = case.geometry(mu) itg = fn.outer(p_vbasis.grad(trfgeom)).sum([-1, -2]) phys_mx = case.domain.integrate(itg * fn.J(trfgeom), ischeme='gauss9') test_mx = case['v-h1s'](mu) np.testing.assert_almost_equal(phys_mx.export('dense'), test_mx.toarray())
def test_laplacian_matrix(mu, case): vbasis, pbasis = case.bases['v'].obj, case.bases['p'].obj trfgeom = case.geometry(mu) itg = fn.outer(vbasis.grad(trfgeom)).sum([-1, -2]) phys_mx = case.domain.integrate(itg * fn.J(trfgeom), ischeme='gauss9') test_mx = case['laplacian'](mu) np.testing.assert_almost_equal(phys_mx.export('dense'), test_mx.toarray())
def test_divergence_matrix(mu, case): vbasis, pbasis = case.bases['v'].obj, case.bases['p'].obj trfgeom = case.geometry(mu) itg = -fn.outer(vbasis.div(trfgeom), pbasis) phys_mx = case.domain.integrate(itg * fn.J(trfgeom), ischeme='gauss9') test_mx = case['divergence'](mu) np.testing.assert_almost_equal(phys_mx.export('dense'), test_mx.toarray())
def test_nutils_tensor(): domain, geom = mesh.rectilinear([[0, 1], [0, 1]]) basis = domain.basis('spline', degree=1) itg = basis[:, _, _] * basis[_, :, _] * basis[_, _, :] a = AffineIntegral(1, itg) a.prop(domain=domain, geometry=geom, ischeme='gauss9') a = a.cache_main(force=True)(None, {}) b = domain.integrate(itg * fn.J(geom), ischeme='gauss9') np.testing.assert_almost_equal(a, b)
def project(self, projection): ns = fn.Namespace() for name, func in self._kwargs.items(): setattr(ns, name, func) for p, (name, func) in zip(projection, self._defaults.items()): if p is not None: func = fn.matmat(p, func) setattr(ns, name, func) integrand = getattr(ns, self._evaluator)(self._code) domain, geom, ischeme = self.prop('domain', 'geometry', 'ischeme') with matrix.Numpy(): retval = domain.integrate(integrand * fn.J(geom), ischeme=ischeme) return NumpyArrayIntegrand(retval)
def project(self, projection): obj = self.obj s = slice(None) for i, p in enumerate(projection): if p is None: continue obj = obj[(s,)*i + (_,s,Ellipsis)] obj = obj * p[(_,)*i + (s,s) + (_,) * (self.ndim - i - 1)] obj = obj.sum(i+1) domain, geom, ischeme = self.prop('domain', 'geometry', 'ischeme') with matrix.Numpy(): retval = domain.integrate(obj * fn.J(geom), ischeme=ischeme) return NumpyArrayIntegrand(retval)
def cache(self, force=False, **kwargs): if self.ndim >= 3 and force: return self._highdim_cache(**kwargs) elif self.ndim >= 3: # Store properties for later integration self.prop(**kwargs) return self domain, geom, ischeme = self.prop('domain', 'geometry', 'ischeme', **kwargs) with MaybeScipyBackend(): value = domain.integrate(self.obj * fn.J(geom), ischeme=ischeme) if isinstance(value, matrix.Matrix): value = value.core return Integrand.make(value)
def test_convection(mu, case): vbasis, pbasis = case.bases['v'].obj, case.bases['p'].obj trfgeom = case.geometry(mu) a, b, c = [np.random.rand(vbasis.shape[0]) for __ in range(3)] u = vbasis.dot(b) v = vbasis.dot(c).grad(trfgeom) w = vbasis.dot(a) itg = (w[:, _] * u[_, :] * v[:, :]).sum([-1, -2]) phys_conv = case.domain.integrate(itg * fn.J(trfgeom), ischeme='gauss9') test_conv = affine.integrate(case['convection'](mu, cont=(a, b, c))) np.testing.assert_almost_equal(phys_conv, test_conv)
def test_exact_stokes(e_exact, a_exact, mu): mu = e_exact.parameter(*mu) elhs = stokes(e_exact, mu) alhs = stokes(a_exact, mu) _check_exact(e_exact, mu, elhs, with_p=False) # TODO # Size of physical geometry pgeom = e_exact.geometry(mu) size = e_exact.domain.integrate(fn.J(pgeom), ischeme='gauss9') np.testing.assert_almost_equal(size, mu['w'] * mu['h']) # Solenoidal in physical coordinates pgeom = e_exact.geometry(mu) vdiv = e_exact.basis('v', mu).dot(elhs + e_exact.lift(mu)).div(pgeom) vdiv = np.sqrt( e_exact.domain.integrate(vdiv**2 * fn.J(pgeom), ischeme='gauss9')) np.testing.assert_almost_equal(0.0, vdiv) pgeom = a_exact.geometry(mu) vdiv = a_exact.basis('v', mu).dot(alhs + a_exact.lift(mu)).div(pgeom) vdiv = np.sqrt( a_exact.domain.integrate(vdiv**2 * fn.J(pgeom), ischeme='gauss9')) np.testing.assert_almost_equal(0.0, vdiv) # Solenoidal in reference coordinates rgeom = e_exact.refgeom vdiv = e_exact.basis('v').dot(elhs).div(rgeom) vdiv = np.sqrt( e_exact.domain.integrate(vdiv**2 * fn.J(rgeom), ischeme='gauss9')) np.testing.assert_almost_equal(0.0, vdiv) rgeom = a_exact.refgeom vdiv = a_exact.basis('v').dot(alhs).div(rgeom) vdiv = np.sqrt( a_exact.domain.integrate(vdiv**2 * fn.J(rgeom), ischeme='gauss9')) np.testing.assert_almost_equal(0.0, vdiv)
class NSDivergence(MuCallable): _ident_ = 'NSDivergence' def __init__(self, n, *deps, scale=1): super().__init__((n, n), deps, scale=scale) def evaluate(self, case, mu, cont): geom = case['geometry'](mu) vbasis = case.basis('v', mu) pbasis = case.basis('p', mu) itg = -fn.outer(vbasis.div(geom), pbasis) itg = util.contract(itg, cont) with matrix.Scipy(): return unwrap(case.domain.integrate(itg * fn.J(geom), ischeme='gauss9'))
class NSConvection(MuCallable): _ident_ = 'NSConvection' def __init__(self, n, *deps, scale=1): super().__init__((n, n, n), deps, scale=scale) def evaluate(self, case, mu, cont): geom = case['geometry'](mu) vbasis = case.basis('v', mu) vgrad = vbasis.grad(geom) itg = (vbasis[:,_,_,:,_] * vbasis[_,:,_,_,:] * vgrad[_,_,:,:,:]).sum([-1, -2]) itg = util.contract(itg, cont) backend = matrix.Scipy if itg.ndim < 3 else COOTensorBackend with backend(): return unwrap(case.domain.integrate(itg * fn.J(geom), ischeme='gauss9'))
def mu(): return { 'viscosity': 1.0, 'length': 10.0, 'height': 1.5, 'velocity': 1.0, } def test_divergence_matrix(case, mu): domain, vbasis, pbasis = case.domain, case.bases['v'].obj, case.bases[ 'p'].obj geom = case.geometry(mu) itg = -fn.outer(vbasis.div(geom), pbasis) phys_mx = domain.integrate(itg * fn.J(geom), ischeme='gauss9').export('dense') test_mx = case['divergence'](mu).toarray() np.testing.assert_almost_equal(phys_mx, test_mx) def test_laplacian_matrix(case, mu): domain, vbasis, pbasis = case.domain, case.bases['v'].obj, case.bases[ 'p'].obj geom = case.geometry(mu) itg = fn.outer(vbasis.grad(geom)).sum([-1, -2]) / mu['viscosity'] phys_mx = domain.integrate(itg * fn.J(geom), ischeme='gauss9').export('dense') test_mx = case['laplacian'](mu).toarray()
def _highdim_cache(self, **kwargs): domain, geom, ischeme = self.prop('domain', 'geometry', 'ischeme', **kwargs) with COOTensorBackend(): value = domain.integrate(self.obj * fn.J(geom), ischeme=ischeme) return value
import pytest from aroma import cases from aroma.solvers import stokes, navierstokes def _check_exact(case, mu, lhs, with_p=True): vsol = case.basis('v', mu).dot(lhs + case.lift(mu)) psol = case.basis('p', mu).dot(lhs + case.lift(mu)) vexc, pexc = case.exact(mu, ['v', 'p']) vdiff = fn.norm2(vsol - vexc) pdiff = (psol - pexc)**2 geom = case.geometry(mu) verr, perr = case.domain.integrate( [vdiff * fn.J(geom), pdiff * fn.J(geom)], ischeme='gauss9') np.testing.assert_almost_equal(verr, 0.0) if with_p: np.testing.assert_almost_equal(perr, 0.0) @pytest.fixture() def cavity(): case = cases.cavity(nel=2) case.precompute() return case @pytest.fixture() def e_exact():
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 poisson_patch(bottom, right, top, left): from nutils import version if int(version[0]) != 4: raise ImportError( 'Mismatching nutils version detected, only version 4 supported. Upgrade by \"pip install --upgrade nutils\"' ) from nutils import mesh, function as fn from nutils import _, log # error test input if left.rational or right.rational or top.rational or bottom.rational: raise RuntimeError('poisson_patch not supported for rational splines') # these are given as a oriented loop, so make all run in positive parametric direction top.reverse() left.reverse() # in order to add spline surfaces, they need identical parametrization Curve.make_splines_identical(top, bottom) Curve.make_splines_identical(left, right) # create computational (nutils) mesh p1 = bottom.order(0) p2 = left.order(0) n1 = len(bottom) n2 = len(left) dim = left.dimension k1 = bottom.knots(0) k2 = left.knots(0) m1 = [bottom.order(0) - bottom.continuity(k) - 1 for k in k1] m2 = [left.order(0) - left.continuity(k) - 1 for k in k2] domain, geom = mesh.rectilinear([k1, k2]) basis = domain.basis('spline', [p1 - 1, p2 - 1], knotmultiplicities=[m1, m2]) # assemble system matrix grad = basis.grad(geom) outer = fn.outer(grad, grad) integrand = outer.sum(-1) matrix = domain.integrate(integrand * fn.J(geom), ischeme='gauss' + str(max(p1, p2) + 1)) # initialize variables controlpoints = np.zeros((n1, n2, dim)) rhs = np.zeros((n1 * n2)) constraints = np.array([[np.nan] * n2] * n1) # treat all dimensions independently for d in range(dim): # add boundary conditions constraints[0, :] = left[:, d] constraints[-1, :] = right[:, d] constraints[:, 0] = bottom[:, d] constraints[:, -1] = top[:, d] # solve system lhs = matrix.solve(rhs, constrain=np.ndarray.flatten(constraints)) # wrap results into splipy datastructures controlpoints[:, :, d] = np.reshape(lhs, (n1, n2), order='C') return Surface(bottom.bases[0], left.bases[0], controlpoints, bottom.rational, raw=True)
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
pressure = pressure[:npts] linbasis = case.domain.basis('spline', degree=1) vsol = linbasis.dot(velocity[:,0])[_] * (1,0) + linbasis.dot(velocity[:,1])[_] * (0,1) psol = linbasis.dot(pressure) geom = case.physical_geometry(param) vbasis = case.basis('v', param) vgrad = vbasis.grad(geom) pbasis = case.basis('p', param) lhs = case.domain.project(psol, onto=pbasis, geometry=geom, ischeme='gauss9') vind = case.basis_indices('v') itg = fn.outer(vbasis).sum([-1]) + fn.outer(vgrad).sum([-1,-2]) with matrix.Scipy(): mx = case.domain.integrate(itg * fn.J(geom), ischeme='gauss9').core[np.ix_(vind,vind)] itg = (vbasis * vsol[_,:]).sum([-1]) + (vgrad * vsol.grad(geom)[_,:,:]).sum([-1,-2]) rhs = case.domain.integrate(itg * fn.J(geom), ischeme='gauss9')[vind] lhs[vind] = matrix.ScipyMatrix(mx).solve(rhs) lift = case._lift(param) solutions.append((lhs - lift) * weight) solutions = np.array(solutions) supremizers = ens.make_ensemble( case, solvers.supremizer, scheme, weights=False, parallel=False, args=[solutions], ) return scheme, solutions, supremizers @util.pickle_cache('airfoil-{piola}-{imported}-{nred}.rcase')