Surrogates

Surrogates can be contructed in place of thermodynamic functions to reduce computational time of the KWN model. This is useful for sensitivity analysis where certain parameters need to be pertubated often.

As with the Thermodynamics module, the Surrogates module are split into two classes for binary and multicomponent systems.

Binary Systems

Surrogates for driving force, interfacial composition and diffusivity can be created for binary systems.

Both the Binary and Multicomponent surrogates require the thermodynamic functions for the various terms. While these can be user-defined, it is easiest to use a Thermodynamics object.

1  from kawin.thermo import BinaryThermodynamics
2  from kawin.thermo import BinarySurrogate
3  
4  #Load TDB file into a Thermodynamics object
5  binaryTherm = BinaryThermodynamics('AlScZr.tdb', ['AL', 'ZR'], ['FCC_A1', 'AL3ZR'])
6  binaryTherm.setGuessComposition(0.24)
7  
8  #Create Surrogate object
9  binarySurr = BinarySurrogate(binaryTherm)

Driving force

Training a surrogate model for driving forces in a binary system requires a set of compositions and temperatures (or a single temperature for isothermal systems). An additional parameter called ‘scale’ will convert the set of training compositions into linear or logarithmic spacing. This will allow for training on both dilute (logarithmic spacing) and non-dilute (linear spacing) systems.

10  %matplotlib inline
11  import matplotlib.pyplot as plt
12  import numpy as np
13  
14  #Train driving forces
15  T = 723.15
16  xtrain = np.logspace(-5, -2, 20)
17  binarySurr.trainDrivingForce(xtrain, T)
18  
19  #Compare surrogate and thermodynamics modules
20  xTest = np.linspace(1e-7, 1.5e-2, 100)
21  binaryTherm.clearCache()
22  dgTherm, _ = binaryTherm.getDrivingForce(xTest, np.ones(100)*T)
23  dgSurr, _ = binarySurr.getDrivingForce(xTest, np.ones(100)*T)
24  
25  fig, ax = plt.subplots(figsize=(6,5))
26  ax.plot(xTest, dgTherm, label='Thermodynamics', linewidth=2, alpha=0.75)
27  ax.plot(xTest, dgSurr, label='Surrogate', color='k', linestyle=(0,(5,5)), linewidth=2)
28  ax.set_xlim([0, 0.014])
29  ax.set_ylim([-10000, 10000])
30  ax.set_xlabel('xZr (mole fraction)')
31  ax.set_ylabel('Driving Force (J/mol)')
32  ax.legend()
33  plt.show()

Interfacial composition

Training a surrogate for interfacial compositions requires a set of temperatures and free energy contributions. For the free energy contributions, it may be useful to setup the KWN model first, then calling $ KWNBase.particleGibbs(R) $ where R is a set of radii. In practice, R should encompass a larger domain than what is set for the particle size distribution in the KWN model in case the particle size distribution is updated to include large size classes during a simulation.

34  from kawin.precipitation import PrecipitateParameters
35  
36  #Create training points
37  T = 723.15
38  precipitate = PrecipitateParameters('AL3ZR')
39  precipitate.gamma = 0.1
40  precipitate.volume.setVolume(1e-5, 'VM', 4)
41  R = np.linspace(1e-10, 5e-9, 100)
42  G = precipitate.computeGibbsThomsonContribution(R)
43  
44  #Train surrogate
45  binarySurr.trainInterfacialComposition(T, G, logY=True)
46  
47  #Compare surrogate and thermodynamics modules
48  Rtest = np.linspace(1e-11, 1e-9, 100)
49  Gtest = precipitate.computeGibbsThomsonContribution(Rtest)
50  binaryTherm.clearCache()
51  xMTherm, _ = binaryTherm.getInterfacialComposition(T, Gtest)
52  xMSurr, _ = binarySurr.getInterfacialComposition(T, Gtest)
53  
54  fig, ax = plt.subplots(figsize=(6,5))
55  ax.plot(Rtest[xMTherm != -1], xMTherm[xMTherm != -1], label='Thermodynamics', linewidth=2, alpha=0.75)
56  ax.plot(Rtest[xMSurr != -1], xMSurr[xMSurr != -1], label='Surrogate', color='k', linestyle=(0,(5,5)), linewidth=2)
57  ax.set_xlim([0, 1e-9])
58  ax.set_ylim([0, 0.2])
59  ax.set_xlabel('Radius (m)')
60  ax.set_ylabel('Matrix Composition of Zr (mole fraction)')
61  ax.legend()
62  plt.show()
63

Interdiffusivity

Training a surrogate on the interdiffusivity requires a set of compositions and temperatures. If the interdiffusivity only depends on temperature, then only a single value for the composition is required.

63  #Train interdiffusivity
64  Ttrain = np.linspace(500, 1000, 10)
65  binarySurr.trainDiffusivity(0.01, Ttrain)
66  
67  #Compare surrogate and thermodynamics modules
68  Ttest = np.linspace(500, 1000, 100)
69  binaryTherm.clearCache()
70  dTherm = binaryTherm.getInterdiffusivity(0.01, Ttest)
71  dSurr = binarySurr.getInterdiffusivity(0.01, Ttest)
72  
73  fig, ax = plt.subplots(figsize=(6,5))
74  ax.plot(1/Ttest, np.log(dTherm), label='Thermodynamics', linewidth=2, alpha=0.75)
75  ax.plot(1/Ttest, np.log(dSurr), label='Surrogate', color='k', linestyle=(0,(5,5)), linewidth=2)
76  ax.set_xlim([1/1000, 1/500])
77  ax.set_xlabel('1/T ($K^{-1}$)')
78  ax.set_ylabel('$ln(D (m^2/s))$')
79  ax.legend()
80  plt.show()

Multicomponent Systems

Surrogates for driving force, interfacial composition, growth rate and impingement factor can be created for multicomponent systems. Note that as the interfacial composition, growth rate and impingement factor can all be determined by a single equilibrium calculation, these terms are grouped into ‘curvature factors’. This is similar to how these terms are handled in the Thermodynamics module.

As with the Binary surrogates, the multicomponent surrogate object only requires a MulticomponentThermodynamics object.

81  from kawin.thermo import MulticomponentThermodynamics
82  from kawin.thermo import MulticomponentSurrogate
83  
84  multiTherm = MulticomponentThermodynamics('NiCrAl.tdb', ['NI', 'CR', 'AL'], ['FCC_A1', 'FCC_L12'])
85  multiSurr = MulticomponentSurrogate(multiTherm)

Driving force

Training a surrogate for driving force calculations requires a set of compositions and temperatures. The difference between the Binary and Multicomponent surrogate objects is that the set of compositions for a multicomponent systems is a 2D array of size m x n, where m is the number of training points and n is the number of solutes.

A utility function is provided to create a cartesian product of multiple arrays for each solute.

 86  from kawin.thermo import generateTrainingPoints
 87  
 88  #Create training points
 89  T = 1273.15
 90  xCr = np.linspace(0.01, 0.05, 8)
 91  xAl = np.linspace(0.1, 0.2, 8)
 92  xTrain = generateTrainingPoints(xCr, xAl)
 93  
 94  #Train driving force
 95  multiSurr.trainDrivingForce(xTrain, T)
 96  
 97  xCrTest = np.linspace(0.01, 0.05, 50)
 98  xAlTest = np.linspace(0.1, 0.2, 50)
 99  X, Y = np.meshgrid(xCrTest, xAlTest)
100  points = np.array([X.flatten(), Y.flatten()]).T
101  dg, xb = multiSurr.getDrivingForce(points, T)
102  dg = np.reshape(dg, X.shape)
103  
104  fig, ax = plt.subplots(figsize=(6,5))
105  c = ax.pcolormesh(X, Y, dg, shading='gouraud', cmap='coolwarm', vmin=-1500, vmax=1500)
106  ax.contour(X, Y, dg, colors='k', levels=[0], linestyles='--')
107  ax.set_xlabel('X(Cr)')
108  ax.set_ylabel('X(Al)')
109  fig.colorbar(c, ax=ax, label='Driving Force (J/mol)')
110  plt.show()

Curvature factors

The growth rate, interfacial composition, interdiffusivity and impingement rate can all be determined from the curvature of the Gibbs free energy surface. Thus, these terms are lumped into a single group that will be referred to as ‘curvature factors’. Training the curvature factors only requires a set of compositions and temperatures.

111  #Create training points
112  T = 1273.15
113  xCr = np.linspace(0.01, 0.05, 16)
114  xAl = np.linspace(0.1, 0.2, 16)
115  xTrain = generateTrainingPoints(xCr, xAl)
116  
117  #Train curvature surrogate
118  multiSurr.trainCurvature(xTrain, T)
119  
120  precipitate = PrecipitateParameters('FCC_L12')
121  precipitate.gamma = 0.023
122  precipitate.volume.setVolume(1e-5, 'VM', 4)
123  Rtest = np.linspace(1e-10, 3e-8, 300)
124  Gtest = precipitate.computeGibbsThomsonContribution(Rtest)
125  
126  fig, ax = plt.subplots(figsize=(6,5))
127  
128  xset = {'Ni-3Cr-10Al': [0.03, 0.1], 'Ni-3Cr-15Al': [0.03, 0.15], 'Ni-3Cr-17.5Al': [0.03, 0.175], 'Ni-3Cr-20Al': [0.03, 0.2]}
129  
130  for x in xset:
131      multiTherm.clearCache()
132      dgTherm, xb = multiTherm.getDrivingForce(xset[x], T)
133      growthTherm = multiTherm.getGrowthAndInterfacialComposition(xset[x], T, dgTherm, Rtest, Gtest, searchDir=xb)
134  
135      dgSurr, _ = multiSurr.getDrivingForce(xset[x], T)
136      growthSurr = multiSurr.getGrowthAndInterfacialComposition(xset[x], T, dgSurr, Rtest, Gtest)
137  
138      ax.plot(Rtest, growthTherm.growth_rate, label=f'Thermodynamics ({x})', linewidth=2, alpha=0.75)
139      ax.plot(Rtest, growthSurr.growth_rate, label='Surrogate', color='k', linestyle=(0,(5,5)), linewidth=2)
140  
141  ax.set_xlim([0, 3e-8])
142  ax.set_ylim([-2e-6, 4e-6])
143  ax.set_xlabel('Radius (m)')
144  ax.set_ylabel('Growth Rate (m/s)')
145  handles, labels = ax.get_legend_handles_labels()
146  plotLabels = [f'Thermodynamics ({x})' for x in xset] + ['Surrogate']
147  ax.legend([handles[labels.index(p)] for p in plotLabels], plotLabels)
148  plt.show()

Hyperparameters

The surrogates are created through scipy’s radial basis functions. The same hyperparameters used in the scipy’s implementation can be used for these surrogates. These include: ‘function’, ‘epsilon’, ‘smooth’. ‘function’ is the basis function to use, ‘epsilon’ is the scale between training points (the surrogates will automatically scale the training points such that the optimal value for ‘epsilon’ should be near 1), and ‘smooth’ allows for smoothing the interpolation (a value of 0 means that the surrogate will cross all training points). When training the surrogates, these are set as additional parameters. For example:

$ Surrogate.trainDrivingForce(x, T, function=‘linear’, epsilon=1, smooth=0) $

If a surrogate is already trained, the hyperparameters can be changed without the need for re-training.

$ Surrogate.changeDrivingForceHyperparameters(function, epsilon, smooth) $

Saving and loading

The surrogates can be saved and loaded for later usage. These will not retain the thermodynamic functions used for the training, so re-training of the surrogate cannot be done after saving/loading; however, the hyperparameters can still be changed.

$ Surrogate.toJson(filename) $

$ Surrogate.fromJson(filename) $

Usage in the KWN Model

As with the Thermodynamics module, the Surrogate objects can be easily used in the KWN model by:

$ KWNModel.setSurrogate(Surrogate) $

For binary systems, the interdiffusivity also has to be inputted separately.

$ KWNModel.setDiffusivity(BinarySurrogate.getInterdiffusivity) $