Source code for pymatgen.analysis.diffusion.neb.tests.test_pathfinder

# Copyright (c) Materials Virtual Lab.
# Distributed under the terms of the BSD License.
from __future__ import annotations

import glob
import os
import unittest

import numpy as np

from pymatgen.analysis.diffusion.neb.full_path_mapper import MigrationGraph
from pymatgen.analysis.diffusion.neb.pathfinder import DistinctPathFinder, IDPPSolver, MigrationHop
from pymatgen.analysis.diffusion.utils.supercells import get_start_end_structures
from pymatgen.core import Composition, PeriodicSite, Structure
from pymatgen.symmetry.analyzer import SpacegroupAnalyzer
from pymatgen.util.testing import PymatgenTest

__author__ = "Iek-Heng Chu"
__version__ = "1.0"
__date__ = "March 14, 2017"


[docs] def get_path(path_str, dirname="./"): cwd = os.path.abspath(os.path.dirname(__file__)) return os.path.join(cwd, dirname, path_str)
[docs] class IDPPSolverTest(unittest.TestCase): init_struct = Structure.from_file(get_path("CONTCAR-0", dirname="pathfinder_files")) final_struct = Structure.from_file(get_path("CONTCAR-1", dirname="pathfinder_files"))
[docs] def test_idpp_from_ep(self): obj = IDPPSolver.from_endpoints([self.init_struct, self.final_struct], nimages=3, sort_tol=1.0) new_path = obj.run( maxiter=5000, tol=1e-5, gtol=1e-3, step_size=0.05, max_disp=0.05, spring_const=5.0, species=["Li"], ) assert len(new_path) == 5 assert new_path[1].num_sites == 111 assert np.allclose(new_path[0][2].frac_coords, np.array([0.50000014, 0.99999998, 0.74999964])) assert np.allclose(new_path[1][0].frac_coords, np.array([0.482439, 0.68264727, 0.26525066])) assert np.allclose(new_path[2][10].frac_coords, np.array([0.50113915, 0.74958704, 0.75147021])) assert np.allclose(new_path[3][22].frac_coords, np.array([0.28422885, 0.62568764, 0.98975444])) assert np.allclose(new_path[4][47].frac_coords, np.array([0.59767531, 0.12640952, 0.37745006]))
[docs] def test_idpp_from_ep_diff_latt(self): # This is the same test as test_idpp_from_ep where we want to interpolate # different lattices. # make a copy of the final_struct and distort the lattice by 5% final_struct_strain = self.final_struct.copy() final_struct_strain.apply_strain(0.05) obj = IDPPSolver.from_endpoints( [self.init_struct, final_struct_strain], nimages=3, sort_tol=1.0, interpolate_lattices=True, ) new_path = obj.run( maxiter=5000, tol=1e-5, gtol=1e-3, step_size=0.05, max_disp=0.05, spring_const=5.0, species=["Li"], ) assert len(new_path) == 5 assert new_path[1].num_sites == 111 # For checking coords, the endpoints will have the same frac coords as the # original test, whereas the midpoints will not have altered coord because # of the differing lattice. assert np.allclose(new_path[0][2].frac_coords, np.array([0.50000014, 0.99999998, 0.74999964])) assert np.allclose(new_path[1][0].frac_coords, np.array([0.47483465, 0.6740367, 0.26270862])) assert np.allclose(new_path[2][10].frac_coords, np.array([0.48839604, 0.73135841, 0.73315875])) assert np.allclose(new_path[3][22].frac_coords, np.array([0.27395552, 0.60307242, 0.95398018])) assert np.allclose(new_path[4][47].frac_coords, np.array([0.59767531, 0.12640952, 0.37745006])) # ensure the lattices are interpolated assert np.allclose( new_path[0].lattice.matrix, np.array( [ [10.4117233, 0.0, 0.0], [0.0, 12.07835177, 0.0], [0.0, 0.0, 9.47759962], ] ), ) assert np.allclose( new_path[1].lattice.matrix, np.array( [ [10.54186984, 0.0, 0.0], [0.0, 12.22933117, 0.0], [0.0, 0.0, 9.59606961], ] ), ) assert np.allclose( new_path[2].lattice.matrix, np.array( [ [10.67201638, 0.0, 0.0], [0.0, 12.38031057, 0.0], [0.0, 0.0, 9.71453961], ] ), ) assert np.allclose( new_path[3].lattice.matrix, np.array( [ [10.80216292, 0.0, 0.0], [0.0, 12.53128996, 0.0], [0.0, 0.0, 9.8330096], ] ), ) assert np.allclose( new_path[4].lattice.matrix, np.array( [ [10.93230946, 0.0, 0.0], [0.0, 12.68226936, 0.0], [0.0, 0.0, 9.9514796], ] ), )
[docs] def test_idpp(self): images = self.init_struct.interpolate(self.final_struct, nimages=4, autosort_tol=1.0) obj = IDPPSolver(images) new_path = obj.run( maxiter=5000, tol=1e-5, gtol=1e-3, step_size=0.05, max_disp=0.05, spring_const=5.0, species=["Li"], ) assert len(new_path) == 5 assert new_path[1].num_sites == 111 assert np.allclose(new_path[0][2].frac_coords, np.array([0.50000014, 0.99999998, 0.74999964])) assert np.allclose(new_path[1][0].frac_coords, np.array([0.482439, 0.68264727, 0.26525066])) assert np.allclose(new_path[4][47].frac_coords, np.array([0.59767531, 0.12640952, 0.37745006]))
[docs] class DistinctPathFinderTest(PymatgenTest):
[docs] def test_get_paths(self): s = self.get_structure("LiFePO4") # Only one path in LiFePO4 with 4 A. p = DistinctPathFinder(s, "Li", max_path_length=4) paths = p.get_paths() assert len(paths) == 1 # Make sure this is robust to supercells. s.make_supercell((2, 2, 1)) p = DistinctPathFinder(s, "Li", max_path_length=4) paths = p.get_paths() assert len(paths) == 1 ss = paths[0].get_structures(vac_mode=False) assert len(ss) == 7 paths[0].write_path("pathfindertest_noidpp_vac.cif", idpp=False) paths[0].write_path("pathfindertest_idpp_vac.cif", idpp=True) paths[0].write_path("pathfindertest_idpp_nonvac.cif", idpp=True, vac_mode=False) assert ( str(paths[0]) == "Path of 3.0329 A from Li [0.000, 0.500, 1.000] (ind: 0, Wyckoff: 16a) to Li [-0.000, 0.750, 1.000] (ind: 0, Wyckoff: 16a)" ) p = DistinctPathFinder(s, "Li", max_path_length=6) paths = p.get_paths() assert len(paths) == 4 s = self.get_structure("Graphite") # Only one path in graphite with 2 A. p = DistinctPathFinder(s, "C0+", max_path_length=2) paths = p.get_paths() assert len(paths) == 1 s = self.get_structure("Li3V2(PO4)3") p = DistinctPathFinder(s, "Li0+", max_path_length=4) paths = p.get_paths() assert len(paths) == 4 p.write_all_paths("pathfindertest_LVPO.cif", nimages=10, idpp=True) for f in glob.glob("pathfindertest_*.cif"): os.remove(f)
[docs] def test_max_path_length(self): s = Structure.from_file(get_path("LYPS.cif", dirname="pathfinder_files")) dp1 = DistinctPathFinder(s, "Li", perc_mode="1d") self.assertAlmostEqual(dp1.max_path_length, 4.11375354207, 7) dp2 = DistinctPathFinder(s, "Li", 5, perc_mode="1d") self.assertAlmostEqual(dp2.max_path_length, 5.0, 7)
[docs] class MigrationHopTest(PymatgenTest):
[docs] def setUp(self): self.lifepo = self.get_structure("LiFePO4") m_graph = MigrationGraph.with_distance(self.lifepo, max_distance=4.0, migrating_specie="Li") gen = iter(m_graph.m_graph.graph.edges(data=True)) u, v, d = next(gen) i_site = PeriodicSite("Li", coords=d["ipos"], lattice=self.lifepo.lattice) e_site = PeriodicSite("Li", coords=d["epos"], lattice=self.lifepo.lattice) a = SpacegroupAnalyzer(self.lifepo) symm_structure = a.get_symmetrized_structure() self.m_hop = MigrationHop(i_site, e_site, symm_structure)
[docs] def test_msonable(self): hop_dict = self.m_hop.as_dict() assert isinstance(hop_dict, dict)
[docs] def test_get_start_end_structs_from_hop(self): dist_ref = self.m_hop.length base = self.lifepo.copy() base.remove_species(["Li"]) start, end, b_sc = get_start_end_structures( self.m_hop.isite, self.m_hop.esite, base_struct=base, sc_mat=[[2, 1, 0], [-1, 1, 0], [0, 0, 1]], vac_mode=False, ) start_site = next(filter(lambda x: x.species_string == "Li", start.sites)) end_site = next(filter(lambda x: x.species_string == "Li", end.sites)) self.assertAlmostEqual(start_site.distance(end_site), dist_ref, 3)
[docs] def test_get_start_end_structs_from_hop_vac(self): dist_ref = self.m_hop.length start, end, b_sc = get_start_end_structures( self.m_hop.isite, self.m_hop.esite, base_struct=self.lifepo, sc_mat=[[2, 1, 0], [-1, 1, 0], [0, 0, 2]], vac_mode=True, ) s1 = set() s2 = set() for itr_start, start_site in enumerate(start.sites): for itr_end, end_site in enumerate(end.sites): dist_ = start_site.distance(end_site) if dist_ < 1e-5: s1.add(itr_start) s2.add(itr_end) s1 = sorted(s1, reverse=True) s2 = sorted(s2, reverse=True) start.remove_sites(s1) end.remove_sites(s2) self.assertAlmostEqual(start.sites[0].distance(end.sites[0]), dist_ref, 3)
[docs] def test_get_sc_structures(self): dist_ref = self.m_hop.length start, end, b_sc = self.m_hop.get_sc_structures(vac_mode=False) start_site = next(filter(lambda x: x.species_string == "Li", start.sites)) end_site = next(filter(lambda x: x.species_string == "Li", end.sites)) assert start.composition == end.composition == Composition("Li1 Fe24 P24 O96") assert b_sc.composition == Composition("Fe24 P24 O96") self.assertAlmostEqual(start_site.distance(end_site), dist_ref, 3)
[docs] def test_get_sc_structures_vacmode(self): start, end, b_sc = self.m_hop.get_sc_structures(vac_mode=True) assert start.composition == end.composition == Composition("Li23 Fe24 P24 O96") assert b_sc.composition == Composition("Li24 Fe24 P24 O96")