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) $