示例#1
0
 def setUp(self):
     super().setUp()
     if self.ndims == 2:
         domain, geom = mesh.unitsquare(4, self.variant)
         nverts = 25
     elif self.variant == 'tensor':
         structured, geom = mesh.rectilinear(
             [numpy.linspace(0, 1, 5 - i) for i in range(self.ndims)])
         domain = topology.ConnectedTopology(structured.references,
                                             structured.transforms,
                                             structured.opposites,
                                             structured.connectivity)
         nverts = numpy.product([5 - i for i in range(self.ndims)])
     elif self.variant == 'simplex':
         numpy.random.seed(0)
         nverts = 20
         simplices = numeric.overlapping(numpy.arange(nverts),
                                         n=self.ndims + 1)
         coords = numpy.random.normal(size=(nverts, self.ndims))
         root = transform.Identifier(self.ndims, 'test')
         transforms = transformseq.PlainTransforms(
             [(root, transform.Square((c[1:] - c[0]).T, c[0]))
              for c in coords[simplices]], self.ndims)
         domain = topology.SimplexTopology(simplices, transforms,
                                           transforms)
         geom = function.rootcoords(self.ndims)
     else:
         raise NotImplementedError
     self.domain = domain
     self.basis = domain.basis(
         self.btype) if self.btype == 'bubble' else domain.basis(
             self.btype, degree=self.degree)
     self.geom = geom
     self.nverts = nverts
示例#2
0
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
示例#3
0
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
示例#4
0
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
示例#5
0
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
示例#6
0
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
示例#7
0
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
示例#8
0
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
示例#9
0
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
示例#10
0
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