From c4f23fbe204ec54b440dfbd64e55d051e64584fd Mon Sep 17 00:00:00 2001 From: Martin Bauer <martin.bauer@fau.de> Date: Fri, 9 Feb 2018 17:07:18 +0100 Subject: [PATCH] PressureTensor approach for phase field --- phasefield/analytical.py | 126 ++++++++++++++++++++++----------------- 1 file changed, 70 insertions(+), 56 deletions(-) diff --git a/phasefield/analytical.py b/phasefield/analytical.py index c16028b6..d450b752 100644 --- a/phasefield/analytical.py +++ b/phasefield/analytical.py @@ -8,62 +8,6 @@ surfaceTensionSymbolName = "tau" interfaceWidthSymbol = sp.Symbol("alpha") -def functionalDerivative(functional, v, constants=None): - """ - Computes functional derivative of functional with respect to v using Euler-Lagrange equation - - .. math :: - - \frac{\delta F}{\delta v} = - \frac{\partial F}{\partial v} - \nabla \cdot \frac{\partial F}{\partial \nabla v} - - - assumes that gradients are represented by Diff() node (from Chapman Enskog module) - - Diff(Diff(r)) represents the divergence of r - - the constants parameter is a list with symbols not affected by the derivative. This is used for simplification - of the derivative terms. - """ - functional = expandUsingLinearity(functional, constants=constants) - diffs = functional.atoms(Diff) - - diffV = Diff(v) - assert diffV in diffs # not necessary in general, but for this use case this should be true - - nonDiffPart = functional.subs({d: 0 for d in diffs}) - - partialF_partialV = sp.diff(nonDiffPart, v) - - dummy = sp.Dummy() - partialF_partialGradV = functional.subs(diffV, dummy).diff(dummy).subs(dummy, diffV) - - result = partialF_partialV - Diff(partialF_partialGradV) - return expandUsingLinearity(result, constants=constants) - - -def coshIntegral(f, var): - """Integrates a function f that has exactly one cosh term, from -oo to oo, by - substituting a new helper variable for the cosh argument""" - coshTerm = list(f.atoms(sp.cosh)) - assert len(coshTerm) == 1 - integral = sp.Integral(f, var) - transformedInt = integral.transform(coshTerm[0].args[0], sp.Symbol("u", real=True)) - return sp.integrate(transformedInt.args[0], (transformedInt.args[1][0], -sp.oo, sp.oo)) - - -def discreteLaplace(field, index, dx): - """Returns second order Laplace stencil""" - dim = field.spatialDimensions - count = 0 - result = 0 - for d in range(dim): - for offset in (-1, 1): - count += 1 - result += field.neighbor(d, offset)(index) - - result -= count * field.center(index) - result /= dx ** 2 - return result - - def symmetricSymbolicSurfaceTension(i, j): """Returns symbolic surface tension. The function is symmetric, i.e. interchanging i and j yields the same result. If both phase indices i and j are chosen equal, zero is returned""" @@ -181,6 +125,20 @@ def freeEnergyFunctionalNPhases(numPhases=None, surfaceTensions=symmetricSymboli return result +def separateIntoBulkAndInterface(freeEnergy): + """Separates the bulk and interface parts of a free energy + + >>> F = freeEnergyFunctionalNPhases(3) + >>> bulk, inter = separateIntoBulkAndInterface(F) + >>> assert sp.expand(bulk - freeEnergyFunctionalNPhases(3, includeInterface=False)) == 0 + >>> assert sp.expand(inter - freeEnergyFunctionalNPhases(3, includeBulk=False)) == 0 + """ + freeEnergy = freeEnergy.expand() + bulkPart = freeEnergy.subs({a: 0 for a in freeEnergy.atoms(Diff)}) + interfacePart = freeEnergy - bulkPart + return bulkPart, interfacePart + + def analyticInterfaceProfile(x, interfaceWidth=interfaceWidthSymbol): """Analytic expression for a 1D interface normal to x with given interface width @@ -238,6 +196,62 @@ def createForceUpdateEquations(forceField, phiField, muField, dx=1): return forceSweepEqs +def functionalDerivative(functional, v, constants=None): + """ + Computes functional derivative of functional with respect to v using Euler-Lagrange equation + + .. math :: + + \frac{\delta F}{\delta v} = + \frac{\partial F}{\partial v} - \nabla \cdot \frac{\partial F}{\partial \nabla v} + + - assumes that gradients are represented by Diff() node (from Chapman Enskog module) + - Diff(Diff(r)) represents the divergence of r + - the constants parameter is a list with symbols not affected by the derivative. This is used for simplification + of the derivative terms. + """ + functional = expandUsingLinearity(functional, constants=constants) + diffs = functional.atoms(Diff) + + diffV = Diff(v) + #assert diffV in diffs # not necessary in general, but for this use case this should be true + + nonDiffPart = functional.subs({d: 0 for d in diffs}) + + partialF_partialV = sp.diff(nonDiffPart, v) + + dummy = sp.Dummy() + partialF_partialGradV = functional.subs(diffV, dummy).diff(dummy).subs(dummy, diffV) + + result = partialF_partialV - Diff(partialF_partialGradV) + return expandUsingLinearity(result, constants=constants) + + +def coshIntegral(f, var): + """Integrates a function f that has exactly one cosh term, from -oo to oo, by + substituting a new helper variable for the cosh argument""" + coshTerm = list(f.atoms(sp.cosh)) + assert len(coshTerm) == 1 + integral = sp.Integral(f, var) + transformedInt = integral.transform(coshTerm[0].args[0], sp.Symbol("u", real=True)) + return sp.integrate(transformedInt.args[0], (transformedInt.args[1][0], -sp.oo, sp.oo)) + + +def discreteLaplace(field, index, dx): + """Returns second order Laplace stencil""" + dim = field.spatialDimensions + count = 0 + result = 0 + for d in range(dim): + for offset in (-1, 1): + count += 1 + result += field.neighbor(d, offset)(index) + + result -= count * field.center(index) + result /= dx ** 2 + return result + + def cahnHilliardFdEq(phaseIdx, phi, mu, velocity, mobility, dx, dt): from pystencils.finitedifferences import transient, advection, diffusion, Discretization2ndOrder cahnHilliard = transient(phi, phaseIdx) + advection(phi, velocity, phaseIdx) - diffusion(mu, mobility, phaseIdx) -- GitLab