Diffusion coefficient from a VASP file#

Previously, we looked at obtaining accurate estimates for the mean-squared displacement with kinisi. Here, we show using the same class to evaluate the diffusion coefficient, using the kinisi methodology.

[1]:
import numpy as np
from kinisi.analyze import DiffusionAnalyzer
from pymatgen.io.vasp import Xdatcar
np.random.seed(42)
rng = np.random.RandomState(42)

There the p_params dictionary describes details about the simulation, and are documented in the parser module (also see the MSD Notebook).

[2]:
p_params = {'specie': 'Li',
            'time_step': 2.0,
            'step_skip': 50}

While the b_params dictionary describes the details of the bootstrapping process, these are documented in the diffusion module. Here, we indicate that we only want to investigate diffusion in the xy-plane of the system.

[3]:
u_params = {'dimension': 'xy'}
[4]:
xd = Xdatcar('./example_XDATCAR.gz')
diff = DiffusionAnalyzer.from_Xdatcar(xd, parser_params=p_params, uncertainty_params=u_params)
Reading Trajectory: 0%| | 0/140 [00:00<?, ?it/s]

</pre>

Reading Trajectory: 0%| | 0/140 [00:00<?, ?it/s]

end{sphinxVerbatim}

Reading Trajectory: 0%| | 0/140 [00:00<?, ?it/s]

Reading Trajectory: 100%|██████████| 140/140 [00:00&lt;00:00, 4349.10it/s]

</pre>

Reading Trajectory: 100%|██████████| 140/140 [00:00<00:00, 4349.10it/s]

end{sphinxVerbatim}

Reading Trajectory: 100%|██████████| 140/140 [00:00<00:00, 4349.10it/s]


Getting Displacements: 0%| | 0/100 [00:00&lt;?, ?it/s]

</pre>

Getting Displacements: 0%| | 0/100 [00:00<?, ?it/s]

end{sphinxVerbatim}

Getting Displacements: 0%| | 0/100 [00:00<?, ?it/s]

Getting Displacements: 100%|██████████| 100/100 [00:00&lt;00:00, 17743.15it/s]

</pre>

Getting Displacements: 100%|██████████| 100/100 [00:00<00:00, 17743.15it/s]

end{sphinxVerbatim}

Getting Displacements: 100%|██████████| 100/100 [00:00<00:00, 17743.15it/s]


Finding Means and Variances: 0%| | 0/100 [00:00&lt;?, ?it/s]

</pre>

Finding Means and Variances: 0%| | 0/100 [00:00<?, ?it/s]

end{sphinxVerbatim}

Finding Means and Variances: 0%| | 0/100 [00:00<?, ?it/s]

Finding Means and Variances: 16%|█▌ | 16/100 [00:00&lt;00:00, 156.63it/s]

</pre>

Finding Means and Variances: 16%|█▌ | 16/100 [00:00<00:00, 156.63it/s]

end{sphinxVerbatim}

Finding Means and Variances: 16%|█▌ | 16/100 [00:00<00:00, 156.63it/s]

Finding Means and Variances: 33%|███▎ | 33/100 [00:00&lt;00:00, 162.06it/s]

</pre>

Finding Means and Variances: 33%|███▎ | 33/100 [00:00<00:00, 162.06it/s]

end{sphinxVerbatim}

Finding Means and Variances: 33%|███▎ | 33/100 [00:00<00:00, 162.06it/s]

Finding Means and Variances: 51%|█████ | 51/100 [00:00&lt;00:00, 165.80it/s]

</pre>

Finding Means and Variances: 51%|█████ | 51/100 [00:00<00:00, 165.80it/s]

end{sphinxVerbatim}

Finding Means and Variances: 51%|█████ | 51/100 [00:00<00:00, 165.80it/s]

Finding Means and Variances: 71%|███████ | 71/100 [00:00&lt;00:00, 175.86it/s]

</pre>

Finding Means and Variances: 71%|███████ | 71/100 [00:00<00:00, 175.86it/s]

end{sphinxVerbatim}

Finding Means and Variances: 71%|███████ | 71/100 [00:00<00:00, 175.86it/s]

Finding Means and Variances: 99%|█████████▉| 99/100 [00:00&lt;00:00, 212.23it/s]

</pre>

Finding Means and Variances: 99%|█████████▉| 99/100 [00:00<00:00, 212.23it/s]

end{sphinxVerbatim}

Finding Means and Variances: 99%|█████████▉| 99/100 [00:00<00:00, 212.23it/s]

Finding Means and Variances: 100%|██████████| 100/100 [00:00&lt;00:00, 192.22it/s]

</pre>

Finding Means and Variances: 100%|██████████| 100/100 [00:00<00:00, 192.22it/s]

end{sphinxVerbatim}

Finding Means and Variances: 100%|██████████| 100/100 [00:00<00:00, 192.22it/s]


In the above cells, we parse and determine the uncertainty on the mean-squared displacement as a function of the timestep. We should visualise this, to check that we are observing diffusion in our material and to determine the timescale at which this diffusion begins.

[5]:
import matplotlib.pyplot as plt
[6]:
plt.errorbar(diff.dt, diff.msd, diff.msd_std)
plt.ylabel('MSD/Å$^2$')
plt.xlabel('$\Delta t$/ps')
plt.show()
_images/vasp_d_9_0.png

We can visualise this on a log-log scale, which helps to reveal the diffusive regime (the region where the gradient stops changing).

[7]:
plt.errorbar(diff.dt, diff.msd, diff.msd_std)
plt.axvline(2, color='g')
plt.ylabel('MSD/Å$^2$')
plt.xlabel('$\Delta t$/ps')
plt.yscale('log')
plt.xscale('log')
plt.show()
_images/vasp_d_11_0.png

The green line at 2 ps appears to be a reasonable estimate of the start of the diffusive regime. Therefore, we want to pass 2 as the argument to the diffusion analysis below.

[8]:
diff.diffusion(2, diffusion_params={'random_state': rng})
/home/docs/checkouts/readthedocs.org/user_builds/kinisi/envs/latest/lib/python3.9/site-packages/scipy/optimize/_numdiff.py:590: RuntimeWarning: invalid value encountered in subtract
  df = fun(x) - f0
Likelihood Sampling: 0%| | 0/1500 [00:00&lt;?, ?it/s]

</pre>

Likelihood Sampling: 0%| | 0/1500 [00:00<?, ?it/s]

end{sphinxVerbatim}

Likelihood Sampling: 0%| | 0/1500 [00:00<?, ?it/s]

Likelihood Sampling: 3%|▎ | 41/1500 [00:00&lt;00:03, 409.41it/s]

</pre>

Likelihood Sampling: 3%|▎ | 41/1500 [00:00<00:03, 409.41it/s]

end{sphinxVerbatim}

Likelihood Sampling: 3%|▎ | 41/1500 [00:00<00:03, 409.41it/s]

Likelihood Sampling: 9%|▉ | 142/1500 [00:00&lt;00:01, 759.47it/s]

</pre>

Likelihood Sampling: 9%|▉ | 142/1500 [00:00<00:01, 759.47it/s]

end{sphinxVerbatim}

Likelihood Sampling: 9%|▉ | 142/1500 [00:00<00:01, 759.47it/s]

Likelihood Sampling: 16%|█▌ | 241/1500 [00:00&lt;00:01, 862.46it/s]

</pre>

Likelihood Sampling: 16%|█▌ | 241/1500 [00:00<00:01, 862.46it/s]

end{sphinxVerbatim}

Likelihood Sampling: 16%|█▌ | 241/1500 [00:00<00:01, 862.46it/s]

Likelihood Sampling: 23%|██▎ | 342/1500 [00:00&lt;00:01, 918.84it/s]

</pre>

Likelihood Sampling: 23%|██▎ | 342/1500 [00:00<00:01, 918.84it/s]

end{sphinxVerbatim}

Likelihood Sampling: 23%|██▎ | 342/1500 [00:00<00:01, 918.84it/s]

Likelihood Sampling: 30%|██▉ | 444/1500 [00:00&lt;00:01, 954.35it/s]

</pre>

Likelihood Sampling: 30%|██▉ | 444/1500 [00:00<00:01, 954.35it/s]

end{sphinxVerbatim}

Likelihood Sampling: 30%|██▉ | 444/1500 [00:00<00:01, 954.35it/s]

Likelihood Sampling: 36%|███▋ | 545/1500 [00:00&lt;00:00, 970.26it/s]

</pre>

Likelihood Sampling: 36%|███▋ | 545/1500 [00:00<00:00, 970.26it/s]

end{sphinxVerbatim}

Likelihood Sampling: 36%|███▋ | 545/1500 [00:00<00:00, 970.26it/s]

Likelihood Sampling: 43%|████▎ | 645/1500 [00:00&lt;00:00, 979.87it/s]

</pre>

Likelihood Sampling: 43%|████▎ | 645/1500 [00:00<00:00, 979.87it/s]

end{sphinxVerbatim}

Likelihood Sampling: 43%|████▎ | 645/1500 [00:00<00:00, 979.87it/s]

Likelihood Sampling: 50%|████▉ | 746/1500 [00:00&lt;00:00, 988.00it/s]

</pre>

Likelihood Sampling: 50%|████▉ | 746/1500 [00:00<00:00, 988.00it/s]

end{sphinxVerbatim}

Likelihood Sampling: 50%|████▉ | 746/1500 [00:00<00:00, 988.00it/s]

Likelihood Sampling: 57%|█████▋ | 848/1500 [00:00&lt;00:00, 997.73it/s]

</pre>

Likelihood Sampling: 57%|█████▋ | 848/1500 [00:00<00:00, 997.73it/s]

end{sphinxVerbatim}

Likelihood Sampling: 57%|█████▋ | 848/1500 [00:00<00:00, 997.73it/s]

Likelihood Sampling: 63%|██████▎ | 951/1500 [00:01&lt;00:00, 1005.59it/s]

</pre>

Likelihood Sampling: 63%|██████▎ | 951/1500 [00:01<00:00, 1005.59it/s]

end{sphinxVerbatim}

Likelihood Sampling: 63%|██████▎ | 951/1500 [00:01<00:00, 1005.59it/s]

Likelihood Sampling: 70%|███████ | 1053/1500 [00:01&lt;00:00, 1009.98it/s]

</pre>

Likelihood Sampling: 70%|███████ | 1053/1500 [00:01<00:00, 1009.98it/s]

end{sphinxVerbatim}

Likelihood Sampling: 70%|███████ | 1053/1500 [00:01<00:00, 1009.98it/s]

Likelihood Sampling: 77%|███████▋ | 1156/1500 [00:01&lt;00:00, 1013.28it/s]

</pre>

Likelihood Sampling: 77%|███████▋ | 1156/1500 [00:01<00:00, 1013.28it/s]

end{sphinxVerbatim}

Likelihood Sampling: 77%|███████▋ | 1156/1500 [00:01<00:00, 1013.28it/s]

Likelihood Sampling: 84%|████████▍ | 1259/1500 [00:01&lt;00:00, 1015.13it/s]

</pre>

Likelihood Sampling: 84%|████████▍ | 1259/1500 [00:01<00:00, 1015.13it/s]

end{sphinxVerbatim}

Likelihood Sampling: 84%|████████▍ | 1259/1500 [00:01<00:00, 1015.13it/s]

Likelihood Sampling: 91%|█████████ | 1362/1500 [00:01&lt;00:00, 1017.00it/s]

</pre>

Likelihood Sampling: 91%|█████████ | 1362/1500 [00:01<00:00, 1017.00it/s]

end{sphinxVerbatim}

Likelihood Sampling: 91%|█████████ | 1362/1500 [00:01<00:00, 1017.00it/s]

Likelihood Sampling: 98%|█████████▊| 1464/1500 [00:01&lt;00:00, 1014.13it/s]

</pre>

Likelihood Sampling: 98%|█████████▊| 1464/1500 [00:01<00:00, 1014.13it/s]

end{sphinxVerbatim}

Likelihood Sampling: 98%|█████████▊| 1464/1500 [00:01<00:00, 1014.13it/s]

Likelihood Sampling: 100%|██████████| 1500/1500 [00:01&lt;00:00, 970.94it/s]

</pre>

Likelihood Sampling: 100%|██████████| 1500/1500 [00:01<00:00, 970.94it/s]

end{sphinxVerbatim}

Likelihood Sampling: 100%|██████████| 1500/1500 [00:01<00:00, 970.94it/s]


This method estimates the correlation matrix between the timesteps and uses likelihood sampling to find the diffusion coefficient, \(D\) and intercept (the units are cm :sup:2 /s and Å :sup:2 respectively). Now we can probe these objects.

We can get the median and 95 % confidence interval using,

[9]:
diff.D.n, diff.D.ci()
[9]:
(1.3971885398686655e-05, array([1.21260747e-05, 1.58038003e-05]))
[10]:
diff.intercept.n, diff.intercept.ci()
[10]:
(0.5100107089129079, array([0.37768324, 0.63717078]))

Or we can get all of the sampled values from one of these objects.

[11]:
diff.D.samples
[11]:
array([1.31593150e-05, 1.39723383e-05, 1.27100560e-05, ...,
       1.50135430e-05, 1.42668350e-05, 1.40028450e-05])

Having completed the analysis, we can save the object for use later (such as downstream by a plotting script).

[13]:
diff.save('example.hdf')

The data is saved in a HDF5 file format which helps us efficiently store our data. We can then load the data with the following class method.

[14]:
loaded_diff = DiffusionAnalyzer.load('example.hdf')

We can plot the data with the credible intervals from the \(D\) and \(D_{\text{offset}}\) distribution.

[15]:
credible_intervals = [[16, 84], [2.5, 97.5], [0.15, 99.85]]
alpha = [0.6, 0.4, 0.2]

plt.plot(loaded_diff.dt, loaded_diff.msd, 'k-')
for i, ci in enumerate(credible_intervals):
    plt.fill_between(loaded_diff.dt,
                     *np.percentile(loaded_diff.distribution, ci, axis=1),
                     alpha=alpha[i],
                     color='#0173B2',
                     lw=0)
plt.ylabel('MSD/Å$^2$')
plt.xlabel('$\Delta t$/ps')
plt.show()
_images/vasp_d_26_0.png

Additionally, we can visualise the distribution of the diffusion coefficient that has been determined.

[16]:
plt.hist(loaded_diff.D.samples, density=True)
plt.axvline(loaded_diff.D.n, c='k')
plt.xlabel('$D$/cm$^2$s$^{-1}$')
plt.ylabel('$p(D$/cm$^2$s$^{-1})$')
plt.show()
_images/vasp_d_28_0.png

Or the joint probability distribution for the diffusion coefficient and intercept.

[17]:
from corner import corner
[18]:
corner(loaded_diff.flatchain, labels=['$D$/cm$^2$s$^{-1}$', 'intercept/Å$^2$'])
plt.show()
_images/vasp_d_31_0.png

Above, the posterior distribution is plotted as a function of \(\Delta t\). kinisi can also compute the posterior predictive distribution of mean-squared displacement data that would be expected from simulations, given the set of linear models for the ensemble MSD described by the posterior distribution, above. Calculating this posterior predictive distribution involves an additional sampling procedure that samples probable simulated MSDs for each of the probable linear models in the (previously sampled) posterior distribution. The posterior predictive distribution captures uncertainty in the true ensemble MSD and uncertainty in the simulated MSD data expected for each ensemble MSD model, and so has a larger variance than the ensemble MSD posterior distribution.

[19]:
ppd = loaded_diff.posterior_predictive({'n_posterior_samples': 128,
                                        'progress': False})
[20]:
plt.plot(loaded_diff.dt, loaded_diff.msd, 'k-')
for i, ci in enumerate(credible_intervals):
    plt.fill_between(loaded_diff.dt[14:],
                     *np.percentile(ppd.T, ci, axis=1),
                     alpha=alpha[i],
                     color='#0173B2',
                     lw=0)
plt.ylabel('MSD/Å$^2$')
plt.xlabel('$\Delta t$/ps')
plt.show()
_images/vasp_d_34_0.png