First we import useful libraries and declare figure parameters for making nice figures.
In [1]:
%matplotlib inline
import numpy as np
import scipy.integrate as sci
import scipy.special as scs
import matplotlib.pyplot as plt
import math
import warnings
warnings.filterwarnings('ignore')
fsize = 15
newparams = {'axes.titlesize': fsize, 'axes.labelsize': fsize,
'axes.linewidth': 2, 'savefig.dpi': 200,
'lines.linewidth': 2.0, 'lines.markersize': 7,
'figure.figsize': (16, 5), 'figure.subplot.wspace': 0.4,
'ytick.labelsize': fsize, 'xtick.labelsize': fsize,
'ytick.major.pad': 3, 'xtick.major.pad': 3,
'xtick.major.size': 2, 'ytick.major.size': 2,
'legend.handlelength': 1.5, 'legend.fontsize': fsize}
plt.rcParams.update(newparams)
Calculating exact values of factorials of large numbers poses no problem in Python when using libraries such as scipy
or numpy
. The implementation of Stirling's formula
is therefore straight forward.
In [2]:
def stirling(N):
return [i*math.log(i) - i for i in N]
def exact(N):
return [math.log(scs.factorial(i, exact=True)) for i in N]
Declaring and calculating exact values and approximated values for $N$ from 0 to 10 000.
In [3]:
N = np.linspace(1, 1e4, 1e4+1, dtype=np.int64)
exact_N = exact(N)
stirling_N = stirling(N)
Creating plots of exact and approximated values and the relative error
$$ re_{\mathrm{i}} = \frac{\mathrm{exact_{i}} - \mathrm{stirling_{i}}}{N_{\mathrm{i}}}, $$to check the validity of the approximation.
In [4]:
fig, ax = plt.subplots(ncols=2)
ax[0].plot(N, exact_N, label='Exact, $\ln{N!}$');
ax[0].plot(N, stirling_N, '--', label='Stirling, $N \ln{N} - N$');
ax[0].set_xlabel('$N$');
ax[0].legend();
ax[1].plot(N, np.subtract(exact_N, stirling_N)/N);
ax[1].set_xlabel('$N$');
ax[1].set_ylabel('Relative error (exact - Stirling)/$N$');
The relative error falls below $10\%$ after $N = 26$ and below $1\%$ after $N = 245$.
Calculation of the definite integral in the Debye model is handled by scipy.integration.quad
which uses a technique from the Fortran library QUADPACK
. We define constants and declare one function of the Debye integrand to pass to quad
and another function for calculating $C_\mathrm{V}$ using either the Debye or Einstein model.
In [5]:
R = 8.314472 # [J/(mol K)]
theta_ECu = 244 # [K]
theta_DCu = 315 # [K]
def debye_integrand(x):
return x**4*(np.exp(x)/(np.exp(x) - 1)**2)
def heat_capacity(T, theta, model='debye'):
if model=='debye':
C_V = [9*R*(i/theta)**3*sci.quad(debye_integrand, 0, theta/i)[0] for i in T]
else:
C_V = [3*R*(theta/i)**2*(np.exp(theta/i)/(np.exp(theta/i) - 1)**2) for i in T]
return C_V
Instead of just comparing the two models, we can also compare them to experimental data (White and Collocott, 1984).
In [6]:
C_V_exp = np.array([3.74, 6.14, 8.58, 10.83, 12.80, 14.49, 15.91, 18.11, 19.65, 20.78, 21.59,
22.23, 22.72, 23.07, 23.36, 23.58, 23.74, 24.02, 24.23, 24.44, 24.58,
24.70, 24.79, 24.87, 24.94, 25.03, 25.15, 25.30, 25.57, 25.98, 26.22,
26.80])
T_exp = np.array([40, 50, 60, 70, 80, 90, 100, 120, 140, 160, 180, 200, 220, 240, 260, 280,
300, 350, 400, 450, 500, 550, 600, 650, 700, 800, 900, 1000, 1100, 1200,
1250, 1300])
# Declare T values from 20 to 400
T = np.linspace(20, 400, 400 - 20 + 1)
threeR = np.full(len(T), 3*R)
We then calculate values for $C_\mathrm{V}$ using both models implemented above, and plot them.
In [7]:
C_V_E = heat_capacity(T, theta_ECu, model='einstein')
C_V_D = heat_capacity(T, theta_DCu)
In [8]:
fig, ax = plt.subplots()
ax.plot(T, threeR, '--', label='$3R$')
ax.plot(T, C_V_D, '--', label='Debye')
ax.plot(T, C_V_E, label='Einstein')
ax.plot(T_exp[:19], C_V_exp[:19], 'o', label='White & Collocott (1984)')
ax.set_xlabel('$T$ [K]')
ax.set_ylabel('$C_\mathrm{V}$ [J mol$^{-1}$ K$^{-1}$]')
ax.legend()
Out[8]:
The implemented Debye model describes the experimental results well, except for at very low temperatures which are best described by the Debye $T^3$ law.
We implement $\Delta H_\mathrm{mix}$, $T \Delta S_\mathrm{mix}$ and $\Delta G_\mathrm{mix}$, with the expression of the latter (assuming $G_\mathrm{A}^{0} = G_\mathrm{B}^{0} = 0$ and an endothermic reaction with $\Omega = 2000 R$)
$$ \Delta G_\mathrm{mix} = \Omega (1 - X_\mathrm{B}) X_\mathrm{B} + RT[(1 - X_\mathrm{B})\ln{(1 - X_\mathrm{B})} + X_\mathrm{B}\ln{X_\mathrm{B}}], $$and plot the free energy of mixing for the composition range $X_\mathrm{B} \in{[0.01, 0.99]}$ for constant temperatures 400 K, 500 K, 550 K, 600 K, 650 K and 700 K.
In [9]:
def enthalpy_mixing(Xb, omega=2000*R):
return [omega*(1 - i)*i for i in Xb]
def entropy_mixing(Xb, T):
return [R*T*((1 - i)*math.log(1 - i) + i*math.log(i)) for i in Xb]
def free_energy_mixing(Xb, T, omega=2000*R):
return [omega*(1 - i)*i + R*T*((1 - i)*math.log(1 - i) + i*math.log(i)) for i in Xb]
In [10]:
Xb = np.linspace(0.01, 0.99, 101)
G_0 = np.full(len(Xb), 0)
In [11]:
fig, ax = plt.subplots(nrows=3, ncols=2, sharex=True, figsize=(16, 15))
ax[0, 0].set_title('$T$ = 400 K')
ax[0, 0].plot(Xb, free_energy_mixing(Xb, 400), label='$\Delta G_\mathrm{mix}$')
ax[0, 0].plot(Xb, enthalpy_mixing(Xb), label='$\Delta H_\mathrm{mix}$')
ax[0, 0].plot(Xb, entropy_mixing(Xb, 400), label='$-T\Delta S_\mathrm{mix}$')
ax[0, 0].plot(Xb, G_0, '--')
ax[0, 0].legend()
ax[0, 1].set_title('$T$ = 500 K')
ax[0, 1].plot(Xb, free_energy_mixing(Xb, 500), label='$\Delta G_\mathrm{mix}$')
ax[0, 1].plot(Xb, enthalpy_mixing(Xb), label='$\Delta H_\mathrm{mix}$')
ax[0, 1].plot(Xb, entropy_mixing(Xb, 500), label='$-T\Delta S_\mathrm{mix}$')
ax[0, 1].plot(Xb, G_0, '--')
ax[0, 1].legend()
ax[1, 0].set_title('$T$ = 550 K')
ax[1, 0].plot(Xb, free_energy_mixing(Xb, 550), label='$\Delta G_\mathrm{mix}$')
ax[1, 0].plot(Xb, enthalpy_mixing(Xb), label='$\Delta H_\mathrm{mix}$')
ax[1, 0].plot(Xb, entropy_mixing(Xb, 550), label='$-T\Delta S_\mathrm{mix}$')
ax[1, 0].plot(Xb, G_0, '--')
ax[1, 0].legend()
ax[1, 1].set_title('$T$ = 600 K')
ax[1, 1].plot(Xb, free_energy_mixing(Xb, 600), label='$\Delta G_\mathrm{mix}$')
ax[1, 1].plot(Xb, enthalpy_mixing(Xb), label='$\Delta H_\mathrm{mix}$')
ax[1, 1].plot(Xb, entropy_mixing(Xb, 600), label='$-T\Delta S_\mathrm{mix}$')
ax[1, 1].plot(Xb, G_0, '--')
ax[1, 1].legend()
ax[2, 0].set_title('$T$ = 650 K')
ax[2, 0].plot(Xb, free_energy_mixing(Xb, 650), label='$\Delta G_\mathrm{mix}$')
ax[2, 0].plot(Xb, enthalpy_mixing(Xb), label='$\Delta H_\mathrm{mix}$')
ax[2, 0].plot(Xb, entropy_mixing(Xb, 650), label='$-T\Delta S_\mathrm{mix}$')
ax[2, 0].plot(Xb, G_0, '--')
ax[2, 0].legend()
ax[2, 1].set_title('$T$ = 700 K')
ax[2, 1].plot(Xb, free_energy_mixing(Xb, 700), label='$\Delta G_\mathrm{mix}$')
ax[2, 1].plot(Xb, enthalpy_mixing(Xb), label='$\Delta H_\mathrm{mix}$')
ax[2, 1].plot(Xb, entropy_mixing(Xb, 700), label='$-T\Delta S_\mathrm{mix}$')
ax[2, 1].plot(Xb, G_0, '--')
ax[2, 1].legend()
ax[2, 0].set_xlabel('$X_\mathrm{B}$')
ax[2, 1].set_xlabel('$X_\mathrm{B}$')
Out[11]:
For the selected, large value $\Omega = 2000 R$, the negative curvature in the middle develops after about 400 K and diminishes after 700 K.
The final expression for the solvus line $X_\mathrm{B}^\mathrm{S}(T)$ reads
$$ \frac{\mathrm{d}G}{\mathrm{d}X_\mathrm{B}}\Bigr|_{\substack{X_\mathrm{B} = X_\mathrm{B}^{\mathrm{S}}}} = 0 \rightarrow T(X_\mathrm{B}^\mathrm{S}) = \frac{\Omega}{R}\frac{2X_\mathrm{B}^\mathrm{S} - 1}{\ln{X_\mathrm{B}^\mathrm{S}} - \ln{(1 - X_\mathrm{B}^\mathrm{S})}}. $$
In [12]:
def temperature(Xb, omega=2000*R):
return [omega/R*(2*i - 1)/(math.log(i) - math.log(1 - i)) for i in Xb]
In [13]:
fig, ax = plt.subplots()
ax.plot(Xb, temperature(Xb), label='$X_\mathrm{B}^{\mathrm{S}}$')
ax.legend()
ax.set_xlabel('$X_\mathrm{B}$')
ax.set_ylabel('$T$ [K]')
Out[13]: