In [1]:
from pycalphad import Database, Model, calculate, equilibrium, variables as v
from xarray import DataArray
In [2]:
class PrecipitateModel(Model):
matrix_chempots = []
@property
def matrix_hyperplane(self):
return sum(self.moles(self.nonvacant_elements[i])*self.matrix_chempots[i]
for i in range(len(self.nonvacant_elements)))
@property
def GM(self):
return self.ast - self.matrix_hyperplane
class GibbsThompsonModel(Model):
"Spherical particle."
radius = 1e-6 # m
volume = 7.3e-6 # m^3/mol
interfacial_energy = 250e-3 # J/m^2
elastic_misfit_energy = 0 # J/mol
@property
def GM(self):
return self.ast + (2*self.interfacial_energy/self.radius + self.elastic_misfit_energy) * self.volume
In [3]:
import numpy as np
def parallel_tangent(dbf, comps, matrix_phase, matrix_comp, precipitate_phase, temp):
conds = {v.N: 1, v.P: 1e5, v.T: temp}
conds.update(matrix_comp)
matrix_eq = equilibrium(dbf, comps, matrix_phase, conds)
# pycalphad currently doesn't have a way to turn global minimization off and directly specify starting points
if matrix_eq.isel(vertex=1).Phase.values.flatten() != ['']:
raise ValueError('Matrix phase has miscibility gap. This bug will be fixed in the future')
matrix_chempots = matrix_eq.MU.values.flatten()
# This part will not work until mass balance constraint can be relaxed
#precip = PrecipitateModel(dbf, comps, precipitate_phase)
#precip.matrix_chempots = matrix_chempots
#conds = {v.N: 1, v.P: 1e5, v.T: temp}
#df_eq = equilibrium(dbf, comps, precipitate_phase, conds, model=precip)
df_eq = calculate(dbf, comps, precipitate_phase, T=temp, N=1, P=1e5)
df_eq['GM'] = df_eq.X.values[0,0,0].dot(matrix_chempots) - df_eq.GM
selected_idx = df_eq.GM.argmax()
return matrix_eq.isel(vertex=0), df_eq.isel(points=selected_idx)
def nucleation_barrier(dbf, comps, matrix_phase, matrix_comp, precipitate_phase, temp,
interfacial_energy, precipitate_volume):
"Spherical precipitate."
matrix_eq, precip_eq = parallel_tangent(dbf, comps, matrix_phase, matrix_comp, precipitate_phase, temp)
precip_driving_force = float(precip_eq.GM.values) # J/mol
elastic_misfit_energy = 0 # J/m^3
barrier = 16./3 * np.pi * interfacial_energy **3 / (precip_driving_force + elastic_misfit_energy) ** 2 # J/mol
critical_radius = 2 * interfacial_energy / ((precip_driving_force / precipitate_volume) + elastic_misfit_energy) # m
print(temp, critical_radius)
if critical_radius < 0:
barrier = np.inf
cluster_area = 4*np.pi*critical_radius**2
cluster_volume = (4./3) * np.pi * critical_radius**3
return barrier, precip_driving_force, cluster_area, cluster_volume, matrix_eq, precip_eq
def growth_rate(dbf, comps, matrix_phase, matrix_comp, precipitate_phase, temp, particle_radius):
conds = {v.N: 1, v.P: 1e5, v.T: temp}
conds.update(matrix_comp)
# Fictive; Could retrieve from TDB in principle
mobility = 1e-7 * np.exp(-14e4/(8.3145*temp)) # m^2 / s
mobilities = np.eye(len(matrix_comp)+1) * mobility
matrix_ff_eq, precip_eq = parallel_tangent(dbf, comps, matrix_phase, matrix_comp, precipitate_phase, temp)
precip_driving_force = float(precip_eq.GM.values) # J/mol
interfacial_energy = 250e-3 # J/m^2
precipitate_volume = 7.3e-6 # m^3/mol
elastic_misfit_energy = 0 # J/m^3
# Spherical particle
critical_radius = 2 * interfacial_energy / ((precip_driving_force / precipitate_volume) + elastic_misfit_energy) # m
# XXX: Should really be done with global min off, fixed-phase conditions, etc.
# As written, this will break with miscibility gaps
particle_mod = GibbsThompsonModel(dbf, comps, precipitate_phase)
particle_mod.radius = particle_radius
interface_eq = equilibrium(dbf, comps, [matrix_phase, precipitate_phase], conds,
model={precipitate_phase: particle_mod})
matrix_idx = np.nonzero((interface_eq.Phase==matrix_phase).values.flatten())[0]
if len(matrix_idx) > 1:
raise ValueError('Matrix phase has miscibility gap')
elif len(matrix_idx) == 0:
# Matrix is metastable at this composition; massive transformation kinetics?
print(interface_eq)
raise ValueError('Matrix phase is not stable')
else:
matrix_idx = matrix_idx[0]
matrix_interface_eq = interface_eq.isel(vertex=matrix_idx)
precip_idx = np.nonzero((interface_eq.Phase==precipitate_phase).values.flatten())[0]
if len(precip_idx) > 1:
raise ValueError('Precipitate phase has miscibility gap')
elif len(precip_idx) == 0:
precip_conc = np.zeros(len(interface_eq.component))
# Precipitate is metastable at this radius (it will start to dissolve)
# Compute equilibrium for precipitate by itself at parallel tangent composition
pt_comp = {v.X(str(comp)): precip_eq.X.values.flatten()[idx] for idx, comp in enumerate(precip_eq.component.values[:-1])}
conds = {v.N: 1, v.P: 1e5, v.T: temp}
conds.update(pt_comp)
precip_interface_eq = equilibrium(dbf, comps, precipitate_phase, conds,
model={precipitate_phase: particle_mod})
precip_interface_eq = precip_interface_eq.isel(vertex=0)
else:
precip_idx = precip_idx[0]
precip_interface_eq = interface_eq.isel(vertex=precip_idx)
eta_geo_factor = 1.0
# Growth equation from Eq. 12, M. Paliwal and I.-H Jung, Calphad, 2019
velocity = 2*interfacial_energy * precipitate_volume * (1./critical_radius - 1./particle_radius)
x_int_precip = precip_interface_eq.X.values.flatten()
x_int_matrix = matrix_interface_eq.X.values.flatten()
denominator = 0
for i in range(mobilities.shape[0]-1):
for j in range(mobilities.shape[1]-1):
denominator += (x_int_precip[i] - x_int_matrix[i]) * (x_int_precip[j] - x_int_matrix[j]) / mobilities[i,j]
velocity /= denominator
return velocity
In [21]:
dbf = Database('Al-Cu-Zr_Zhou.tdb')
comps = ['AL', 'CU', 'VA']
matrix_compositions = np.linspace(1e-4, 1-1e-4, num=30)
particle_radii = np.logspace(-10, -3, num=60)
velocities = np.zeros((matrix_compositions.shape[0], particle_radii.shape[0]))
for mc_idx in range(velocities.shape[0]):
matrix_comp = {v.X('CU'): matrix_compositions[mc_idx]}
for r_idx in range(velocities.shape[1]):
print(mc_idx, r_idx)
velocities[mc_idx, r_idx] = growth_rate(dbf, comps, 'BCC_A2', matrix_comp, 'ETA2', 800, particle_radii[r_idx])
In [24]:
from pycalphad import calculate
import matplotlib.pyplot as plt
X, Y = np.meshgrid(matrix_compositions, np.log(particle_radii))
plt.contourf(X, Y, velocities.T, levels=5)
Out[24]:
In [43]:
import matplotlib.pyplot as plt
plt.plot(matrix_compositions, velocities[:,55])
Out[43]:
In [ ]: