Transport barriers and Voltage profile
You will calculate the energy barriers for transport of Li intercalated in the graphite anode. You will examine how sensitive this barrier is to the interlayer distance in graphite. You will also examine the energy of intermediate states during the charge/discharge process. This will allow some basic discussion of the voltage profile of the battery.
Create initial and final structures for a NEB calculation, that will determine the transition state
If time permits you can study the influence of changing the interlayer graphite distance on the energy barrier.
Create structures for a Li vacancy in LiFePO4 and a single Li in FePO4
Calculate the Li vacancy/insertion energies and compare them to the equilibrium potential
What can they tell you about the charge/discharge potential curves?
You will in general be provided less code than yesterday, especially towards the end of this notebook. You will have to use what you have already seen and learned so far.
There will be some natural pauses while you wait for calculations to finish. If you do not finish this entire notebook today, do not despair.
Transport barrier of Li in graphite
You will now calculate the energy barrier for Li diffusion in the graphite
anode. You will do this using the Nudged Elastic Band (NEB)
method.
You can use your work from Day 2, but for simplicity you are advised to load in the initial atomic configuration from file.
from ase import Atoms
from ase.mep import NEB
from ase.optimize import BFGS
from gpaw import GPAW, PW
from ase.constraints import FixAtoms
ccdist = 1.39
a = ccdist * 3**0.5
c = 3.7
initial = Atoms(
'C6Li',
positions=[(0, 0, 0),
(0, ccdist, 0),
(a, 0, 0),
(-a, 0, 0),
(-a / 2, -ccdist / 2, 0),
(a / 2, -ccdist / 2, 0),
(0, -ccdist, c / 2)],
cell=([1.5 * a, -1.5 * ccdist, 0],
[1.5 * a, 1.5 * ccdist, 0],
[0, 0, c]),
pbc=(1, 1, 1))
You will now make a final structure, where the Li atom has been moved to a
neighbouring equivalent site. The
get_positions(),
set_positions() and
get_cell() methods
are highly useful for such a task. HINT: Displace the Li atom
\(\frac{1}{n} (\vec{a}+\vec{b})\)
final = initial.copy()
...
...
Visualize that you have made the final strcuture correctly.
from ase.visualize import view
view(final)
Make a band consisting of 7 images including the initial and final.
# These will become the minimum energy path images:
images = [initial]
images += [initial.copy() for i in range(5)]
images += [final]
It this point images consist of 6 copies of initial and one entry of
final. Use the NEB method to create an initial guess for the minimum
energy path (MEP). In the cell below a simple interpolation between the
initial and final image is used as initial guess.
neb = NEB(images, method='improvedtangent')
neb.interpolate()
Add a view(images) line to your script and run it so that you can
visualize the NEB images.
It turns out, that while running the NEB calculation, the largest amount
of resources will be spend translating the carbon layer without any
noticeable buckling. You will thus
constrain the
positions of the carbon atoms to save computational time.
Each image in the NEB requires a unique calculator.
This very simple case is highly symmetric. To better illustrate how the
NEB method works, the symmetry is broken using the
ase.Atoms.rattle() method.
# Constrain C-atoms:
mask = [atom.symbol == 'C' for atom in initial]
constraint = FixAtoms(mask=mask)
for image in images[0:7]:
calc = GPAW(
mode=PW(500),
kpts=(5, 5, 6),
xc='LDA',
txt=None,
symmetry={'point_group': False})
image.calc = calc
image.set_constraint(constraint)
images[3].rattle(stdev=0.05, seed=42)
Start by calculating the energy and forces of the first (initial) and
last (final) images as this is not done during the actual NEB
calculation.
Note, that this can take a while if you opt to do it inside the notebook.
images[0].get_potential_energy()
images[0].get_forces()
images[6].get_potential_energy()
images[6].get_forces()
You can run the NEB calculation by running an optimization on the NEB
object the same way you would on an atoms object. Note the fmax is
larger for this tutorial example than you would normally use.
optimizer = BFGS(neb, trajectory='neb.traj', logfile='neb.log')
optimizer.run(fmax=0.10)
Submit the calculation to the HPC cluster. Do this by first building a
complete script in the cell below using the cells above (minus the
view() commands). Make sure the cell runs and then interrupt the kernel.
$ mq submit NEB.py -R 8:1h # submits the calculation to 8 cores, 1 hour
$ ...
$ mq ls
$ ...
$ tail neb.log
You can move on while you wait for the calculation to finish.
Once the maximum force (fmax) in the log is below 0.1, the calculation
is finished. Load in the full trajectory.
You will use the ase gui to inspect the result. The below line reads in
the last 7 images in the file. In this case the MEP images.
$ ase gui neb.traj@-7:
In the GUI use .
Now inspect how the TS image has developed.
$ ase gui neb.traj@3::7
For more complicated MEP’s, use the Climbing image method to determine the transition state. Why is it not required here?
Bonus
You will now study the influence of changing the interlayer graphite distance on the energy barrier. Due to the high degree of symmetry, this can be done easily in this case. Load in the initial state (IS) and transition state (TS) images from the converged MEP.
from ase.io import read
from gpaw import GPAW, PW
images = read('neb.traj@-7:')
IS_image = images[0]
TS_image = images[3]
Now calculate the energy of the initial state (IS) image and the
transition state (TS) image using the
get_potential_energy() method.
epot_IS = IS_image.get_potential_energy()
epot_TS = TS_image.get_potential_energy()
barrier = epot_TS - epot_IS
print('Energy barrier:', barrier)
New change the graphite layer distance by changing the the size of the unit cell in the z direction by ±3 %. and use the same calculator object as you did above and calculate the potential energy of the compressed initial and final state.
for strain in [-0.03, 0.03]:
cell = IS_image.get_cell()
cell[2] *= 1 + strain
IS_image97 = IS_image.copy()
IS_image97.set_cell(cell, scale_atoms=True)
TS_image97 = TS_image.copy()
TS_image97.set_cell(cell, scale_atoms=True)
# Create GPAW calculator and calculate energies:
How does the energy barrier change?
FePO4 with one Li
You will now calculate the energy gain of adding a single Li atom into the FePO4 cell you made on Day 3. This corresponds to a charge of 25 %. You can compare this energy to the equilibrium potential.
Start preparing a new Python script (say, fepo4_1li.py) and load in the FePO4 structure you wrote to file on in a previous
exercise and add Li. Assume that the cell dimension remain unchanged.
from ase.io import read, write
from gpaw import GPAW, PW, FermiDirac, Mixer
from ase.dft.bee import BEEFEnsemble
from ase.parallel import paropen
fepo4 = read('fepo4_out.traj')
fepo4_1li = fepo4.copy()
print(fepo4_1li.get_initial_magnetic_moments())
Add a Li atoms at \((x,y,z)=(0,0,0)\):
fepo4_1li.append('Li')
fepo4_1li.positions[-1] = (0, 0, 0) # not really needed
Now finish the script:
calculate the energy with the BEEF functional
write result to a
.trajfile:from ase.io import writeandwrite('fepo4_1li_out.traj', fepo4_1li)calculate a BEEF-ensemble
and submit the job to the queue.
Once the calculation is finished, you are ready to calculate the energy gained by intercalating a single Li ion into the cathode. Start by loading in the relevant reference structures and obtain the potential energies. This should not require any new DFT calculations.
li_metal = read('li_metal.traj')
fepo4 = read('fepo4_out.traj')
fepo4_1li = read('fepo4_1li_out.traj')
epot_li_metal = li_metal.get_potential_energy() / len(li_metal)
epot_fepo4 = ...
epot_fepo4_1li = ...
Calculate the energy of intercalting a single Li in the FePO4 cell. How does this energy compare with the equilibirum potential? What can it tell you about the charge/discharge potential curves?
Bonus: LiFePO4 with one vacancy
If time permits, you will now do a similar calculation but this time with LiFePO4 contraining one vacancy. Once again you should assume that the cell dimension remain unchanged compaired to LiFePO4.
There are numerous ways to obtain this structure. You can get
inspiration from the way LiFePO4 was made on Exercise day 3. Use
del atoms[index], the ase.Atoms.pop() method
or even the GUI to delete an atom and save the structure afterwards.
When you have made your script (say lifepo4_vac.py), submit it to the
HPC cluster. Once the calculation has finished you are ready to
calculate the energy cost of creating a Li vacancy in the fully lithiated
LiFePO4. Start by loading in the relevant reference structures and
obtain the potential energies. This should not require any calculations.
# Loading in files from exercise day 3.
li_metal = read('li_metal.traj') # you should have already read this in above
lifepo4 = read('lifepo4_out.traj')
epot_li_metal = li_metal.get_potential_energy() / len(li_metal)
epot_lifepo4 = lifepo4.get_potential_energy()
vac_cost = ...
print(vac_cost)
How does this energy compare with the equilibirum potential? What can it tell you about the charge/discharge potential curves?
Bonus
Calculate the error estimates of the energy for the added Li atom and vacancy formation using the ensembles.