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
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, degree: int, reynolds: float): ''' Driven cavity benchmark problem. .. arguments:: nelems [12] Number of elements along edge. etype [square] Element type (square/triangle/mixed). degree [2] Polynomial degree for velocity; the pressure space is one degree less. reynolds [1000] Reynolds number, taking the domain size as characteristic length. ''' domain, geom = mesh.unitsquare(nelems, etype) ns = function.Namespace() ns.Re = reynolds ns.x = geom ns.ubasis, ns.pbasis = function.chain([ domain.basis('std', degree=degree).vector(2), domain.basis('std', degree=degree - 1), ]) ns.u_i = 'ubasis_ni ?lhs_n' ns.p = 'pbasis_n ?lhs_n' ns.stress_ij = '(u_i,j + u_j,i) / Re - p δ_ij' sqr = domain.boundary.integral('u_k u_k d:x' @ ns, degree=degree * 2) wallcons = solver.optimize('lhs', sqr, droptol=1e-15) sqr = domain.boundary['top'].integral('(u_0 - 1)^2 d:x' @ ns, degree=degree * 2) lidcons = solver.optimize('lhs', sqr, droptol=1e-15) cons = numpy.choose(numpy.isnan(lidcons), [lidcons, wallcons]) cons[-1] = 0 # pressure point constraint res = domain.integral('(ubasis_ni,j stress_ij + pbasis_n u_k,k) d:x' @ ns, degree=degree * 2) with treelog.context('stokes'): lhs0 = solver.solve_linear('lhs', res, constrain=cons) postprocess(domain, ns, lhs=lhs0) res += domain.integral( '.5 (ubasis_ni u_i,j - ubasis_ni,j u_i) u_j d:x' @ ns, degree=degree * 3) with treelog.context('navierstokes'): lhs1 = solver.newton('lhs', res, lhs0=lhs0, constrain=cons).solve(tol=1e-10) postprocess(domain, ns, lhs=lhs1) return lhs0, lhs1
def main(nelems:int, etype:str, degree:int, reynolds:float): ''' Driven cavity benchmark problem. .. arguments:: nelems [12] Number of elements along edge. etype [square] Element type (square/triangle/mixed). degree [2] Polynomial degree for velocity; the pressure space is one degree less. reynolds [1000] Reynolds number, taking the domain size as characteristic length. ''' domain, geom = mesh.unitsquare(nelems, etype) ns = function.Namespace() ns.Re = reynolds ns.x = geom ns.ubasis = domain.basis('std', degree=degree).vector(domain.ndims) ns.pbasis = domain.basis('std', degree=degree-1) ns.u_i = 'ubasis_ni ?u_n' ns.p = 'pbasis_n ?p_n' ns.stress_ij = '(d(u_i, x_j) + d(u_j, x_i)) / Re - p δ_ij' usqr = domain.boundary.integral('u_k u_k d:x' @ ns, degree=degree*2) wallcons = solver.optimize('u', usqr, droptol=1e-15) usqr = domain.boundary['top'].integral('(u_0 - 1)^2 d:x' @ ns, degree=degree*2) lidcons = solver.optimize('u', usqr, droptol=1e-15) ucons = numpy.choose(numpy.isnan(lidcons), [lidcons, wallcons]) pcons = numpy.zeros(len(ns.pbasis), dtype=bool) pcons[-1] = True # constrain pressure to zero in a point cons = dict(u=ucons, p=pcons) ures = domain.integral('d(ubasis_ni, x_j) stress_ij d:x' @ ns, degree=degree*2) pres = domain.integral('pbasis_n d(u_k, x_k) d:x' @ ns, degree=degree*2) with treelog.context('stokes'): state0 = solver.solve_linear(('u', 'p'), (ures, pres), constrain=cons) postprocess(domain, ns, **state0) ures += domain.integral('.5 (ubasis_ni d(u_i, x_j) - d(ubasis_ni, x_j) u_i) u_j d:x' @ ns, degree=degree*3) with treelog.context('navierstokes'): state1 = solver.newton(('u', 'p'), (ures, pres), arguments=state0, constrain=cons).solve(tol=1e-10) postprocess(domain, ns, **state1) return state0, state1
def main(nelems: int, etype: str, btype: str, degree: int, poisson: float): ''' Horizontally loaded linear elastic plate. .. arguments:: nelems [10] Number of elements along edge. etype [square] Type of elements (square/triangle/mixed). btype [std] Type of basis function (std/spline), with availability depending on the configured element type. degree [1] Polynomial degree. poisson [.25] Poisson's ratio, nonnegative and strictly smaller than 1/2. ''' domain, geom = mesh.unitsquare(nelems, etype) ns = function.Namespace() ns.x = geom ns.basis = domain.basis(btype, degree=degree).vector(2) ns.u_i = 'basis_ni ?lhs_n' ns.X_i = 'x_i + u_i' ns.lmbda = 2 * poisson ns.mu = 1 - 2 * poisson ns.strain_ij = '(d(u_i, x_j) + d(u_j, x_i)) / 2' ns.stress_ij = 'lmbda strain_kk δ_ij + 2 mu strain_ij' sqr = domain.boundary['left'].integral('u_k u_k J(x)' @ ns, degree=degree * 2) sqr += domain.boundary['right'].integral('(u_0 - .5)^2 J(x)' @ ns, degree=degree * 2) cons = solver.optimize('lhs', sqr, droptol=1e-15) res = domain.integral('d(basis_ni, x_j) stress_ij J(x)' @ ns, degree=degree * 2) lhs = solver.solve_linear('lhs', res, constrain=cons) bezier = domain.sample('bezier', 5) X, sxy = bezier.eval(['X', 'stress_01'] @ ns, lhs=lhs) export.triplot('shear.png', X, sxy, tri=bezier.tri, hull=bezier.hull) return cons, lhs
def main(nelems: 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, 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 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
def main(etype: str, btype: str, degree: int, nrefine: int): ''' Adaptively refined Laplace problem on an L-shaped domain. .. arguments:: etype [square] Type of elements (square/triangle/mixed). btype [h-std] Type of basis function (h/th-std/spline), with availability depending on the configured element type. degree [2] Polynomial degree nrefine [5] Number of refinement steps to perform. ''' domain, geom = mesh.unitsquare(2, etype) x, y = geom - .5 exact = (x**2 + y**2)**(1 / 3) * function.cos( function.arctan2(y + x, y - x) * (2 / 3)) domain = domain.trim(exact - 1e-15, maxrefine=0) linreg = util.linear_regressor() with treelog.iter.fraction('level', range(nrefine + 1)) as lrange: for irefine in lrange: if irefine: refdom = domain.refined ns.refbasis = refdom.basis(btype, degree=degree) indicator = refdom.integral( 'd(refbasis_n, x_k) d(u, x_k) J(x)' @ ns, degree=degree * 2).eval(lhs=lhs) indicator -= refdom.boundary.integral( 'refbasis_n d(u, x_k) n(x_k) J(x)' @ ns, degree=degree * 2).eval(lhs=lhs) supp = ns.refbasis.get_support( indicator**2 > numpy.mean(indicator**2)) domain = domain.refined_by(refdom.transforms[supp]) ns = function.Namespace() ns.x = geom ns.basis = domain.basis(btype, degree=degree) ns.u = 'basis_n ?lhs_n' ns.du = ns.u - exact sqr = domain.boundary['trimmed'].integral('u^2 J(x)' @ ns, degree=degree * 2) cons = solver.optimize('lhs', sqr, droptol=1e-15) sqr = domain.boundary.integral('du^2 J(x)' @ ns, degree=7) cons = solver.optimize('lhs', sqr, droptol=1e-15, constrain=cons) res = domain.integral('d(basis_n, x_k) d(u, x_k) J(x)' @ ns, degree=degree * 2) lhs = solver.solve_linear('lhs', res, constrain=cons) ndofs = len(ns.basis) error = domain.integral('<du^2, sum:k(d(du, x_k)^2)>_i J(x)' @ ns, degree=7).eval(lhs=lhs)**.5 rate, offset = linreg.add(numpy.log(len(ns.basis)), numpy.log(error)) treelog.user( 'ndofs: {ndofs}, L2 error: {error[0]:.2e} ({rate[0]:.2f}), H1 error: {error[1]:.2e} ({rate[1]:.2f})' .format(ndofs=len(ns.basis), error=error, rate=rate)) bezier = domain.sample('bezier', 9) x, u, du = bezier.eval(['x', 'u', 'du'] @ ns, lhs=lhs) export.triplot('sol.png', x, u, tri=bezier.tri, hull=bezier.hull) export.triplot('err.png', x, du, tri=bezier.tri, hull=bezier.hull) return ndofs, error, lhs
def main(nelems: int, etype: str, btype: str, degree: int, 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