Hello,

I am currently using Fipy, to solve a set of phase field equations. These 
equations are based on the KWC model but with an additional extension made 
to it, which enables it to account for stored energy of a material to be a 
driving force.

 

Currently, I have three equations which I attempt to solve using Fipy:

   1. Phase equation 
   2. Theta (misorientation) equation 
   3. Equation for the stored energy. 

 

If I solve without the stored energy, the Fipy solver works fine and I get 
expected behavior (in 1D at least). But when I also attempt to solve 
equation for stored energy, I get a runtime warning:

 
“/nethome/v.shah/.local/lib/python3.8/site-packages/fipy/solvers/scipy/linearLUSolver.py:41:
 
RuntimeWarning: invalid value encountered in double_scalars  if 
(numerix.sqrt(numerix.sum(errorVector**2)) / error0)  <= self.tolerance:” 

So I suspect that solver is not handling these equations properly. And this 
affects the solution that we get. 

 

We expect that for 1D case, the phase boundary should move when stored 
energy is introduced (in normal phase field it should remain stationary). 
But this behavior is not observed. 

Also when I compare the analytical rates and the rates that I observe from 
Fipy and they have quite a different behavior. 

 

I am attaching a jupyter notebook here, so that the whole code (with 
references to the papers) can be looked at. (link: if the attachment doesnt 
work)

 

It would be great, if you can give me some suggestions or point out some 
mistakes that I might have made. 

 

With regards,

Vitesh Shah

-- 
To unsubscribe from this group, send email to fipy+unsubscr...@list.nist.gov

View this message at https://list.nist.gov/fipy
--- 
To unsubscribe from this group and stop receiving emails from it, send an email 
to fipy+unsubscr...@list.nist.gov.
#!/usr/bin/env python
# coding: utf-8

# # KWC model with stored energy as extra driving force
# We try to implement a phase field model described by Abrivard et al. (2012).
# The link to the paper is https://doi.org/10.1080/14786435.2012.713135
# 
# This notebook uses the example code of for 1D as an example:
# 
https://www.ctcms.nist.gov/fipy/examples/phase/generated/examples.phase.impingement.mesh40x1.html

# ### Import necessary modules

# In[1]:


import numpy as np
import os
from scipy import constants


# In[2]:


import matplotlib as mpl
mpl.use('Agg')
import matplotlib.pyplot as PyPlot
import matplotlib.ticker as ticker
from matplotlib.font_manager import FontProperties
get_ipython().run_line_magic('matplotlib', 'inline')


# In[3]:


from fipy import CellVariable, ModularVariable, Grid1D, TransientTerm, 
DiffusionTerm, ExplicitDiffusionTerm, ImplicitSourceTerm, GeneralSolver, Viewer
from fipy.tools import numerix
from fipy.tools.parser import parse
numberOfElements = parse('--numberOfElements', action = 'store',
                         type = 'int', default = 40000)
numberOfSteps = parse('--numberOfSteps', action = 'store',
                      type = 'int', default = 100)


# ### Build geometry

# In[4]:


steps = numberOfSteps
nx = 200
Lx = 1.0 * nx / 100.
dx = Lx / nx
mesh = Grid1D(dx=dx, nx=nx)


# In[5]:


dx,Lx


# ### Parameters

# In[6]:


timeStepDuration = 2.0E-03 #0.02
beta_Theta_0 = 1.126e-15
beta_phi_0 = 1.126e-17
#beta = 1e5
mu = 1e3
gamma= 1e3
epsilon = 0.8
s = 0.01
alpha = 0.04 #0.04 #0.04
omega = 7.5
a     = np.array([1.0,0.95,1.35])  # static parameters - 2.1,0.95,1.35
d     = 0.45
e     = 1.0E-03 # the second paper of Abrivard uses 0.015 originally 1e-03
beta_s = 3.0e-06
beta_r = 1.e6
Q      = 2.72e-19
C_d    = 20.0


# In[7]:


temperature = 573.


# In[8]:


dt=timeStepDuration


# ## Defining functions for calculations

# In[9]:


def beta(beta_0,Q,T):
    return beta_0 * np.exp(Q/(constants.k*T))


# In[10]:


def tau_phi(beta_phi_0,T,Q,omega):
    return beta(beta_phi_0,Q,T)*omega**2.


# In[11]:


def tau_Theta(beta_Theta_0,T,Q,omega,phi,beta_r,beta_s):
    return beta(beta_Theta_0,Q,T)*omega**2.


# In[12]:


def P_m(phi):
    return phi**2.


# In[13]:


def P_r(phi,beta_r,beta_s):
    return 1. + 
beta_r*(1.-np.exp(-beta_s*phi.value*one_over(phi.grad.mag.value)))


# In[14]:


def g(phi,a):
    return a[0]*phi + a[1]*phi**2. + a[2]*phi**3.


# In[15]:


def g_dash(phi,a):
    return a[0] + 2.*a[1]*phi + 3.*a[2]*phi**2.


# In[16]:


def h(phi,d,e):
    return d*phi**2. + e


# In[17]:


def h_dash(phi,d):
    return 2.*d*phi


# In[18]:


def one_over(f,gamma=1.e-12):
  """ 
  Gamma in the function of Kobayashi is upper limit of 1/0.0.
  Therefore, the Gamma is defined as 1/gamma here.

  Parameters:
  -----------
  f : np.ndarray
    Field
  gamma : float
    tolerance to check for zero
  """
  return np.where(np.isclose(f,0.0,atol=gamma),1.0/gamma,np.tanh(f/gamma)/f)


# In[19]:


def localize_E(theta):
    return numerix.tanh(theta.old.grad.mag**2.0)


# ## Defining phase field variables

# ### Phase variable $ \phi $

# In[20]:


phi = CellVariable(
    name='phase field',
    mesh=mesh,
    value=1.,
    hasOld=1
    )  
# the original example doesn#t have the attribute hasOld, but we need it here 
for the evolution equation of stored energy


# ## Orientation  variable $ \theta $

# In[21]:


theta = ModularVariable(
    name='theta',
    mesh=mesh,
    value=1.,
    hasOld=1
    )


# In[22]:


theta.setValue(0., where=mesh.cellCenters[0] > Lx / 2.)


# ## Stored energy field $ E_{st} $

# In[23]:


E_st = CellVariable(name='Stored_energy', mesh=mesh,value = 0.0, hasOld=True)


# If the value of $E_{st}$ is yero everywhere, then it behaves like regular KWC 
implementation. 
# Is setValue if made 0.0, we get regular KWC behaviour

# In[24]:


E_st.setValue(0.9, where=mesh.cellCenters[0] > Lx / 2.)


# ## Bulding equations

# ### Evolution equation for phase
# 
# $ \tau_\phi \frac{\partial \phi}{\partial t}$ = $ \alpha^2 \nabla^2 \phi - 
f^{'}(\phi) - C E_{st} - g^{'}(\phi) | \nabla \theta | - h^{'}(\phi) | \nabla 
\theta |^2 $
# 
# For this case we have assumed, $f(\phi) = \frac{\omega^2}{2} (1 - \phi)^2$
# 
# The expanded form of the equation split in implicit and source terms is:
# 
# $\tau_\phi \frac{\partial \phi}{\partial t}$ = $ \alpha^2 \nabla^2 \phi + 
\omega^{2} - \omega^{2} \phi - C E_{st} - a_{0}| \nabla \theta | - 2 a_{1} \phi 
| \nabla \theta | - 3a_{2} \phi^{2} | \nabla \theta | - 2 d \phi | \nabla 
\theta |^2 $
# 
# $\tau_\phi \frac{\partial \phi}{\partial t}$ = $ \alpha^2 \nabla^2 \phi - 
\bigl( \omega^{2} + \bigl[ 2 a_{1} + 3a_{2} \phi + 2 d | \nabla \theta | \bigr] 
| \nabla \theta | \bigr) \phi + \omega^{2} - C E_{st} - a_{0}| \nabla \theta |$

# In[25]:


def buildPhiEquation(phi, theta, E_st):

    old_theta_grad = theta.old.grad.mag
    implicitSource = omega**2.0
    implicitSource += (2.0*a[1] + 3.0*a[2]*phi + 
2*d*old_theta_grad)*old_theta_grad 
    sourceCoeff    = omega**2.0 - a[0]*old_theta_grad                       - 
E_st*(omega**2.0) #assuming c/w^2 = 1, therefore c = w^2
    
    return TransientTerm(tau_phi(beta_phi_0,temperature,Q,omega)) ==            
   ExplicitDiffusionTerm(alpha**2)               - 
ImplicitSourceTerm(implicitSource) + sourceCoeff


# In[26]:


phiEq = buildPhiEquation(phi, theta, E_st)


# ### Evolution equation for theta
# 
# $ \tau_\theta \frac{\partial \theta}{\partial t}$ = $\nabla \biggl ( 2 h 
(\phi) + \frac{g(\phi)}{| \nabla  \theta | } \biggr) \nabla \theta$
# 
# Expanding it leads to:
# $ \tau_\theta \frac{\partial \theta}{\partial t}$ = $\nabla \biggl ( 2 (d 
\phi^2 + e) + \frac{a_0 \phi + a_{1}\phi^2 + a_{3}\phi^3}{| \nabla \theta| } 
\biggr) \nabla \theta $
# 
# Moreover, $\tau_\theta$ is defined as,
# 
# $\tau_\theta $ = $\beta_{\theta} \mathrm{exp} (\frac{Q}{k_{B} T}) P_{m}(\phi) 
P_{r}(\phi,\nabla \phi)$
# 
# $\tau_\theta $ = $\beta_{\theta} \mathrm{exp} (\frac{Q}{k_{B} T}) \phi^2 (1 + 
\beta_{r} [ 1 - \mathrm{exp}(-\beta_{s} \frac{\phi}{| \nabla \phi |})])$

# In[27]:


def buildThetaEquation(phi, theta):
                           
    phiFace = phi.arithmeticFaceValue
    phiSq = phiFace * phiFace
    gradMag = theta.faceGrad.mag
    eps = 1. / gamma / 10.
    gradMag += (gradMag < eps) * eps
    IGamma = (gradMag > 1. / gamma) * (1 / gradMag - gamma) + gamma
    diffusionCoeff = (g(phiFace,a).value * IGamma.value + 2.0*h(phiFace,d,e))
    thetaGradDiff = theta.faceGrad - theta.faceGradNoMod
    sourceCoeff = (diffusionCoeff * thetaGradDiff).divergence
 
    return 
TransientTerm(tau_Theta(beta_Theta_0,temperature,Q,omega,phi,beta_r,beta_s) * 
P_m(phi) * P_r(phi,beta_r,beta_s)) ==                
DiffusionTerm(diffusionCoeff)                + sourceCoeff


# In[28]:


thetaEq = buildThetaEquation(phi, theta)


# ### Evolution of stored energy

# The evolution rate of the stored energy $E_{st}$ is given as,
# 
# $\frac{\partial{E_{st}}}{\partial t}$ = $- E_{st} C_{d} \mathrm{tanh}(|\nabla 
\theta|^2) \frac{\partial \phi}{\partial t}$ if $\frac{\partial \phi}{\partial 
t} > 0$
# 
# $\frac{\partial{E_{st}}}{\partial t}$ = 0 if $\frac{\partial \phi}{\partial 
t} \leq 0$
# 
# This equation is solved as described here: 
# 
# https://tsapps.nist.gov/publication/get_pdf.cfm?pub_id=854461

# In[29]:


def build_E_st_equation(phi,theta,C_d,E_st):
    prefactor = -E_st*((phi - phi.old()) > 0.0)
    prefactor = prefactor*localize_E(theta)
    return TransientTerm() == prefactor*(phi - phi.old())/dt


# In[30]:


E_st_eq = build_E_st_equation(phi,theta,C_d,E_st)


# In[31]:


if __name__ == '__main__':
    phiViewer = Viewer(vars=phi, datamin=0., datamax=1.)
    thetaViewer = Viewer(vars=theta,
                                datamin=-numerix.pi/3.0, datamax=numerix.pi/3.0)
    EstViewer = Viewer(vars=E_st, datamin=0., datamax=1.)
    phiViewer.plot()
    thetaViewer.plot()
    EstViewer.plot()


# #### Solve per time step

# In[32]:


from builtins import range
for i in range(steps):
    theta.updateOld()
    phi.updateOld()
    E_st.updateOld()
    thetaEq.solve(theta, dt=timeStepDuration, 
solver=GeneralSolver(iterations=3000, tolerance=1e-15))
    phiEq.solve(phi, dt=timeStepDuration, solver=GeneralSolver(iterations=3000, 
tolerance=1e-15))
    E_st_eq.solve(E_st,dt=timeStepDuration, 
solver=GeneralSolver(iterations=3000, tolerance=1e-15))
    if __name__ == '__main__':
        if (i+1)%100 == 0:
            phiViewer.plot()
            thetaViewer.plot()


# ## Visualization

# In[33]:


phiViewer.plot()


# In[34]:


thetaViewer.plot()


# In[35]:


PyPlot.plot((phi-phi.old).value)
PyPlot.title('phi_dot')


# #### Different rates in the $\phi$ equation plotted
# If we sum these indivual contributions, we get the 'expected' evolution rate 
of $\phi$

# In[36]:


PyPlot.figure(figsize=(12,5))
PyPlot.plot((alpha**2.0)*np.gradient(np.gradient(phi.value)),label='divergence_phi')
PyPlot.plot((omega**2.0)*(1.0 - phi.value),label='$ \omega^2 (1 -\phi) $')
PyPlot.plot(-(g_dash(phi,a)*theta.old.grad.mag).value,label='$ -g(\phi) 
grad(theta) $')
PyPlot.plot(-(h_dash(phi,d)*(theta.old.grad.mag)**2.0).value,label='$-h(\phi) 
sq(grad(Theta)) $')
PyPlot.plot(-E_st.value*(omega**2.0),label='$- E \omega^2$')
PyPlot.legend()


# In[37]:


example_phi_dot = (alpha**2.0)*np.gradient(np.gradient(phi.value))              
     + (omega**2.0)*(1.0 - phi.value)                   - 
(g_dash(phi,a)*theta.old.grad.mag).value                   - 
(h_dash(phi,d)*(theta.old.grad.mag)**2.0).value                   - 
E_st.value*(omega**2.0)


# In[38]:


PyPlot.plot(example_phi_dot)
PyPlot.title('phi_dot_expected')


# In[ ]:




Reply via email to