Skip to content

Commit

Permalink
nucleation rate example and dynamic updating of nucleation site param…
Browse files Browse the repository at this point in the history
…eters
  • Loading branch information
Nicholas Ury committed Dec 26, 2024
1 parent e121edf commit dc7977a
Show file tree
Hide file tree
Showing 12 changed files with 435 additions and 73 deletions.
4 changes: 2 additions & 2 deletions examples/01_Binary_Precipitation.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@
"matrix = MatrixParameters(solutes=['ZR'])\n",
"matrix.initComposition = 4e-3 # Mole fraction\n",
"matrix.volume.setVolume(Va, 'VA', atomsPerCell)\n",
"matrix.nucleationSites.setNucleationDensity(dislocationDensity = 1e15)\n",
"matrix.nucleationSites.setDislocationDensity(1e15)\n",
"\n",
"precipitate = PrecipitateParameters('AL3ZR')\n",
"precipitate.gamma = 0.1 # J/m2\n",
Expand Down Expand Up @@ -122,7 +122,7 @@
"\tAL3ZR\t0.000e+00\t\t0.0000\t\t0.0000e+00\t5.7737e+03\n",
"\n",
"N\tTime (s)\tSim Time (s)\tTemperature (K)\tMatrix Comp\n",
"4069\t1.8e+06\t\t24.5\t\t723\t\t0.0127\n",
"4069\t1.8e+06\t\t29.1\t\t723\t\t0.0127\n",
"\n",
"\tPhase\tPrec Density (#/m3)\tVolume Frac\tAvg Radius (m)\tDriving Force (J/mol)\n",
"\tAL3ZR\t1.596e+22\t\t1.5498\t\t5.8031e-09\t3.4655e+02\n",
Expand Down
12 changes: 6 additions & 6 deletions examples/02_Multicomponent_Precipitation.ipynb

Large diffs are not rendered by default.

12 changes: 2 additions & 10 deletions examples/03_Multiphase_Precipitation.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -130,14 +130,6 @@
"execution_count": 4,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Nucleation density not set.\n",
"Setting nucleation density assuming grain size of 100 um and dislocation density of 5e+12 #/m2\n"
]
},
{
"name": "stderr",
"output_type": "stream",
Expand All @@ -161,7 +153,7 @@
"\tU2_PHASE\t0.000e+00\t\t0.0000\t\t0.0000e+00\t7.1719e+03\n",
"\n",
"N\tTime (s)\tSim Time (s)\tTemperature (K)\tMG\tSI\t\n",
"10000\t6.1e+04\t\t154.0\t\t523\t\t0.0631\t0.2068\t\n",
"10000\t6.1e+04\t\t166.9\t\t523\t\t0.0631\t0.2068\t\n",
"\n",
"\tPhase\tPrec Density (#/m3)\tVolume Frac\tAvg Radius (m)\tDriving Force (J/mol)\n",
"\tMGSI_B_P\t2.552e+22\t\t1.0228\t\t4.5242e-09\t7.9550e+02\n",
Expand All @@ -171,7 +163,7 @@
"\tU2_PHASE\t0.000e+00\t\t0.0000\t\t0.0000e+00\t-3.5897e+02\n",
"\n",
"N\tTime (s)\tSim Time (s)\tTemperature (K)\tMG\tSI\t\n",
"17084\t9.0e+04\t\t241.1\t\t523\t\t0.0566\t0.2032\t\n",
"17084\t9.0e+04\t\t268.5\t\t523\t\t0.0566\t0.2032\t\n",
"\n",
"\tPhase\tPrec Density (#/m3)\tVolume Frac\tAvg Radius (m)\tDriving Force (J/mol)\n",
"\tMGSI_B_P\t4.536e+21\t\t1.0329\t\t7.6846e-09\t4.6375e+02\n",
Expand Down
4 changes: 2 additions & 2 deletions examples/04_Precipitation_with_Elastic_Energy.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
"matrix = MatrixParameters(['TI'])\n",
"matrix.initComposition = 0.019\n",
"matrix.volume.setVolume(7.11e-6, 'VM', 4)\n",
"matrix.nucleationSites.setNucleationDensity(bulkN0=1e30)\n",
"matrix.nucleationSites.setBulkDensity(1e30)\n",
"\n",
"precipitate = PrecipitateParameters('CU4TI')\n",
"precipitate.gamma = 0.035\n",
Expand Down Expand Up @@ -119,7 +119,7 @@
"\tCU4TI\t0.000e+00\t\t0.0000\t\t0.0000e+00\t1.9660e+03\n",
"\n",
"N\tTime (s)\tSim Time (s)\tTemperature (K)\tMatrix Comp\n",
"4127\t1.0e+05\t\t57.4\t\t623\t\t0.1758\n",
"4127\t1.0e+05\t\t53.9\t\t623\t\t0.1758\n",
"\n",
"\tPhase\tPrec Density (#/m3)\tVolume Frac\tAvg Radius (m)\tDriving Force (J/mol)\n",
"\tCU4TI\t1.476e+23\t\t9.3686\t\t5.0967e-09\t1.2302e+02\n",
Expand Down
169 changes: 169 additions & 0 deletions examples/13_Nucleation_Rate.ipynb

Large diffs are not rendered by default.

20 changes: 1 addition & 19 deletions kawin/diffusion/DiffusionParameters.py
Original file line number Diff line number Diff line change
Expand Up @@ -682,22 +682,4 @@ def reset(self):
# Max composition change a node can see per iteration in the homogenization model
# There is not an analagous method for von Neumann stability as we have for the single
# phase diffusion model, so this is a naive approach to numerical stability
self.maxCompositionChange = 0.002

class DiffusionParameters:
def __init__(self, elements,
temperatureParameters = None,
boundaryCondition = None,
compositionProfile = None,
hashTable = None,
homogenizationParameters = None,
minComposition = 1e-8,
maxCompositionChange = 0.002):
self.temperature = TemperatureParameters() if temperatureParameters is None else temperatureParameters
self.boundaryConditions = BoundaryConditions(elements) if boundaryCondition is None else boundaryCondition
self.compositionProfile = CompositionProfile(elements) if compositionProfile is None else compositionProfile
self.hashTable = HashTable() if hashTable is None else hashTable
self.homogenizationParameters = HomogenizationParameters() if homogenizationParameters is None else homogenizationParameters

self.minComposition = minComposition
self.maxCompositionChange = maxCompositionChange
self.maxCompositionChange = 0.002
6 changes: 0 additions & 6 deletions kawin/precipitation/KWNBase.py
Original file line number Diff line number Diff line change
Expand Up @@ -484,12 +484,6 @@ def setup(self):
if self._isSetup:
return

if not self.matrixParameters.nucleationSites._parametersSet:
#Set nucleation density assuming grain size of 100 um and dislocation density of 5e12 m/m3 (Thermocalc default)
print('Nucleation density not set.\nSetting nucleation density assuming grain size of {:.0f} um and dislocation density of {:.0e} #/m2'.format(100, 5e12))
self.matrixParameters.nucleationSites.setNucleationDensity(100, 1, 5e12)
self.matrixParameters.nucleationSites._parametersSet = True
self.matrixParameters.nucleationSites.setupNucleationDensity(self.matrixParameters.initComposition, self.matrixParameters.volume.Vm)
for p in range(len(self.phases)):
self.precipitateParameters[p].nucleation.gbEnergy = self.matrixParameters.GBenergy
self.precipitateParameters[p].validate()
Expand Down
23 changes: 21 additions & 2 deletions kawin/precipitation/PrecipitationParameters.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,15 +109,34 @@ def __call__(self, t):
class MatrixParameters:
def __init__(self, solutes):
self.solutes = solutes
self.initComposition = None
self._initComposition = None

self.volume = VolumeParameter()
self.volume._updateCallbacks.append(self.update)
self.nucleationSites = NucleationSiteParameters()
self.GBenergy = 0.3

self.effectiveDiffusion = EffectiveDiffusionFunctions()
self.theta = 2

@property
def initComposition(self):
return self._initComposition

@initComposition.setter
def initComposition(self, value):
self._initComposition = value
self.update()

def update(self):
# update nucleation site volume and bulkN0
# only do this once both volume and composition is defined
# if bulkN0 is set before composition or volume, then we leave to the user defined bulkN0
self.nucleationSites.VmAlpha = self.volume.Vm
if self._initComposition is not None and self.nucleationSites.VmAlpha is not None:
if self.nucleationSites._compositionDependentBulkN0:
self.nucleationSites.setBulkDensityFromComposition(self._initComposition)

class PrecipitateParameters:
'''
Parameters for a single precipitate
Expand Down Expand Up @@ -162,7 +181,7 @@ def validate(self):
self.nucleation.gamma = self.gamma

if self.nucleation.description.isGrainBoundaryNucleation and not isinstance(self.shapeFactor.description, SphereDescription):
raise ValueError('Nucleation is set to grain boundary nucleaiton and shape factor not set to spherical. \
raise ValueError('Nucleation is set to grain boundary nucleation and shape factor not set to spherical. \
If using GB nucleation, shape factor should be spherical. If shape factor is spherical, nucleation should be bulk or dislocations')

# If strain energy is not constant, then switch to description that matches shapeFactor
Expand Down
124 changes: 99 additions & 25 deletions kawin/precipitation/parameters/Nucleation.py
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,7 @@ def setNucleationType(self, site):

def _validateInputs(self):
if self.gamma is None or self.gamma == 0:
raise ValueError(f"Interfacial energy (gamma) is not set. NucleationBarrierParametesr.gamma = {self.gamma}")
raise ValueError(f"Interfacial energy (gamma) is not set. NucleationBarrierParameters.gamma = {self.gamma}")
if self.gbEnergy is None:
raise ValueError(f"Grain boundary energy (gbEnergy) is not set. NucleationBarrierParameters.gbEnergy = {self.gbEnergy}")

Expand Down Expand Up @@ -350,14 +350,50 @@ def Gcrit(self, dG, Rcrit):
return Rcrit**2 * ((self.areaFactor * self.gamma - self.gbRemoval * self.gbEnergy) - self.volumeFactor * dG * Rcrit)

class NucleationSiteParameters:
def __init__(self, grainSize = 100, aspectRatio = 1, dislocationDensity = 5e12, bulkN0 = None):
self.setNucleationDensity(grainSize, aspectRatio, dislocationDensity, bulkN0)
def __init__(self, grainSize = 100, aspectRatio = 1, dislocationDensity = 5e12):
self._grainSize = grainSize
self._grainAspectRatio = aspectRatio
self._dislocationDensity = dislocationDensity

self._parametersSet = False
self.GBareaN0 = None
self.GBedgeN0 = None
self.GBcornerN0 = None
self.dislocationN0 = None
self.VmAlpha = None

self._bulkN0 = None
self._compositionDependentBulkN0 = True
self._GBareaN0 = None
self._GBedgeN0 = None
self._GBcornerN0 = None
self._dislocationN0 = None

@property
def grainSize(self):
return self._grainSize

@grainSize.setter
def grainSize(self, value):
self._grainSize = value
self._GBareaN0 = None
self._GBedgeN0 = None
self._GBcornerN0 = None

@property
def grainAspectRatio(self):
return self._grainAspectRatio

@grainAspectRatio.setter
def grainAspectRatio(self, value):
self._grainAspectRatio = value
self._GBareaN0 = None
self._GBedgeN0 = None
self._GBcornerN0 = None

@property
def dislocationDensity(self):
return self._dislocationDensity

@dislocationDensity.setter
def dislocationDensity(self, value):
self._dislocationDensity = value
self._dislocationN0 = None

def setNucleationDensity(self, grainSize = 100, aspectRatio = 1, dislocationDensity = 5e12, bulkN0 = None):
'''
Expand All @@ -379,16 +415,64 @@ def setNucleationDensity(self, grainSize = 100, aspectRatio = 1, dislocationDens
self.grainSize = grainSize * 1e-6
self.grainAspectRatio = aspectRatio
self.dislocationDensity = dislocationDensity
if bulkN0 is not None:
self.bulkN0 = bulkN0

def setGrainSize(self, grainSize = 100, aspectRatio = 1):
self.grainSize = grainSize * 1e-6
self.grainAspectRatio = aspectRatio

def setDislocationDensity(self, dislocationDensity):
self.dislocationDensity = dislocationDensity

def setBulkDensity(self, bulkN0):
self.bulkN0 = bulkN0
self._parametersSet = True

def bulkSites(self, x0, VmAlpha):
def setBulkDensityFromComposition(self, x0):
#Set bulk nucleation site to the number of solutes per unit volume
# This is the represent that any solute atom can be a nucleation site
#NOTE: some texts will state the bulk nucleation sites to just be the number
# of lattice sites per unit volume. The justification for this would be
# the solutes can diffuse around to any lattice site and nucleate there
return np.amin(x0) * (AVOGADROS_NUMBER / VmAlpha)
self._validateVolume('bulkN0 from composition')
self.bulkN0 = np.amin(x0) * (AVOGADROS_NUMBER / self.VmAlpha)
self._compositionDependentBulkN0 = True

@property
def bulkN0(self):
return self._bulkN0

@bulkN0.setter
def bulkN0(self, value):
self._bulkN0 = value
self._compositionDependentBulkN0 = False

@property
def dislocationN0(self):
if self._dislocationN0 is None:
self._validateVolume('dislocationN0')
self._dislocationN0 = self.dislocationSites(self.VmAlpha)
return self._dislocationN0

@property
def GBareaN0(self):
if self._GBareaN0 is None:
self._validateVolume('GBareaN0')
self._GBareaN0 = self.grainBoundarySites(self.grainSize, self.grainAspectRatio, self.VmAlpha)
return self._GBareaN0

@property
def GBedgeN0(self):
if self._GBedgeN0 is None:
self._validateVolume('GBedgeN0')
self._GBedgeN0 = self.grainEdgeSites(self.grainSize, self.grainAspectRatio, self.VmAlpha)
return self._GBedgeN0

@property
def GBcornerN0(self):
if self._GBcornerN0 is None:
self._GBcornerN0 = self.grainCornerSites(self.grainSize, self.grainAspectRatio, self.VmAlpha)
return self._GBcornerN0

def dislocationSites(self, VmAlpha):
return self.dislocationDensity * (AVOGADROS_NUMBER / VmAlpha)**(1/3)
Expand Down Expand Up @@ -420,18 +504,8 @@ def grainCornerDensity(self, grainSize, grainAspectRatio):
def grainCornerSites(self, grainSize, grainAspectRatio, VmAlpha):
rho = self.grainCornerDensity(grainSize, grainAspectRatio)
return rho

def setupNucleationDensity(self, x0, VmAlpha):
if self.bulkN0 is None:
self.bulkN0 = self.bulkSites(x0, VmAlpha)
self.dislocationN0 = self.dislocationSites(VmAlpha)

if self.grainSize != np.inf:
self.GBareaN0 = self.grainBoundarySites(self.grainSize, self.grainAspectRatio, VmAlpha)
self.GBedgeN0 = self.grainEdgeSites(self.grainSize, self.grainAspectRatio, VmAlpha)
self.GBcornerN0 = self.grainCornerSites(self.grainSize, self.grainAspectRatio, VmAlpha)
else:
self.GBareaN0 = 0
self.GBedgeN0 = 0
self.GBcornerN0 = 0

def _validateVolume(self, term):
if self.VmAlpha is None:
raise ValueError(f'NucleationSiteParameters.VMalpha must be set to compute {term}.')

7 changes: 7 additions & 0 deletions kawin/precipitation/parameters/ShapeFactors.py
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,13 @@ def setPrecipitateShape(self, precipitateShape, ar = 1):
General shape setting function
Defaults to spherical
Parameters
----------
precipitateShape: str
'sphere', 'needle', 'plate' or 'cubic'
ar: float or callable
Aspect ratio. If callable, then it must be a function of equivalent spherical radius
'''
descriptionDict = {
SphereDescription.name.upper(): SphereDescription(),
Expand Down
6 changes: 5 additions & 1 deletion kawin/precipitation/parameters/Volume.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ def __init__(self, value=None, volumeType=None, atomsPerCell=None):
self.atomsPerCell = None
else:
self.setVolume(value, volumeType, atomsPerCell)
self._updateCallbacks = []

def setVolume(self, value, volumeType, atomsPerCell):
'''
Expand Down Expand Up @@ -44,4 +45,7 @@ def setVolume(self, value, volumeType, atomsPerCell):
self.Vm = self.Va * AVOGADROS_NUMBER / atomsPerCell
else:
valid_values = "['VM', 'VA', 'a', VolumeParameter.MOLAR_VOLUME, VolumeParameter.ATOMIC_VOLUME, VolumeParameter.LATTICE_PARAMETER]"
raise ValueError(f'Unknown volume type {volumeType}. Values must be: {valid_values}')
raise ValueError(f'Unknown volume type {volumeType}. Values must be: {valid_values}')

for callback in self._updateCallbacks:
callback()
Loading

0 comments on commit dc7977a

Please sign in to comment.