def get_basis_and_potential(species, basis_and_potential_map, functional="PBE"): """ Retrieve basis/potential dictionary Args: species: (list) list of species for which to get the potential/basis strings basis_and_potential_map: (dict or str) a keyword string or a dictionary specifying how bases and/or potentials should be assigned functional: (str) functional type. Default: 'PBE' Returns: (dict) of the form {'specie': {'potential': potential, 'basis': basis}...} """ potential_filename = SETTINGS.get("PMG_DEFAULT_CP2K_POTENTIAL_FILE", "GTH_POTENTIALS") basis_filenames = ["BASIS_MOLOPT", "BASIS_MOLOPT_UCL"] functional = functional or SETTINGS.get("PMG_DEFAULT_FUNCTIONAL", "PBE") basis_and_potential = { "basis_filenames": basis_filenames, "potential_filename": potential_filename, } with open(os.path.join(MODULE_DIR, "settings.yaml")) as f: yaml = YAML(typ="unsafe", pure=True) settings = yaml.load(f) if basis_and_potential_map == "best": basis_and_potential.update({ s: { "basis": settings[s]["basis_sets"]["best_basis"], "potential": [ p for p in settings[s]["potentials"]["gth_potentials"] if functional in p ][0], } for s in species }) elif basis_and_potential_map == "preferred": basis_and_potential.update({ s: { "basis": settings[s]["basis_sets"]["preferred_basis"], "potential": [ p for p in settings[s]["potentials"]["gth_potentials"] if functional in p ][0], } for s in species }) else: basis_and_potential.update(basis_and_potential_map) return basis_and_potential
def __init__(self, package_options=None): """ :param package_options: """ if SETTINGS.get("PMG_MAPI_KEY", "") != "": self.materials_project_configuration = SETTINGS.get("PMG_MAPI_KEY", "") else: self.materials_project_configuration = None if package_options is None: self.package_options = self.DEFAULT_PACKAGE_OPTIONS else: self.package_options = package_options
def test_chemenv_config(self): with ScratchDir("."): config = ChemEnvConfig() if SETTINGS.get("PMG_MAPI_KEY", "") != "": self.assertTrue(config.has_materials_project_access) else: self.assertFalse(config.has_materials_project_access) package_options = ChemEnvConfig.DEFAULT_PACKAGE_OPTIONS package_options["default_max_distance_factor"] = 1.8 config = ChemEnvConfig(package_options=package_options) self.assertEqual( config.package_options_description(), "Package options :\n" " - Maximum distance factor : 1.8000\n" ' - Default strategy is "SimplestChemenvStrategy" :\n' " Simplest ChemenvStrategy using fixed angle and distance parameters \n" " for the definition of neighbors in the Voronoi approach. \n" " The coordination environment is then given as the one with the \n" " lowest continuous symmetry measure.\n" " with options :\n" " - distance_cutoff : 1.4\n" " - angle_cutoff : 0.3\n" " - additional_condition : 1\n" " - continuous_symmetry_measure_cutoff : 10.0\n", ) config.save(root_dir="tmp_dir") config = config.auto_load(root_dir="tmp_dir") self.assertEqual(config.package_options, package_options)
def setUp(self, lpad=True): """ Create scratch directory(removes the old one if there is one) and change to it. Also initialize launchpad. """ if not SETTINGS.get("PMG_VASP_PSP_DIR"): SETTINGS["PMG_VASP_PSP_DIR"] = os.path.abspath( os.path.join(MODULE_DIR, "..", "vasp", "test_files")) print( "This system is not set up to run VASP jobs. " "Please set PMG_VASP_PSP_DIR variable in your ~/.pmgrc.yaml file." ) self.scratch_dir = os.path.join(MODULE_DIR, "scratch") if os.path.exists(self.scratch_dir): shutil.rmtree(self.scratch_dir) os.makedirs(self.scratch_dir) os.chdir(self.scratch_dir) if lpad: try: self.lp = LaunchPad.from_file( os.path.join(DB_DIR, "my_launchpad.yaml")) self.lp.reset("", require_password=False) except: raise unittest.SkipTest( "Cannot connect to MongoDB! Is the database server running? " "Are the credentials correct?")
def get_aux_basis(basis_type, default_basis_type="cpFIT"): """ Get auxiliary basis info for a list of species. Args: basis_type (dict): dict of auxiliary basis sets to use. i.e: basis_type = {'Si': 'cFIT', 'O': 'cpFIT'}. Basis type needs to exist for that species. Basis types: FIT cFIT pFIT cpFIT GTH-def2 aug-{FIT,cFIT,pFIT,cpFIT, GTH-def2} default_basis_type (str) default basis type if n """ with open(os.path.join(MODULE_DIR, "settings.yaml")) as f: yaml = YAML(typ="unsafe", pure=True) settings = yaml.load(f) aux_bases = { s: [settings[s]["basis_sets"]["preferred_aux_basis"]] if "preferred_aux_basis" in settings[s]["basis_sets"] else settings[s]["basis_sets"]["aux_basis"] for s in settings } default_basis_type = default_basis_type or SETTINGS.get( "PMG_CP2K_DEFAULT_AUX_BASIS_TYPE") basis_type = { k: basis_type[k] if basis_type[k] else default_basis_type for k in basis_type } basis = {k: {} for k in basis_type} for k in basis_type: if aux_bases.get(k) is None: basis[k] = None continue for i in aux_bases[k]: if i.startswith(basis_type[k]): basis[k] = i break for k in basis: if not basis[k]: if aux_bases[k]: basis[k] = aux_bases[k][0] else: raise LookupError(f"No basis of that type found for: {k}") return basis
def get_aux_basis(basis_type, default_basis_type="cFIT"): """ Get auxiliary basis info for a list of species. Args: basis_type (dict): dict of auxiliary basis sets to use. i.e: basis_type = {'Si': 'cFIT', 'O': 'cpFIT'}. Basis type needs to exist for that species. Basis types: FIT cFIT pFIT cpFIT GTH-def2 aug-{FIT,cFIT,pFIT,cpFIT, GTH-def2} default_basis_type (str) default basis type if n """ default_basis_type = default_basis_type or SETTINGS.get( "PMG_CP2K_DEFAULT_AUX_BASIS_TYPE") basis_type = { k: basis_type[k] if basis_type[k] else default_basis_type for k in basis_type } basis = {k: {} for k in basis_type} aux_bases = loadfn(os.path.join(MODULE_DIR, "aux_basis.yaml")) for k in basis_type: for i in aux_bases[k]: if i.startswith(basis_type[k]): basis[k] = i break for k in basis: if not basis[k]: if aux_bases[k]: basis[k] = aux_bases[k][0] else: raise LookupError("NO BASIS OF THAT TYPE") return basis
from pymatgen.entries.computed_entries import ComputedEntry from pymatgen.ext.matproj import MP_LOG_FILE, MPRester, MPRestError, TaskType from pymatgen.io.cif import CifParser from pymatgen.phonon.bandstructure import PhononBandStructureSymmLine from pymatgen.phonon.dos import CompletePhononDos from pymatgen.util.testing import PymatgenTest try: website_is_up = requests.get( "https://www.materialsproject.org").status_code == 200 except requests.exceptions.ConnectionError: website_is_up = False @unittest.skipIf( (not SETTINGS.get("PMG_MAPI_KEY")) or (not website_is_up), "PMG_MAPI_KEY environment variable not set or MP is down.", ) class MPResterTest(PymatgenTest): _multiprocess_shared_ = True def setUp(self): self.rester = MPRester() warnings.simplefilter("ignore") def tearDown(self): warnings.simplefilter("default") self.rester.session.close() def test_get_all_materials_ids_doc(self): mids = self.rester.get_materials_ids("Al2O3")
class PourbaixDiagramTest(unittest.TestCase): _multiprocess_shared_ = True @classmethod def setUpClass(cls): cls.test_data = loadfn( os.path.join(PymatgenTest.TEST_FILES_DIR, "pourbaix_test_data.json")) cls.pbx = PourbaixDiagram(cls.test_data["Zn"], filter_solids=True) cls.pbx_nofilter = PourbaixDiagram(cls.test_data["Zn"], filter_solids=False) def test_pourbaix_diagram(self): self.assertEqual( set([e.name for e in self.pbx.stable_entries]), {"ZnO(s)", "Zn[2+]", "ZnHO2[-]", "ZnO2[2-]", "Zn(s)"}, "List of stable entries does not match", ) self.assertEqual( set([e.name for e in self.pbx_nofilter.stable_entries]), { "ZnO(s)", "Zn[2+]", "ZnHO2[-]", "ZnO2[2-]", "Zn(s)", "ZnO2(s)", "ZnH(s)" }, "List of stable entries for unfiltered pbx does not match", ) pbx_lowconc = PourbaixDiagram(self.test_data["Zn"], conc_dict={"Zn": 1e-8}, filter_solids=True) self.assertEqual( set([e.name for e in pbx_lowconc.stable_entries]), {"Zn(HO)2(aq)", "Zn[2+]", "ZnHO2[-]", "ZnO2[2-]", "Zn(s)"}, ) def test_properties(self): self.assertEqual(len(self.pbx.unstable_entries), 2) def test_multicomponent(self): # Assure no ions get filtered at high concentration ag_n = [ e for e in self.test_data["Ag-Te-N"] if "Te" not in e.composition ] highconc = PourbaixDiagram(ag_n, filter_solids=True, conc_dict={ "Ag": 1e-5, "N": 1 }) entry_sets = [set(e.entry_id) for e in highconc.stable_entries] self.assertIn({"mp-124", "ion-17"}, entry_sets) # Binary system pd_binary = PourbaixDiagram( self.test_data["Ag-Te"], filter_solids=True, comp_dict={ "Ag": 0.5, "Te": 0.5 }, conc_dict={ "Ag": 1e-8, "Te": 1e-8 }, ) self.assertEqual(len(pd_binary.stable_entries), 30) test_entry = pd_binary.find_stable_entry(8, 2) self.assertTrue("mp-499" in test_entry.entry_id) # Find a specific multientry to test self.assertEqual(pd_binary.get_decomposition_energy(test_entry, 8, 2), 0) pd_ternary = PourbaixDiagram(self.test_data["Ag-Te-N"], filter_solids=True) self.assertEqual(len(pd_ternary.stable_entries), 49) # Fetch a solid entry and a ground state entry mixture ag_te_n = self.test_data["Ag-Te-N"][-1] ground_state_ag_with_ions = MultiEntry( [self.test_data["Ag-Te-N"][i] for i in [4, 18, 30]], weights=[1 / 3, 1 / 3, 1 / 3], ) self.assertAlmostEqual( pd_ternary.get_decomposition_energy(ag_te_n, 2, -1), 2.767822855765) self.assertAlmostEqual( pd_ternary.get_decomposition_energy(ag_te_n, 10, -2), 3.756840056890625) self.assertAlmostEqual( pd_ternary.get_decomposition_energy(ground_state_ag_with_ions, 2, -1), 0) # Test invocation of pourbaix diagram from ternary data new_ternary = PourbaixDiagram(pd_ternary.all_entries) self.assertEqual(len(new_ternary.stable_entries), 49) self.assertAlmostEqual( new_ternary.get_decomposition_energy(ag_te_n, 2, -1), 2.767822855765) self.assertAlmostEqual( new_ternary.get_decomposition_energy(ag_te_n, 10, -2), 3.756840056890625) self.assertAlmostEqual( new_ternary.get_decomposition_energy(ground_state_ag_with_ions, 2, -1), 0) def test_get_pourbaix_domains(self): domains = PourbaixDiagram.get_pourbaix_domains(self.test_data["Zn"]) self.assertEqual(len(domains[0]), 7) def test_get_decomposition(self): # Test a stable entry to ensure that it's zero in the stable region entry = self.test_data["Zn"][12] # Should correspond to mp-2133 self.assertAlmostEqual( self.pbx.get_decomposition_energy(entry, 10, 1), 0.0, 5, "Decomposition energy of ZnO is not 0.", ) # Test an unstable entry to ensure that it's never zero entry = self.test_data["Zn"][11] ph, v = np.meshgrid(np.linspace(0, 14), np.linspace(-2, 4)) result = self.pbx_nofilter.get_decomposition_energy(entry, ph, v) self.assertTrue((result >= 0).all(), "Unstable energy has hull energy of 0 or less") # Test an unstable hydride to ensure HER correction works self.assertAlmostEqual( self.pbx.get_decomposition_energy(entry, -3, -2), 3.6979147983333) # Test a list of pHs self.pbx.get_decomposition_energy(entry, np.linspace(0, 2, 5), 2) # Test a list of Vs self.pbx.get_decomposition_energy(entry, 4, np.linspace(-3, 3, 10)) # Test a set of matching arrays ph, v = np.meshgrid(np.linspace(0, 14), np.linspace(-3, 3)) self.pbx.get_decomposition_energy(entry, ph, v) def test_get_stable_entry(self): entry = self.pbx.get_stable_entry(0, 0) self.assertEqual(entry.entry_id, "ion-0") def test_multielement_parallel(self): # Simple test to ensure that multiprocessing is working test_entries = self.test_data["Ag-Te-N"] nproc = multiprocessing.cpu_count() pbx = PourbaixDiagram(test_entries, filter_solids=True, nproc=nproc) self.assertEqual(len(pbx.stable_entries), 49) def test_solid_filter(self): entries = self.test_data["Zn"] pbx = PourbaixDiagram(entries, filter_solids=False) oxidized_phase = pbx.find_stable_entry(10, 2) self.assertEqual(oxidized_phase.name, "ZnO2(s)") entries = self.test_data["Zn"] pbx = PourbaixDiagram(entries, filter_solids=True) oxidized_phase = pbx.find_stable_entry(10, 2) self.assertEqual(oxidized_phase.name, "ZnO(s)") def test_serialization(self): d = self.pbx.as_dict() new = PourbaixDiagram.from_dict(d) self.assertEqual( set([e.name for e in new.stable_entries]), {"ZnO(s)", "Zn[2+]", "ZnHO2[-]", "ZnO2[2-]", "Zn(s)"}, "List of stable entries does not match", ) # Test with unprocessed entries included, this should result in the # previously filtered entries being included d = self.pbx.as_dict(include_unprocessed_entries=True) new = PourbaixDiagram.from_dict(d) self.assertEqual( set([e.name for e in new.stable_entries]), { "ZnO(s)", "Zn[2+]", "ZnHO2[-]", "ZnO2[2-]", "Zn(s)", "ZnO2(s)", "ZnH(s)" }, "List of stable entries for unfiltered pbx does not match", ) pd_binary = PourbaixDiagram( self.test_data["Ag-Te"], filter_solids=True, comp_dict={ "Ag": 0.5, "Te": 0.5 }, conc_dict={ "Ag": 1e-8, "Te": 1e-8 }, ) new_binary = PourbaixDiagram.from_dict(pd_binary.as_dict()) self.assertEqual(len(pd_binary.stable_entries), len(new_binary.stable_entries)) # The two tests below rely on the MP Rest interface. @unittest.skipIf(not SETTINGS.get("PMG_MAPI_KEY"), "PMG_MAPI_KEY environment variable not set.") def test_heavy(self): from pymatgen.ext.matproj import MPRester mpr = MPRester() entries = mpr.get_pourbaix_entries(["Li", "Mg", "Sn", "Pd"]) pbx = PourbaixDiagram(entries, nproc=4, filter_solids=False) entries = mpr.get_pourbaix_entries(["Ba", "Ca", "V", "Cu", "F"]) pbx = PourbaixDiagram(entries, nproc=4, filter_solids=False) entries = mpr.get_pourbaix_entries(["Ba", "Ca", "V", "Cu", "F", "Fe"]) pbx = PourbaixDiagram(entries, nproc=4, filter_solids=False) entries = mpr.get_pourbaix_entries(["Na", "Ca", "Nd", "Y", "Ho", "F"]) pbx = PourbaixDiagram(entries, nproc=4, filter_solids=False) @unittest.skipIf(not SETTINGS.get("PMG_MAPI_KEY"), "PMG_MAPI_KEY environment variable not set.") def test_mpr_pipeline(self): from pymatgen.ext.matproj import MPRester mpr = MPRester() data = mpr.get_pourbaix_entries(["Zn"]) pbx = PourbaixDiagram(data, filter_solids=True, conc_dict={"Zn": 1e-8}) pbx.find_stable_entry(10, 0) data = mpr.get_pourbaix_entries(["Ag", "Te"]) pbx = PourbaixDiagram(data, filter_solids=True, conc_dict={ "Ag": 1e-8, "Te": 1e-8 }) self.assertEqual(len(pbx.stable_entries), 29) test_entry = pbx.find_stable_entry(8, 2) self.assertAlmostEqual(test_entry.energy, 2.3894017960000009, 1) # Test custom ions entries = mpr.get_pourbaix_entries(["Sn", "C", "Na"]) ion = IonEntry(Ion.from_formula("NaO28H80Sn12C24+"), -161.676) custom_ion_entry = PourbaixEntry(ion, entry_id="my_ion") pbx = PourbaixDiagram( entries + [custom_ion_entry], filter_solids=True, comp_dict={ "Na": 1, "Sn": 12, "C": 24 }, ) self.assertAlmostEqual( pbx.get_decomposition_energy(custom_ion_entry, 5, 2), 2.1209002582, 1) # Test against ion sets with multiple equivalent ions (Bi-V regression) entries = mpr.get_pourbaix_entries(["Bi", "V"]) pbx = PourbaixDiagram(entries, filter_solids=True, conc_dict={ "Bi": 1e-8, "V": 1e-8 }) self.assertTrue( all([ "Bi" in entry.composition and "V" in entry.composition for entry in pbx.all_entries ]))
def get_basis_and_potential(species, d, cardinality="DZVP", functional="PBE"): """ Given a specie and a potential/basis type, this function accesses the available basis sets and potentials. Generally, the GTH potentials are used with the GTH basis sets. Args: species: (list) list of species for which to get the potential/basis strings d: (dict) a dictionary specifying how bases and/or potentials should be assigned to species E.g. {'Si': {'cardinality': 'DZVP', 'sr': True}, 'O': {'cardinality': 'TZVP'}} functional: (str) functional type. Default: 'PBE' basis_type: (str) the basis set type. Default: 'MOLOPT' cardinality: (str) basis set cardinality. Default: 'DZVP' Returns: (dict) of the form {'specie': {'potential': potential, 'basis': basis}...} """ potential_filename = SETTINGS.get("PMG_DEFAULT_CP2K_POTENTIAL_FILE", "GTH_POTENTIALS") basis_filenames = ["BASIS_MOLOPT", "BASIS_MOLOPT_UCL"] functional = functional or SETTINGS.get("PMG_DEFAULT_FUNCTIONAL", "PBE") cardinality = cardinality or SETTINGS.get("PMG_DEFAULT_BASIS_CARDINALITY", "DZVP") basis_and_potential = { "basis_filenames": basis_filenames, "potential_filename": potential_filename, } for s in species: if s not in d: d[s] = {} if "sr" not in d[s]: d[s]["sr"] = True if "cardinality" not in d[s]: d[s]["cardinality"] = cardinality yaml = YAML() with open(os.path.join(MODULE_DIR, "basis_molopt.yaml")) as f: data_b = yaml.load(f) with open(os.path.join(MODULE_DIR, "gth_potentials.yaml")) as f: data_p = yaml.load(f) for s in species: basis_and_potential[s] = {} b = [_ for _ in data_b[s] if d[s]["cardinality"] in _.split("-")] if d[s]["sr"] and any("SR" in _ for _ in b): b = [_ for _ in b if "SR" in _] else: b = [_ for _ in b if "SR" not in _] if "q" in d[s]: b = [_ for _ in b if d[s]["q"] in _] else: def srt(x): return int(x.split("q")[-1]) b = sorted(b, key=srt)[-1:] if len(b) == 0: raise LookupError("NO BASIS OF THAT TYPE AVAILABLE") if len(b) > 1: raise LookupError("AMBIGUITY IN BASIS. PLEASE SPECIFY FURTHER") basis_and_potential[s]["basis"] = b[0] p = [_ for _ in data_p[s] if functional in _.split("-")] if len(p) == 0: raise LookupError("NO PSEUDOPOTENTIAL OF THAT TYPE AVAILABLE") if len(p) > 1: raise LookupError("AMBIGUITY IN POTENTIAL. PLEASE SPECIFY FURTHER") basis_and_potential[s]["potential"] = p[0] return basis_and_potential
def main(): """ Handle main. """ parser = argparse.ArgumentParser( description=""" pmg is a convenient script that uses pymatgen to perform many analyses, plotting and format conversions. This script works based on several sub-commands with their own options. To see the options for the sub-commands, type "pmg sub-command -h".""", epilog="""Author: Pymatgen Development Team""", ) subparsers = parser.add_subparsers() parser_config = subparsers.add_parser( "config", help= "Tools for configuring pymatgen, e.g., potcar setup, modifying .pmgrc.yaml configuration file.", ) groups = parser_config.add_mutually_exclusive_group(required=True) groups.add_argument( "-p", "--potcar", dest="potcar_dirs", metavar="dir_name", nargs=2, help="Initial directory where downloaded VASP " "POTCARs are extracted to, and the " "output directory where the reorganized " "potcars will be stored. The input " "directory should be " "the parent directory that contains the " "POT_GGA_PAW_PBE or potpaw_PBE type " "subdirectories.", ) groups.add_argument( "-i", "--install", dest="install", metavar="package_name", choices=["enumlib", "bader"], help= "Install various optional command line tools needed for full functionality.", ) groups.add_argument( "-a", "--add", dest="var_spec", nargs="+", help= "Variables to add in the form of space separated key value pairs. E.g., PMG_VASP_PSP_DIR ~/psps", ) parser_config.set_defaults(func=configure_pmg) parser_analyze = subparsers.add_parser( "analyze", help="Vasp calculation analysis tools.") parser_analyze.add_argument( "directories", metavar="dir", default=".", type=str, nargs="*", help="directory to process (default to .)", ) parser_analyze.add_argument( "-e", "--energies", dest="get_energies", action="store_true", help="Print energies", ) parser_analyze.add_argument( "-m", "--mag", dest="ion_list", type=str, nargs=1, help= "Print magmoms. ION LIST can be a range (e.g., 1-2) or the string 'All' for all ions.", ) parser_analyze.add_argument( "-r", "--reanalyze", dest="reanalyze", action="store_true", help="Force reanalysis. Typically, vasp_analyzer" " will just reuse a vasp_analyzer_data.gz if " "present. This forces the analyzer to reanalyze " "the data.", ) parser_analyze.add_argument( "-f", "--format", dest="format", choices=tabulate_formats, default="simple", help="Format for table. Supports all options in tabulate package.", ) parser_analyze.add_argument( "-v", "--verbose", dest="verbose", action="store_true", help="Verbose mode. Provides detailed output on progress.", ) parser_analyze.add_argument( "-q", "--quick", dest="quick", action="store_true", help= "Faster mode, but less detailed information. Parses individual vasp files.", ) parser_analyze.add_argument( "-s", "--sort", dest="sort", choices=["energy_per_atom", "filename"], default="energy_per_atom", help="Sort criteria. Defaults to energy / atom.", ) parser_analyze.set_defaults(func=analyze) parser_query = subparsers.add_parser( "query", help="Search for structures and data from the Materials Project.") parser_query.add_argument( "criteria", metavar="criteria", help= "Search criteria. Supported formats in formulas, chemical systems, Materials Project ids, etc.", ) group = parser_query.add_mutually_exclusive_group(required=True) group.add_argument( "-s", "--structure", dest="structure", metavar="format", choices=["poscar", "cif", "cssr"], type=str.lower, help= "Get structures from Materials Project and write them to a specified format.", ) group.add_argument( "-e", "--entries", dest="entries", metavar="filename", help= "Get entries from Materials Project and write them to serialization file. JSON and YAML supported.", ) group.add_argument( "-d", "--data", dest="data", metavar="fields", nargs="*", help="Print a summary of entries in the Materials Project satisfying " "the criteria. Supply field names to include additional data. " "By default, the Materials Project id, formula, spacegroup, " "energy per atom, energy above hull are shown.", ) parser_query.set_defaults(func=do_query) parser_plot = subparsers.add_parser( "plot", help="Plotting tool for DOS, CHGCAR, XRD, etc.") group = parser_plot.add_mutually_exclusive_group(required=True) group.add_argument( "-d", "--dos", dest="dos_file", metavar="vasprun.xml", help="Plot DOS from a vasprun.xml", ) group.add_argument( "-c", "--chgint", dest="chgcar_file", metavar="CHGCAR", help="Generate charge integration plots from any CHGCAR", ) group.add_argument( "-x", "--xrd", dest="xrd_structure_file", metavar="structure_file", help= "Generate XRD plots from any supported structure file, e.g., CIF, POSCAR, vasprun.xml, etc.", ) parser_plot.add_argument( "-s", "--site", dest="site", action="store_const", const=True, help="Plot site projected DOS", ) parser_plot.add_argument( "-e", "--element", dest="element", type=str, nargs=1, help="List of elements to plot as comma-separated values e.g., Fe,Mn", ) parser_plot.add_argument( "-o", "--orbital", dest="orbital", action="store_const", const=True, help="Plot orbital projected DOS", ) parser_plot.add_argument( "-i", "--indices", dest="inds", type=str, nargs=1, help="Comma-separated list of indices to plot " "charge integration, e.g., 1,2,3,4. If not " "provided, the code will plot the chgint " "for all symmetrically distinct atoms " "detected.", ) parser_plot.add_argument( "-r", "--radius", dest="radius", type=float, default=3, help="Radius of integration for charge integration plot.", ) parser_plot.add_argument( "--out_file", dest="out_file", type=str, help="Save plot to file instead of displaying.", ) parser_plot.set_defaults(func=plot) parser_structure = subparsers.add_parser( "structure", help="Structure conversion and analysis tools.") parser_structure.add_argument( "-f", "--filenames", dest="filenames", metavar="filename", nargs="+", help="List of structure files.", ) groups = parser_structure.add_mutually_exclusive_group(required=True) groups.add_argument( "-c", "--convert", dest="convert", action="store_true", help="Convert from structure file 1 to structure " "file 2. Format determined from filename. " "Supported formats include POSCAR/CONTCAR, " "CIF, CSSR, etc. If the keyword'prim' is within " "the filename, the code will automatically attempt " "to find a primitive cell.", ) groups.add_argument( "-s", "--symmetry", dest="symmetry", metavar="tolerance", type=float, help="Determine the spacegroup using the " "specified tolerance. 0.1 is usually a good " "value for DFT calculations.", ) groups.add_argument( "-g", "--group", dest="group", choices=["element", "species"], metavar="mode", help="Compare a set of structures for similarity. " "Element mode does not compare oxidation states. " "Species mode will take into account oxidations " "states.", ) groups.add_argument( "-l", "--localenv", dest="localenv", nargs="+", help="Local environment analysis. Provide bonds in the format of" "Center Species-Ligand Species=max_dist, e.g., H-O=0.5.", ) parser_structure.set_defaults(func=analyze_structures) parser_view = subparsers.add_parser("view", help="Visualize structures") parser_view.add_argument("filename", metavar="filename", type=str, nargs=1, help="Filename") parser_view.add_argument( "-e", "--exclude_bonding", dest="exclude_bonding", type=str, nargs=1, help="List of elements to exclude from bonding analysis. E.g., Li,Na", ) parser_view.set_defaults(func=parse_view) parser_diff = subparsers.add_parser( "diff", help="Diffing tool. For now, only INCAR supported.") parser_diff.add_argument( "-i", "--incar", dest="incars", metavar="INCAR", required=True, nargs=2, help="List of INCARs to compare.", ) parser_diff.set_defaults(func=diff_incar) parser_potcar = subparsers.add_parser("potcar", help="Generate POTCARs") parser_potcar.add_argument( "-f", "--functional", dest="functional", type=str, choices=sorted(Potcar.FUNCTIONAL_CHOICES), default=SETTINGS.get("PMG_DEFAULT_FUNCTIONAL", "PBE"), help= "Functional to use. Unless otherwise stated (e.g., US), refers to PAW psuedopotential.", ) group = parser_potcar.add_mutually_exclusive_group(required=True) group.add_argument( "-s", "--symbols", dest="symbols", type=str, nargs="+", help= "List of POTCAR symbols. Use -f to set functional. Defaults to PBE.", ) group.add_argument( "-r", "--recursive", dest="recursive", type=str, help="Dirname to find and generate from POTCAR.spec.", ) parser_potcar.set_defaults(func=generate_potcar) try: import argcomplete argcomplete.autocomplete(parser) except ImportError: # argcomplete not present. pass args = parser.parse_args() try: getattr(args, "func") except AttributeError: parser.print_help() sys.exit(-1) return args.func(args)
class PymatgenTest(unittest.TestCase): """ Extends unittest.TestCase with functions (taken from numpy.testing.utils) that support the comparison of arrays. """ _multiprocess_shared_ = True MODULE_DIR = Path(__file__).absolute().parent STRUCTURES_DIR = MODULE_DIR / "structures" try: TEST_FILES_DIR = Path(SETTINGS["PMG_TEST_FILES_DIR"]) except KeyError: import warnings warnings.warn( "It is recommended that you set the PMG_TEST_FILES_DIR environment variable explicitly. " "Now using a fallback location based on relative path from this module." ) TEST_FILES_DIR = MODULE_DIR / ".." / ".." / "test_files" """ Dict for test structures to aid testing. """ TEST_STRUCTURES = {} for fn in STRUCTURES_DIR.iterdir(): TEST_STRUCTURES[fn.name.rsplit(".", 1)[0]] = loadfn(str(fn)) @classmethod def get_structure(cls, name): """ Get a structure from the template directories. :param name: Name of a structure. :return: Structure """ return cls.TEST_STRUCTURES[name].copy() @classmethod @requires(SETTINGS.get("PMG_MAPI_KEY"), "PMG_MAPI_KEY needs to be set.") def get_mp_structure(cls, mpid): """ Get a structure from MP. :param mpid: Materials Project id. :return: Structure """ m = MPRester() return m.get_structure_by_material_id(mpid) @staticmethod def assertArrayAlmostEqual(actual, desired, decimal=7, err_msg="", verbose=True): """ Tests if two arrays are almost equal to a tolerance. The CamelCase naming is so that it is consistent with standard unittest methods. """ return nptu.assert_almost_equal(actual, desired, decimal, err_msg, verbose) @staticmethod def assertDictsAlmostEqual(actual, desired, decimal=7, err_msg="", verbose=True): """ Tests if two arrays are almost equal to a tolerance. The CamelCase naming is so that it is consistent with standard unittest methods. """ for k, v in actual.items(): if k not in desired: return False v2 = desired[k] if isinstance(v, dict): pass_test = PymatgenTest.assertDictsAlmostEqual( v, v2, decimal=decimal, err_msg=err_msg, verbose=verbose) if not pass_test: return False elif isinstance(v, (list, tuple)): pass_test = nptu.assert_almost_equal(v, v2, decimal, err_msg, verbose) if not pass_test: return False elif isinstance(v, (int, float)): PymatgenTest().assertAlmostEqual(v, v2) # pylint: disable=E1120 else: assert v == v2 return True @staticmethod def assertArrayEqual(actual, desired, err_msg="", verbose=True): """ Tests if two arrays are equal. The CamelCase naming is so that it is consistent with standard unittest methods. """ return nptu.assert_equal(actual, desired, err_msg=err_msg, verbose=verbose) @staticmethod def assertStrContentEqual(actual, desired, err_msg="", verbose=True): """ Tests if two strings are equal, ignoring things like trailing spaces, etc. """ lines1 = actual.split("\n") lines2 = desired.split("\n") if len(lines1) != len(lines2): return False failed = [] for l1, l2 in zip(lines1, lines2): if l1.strip() != l2.strip(): failed.append(f"{l1} != {l2}") return len(failed) == 0 def serialize_with_pickle(self, objects, protocols=None, test_eq=True): """ Test whether the object(s) can be serialized and deserialized with pickle. This method tries to serialize the objects with pickle and the protocols specified in input. Then it deserializes the pickle format and compares the two objects with the __eq__ operator if test_eq == True. Args: objects: Object or list of objects. protocols: List of pickle protocols to test. If protocols is None, HIGHEST_PROTOCOL is tested. Returns: Nested list with the objects deserialized with the specified protocols. """ # Use the python version so that we get the traceback in case of errors import pickle from pymatgen.util.serialization import pmg_pickle_dump, pmg_pickle_load # Build a list even when we receive a single object. got_single_object = False if not isinstance(objects, (list, tuple)): got_single_object = True objects = [objects] if protocols is None: # protocols = set([0, 1, 2] + [pickle.HIGHEST_PROTOCOL]) protocols = [pickle.HIGHEST_PROTOCOL] # This list will contains the object deserialized with the different # protocols. objects_by_protocol, errors = [], [] for protocol in protocols: # Serialize and deserialize the object. mode = "wb" fd, tmpfile = tempfile.mkstemp(text="b" not in mode) try: with open(tmpfile, mode) as fh: pmg_pickle_dump(objects, fh, protocol=protocol) except Exception as exc: errors.append( f"pickle.dump with protocol {protocol} raised:\n{exc}") continue try: with open(tmpfile, "rb") as fh: new_objects = pmg_pickle_load(fh) except Exception as exc: errors.append( f"pickle.load with protocol {protocol} raised:\n{exc}") continue # Test for equality if test_eq: for old_obj, new_obj in zip(objects, new_objects): self.assertEqual(old_obj, new_obj) # Save the deserialized objects and test for equality. objects_by_protocol.append(new_objects) if errors: raise ValueError("\n".join(errors)) # Return nested list so that client code can perform additional tests. if got_single_object: return [o[0] for o in objects_by_protocol] return objects_by_protocol def assertMSONable(self, obj, test_if_subclass=True): """ Tests if obj is MSONable and tries to verify whether the contract is fulfilled. By default, the method tests whether obj is an instance of MSONable. This check can be deactivated by setting test_if_subclass to False. """ if test_if_subclass: self.assertIsInstance(obj, MSONable) self.assertDictEqual(obj.as_dict(), obj.__class__.from_dict(obj.as_dict()).as_dict()) json.loads(obj.to_json(), cls=MontyDecoder)
class TestConversions(TestCase): def test_conversion_overwrite(self): # Test with overwrite d = {'comp_str': ["Fe2", "MnO2"]} df = DataFrame(data=d) stc = StrToComposition(target_col_id='comp_str', overwrite_data=False) with self.assertRaises(ValueError): df = stc.featurize_dataframe(df, 'comp_str', inplace=True) with self.assertRaises(ValueError): df = stc.featurize_dataframe(df, 'comp_str', inplace=False) stc = StrToComposition(target_col_id='comp_str', overwrite_data=True) dfres_ipt = df.copy() stc.featurize_dataframe(dfres_ipt, 'comp_str', inplace=True) self.assertListEqual(dfres_ipt.columns.tolist(), ["comp_str"]) dfres_ipf = stc.featurize_dataframe(df, 'comp_str', inplace=False) self.assertListEqual(dfres_ipf.columns.tolist(), ["comp_str"]) def test_str_to_composition(self): d = {'comp_str': ["Fe2", "MnO2"]} df = DataFrame(data=d) df = StrToComposition().featurize_dataframe(df, 'comp_str') self.assertEqual( df["composition"].tolist(), [Composition("Fe2"), Composition("MnO2")]) stc = StrToComposition(reduce=True, target_col_id='composition_red') df = stc.featurize_dataframe(df, 'comp_str') self.assertEqual( df["composition_red"].tolist(), [Composition("Fe"), Composition("MnO2")]) def test_structure_to_composition(self): coords = [[0, 0, 0], [0.75, 0.5, 0.75]] lattice = Lattice([[3.8401979337, 0.00, 0.00], [1.9200989668, 3.3257101909, 0.00], [0.00, -2.2171384943, 3.1355090603]]) struct = Structure(lattice, ["Si"] * 2, coords) df = DataFrame(data={'structure': [struct]}) stc = StructureToComposition() df = stc.featurize_dataframe(df, 'structure') self.assertEqual(df["composition"].tolist()[0], Composition("Si2")) stc = StructureToComposition(reduce=True, target_col_id='composition_red') df = stc.featurize_dataframe(df, 'structure') self.assertEqual(df["composition_red"].tolist()[0], Composition("Si")) def test_dict_to_object(self): coords = [[0, 0, 0], [0.75, 0.5, 0.75]] lattice = Lattice([[3.8401979337, 0.00, 0.00], [1.9200989668, 3.3257101909, 0.00], [0.00, -2.2171384943, 3.1355090603]]) struct = Structure(lattice, ["Si"] * 2, coords) d = {'structure_dict': [struct.as_dict(), struct.as_dict()]} df = DataFrame(data=d) dto = DictToObject(target_col_id='structure') df = dto.featurize_dataframe(df, 'structure_dict') self.assertEqual(df["structure"].tolist()[0], struct) self.assertEqual(df["structure"].tolist()[1], struct) # test dynamic target_col_id setting df = DataFrame(data=d) dto = DictToObject() df = dto.featurize_dataframe(df, 'structure_dict') self.assertEqual(df["structure_dict_object"].tolist()[0], struct) self.assertEqual(df["structure_dict_object"].tolist()[1], struct) def test_json_to_object(self): coords = [[0, 0, 0], [0.75, 0.5, 0.75]] lattice = Lattice([[3.8401979337, 0.00, 0.00], [1.9200989668, 3.3257101909, 0.00], [0.00, -2.2171384943, 3.1355090603]]) struct = Structure(lattice, ["Si"] * 2, coords) struct_json = json.dumps(struct, cls=MontyEncoder) d = {'structure_json': [struct_json]} df = DataFrame(data=d) jto = JsonToObject(target_col_id='structure') df = jto.featurize_dataframe(df, 'structure_json') self.assertEqual(df["structure"].tolist()[0], struct) # test dynamic target_col_id setting df = DataFrame(data=d) jto = JsonToObject() df = jto.featurize_dataframe(df, 'structure_json') self.assertEqual(df["structure_json_object"].tolist()[0], struct) def test_structure_to_oxidstructure(self): cscl = Structure( Lattice([[4.209, 0, 0], [0, 4.209, 0], [0, 0, 4.209]]), ["Cl", "Cs"], [[0.45, 0.5, 0.5], [0, 0, 0]]) d = {'structure': [cscl]} df = DataFrame(data=d) sto = StructureToOxidStructure() df = sto.featurize_dataframe(df, 'structure') self.assertEqual(df["structure_oxid"].tolist()[0][0].specie.oxi_state, -1) self.assertEqual(df["structure_oxid"].tolist()[0][1].specie.oxi_state, +1) sto = StructureToOxidStructure(target_col_id='structure_oxid2', oxi_states_override={ "Cl": [-2], "Cs": [+2] }) df = sto.featurize_dataframe(df, 'structure') self.assertEqual(df["structure_oxid2"].tolist()[0][0].specie.oxi_state, -2) self.assertEqual(df["structure_oxid2"].tolist()[0][1].specie.oxi_state, +2) # original is preserved self.assertEqual(df["structure"].tolist()[0][0].specie, Element("Cl")) # test in-place sto = StructureToOxidStructure(target_col_id=None, overwrite_data=True) df = sto.featurize_dataframe(df, 'structure') self.assertEqual(df["structure"].tolist()[0][0].specie.oxi_state, -1) # test error handling test_struct = Structure([5, 0, 0, 0, 5, 0, 0, 0, 5], ['Sb', 'F', 'O'], [[0, 0, 0], [0.2, 0.2, 0.2], [0.5, 0.5, 0.5]]) df = DataFrame(data={'structure': [test_struct]}) sto = StructureToOxidStructure(return_original_on_error=False, max_sites=2) self.assertRaises(ValueError, sto.featurize_dataframe, df, 'structure') # check non oxi state structure returned correctly sto = StructureToOxidStructure(return_original_on_error=True, max_sites=2) df = sto.featurize_dataframe(df, 'structure') self.assertEqual(df["structure_oxid"].tolist()[0][0].specie, Element("Sb")) def test_composition_to_oxidcomposition(self): df = DataFrame(data={"composition": [Composition("Fe2O3")]}) cto = CompositionToOxidComposition() df = cto.featurize_dataframe(df, 'composition') self.assertEqual(df["composition_oxid"].tolist()[0], Composition({ "Fe3+": 2, "O2-": 3 })) # test error handling df = DataFrame(data={"composition": [Composition("Fe2O3")]}) cto = CompositionToOxidComposition(return_original_on_error=False, max_sites=2) self.assertRaises(ValueError, cto.featurize_dataframe, df, 'composition') # check non oxi state structure returned correctly cto = CompositionToOxidComposition(return_original_on_error=True, max_sites=2) df = cto.featurize_dataframe(df, 'composition') self.assertEqual(df["composition_oxid"].tolist()[0], Composition({ "Fe": 2, "O": 3 })) def test_to_istructure(self): cscl = Structure( Lattice([[4.209, 0, 0], [0, 4.209, 0], [0, 0, 4.209]]), ["Cl", "Cs"], [[0.45, 0.5, 0.5], [0, 0, 0]]) df = DataFrame({"structure": [cscl]}) # Run the conversion sti = StructureToIStructure() df = sti.featurize_dataframe(df, 'structure') # Make sure the new structure is an IStructure, and equal # to the original structure self.assertIsInstance(df["istructure"][0], IStructure) self.assertEqual(df["istructure"][0], df["structure"][0]) def test_conversion_multiindex(self): d = {'comp_str': ["Fe2", "MnO2"]} df_1lvl = DataFrame(data=d) df_1lvl = StrToComposition().featurize_dataframe(df_1lvl, 'comp_str', multiindex=True) self.assertEqual( df_1lvl[("StrToComposition", "composition")].tolist(), [Composition("Fe2"), Composition("MnO2")]) df_2lvl = DataFrame(data=d) df_2lvl.columns = MultiIndex.from_product( (["custom"], df_2lvl.columns.values)) df_2lvl = StrToComposition().featurize_dataframe( df_2lvl, ("custom", "comp_str"), multiindex=True) self.assertEqual( df_2lvl[("StrToComposition", "composition")].tolist(), [Composition("Fe2"), Composition("MnO2")]) df_2lvl = DataFrame(data=d) df_2lvl.columns = MultiIndex.from_product( (["custom"], df_2lvl.columns.values)) sto = StrToComposition(target_col_id='test') df_2lvl = sto.featurize_dataframe(df_2lvl, ("custom", "comp_str"), multiindex=True) self.assertEqual( df_2lvl[("StrToComposition", "test")].tolist(), [Composition("Fe2"), Composition("MnO2")]) # if two level multiindex provided as target, it should be written there # here we test converting multiindex in place df_2lvl = DataFrame(data=d) df_2lvl.columns = MultiIndex.from_product( (["custom"], df_2lvl.columns.values)) sto = StrToComposition(target_col_id=None, overwrite_data=True) df_2lvl = sto.featurize_dataframe(df_2lvl, ("custom", "comp_str"), multiindex=True, inplace=False) self.assertEqual( df_2lvl[("custom", "comp_str")].tolist(), [Composition("Fe2"), Composition("MnO2")]) # Try inplace multiindex conversion with return errors df_2lvl = DataFrame(data=d) df_2lvl.columns = MultiIndex.from_product( (["custom"], df_2lvl.columns.values)) sto = StrToComposition(target_col_id=None, overwrite_data=True) df_2lvl = sto.featurize_dataframe(df_2lvl, ("custom", "comp_str"), multiindex=True, return_errors=True, ignore_errors=True) self.assertTrue( all(df_2lvl[("custom", "StrToComposition Exceptions")].isnull())) def test_conversion_multiindex_dynamic(self): # test dynamic target_col_id setting with multiindex coords = [[0, 0, 0], [0.75, 0.5, 0.75]] lattice = Lattice([[3.8401979337, 0.00, 0.00], [1.9200989668, 3.3257101909, 0.00], [0.00, -2.2171384943, 3.1355090603]]) struct = Structure(lattice, ["Si"] * 2, coords) d = {'structure_dict': [struct.as_dict(), struct.as_dict()]} df_2lvl = DataFrame(data=d) df_2lvl.columns = MultiIndex.from_product( (["custom"], df_2lvl.columns.values)) dto = DictToObject() df_2lvl = dto.featurize_dataframe(df_2lvl, ('custom', 'structure_dict'), multiindex=True) new_col_id = ('DictToObject', 'structure_dict_object') self.assertEqual(df_2lvl[new_col_id].tolist()[0], struct) self.assertEqual(df_2lvl[new_col_id].tolist()[1], struct) @unittest.skipIf(not SETTINGS.get("PMG_MAPI_KEY", ""), "PMG_MAPI_KEY not in environment variables.") def test_composition_to_structurefromMP(self): df = DataFrame(data={ "composition": [Composition("Fe2O3"), Composition("N9Al34Fe234")] }) cto = CompositionToStructureFromMP() df = cto.featurize_dataframe(df, 'composition') structures = df["structure"].tolist() self.assertTrue(isinstance(structures[0], Structure)) self.assertGreaterEqual(len(structures[0]), 5) # has at least 5 sites self.assertTrue(math.isnan(structures[1]))
import unittest from matminer.data_retrieval.retrieve_MP import MPDataRetrieval from pymatgen.core import SETTINGS from pymatgen.electronic_structure.bandstructure import BandStructureSymmLine, \ BandStructure from pymatgen.electronic_structure.dos import CompleteDos @unittest.skipIf(not SETTINGS.get("PMG_MAPI_KEY", ""), "PMG_MAPI_KEY not in environment variables.") class MPDataRetrievalTest(unittest.TestCase): def setUp(self): self.mpdr = MPDataRetrieval() def test_get_data(self): df = self.mpdr.get_dataframe(criteria={"material_id": "mp-23"}, properties=[ "structure", "bandstructure", "bandstructure_uniform", "dos" ]) self.assertEqual(len(df["structure"]), 1) self.assertEqual(df["bandstructure"][0].get_band_gap()["energy"], 0) self.assertTrue( isinstance(df["bandstructure"][0], BandStructureSymmLine)) self.assertTrue( isinstance(df["bandstructure_uniform"][0], BandStructure)) self.assertTrue(isinstance(df["dos"][0], CompleteDos)) if __name__ == "__main__":