def main(nelems:int, etype:str, btype:str, degree:int, poisson:float, angle:float, restol:float, trim:bool): ''' Deformed hyperelastic plate. .. arguments:: nelems [10] Number of elements along edge. etype [square] Type of elements (square/triangle/mixed). btype [std] Type of basis function (std/spline). degree [1] Polynomial degree. poisson [.25] Poisson's ratio, nonnegative and stricly smaller than 1/2. angle [20] Rotation angle for right clamp (degrees). restol [1e-10] Newton tolerance. trim [no] Create circular-shaped hole. ''' domain, geom = mesh.unitsquare(nelems, etype) if trim: domain = domain.trim(function.norm2(geom-.5)-.2, maxrefine=2) bezier = domain.sample('bezier', 5) ns = function.Namespace(fallback_length=domain.ndims) ns.x = geom ns.angle = angle * numpy.pi / 180 ns.lmbda = 2 * poisson ns.mu = 1 - 2 * poisson ns.ubasis = domain.basis(btype, degree=degree) ns.u_i = 'ubasis_k ?lhs_ki' ns.X_i = 'x_i + u_i' ns.strain_ij = '.5 (d(u_i, x_j) + d(u_j, x_i))' ns.energy = 'lmbda strain_ii strain_jj + 2 mu strain_ij strain_ij' sqr = domain.boundary['left'].integral('u_k u_k J(x)' @ ns, degree=degree*2) sqr += domain.boundary['right'].integral('((u_0 - x_1 sin(2 angle) - cos(angle) + 1)^2 + (u_1 - x_1 (cos(2 angle) - 1) + sin(angle))^2) J(x)' @ ns, degree=degree*2) cons = solver.optimize('lhs', sqr, droptol=1e-15) energy = domain.integral('energy J(x)' @ ns, degree=degree*2) lhs0 = solver.optimize('lhs', energy, constrain=cons) X, energy = bezier.eval(['X', 'energy'] @ ns, lhs=lhs0) export.triplot('linear.png', X, energy, tri=bezier.tri, hull=bezier.hull) ns.strain_ij = '.5 (d(u_i, x_j) + d(u_j, x_i) + d(u_k, x_i) d(u_k, x_j))' ns.energy = 'lmbda strain_ii strain_jj + 2 mu strain_ij strain_ij' energy = domain.integral('energy J(x)' @ ns, degree=degree*2) lhs1 = solver.minimize('lhs', energy, arguments=dict(lhs=lhs0), constrain=cons).solve(restol) X, energy = bezier.eval(['X', 'energy'] @ ns, lhs=lhs1) export.triplot('nonlinear.png', X, energy, tri=bezier.tri, hull=bezier.hull) return lhs0, lhs1
def main(nelems: int, 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: int, etype: str, btype: str, degree: int, traction: float, maxrefine: int, radius: float, poisson: float): ''' Horizontally loaded linear elastic plate with FCM hole. .. arguments:: nelems [9] Number of elements along edge. etype [square] Type of elements (square/triangle/mixed). btype [std] Type of basis function (std/spline), with availability depending on the selected element type. degree [2] Polynomial degree. traction [.1] Far field traction (relative to Young's modulus). maxrefine [2] Number or refinement levels used for the finite cell method. radius [.5] Cut-out radius. poisson [.3] Poisson's ratio, nonnegative and strictly smaller than 1/2. ''' domain0, geom = mesh.unitsquare(nelems, etype) domain = domain0.trim(function.norm2(geom) - radius, maxrefine=maxrefine) ns = function.Namespace() ns.x = geom ns.lmbda = 2 * poisson ns.mu = 1 - poisson ns.ubasis = domain.basis(btype, degree=degree).vector(2) ns.u_i = 'ubasis_ni ?lhs_n' ns.X_i = 'x_i + u_i' ns.strain_ij = '(d(u_i, x_j) + d(u_j, x_i)) / 2' ns.stress_ij = 'lmbda strain_kk δ_ij + 2 mu strain_ij' ns.r2 = 'x_k x_k' ns.R2 = radius**2 / ns.r2 ns.k = (3 - poisson) / (1 + poisson) # plane stress parameter ns.scale = traction * (1 + poisson) / 2 ns.uexact_i = 'scale (x_i ((k + 1) (0.5 + R2) + (1 - R2) R2 (x_0^2 - 3 x_1^2) / r2) - 2 δ_i1 x_1 (1 + (k - 1 + R2) R2))' ns.du_i = 'u_i - uexact_i' sqr = domain.boundary['left,bottom'].integral('(u_i n_i)^2 J(x)' @ ns, degree=degree * 2) cons = solver.optimize('lhs', sqr, droptol=1e-15) sqr = domain.boundary['top,right'].integral('du_k du_k J(x)' @ ns, degree=20) cons = solver.optimize('lhs', sqr, droptol=1e-15, constrain=cons) res = domain.integral('d(ubasis_ni, x_j) stress_ij J(x)' @ ns, degree=degree * 2) lhs = solver.solve_linear('lhs', res, constrain=cons) bezier = domain.sample('bezier', 5) X, stressxx = bezier.eval(['X', 'stress_00'] @ ns, lhs=lhs) export.triplot('stressxx.png', X, stressxx, tri=bezier.tri, hull=bezier.hull) err = domain.integral('<du_k du_k, sum:ij(d(du_i, x_j)^2)>_n J(x)' @ ns, degree=max(degree, 3) * 2).eval(lhs=lhs)**.5 treelog.user('errors: L2={:.2e}, H1={:.2e}'.format(*err)) return err, cons, lhs
def main(nelems: int, ndims: int, degree: int, timescale: float, newtontol: float, endtime: float): ''' Burgers equation on a 1D or 2D periodic domain. .. arguments:: nelems [20] Number of elements along a single dimension. ndims [1] Number of spatial dimensions. degree [1] Polynomial degree for discontinuous basis functions. timescale [.5] Fraction of timestep and element size: timestep=timescale/nelems. newtontol [1e-5] Newton tolerance. endtime [inf] Stopping time. ''' domain, geom = mesh.rectilinear([numpy.linspace(0, 1, nelems + 1)] * ndims, periodic=range(ndims)) ns = function.Namespace() ns.x = geom ns.basis = domain.basis('discont', degree=degree) ns.u = 'basis_n ?lhs_n' ns.f = '.5 u^2' ns.C = 1 res = domain.integral('-basis_n,0 f d:x' @ ns, degree=5) res += domain.interfaces.integral( '-[basis_n] n_0 ({f} - .5 C [u] n_0) d:x' @ ns, degree=degree * 2) inertia = domain.integral('basis_n u d:x' @ ns, degree=5) sqr = domain.integral( '(u - exp(-?y_i ?y_i)(y_i = 5 (x_i - 0.5_i)))^2 d:x' @ ns, degree=5) lhs0 = solver.optimize('lhs', sqr) timestep = timescale / nelems bezier = domain.sample('bezier', 7) with treelog.iter.plain( 'timestep', solver.impliciteuler('lhs', res, inertia, timestep=timestep, lhs0=lhs0, newtontol=newtontol)) as steps: for itime, lhs in enumerate(steps): x, u = bezier.eval(['x_i', 'u'] @ ns, lhs=lhs) export.triplot('solution.png', x, u, tri=bezier.tri, hull=bezier.hull, clim=(0, 1)) if itime * timestep >= endtime: break return lhs
def 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(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(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: int, etype: str, btype: str, degree: int, epsilon: typing.Optional[float], contactangle: float, timestep: float, mtol: float, seed: int, circle: bool, stab: stab): ''' Cahn-Hilliard equation on a unit square/circle. .. arguments:: nelems [20] Number of elements along domain 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 [2] Polynomial degree. epsilon [] Interface thickness; defaults to an automatic value based on the configured mesh density if left unspecified. contactangle [90] Wall contact angle in degrees. timestep [.01] Time step. mtol [.01] Threshold value for chemical potential peak to peak difference, used as a stop criterion. seed [0] Random seed for the initial condition. circle [no] Select circular domain as opposed to a unit square. stab [linear] Stabilization method (linear/optimal/none). ''' mineps = 1. / nelems if epsilon is None: treelog.info('setting epsilon={}'.format(mineps)) epsilon = mineps elif epsilon < mineps: treelog.warning('epsilon under crititical threshold: {} < {}'.format( epsilon, mineps)) domain, geom = mesh.unitsquare(nelems, etype) bezier = domain.sample('bezier', 5) # sample for plotting ns = function.Namespace() if not circle: ns.x = geom else: angle = (geom - .5) * (numpy.pi / 2) ns.x = function.sin(angle) * function.cos(angle)[[1, 0 ]] / numpy.sqrt(2) ns.epsilon = epsilon ns.ewall = .5 * numpy.cos(contactangle * numpy.pi / 180) ns.cbasis = ns.mbasis = domain.basis('std', degree=degree) ns.c = 'cbasis_n ?c_n' ns.dc = 'cbasis_n (?c_n - ?c0_n)' ns.m = 'mbasis_n ?m_n' ns.F = '.5 (c^2 - 1)^2 / epsilon^2' ns.dF = stab.value ns.dt = timestep nrg_mix = domain.integral('F J(x)' @ ns, degree=7) nrg_iface = domain.integral('.5 sum:k(d(c, x_k)^2) J(x)' @ ns, degree=7) nrg_wall = domain.boundary.integral('(abs(ewall) + c ewall) J(x)' @ ns, degree=7) nrg = nrg_mix + nrg_iface + nrg_wall + domain.integral( '(dF - m dc - .5 dt epsilon^2 sum:k(d(m, x_k)^2)) J(x)' @ ns, degree=7) numpy.random.seed(seed) state = dict(c=numpy.random.normal(0, .5, ns.cbasis.shape), m=numpy.random.normal(0, .5, ns.mbasis.shape)) # initial condition with treelog.iter.plain('timestep', itertools.count()) as steps: for istep in steps: E = sample.eval_integrals(nrg_mix, nrg_iface, nrg_wall, **state) treelog.user( 'energy: {0:.3f} ({1[0]:.0f}% mixture, {1[1]:.0f}% interface, {1[2]:.0f}% wall)' .format(sum(E), 100 * numpy.array(E) / sum(E))) x, c, m = bezier.eval(['x', 'c', 'm'] @ ns, **state) export.triplot('phase.png', x, c, tri=bezier.tri, clim=(-1, 1)) export.triplot('chempot.png', x, m, tri=bezier.tri) if numpy.ptp(m) < mtol: break state['c0'] = state['c'] state = solver.optimize(['c', 'm'], nrg, arguments=state, tol=1e-10) return state