def __call__(self, lhs):
     angle = self.index * self.timestep * self.rotation
     ns = self.ns(lhs=lhs)
     x, u, normu, p = self.plotdomain.elem_eval(
         [ns.x, ns.u, function.norm2(ns.u), ns.p],
         ischeme='bezier9',
         separate=True)
     with plot.PyPlot('flow', index=self.index) as plt:
         plt.axes([0, 0, 1, 1], yticks=[], xticks=[], frame_on=False)
         tri = plt.mesh(x, normu, mergetol=1e-5, cmap='jet')
         plt.clim(0, 1.5)
         plt.tricontour(tri,
                        p,
                        every=self.every,
                        cmap='gray',
                        linestyles='solid',
                        alpha=.8)
         uv = plot.interpolate(tri, self.xy, u)
         plt.vectors(self.xy, uv, zorder=9, pivot='mid', stems=False)
         plt.plot(0,
                  0,
                  'k',
                  marker=(3, 2, angle * 180 / numpy.pi - 90),
                  markersize=20)
         plt.xlim(self.bbox[0])
         plt.ylim(self.bbox[1])
     self.xy = util.regularize(self.bbox, self.spacing,
                               self.xy + uv * self.timestep)
     self.index += 1
Exemple #2
0
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)
Exemple #3
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
Exemple #4
0
 def __call__( self, velo, pres, angle ):
   self.index += 1
   points, velo, flow, pres = self.plotdomain.elem_eval( [ self.geom, velo, function.norm2(velo), pres ], ischeme='bezier9', separate=True )
   with self.plt if self.plt else plot.PyPlot( 'flow', index=self.index ) as plt:
     plt.axes( [0,0,1,1], yticks=[], xticks=[], frame_on=False )
     tri = plt.mesh( points, flow, mergetol=1e-5 )
     plt.clim( 0, 1.5 )
     plt.tricontour( tri, pres, every=self.every, cmap='gray', linestyles='solid', alpha=.8 )
     uv = plot.interpolate( tri, self.xy, velo )
     plt.vectors( self.xy, uv, zorder=9, pivot='mid', stems=False )
     plt.plot( 0, 0, 'k', marker=(3,2,angle*180/numpy.pi-90), markersize=20 )
     plt.xlim( self.bbox[0] )
     plt.ylim( self.bbox[1] )
   self.xy = util.regularize( self.bbox, self.spacing, self.xy + uv * self.timestep )
 def __call__(self, lhs):
   angle = self.index * self.timestep * self.rotation
   ns = self.ns(lhs=lhs)
   x, u, normu, p = self.plotdomain.elem_eval([ns.x, ns.u, function.norm2(ns.u), ns.p], ischeme='bezier9', separate=True)
   with plot.PyPlot('flow', index=self.index) as plt:
     plt.axes([0,0,1,1], yticks=[], xticks=[], frame_on=False)
     tri = plt.mesh(x, normu, mergetol=1e-5, cmap='jet')
     plt.clim(0, 1.5)
     plt.tricontour(tri, p, every=self.every, cmap='gray', linestyles='solid', alpha=.8)
     uv = plot.interpolate(tri, self.xy, u)
     plt.vectors(self.xy, uv, zorder=9, pivot='mid', stems=False)
     plt.plot(0, 0, 'k', marker=(3,2,angle*180/numpy.pi-90), markersize=20)
     plt.xlim(self.bbox[0])
     plt.ylim(self.bbox[1])
   self.xy = util.regularize(self.bbox, self.spacing, self.xy + uv * self.timestep)
   self.index += 1
Exemple #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
Exemple #7
0
def mk_theta(geom, rmin, rmax):
    r = fn.norm2(geom)
    diam = rmax - rmin
    theta = (lambda x: (1 - x)**3 * (3 * x + 1))((r - rmin) / diam)
    theta = fn.piecewise(r, (rmin, rmax), 1, theta, 0)
    return theta
Exemple #8
0
def main(
    nelems: 'number of elements' = 20,
    epsilon: 'epsilon, 0 for automatic (based on nelems)' = 0,
    timestep: 'time step' = .01,
    maxtime: 'end time' = 1.,
    theta: 'contact angle (degrees)' = 90,
    init: 'initial condition (random/bubbles)' = 'random',
    figures: 'create figures' = True,
):

    mineps = 1. / nelems
    if not epsilon:
        log.info('setting epsilon={}'.format(mineps))
        epsilon = mineps
    elif epsilon < mineps:
        log.warning('epsilon under crititical threshold: {} < {}'.format(
            epsilon, mineps))

    # create namespace
    ns = function.Namespace()
    ns.epsilon = epsilon
    ns.ewall = .5 * numpy.cos(theta * numpy.pi / 180)

    # construct mesh
    xnodes = ynodes = numpy.linspace(0, 1, nelems + 1)
    domain, ns.x = mesh.rectilinear([xnodes, ynodes])

    # prepare bases
    ns.cbasis, ns.mubasis = function.chain(
        [domain.basis('spline', degree=2),
         domain.basis('spline', degree=2)])

    # polulate namespace
    ns.c = 'cbasis_n ?lhs_n'
    ns.c0 = 'cbasis_n ?lhs0_n'
    ns.mu = 'mubasis_n ?lhs_n'
    ns.f = '(6 c0 - 2 c0^3 - 4 c) / epsilon^2'

    # construct initial condition
    if init == 'random':
        numpy.random.seed(0)
        lhs0 = numpy.random.normal(0, .5, ns.cbasis.shape)
    elif init == 'bubbles':
        R1 = .25
        R2 = numpy.sqrt(.5) * R1  # area2 = .5 * area1
        ns.cbubble1 = function.tanh(
            (R1 - function.norm2(ns.x -
                                 (.5 + R2 / numpy.sqrt(2) + .8 * ns.epsilon)))
            / ns.epsilon)
        ns.cbubble2 = function.tanh(
            (R2 - function.norm2(ns.x -
                                 (.5 - R1 / numpy.sqrt(2) - .8 * ns.epsilon)))
            / ns.epsilon)
        sqr = domain.integral('(c - cbubble1 - cbubble2 - 1)^2 + mu^2' @ ns,
                              geometry=ns.x,
                              degree=4)
        lhs0 = solver.optimize('lhs', sqr)
    else:
        raise Exception('unknown init %r' % init)

    # construct residual
    res = domain.integral(
        'epsilon^2 cbasis_n,k mu_,k + mubasis_n (mu + f) - mubasis_n,k c_,k'
        @ ns,
        geometry=ns.x,
        degree=4)
    res -= domain.boundary.integral('mubasis_n ewall' @ ns,
                                    geometry=ns.x,
                                    degree=4)
    inertia = domain.integral('cbasis_n c' @ ns, geometry=ns.x, degree=4)

    # solve time dependent problem
    nsteps = numeric.round(maxtime / timestep)
    makeplots = MakePlots(domain, nsteps,
                          ns) if figures else lambda *args: None
    for istep, lhs in log.enumerate(
            'timestep',
            solver.impliciteuler('lhs',
                                 target0='lhs0',
                                 residual=res,
                                 inertia=inertia,
                                 timestep=timestep,
                                 lhs0=lhs0)):
        makeplots(lhs)
        if istep == nsteps:
            break

    return lhs0, 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
Exemple #10
0
import numpy as np
from nutils import function as fn, matrix
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

Exemple #11
0
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
Exemple #12
0
def main(
    nelems: 'number of elements' = 20,
    epsilon: 'epsilon, 0 for automatic (based on nelems)' = 0,
    timestep: 'time step' = .01,
    maxtime: 'end time' = 1.,
    theta: 'contact angle (degrees)' = 90,
    init: 'initial condition (random/bubbles)' = 'random',
    withplots: 'create plots' = True,
):

    mineps = 1. / nelems
    if not epsilon:
        log.info('setting epsilon=%f' % mineps)
        epsilon = mineps
    elif epsilon < mineps:
        log.warning('epsilon under crititical threshold: %f < %f' %
                    (epsilon, mineps))

    ewall = .5 * numpy.cos(theta * numpy.pi / 180)

    # construct mesh
    xnodes = ynodes = numpy.linspace(0, 1, nelems + 1)
    domain, geom = mesh.rectilinear([xnodes, ynodes])

    # prepare bases
    cbasis, mubasis = function.chain(
        [domain.basis('spline', degree=2),
         domain.basis('spline', degree=2)])

    # define mixing energy and splitting: F' = f_p - f_n
    F = lambda c_: (.5 / epsilon**2) * (c_**2 - 1)**2
    f_p = lambda c_: (1. / epsilon**2) * 4 * c_
    f_n = lambda c_: (1. / epsilon**2) * (6 * c_ - 2 * c_**3)

    # prepare matrix
    A = function.outer( cbasis ) \
      + (timestep*epsilon**2) * function.outer( cbasis.grad(geom), mubasis.grad(geom) ).sum(-1) \
      + function.outer( mubasis, mubasis - f_p(cbasis) ) \
      - function.outer( mubasis.grad(geom), cbasis.grad(geom) ).sum(-1)
    matrix = domain.integrate(A, geometry=geom, ischeme='gauss4')

    # prepare wall energy right hand side
    rhs0 = domain.boundary.integrate(mubasis * ewall,
                                     geometry=geom,
                                     ischeme='gauss4')

    # construct initial condition
    if init == 'random':
        numpy.random.seed(0)
        c = cbasis.dot(numpy.random.normal(0, .5, cbasis.shape))
    elif init == 'bubbles':
        R1 = .25
        R2 = numpy.sqrt(.5) * R1  # area2 = .5 * area1
        c = 1 + function.tanh( (R1-function.norm2(geom-(.5+R2/numpy.sqrt(2)+.8*epsilon)))/epsilon ) \
              + function.tanh( (R2-function.norm2(geom-(.5-R1/numpy.sqrt(2)-.8*epsilon)))/epsilon )
    else:
        raise Exception('unknown init %r' % init)

    # prepare plotting
    nsteps = numeric.round(maxtime / timestep)
    makeplots = MakePlots(domain, geom, nsteps, video=withplots
                          == 'video') if withplots else lambda *args: None

    # start time stepping
    for istep in log.range('timestep', nsteps):

        Emix = F(c)
        Eiface = .5 * (c.grad(geom)**2).sum(-1)
        Ewall = (abs(ewall) + ewall * c)

        b = cbasis * c - mubasis * f_n(c)
        rhs, total, energy_mix, energy_iface = domain.integrate(
            [b, c, Emix, Eiface], geometry=geom, ischeme='gauss4')
        energy_wall = domain.boundary.integrate(Ewall,
                                                geometry=geom,
                                                ischeme='gauss4')
        log.user('concentration {}, energy {}'.format(
            total, energy_mix + energy_iface + energy_wall))

        lhs = matrix.solve(rhs0 + rhs, tol=1e-12, restart=999)
        c = cbasis.dot(lhs)
        mu = mubasis.dot(lhs)

        makeplots(c, mu, energy_mix, energy_iface, energy_wall)

    return lhs, energy_mix, energy_iface, energy_wall
def main(
    nelems: 'number of elements' = 20,
    epsilon: 'epsilon, 0 for automatic (based on nelems)' = 0,
    timestep: 'time step' = .01,
    maxtime: 'end time' = 1.,
    theta: 'contact angle (degrees)' = 90,
    init: 'initial condition (random/bubbles)' = 'random',
    withplots: 'create plots' = True,
  ):

  mineps = 1./nelems
  if not epsilon:
    log.info('setting epsilon={}'.format(mineps))
    epsilon = mineps
  elif epsilon < mineps:
    log.warning('epsilon under crititical threshold: {} < {}'.format(epsilon, mineps))

  # create namespace
  ns = function.Namespace()
  ns.epsilon = epsilon
  ns.ewall = .5 * numpy.cos( theta * numpy.pi / 180 )

  # construct mesh
  xnodes = ynodes = numpy.linspace(0,1,nelems+1)
  domain, ns.x = mesh.rectilinear( [ xnodes, ynodes ] )

  # prepare bases
  ns.cbasis, ns.mubasis = function.chain([
    domain.basis('spline', degree=2),
    domain.basis('spline', degree=2)
  ])

  # polulate namespace
  ns.c = 'cbasis_n ?lhs_n'
  ns.c0 = 'cbasis_n ?lhs0_n'
  ns.mu = 'mubasis_n ?lhs_n'
  ns.f = '(6 c0 - 2 c0^3 - 4 c) / epsilon^2'

  # construct initial condition
  if init == 'random':
    numpy.random.seed(0)
    lhs0 = numpy.random.normal(0, .5, ns.cbasis.shape)
  elif init == 'bubbles':
    R1 = .25
    R2 = numpy.sqrt(.5) * R1 # area2 = .5 * area1
    ns.cbubble1 = function.tanh((R1-function.norm2(ns.x-(.5+R2/numpy.sqrt(2)+.8*ns.epsilon)))/ns.epsilon)
    ns.cbubble2 = function.tanh((R2-function.norm2(ns.x-(.5-R1/numpy.sqrt(2)-.8*ns.epsilon)))/ns.epsilon)
    sqr = domain.integral('(c - cbubble1 - cbubble2 - 1)^2 + mu^2' @ ns, geometry=ns.x, degree=4)
    lhs0 = solver.optimize('lhs', sqr)
  else:
    raise Exception( 'unknown init %r' % init )

  # construct residual
  res = domain.integral('epsilon^2 cbasis_n,k mu_,k + mubasis_n (mu + f) - mubasis_n,k c_,k' @ ns, geometry=ns.x, degree=4)
  res -= domain.boundary.integral('mubasis_n ewall' @ ns, geometry=ns.x, degree=4)
  inertia = domain.integral('cbasis_n c' @ ns, geometry=ns.x, degree=4)

  # solve time dependent problem
  nsteps = numeric.round(maxtime/timestep)
  makeplots = MakePlots(domain, nsteps, ns) if withplots else lambda *args: None
  for istep, lhs in log.enumerate('timestep', solver.impliciteuler('lhs', target0='lhs0', residual=res, inertia=inertia, timestep=timestep, lhs0=lhs0)):
    makeplots(lhs)
    if istep == nsteps:
      break

  return lhs0, lhs