diff --git a/mp_api/client/core/client.py b/mp_api/client/core/client.py index fa5774e1..c192ecc9 100644 --- a/mp_api/client/core/client.py +++ b/mp_api/client/core/client.py @@ -198,7 +198,7 @@ def __init__( warnings.warn( "Ignoring `monty_decode`, as it is no longer a supported option in `mp_api`." "The client by default returns results consistent with `monty_decode=True`.", - category=DeprecationWarning, + category=MPRestWarning, stacklevel=2, ) @@ -1360,7 +1360,7 @@ def new_str(self) -> str: return ( f"\033[4m\033[1m{self.__class__.__name__}" - f"<{self.__class__.__base__.__name__}>\033[0;0m\033[0;0m" + f"<{orig_rester_name}>\033[0;0m\033[0;0m" f"\n{extra}\n\n" f"\033[1mFields not requested:\033[0;0m\n{fields_not_requested}" ) @@ -1608,7 +1608,7 @@ def __getattr__(self, v: str): self.sub_resters[v]( api_key=self.api_key, endpoint=self.base_endpoint, - include_user_agent=self._include_user_agent, + include_user_agent=self.include_user_agent, session=self.session, use_document_model=self.use_document_model, headers=self.headers, @@ -1617,6 +1617,7 @@ def __getattr__(self, v: str): force_renew=self.force_renew, ) return self.sub_resters[v] + raise AttributeError(f"{self.__class__} has no attribute {v}") def __dir__(self): return dir(self.__class__) + list(self._sub_resters) diff --git a/mp_api/client/mprester.py b/mp_api/client/mprester.py index b596f1ac..c04aac7f 100644 --- a/mp_api/client/mprester.py +++ b/mp_api/client/mprester.py @@ -1070,7 +1070,14 @@ def get_entries_in_chemsys( if isinstance(elements, str): elements = elements.split("-") - elements_set = set(elements) # remove duplicate elements + # 9 elements would be sum_{i=1}^{9} (9 choose i) = 511 + # From testing, this is the highest number of chemsys + # we can query before URI lengths are exceeded + if len(elements_set := set(elements)) > 9: # remove duplicate elements + raise MPRestError( + "Please specify fewer elements to query by, " + "or identify a subset of relevant chemical systems to query first." + ) all_chemsyses = [ "-".join(sorted(els)) @@ -1078,17 +1085,13 @@ def get_entries_in_chemsys( for els in itertools.combinations(elements_set, i + 1) ] - entries = [] - - entries.extend( - self.get_entries( - all_chemsyses, - compatible_only=compatible_only, - property_data=property_data, - conventional_unit_cell=conventional_unit_cell, - additional_criteria=additional_criteria or DEFAULT_THERMOTYPE_CRITERIA, - **kwargs, - ) + entries = self.get_entries( + all_chemsyses, + compatible_only=compatible_only, + property_data=property_data, + conventional_unit_cell=conventional_unit_cell, + additional_criteria=additional_criteria or DEFAULT_THERMOTYPE_CRITERIA, + **kwargs, ) if use_gibbs: @@ -1255,11 +1258,16 @@ def get_charge_density_from_material_id( task_id = latest_doc["task_id"] return self.get_charge_density_from_task_id(task_id, inc_task_doc) - def get_download_info(self, material_ids, calc_types=None, file_patterns=None): + def get_download_info( + self, + material_ids: str | MPID | list[str | MPID], + calc_types: list[str | CalcType] | None = None, + file_patterns: list[str] | None = None, + ): """Get a list of URLs to retrieve raw VASP output files from the NoMaD repository Args: - material_ids (list): list of material identifiers (mp-id's) - task_types (list): list of task types to include in download (see CalcType Enum class) + material_ids (str or MPID, or list thereof): list of material identifiers (mp-id's) + calc_types (list of str or CalcType): list of calc types to include in download (see CalcType Enum class) file_patterns (list): list of wildcard file names to include for each task Returns: a tuple of 1) a dictionary mapping material_ids to task_ids and @@ -1267,9 +1275,24 @@ def get_download_info(self, material_ids, calc_types=None, file_patterns=None): NoMaD repository. Each zip archive will contain a manifest.json with metadata info, e.g. the task/external_ids that belong to a directory. """ + warnings.warn( + "Full downloads of raw data are being transitioned to " + "Materials Project's AWS S3 OpenData buckets. " + "These features for accessing legacy raw data via NOMAD " + "are maintained but may not be supported in the future.", + category=MPRestWarning, + stacklevel=2, + ) + # task_id's correspond to NoMaD external_id's + if isinstance(material_ids, str | MPID): + material_ids = [material_ids] + calc_types = ( - [t.value for t in calc_types if isinstance(t, CalcType)] + [ + t.value if isinstance(t, CalcType) else CalcType(t).value + for t in calc_types + ] if calc_types else [] ) diff --git a/mp_api/client/routes/materials/materials.py b/mp_api/client/routes/materials/materials.py index 16e8ca51..4b4b49d1 100644 --- a/mp_api/client/routes/materials/materials.py +++ b/mp_api/client/routes/materials/materials.py @@ -1,5 +1,6 @@ from __future__ import annotations +from pathlib import Path from typing import TYPE_CHECKING from emmet.core.symmetry import CrystalSystem @@ -172,11 +173,11 @@ def search( def find_structure( self, - filename_or_structure, + filename_or_structure: str | Path | Structure, ltol=MAPI_CLIENT_SETTINGS.LTOL, stol=MAPI_CLIENT_SETTINGS.STOL, angle_tol=MAPI_CLIENT_SETTINGS.ANGLE_TOL, - allow_multiple_results=False, + allow_multiple_results: bool | int = False, ) -> list[str] | str: """Finds matching structures from the Materials Project database. @@ -186,48 +187,75 @@ def find_structure( default tolerances. Args: - filename_or_structure: filename or Structure object + filename_or_structure: filename as a str or Path, or a Structure object ltol: fractional length tolerance stol: site tolerance angle_tol: angle tolerance in degrees - allow_multiple_results: changes return type for either - a single material_id or list of material_ids + allow_multiple_results (bool or int): changes return type for either + a single material_id or list of material_ids. + If a bool, returns either all matches (True) or one match at most (False). + If an int, returns that many matches at most. + Returns: A matching material_id if one is found or list of results if allow_multiple_results is True Raises: MPRestError """ - params = {"ltol": ltol, "stol": stol, "angle_tol": angle_tol, "_limit": 1} + from pymatgen.analysis.structure_matcher import ( + ElementComparator, + StructureMatcher, + ) - if isinstance(filename_or_structure, str): + if ( + isinstance(filename_or_structure, str | Path) + and Path(filename_or_structure).exists() + ): s = Structure.from_file(filename_or_structure) elif isinstance(filename_or_structure, Structure): s = filename_or_structure else: raise MPRestError("Provide filename or Structure object.") - results = self._post_resource( - body=s.as_dict(), - params=params, - suburl="find_structure", - use_document_model=False, - ).get("data") - - if not results: + mat_docs = self.search( + formula=s.reduced_formula, fields=["material_id", "structure"] + ) + if not mat_docs: return [] - material_ids = validate_ids([doc["material_id"] for doc in results]) + if isinstance(allow_multiple_results, bool): + max_matches: int = len(mat_docs) if allow_multiple_results else 1 + elif isinstance(allow_multiple_results, int): + max_matches = allow_multiple_results + else: + raise MPRestError( + f"`allow_multiple_results` must be a bool or int, not {type(allow_multiple_results)}" + ) - if len(material_ids) > 1: # type: ignore - if not allow_multiple_results: - raise ValueError( - "Multiple matches found for this combination of tolerances, but " - "`allow_multiple_results` set to False." - ) - return material_ids # type: ignore + matcher = StructureMatcher( + ltol=ltol, + stol=stol, + angle_tol=angle_tol, + primitive_cell=True, + scale=True, + attempt_supercell=False, + comparator=ElementComparator(), + ) - return material_ids[0] + matches: list[str] = [] + for doc in mat_docs: + if matcher.fit( + s, + doc.structure if self.use_document_model else Structure.from_dict(doc["structure"]), # type: ignore + ): + matches.append(doc.material_id.string if self.use_document_model else doc["material_id"]) # type: ignore + if len(matches) >= max_matches: + break + + if not matches: + return [] + material_ids = validate_ids(matches) + return material_ids if allow_multiple_results else material_ids[0] def get_blessed_entries( self, diff --git a/mp_api/client/routes/molecules/bonds.py b/mp_api/client/routes/molecules/bonds.py deleted file mode 100644 index ade0de0e..00000000 --- a/mp_api/client/routes/molecules/bonds.py +++ /dev/null @@ -1,134 +0,0 @@ -from __future__ import annotations - -from emmet.core.molecules.bonds import MoleculeBondingDoc -from emmet.core.mpid import MPculeID - -from mp_api.client.core import BaseRester - - -class MoleculesBondRester(BaseRester): - suffix = "molecules/bonding" - document_model = MoleculeBondingDoc - primary_key = "property_id" - - def search( - self, - molecule_ids: MPculeID | list[MPculeID] | None = None, - property_ids: str | list[str] | None = None, - method: str | None = None, - bond_type: str | None = None, - max_bond_length: float | None = None, - min_bond_length: float | None = None, - charge: int | None = None, - spin_multiplicity: int | None = None, - level_of_theory: str | None = None, - solvent: str | None = None, - lot_solvent: str | None = None, - formula: str | list[str] | None = None, - elements: list[str] | None = None, - exclude_elements: list[str] | None = None, - chemsys: str | list[str] | None = None, - num_chunks: int | None = None, - chunk_size: int = 1000, - all_fields: bool = True, - fields: list[str] | None = None, - ): - """Query molecules bonding docs using a variety of search criteria. - - Arguments: - molecule_ids (MPculeID, List[MPculeID]): List of Materials Project Molecule IDs (MPculeIDs) to return data - for. - property_ids (str, List[str]): List of property IDs to return data for. - method (str): Method used to generate bonding data - (e.g. "nbo", "critic2", or "OpenBabelNN + metal_edge_extender") - bond_type (str): Bond type of interest (e.g. "C-O" for carbon-oxygen bonds) - max_bond_length (float): Maximum bond length in the molecule of the specified type - min_bond_length (float): Minimum bond length in the molecule of the specified type - charge (Tuple[int, int]): Minimum and maximum charge for the molecule. - spin_multiplicity (Tuple[int, int]): Minimum and maximum spin for the molecule. - level_of_theory (str): Desired level of theory (e.g. "wB97X-V/def2-TZVPPD/SMD") - solvent (str): Desired solvent (e.g. "SOLVENT=WATER") - lot_solvent (str): Desired combination of level of theory and solvent - (e.g. "wB97X-V/def2-TZVPPD/SMD(SOLVENT=THF)") - formula (str, List[str]): An alphabetical formula or list of formulas - (e.g. "C2 Li2 O4", ["C2 H4", "C2 H6"]). - elements (List[str]): A list of elements. - exclude_elements (List(str)): List of elements to exclude. - chemsys (str, List[str]): A chemical system, list of chemical systems - (e.g., Li-C-O, [C-O-H-N, Li-N]). - num_chunks (int): Maximum number of chunks of data to yield. None will yield all possible. - chunk_size (int): Number of data entries per chunk. - all_fields (bool): Whether to return all fields in the document. Defaults to True. - fields (List[str]): List of fields in MoleculeDoc to return data for. - Default is "molecule_id", "property_id", "solvent", "method", "last_updated" - if all_fields is False. - - Returns: - ([MoleculeBondingDoc]) List of bonding documents - """ - query_params: dict = {} - - if molecule_ids: - if isinstance(molecule_ids, str): - molecule_ids = [molecule_ids] - - query_params.update({"molecule_ids": ",".join(molecule_ids)}) - - if property_ids: - if isinstance(property_ids, str): - property_ids = [property_ids] - - query_params.update({"property_ids": ",".join(property_ids)}) - - if method: - query_params.update({"method": method}) - - if bond_type: - query_params.update({"bond_type": bond_type}) - - if max_bond_length: - query_params.update({"max_bond_length": max_bond_length}) - - if min_bond_length: - query_params.update({"min_bond_length": max_bond_length}) - - if charge: - query_params.update({"charge": charge}) - - if spin_multiplicity: - query_params.update({"spin_multiplicity": spin_multiplicity}) - - if level_of_theory: - query_params.update({"level_of_theory": level_of_theory}) - - if solvent: - query_params.update({"solvent": solvent}) - - if lot_solvent: - query_params.update({"lot_solvent": lot_solvent}) - - if formula: - if isinstance(formula, str): - formula = [formula] - - query_params.update({"formula": ",".join(formula)}) - - if chemsys: - if isinstance(chemsys, str): - chemsys = [chemsys] - - query_params.update({"chemsys": ",".join(chemsys)}) - - if elements: - query_params.update({"elements": ",".join(elements)}) - - if exclude_elements: - query_params.update({"exclude_elements": ",".join(exclude_elements)}) - - return super()._search( - num_chunks=num_chunks, - chunk_size=chunk_size, - all_fields=all_fields, - fields=fields, - **query_params, - ) diff --git a/mp_api/client/routes/molecules/molecules.py b/mp_api/client/routes/molecules/molecules.py index 45b4cb43..b7600328 100644 --- a/mp_api/client/routes/molecules/molecules.py +++ b/mp_api/client/routes/molecules/molecules.py @@ -1,199 +1,22 @@ """Define core molecules functionality. -Note that these classes are not currently working. -The `MoleculeRester` methods: `search`, `find_molecule`, `get_molecule_by_mpculeid`, -all return 404s when attempting to use them. +Note that the `MoleculeRester`, unlike the `MaterialsRester`, +has no API functionality beyond serving as an access point for +the JCESR and summary resters. """ from __future__ import annotations -from emmet.core.mpid import MPculeID from emmet.core.qchem.molecule import MoleculeDoc -from pymatgen.core.structure import Molecule -from mp_api.client.core.client import CoreRester, MPRestError -from mp_api.client.core.utils import validate_ids +from mp_api.client.core.client import CoreRester from mp_api.client.routes.molecules import MOLECULES_RESTERS -class BaseMoleculeRester(CoreRester): +class MoleculeRester(CoreRester): + """Define molecules stub for accessing JCESR and summary data.""" + document_model = MoleculeDoc primary_key = "molecule_id" - - def get_molecule_by_mpculeid( - self, mpcule_id: str, final: bool = True - ) -> Molecule | list[Molecule]: - """Get a molecule object for a given Materials Project molecules ID (MPculeID). - - Arguments: - mpcule_id (str): Materials project molecule ID - final (bool): Whether to get the final (optimized) molecule, or the list of initial - (pre-optimized) structures. Defaults to True. - - Returns: - molecule (Union[Molecule, List[Molecule]]): Pymatgen Molecule object or list of - pymatgen Molecule objects. - """ - field = "molecule" if final else "initial_molecules" - - response = self.search(molecule_ids=[mpcule_id], fields=[field]) # type: ignore - return response[0][field] if (response and response[0]) else response # type: ignore - - def find_molecule( - self, - filename_or_molecule: str | Molecule, - charge: int | None = None, - spin_multiplicity: int | None = None, - tolerance: float = 0.01, - allow_multiple_results: bool = False, - ) -> list[str] | str: - """Finds matching molecules from the Materials Project molecules database (MPcules). - - Multiple results may be returned of "similar" molecules based on - distance using the pymatgen MoleculeMatcher algorithm. - - Args: - filename_or_molecule: filename or Molecule object - charge: Molecule charge. Default is None, meaning that the charge will not be used to - restrict the output. - spin_multiplicity: Molecule's spin multiplicity. Default is None, meaning that the output will - not be restricted by spin multiplicity. - tolerance: RMSD difference threshold for MoleculeMatcher - allow_multiple_results: changes return type for either - a single mpcule_id or list of mpcule_ids - Returns: - A matching mpcule_id if one is found or list of results if allow_multiple_results - is True - Raises: - MPRestError - """ - if isinstance(filename_or_molecule, str): - m = Molecule.from_file(filename_or_molecule) - elif isinstance(filename_or_molecule, Molecule): - m = filename_or_molecule - else: - raise MPRestError("Provide filename or Structure object.") - - results = self._post_resource( - body=m.as_dict(), - params={ - "tolerance": tolerance, - "charge": charge, - "spin_multiplicity": spin_multiplicity, - }, - suburl="find_molecule", - use_document_model=False, - ).get("data") - - if len(results) > 1: # type: ignore - if not allow_multiple_results: - raise ValueError( - "Multiple matches found for this combination of tolerances, but " - "`allow_multiple_results` set to False." - ) - return results # type: ignore - - return results[0]["molecule_id"] if (results and results[0]) else [] - - def search( - self, - charge: tuple[int, int] | None = None, - spin_multiplicity: tuple[int, int] | None = None, - nelements: tuple[int, int] | None = None, - chemsys: str | list[str] | None = None, - deprecated: bool | None = None, - elements: list[str] | None = None, - exclude_elements: list[str] | None = None, - formula: str | list[str] | None = None, - molecule_ids: MPculeID | list[MPculeID] | None = None, - task_ids: str | list[str] | None = None, - num_chunks: int | None = None, - chunk_size: int = 1000, - all_fields: bool = True, - fields: list[str] | None = None, - ): - """Query molecule docs using a variety of search criteria. - - Arguments: - charge (Tuple[int, int]): Minimum and maximum charge for the molecule. - spin_multiplicity (Tuple[int, int]): Minimum and maximum spin for the molecule. - nelements (Tuple[int, int]): Minimum and maximum number of elements - chemsys (str, List[str]): A chemical system, list of chemical systems - (e.g., Li-C-O, [C-O-H-N, Li-N]). - deprecated (bool): Whether the material is tagged as deprecated. - elements (List[str]): A list of elements. - exclude_elements (List(str)): List of elements to exclude. - formula (str, List[str]): An alphabetical formula or list of formulas - (e.g. "C2 Li2 O4", ["C2 H4", "C2 H6"]). - molecule_ids (MPculeID, List[MPculeID]): List of Materials Project Molecule IDs (MPculeIDs) to return data - for. - task_ids (str, List[str]): List of Materials Project IDs to return data for. - num_chunks (int): Maximum number of chunks of data to yield. None will yield all possible. - chunk_size (int): Number of data entries per chunk. - all_fields (bool): Whether to return all fields in the document. Defaults to True. - fields (List[str]): List of fields in MoleculeDoc to return data for. - Default is molecule_id, last_updated, and formula_alphabetical if all_fields is False. - - Returns: - ([MoleculeDoc]) List of molecules documents - """ - query_params: dict = {"deprecated": deprecated} - - if molecule_ids: - if isinstance(molecule_ids, str): - molecule_ids = [molecule_ids] - - query_params.update({"molecule_ids": ",".join(molecule_ids)}) - - if charge: - query_params.update({"charge": charge}) - - if spin_multiplicity: - query_params.update({"spin_multiplicity": spin_multiplicity}) - - if formula: - if isinstance(formula, str): - formula = [formula] - - query_params.update({"formula": ",".join(formula)}) - - if chemsys: - if isinstance(chemsys, str): - chemsys = [chemsys] - - query_params.update({"chemsys": ",".join(chemsys)}) - - if elements: - query_params.update({"elements": ",".join(elements)}) - - if exclude_elements: - query_params.update({"exclude_elements": ",".join(exclude_elements)}) - - if task_ids: - if isinstance(task_ids, str): - task_ids = [task_ids] - - query_params.update({"task_ids": ",".join(validate_ids(task_ids))}) - - query_params = { - entry: query_params[entry] - for entry in query_params - if query_params[entry] is not None - } - - return super()._search( - num_chunks=num_chunks, - chunk_size=chunk_size, - all_fields=all_fields, - fields=fields, - **query_params, - ) - - -class AssociatedMoleculeRester(BaseMoleculeRester): - suffix = "molecules/assoc" - - -class MoleculeRester(BaseMoleculeRester): suffix = "molecules/core" _sub_resters = MOLECULES_RESTERS diff --git a/mp_api/client/routes/molecules/orbitals.py b/mp_api/client/routes/molecules/orbitals.py deleted file mode 100644 index d54b1f0c..00000000 --- a/mp_api/client/routes/molecules/orbitals.py +++ /dev/null @@ -1,263 +0,0 @@ -from __future__ import annotations - -from emmet.core.molecules.orbitals import OrbitalDoc -from emmet.core.mpid import MPculeID - -from mp_api.client.core import BaseRester - - -class MoleculesOrbitalsRester(BaseRester): - suffix = "molecules/orbitals" - document_model = OrbitalDoc - primary_key = "property_id" - - def search( - self, - molecule_ids: MPculeID | list[MPculeID] | None = None, - property_ids: str | list[str] | None = None, - charge: int | None = None, - spin_multiplicity: int | None = None, - level_of_theory: str | None = None, - solvent: str | None = None, - lot_solvent: str | None = None, - formula: str | list[str] | None = None, - elements: list[str] | None = None, - exclude_elements: list[str] | None = None, - chemsys: str | list[str] | None = None, - electron_type_population: str | None = None, - core_electrons: tuple[float, float] | None = None, - valence_electrons: tuple[float, float] | None = None, - rydberg_electrons: tuple[float, float] | None = None, - total_electrons: tuple[float, float] | None = None, - electron_type_lp: str | None = None, - lp_type: str | None = None, - s_character: tuple[float, float] | None = None, - p_character: tuple[float, float] | None = None, - d_character: tuple[float, float] | None = None, - f_character: tuple[float, float] | None = None, - lp_occupancy: tuple[float, float] | None = None, - electron_type_bond: str | None = None, - bond_type: str | None = None, - s_character_atom1: tuple[float, float] | None = None, - s_character_atom2: tuple[float, float] | None = None, - p_character_atom1: tuple[float, float] | None = None, - p_character_atom2: tuple[float, float] | None = None, - d_character_atom1: tuple[float, float] | None = None, - d_character_atom2: tuple[float, float] | None = None, - f_character_atom1: tuple[float, float] | None = None, - f_character_atom2: tuple[float, float] | None = None, - polarization_atom1: tuple[float, float] | None = None, - polarization_atom2: tuple[float, float] | None = None, - bond_occupancy: tuple[float, float] | None = None, - electron_type_interaction: str | None = None, - donor_type: str | None = None, - acceptor_type: str | None = None, - perturbation_energy: tuple[float, float] | None = None, - energy_difference: tuple[float, float] | None = None, - fock_element: tuple[float, float] | None = None, - num_chunks: int | None = None, - chunk_size: int = 1000, - all_fields: bool = True, - fields: list[str] | None = None, - ): - """Query molecules redox docs using a variety of search criteria. - - Arguments: - molecule_ids (MPculeID, List[MPculeID]): List of Materials Project Molecule IDs (MPculeIDs) to return data - for. - property_ids (str, List[str]): List of property IDs to return data for. - charge (Tuple[int, int]): Minimum and maximum charge for the molecule. - spin_multiplicity (Tuple[int, int]): Minimum and maximum spin for the molecule. - level_of_theory (str): Desired level of theory (e.g. "wB97X-V/def2-TZVPPD/SMD") - solvent (str): Desired solvent (e.g. "SOLVENT=WATER") - lot_solvent (str): Desired combination of level of theory and solvent - (e.g. "wB97X-V/def2-TZVPPD/SMD(SOLVENT=THF)") - correction_level_of_theory (str): Desired correction level of theory (e.g. "wB97X-V/def2-TZVPPD/SMD") - correction_solvent (str): Desired correction solvent (e.g. "SOLVENT=WATER") - correction_lot_solvent (str): Desired correction combination of level of theory and solvent - (e.g. "wB97X-V/def2-TZVPPD/SMD(SOLVENT=THF)") - combined_lot_solvent (str): Desired combination of level of theory and solvent including both main - thermo calculation and single-point energy correction - (e.g. "wB97X-D/def2-SVPD/VACUUM//wB97X-V/def2-TZVPPD/SMD(SOLVENT=THF)") - formula (str, List[str]): An alphabetical formula or list of formulas - (e.g. "C2 Li2 O4", ["C2 H4", "C2 H6"]). - elements (List[str]): A list of elements. - exclude_elements (List(str)): List of elements to exclude. - chemsys (str, List[str]): A chemical system, list of chemical systems - (e.g., Li-C-O, [C-O-H-N, Li-N]). - electron_type_population (str): Should alpha ('alpha'), beta ('beta'), or all electrons (None) be - considered in a query of natural electron populations? - core_electrons (Tuple[float, float]): Minimum and maximum number of core electrons in an atom - in this molecule. - valence_electrons (Tuple[float, float]): Minimum and maximum number of valence electrons in an atom - in this molecule. - rydberg_electrons (Tuple[float, float]): Minimum and maximum number of rydberg electrons in an atom - in this molecule. - total_electrons: (Tuple[float, float]): Minimum and maximum number of total electrons in an atom - in this molecule. - electron_type_lp (str): Should alpha ('alpha'), beta ('beta'), or all electrons (None) be - considered in a query of lone pairs? - lp_type (str): Type of orbital - "LP" for "lone pair" or "LV" for "lone vacant" - s_character (Tuple[float, float]): Min and max percentage of the lone pair constituted by s atomic orbitals. - p_character (Tuple[float, float]): Min and max percentage of the lone pair constituted by p atomic orbitals. - d_character (Tuple[float, float]): Min and max percentage of the lone pair constituted by d atomic orbitals. - f_character (Tuple[float, float]): Min and max percentage of the lone pair constituted by f atomic orbitals. - lp_occupancy(Tuple[float, float]): Min and max number of electrons in the lone pair. - electron_type_bond (str): Should alpha ('alpha'), beta ('beta'), or all electrons (None) be - considered in a query of bonds? - bond_type (str): Type of orbital, e.g. "BD" for bonding or "BD*" for antibonding - s_character_atom1 (Tuple[float, float]): Min and max percentage of the bond constituted by s atomic orbitals - on the first atom. - s_character_atom2 (Tuple[float, float]): Min and max percentage of the bond constituted by s atomic orbitals - on the second atom. - p_character_atom1 (Tuple[float, float]): Min and max percentage of the bond constituted by p atomic orbitals - on the first atom. - p_character_atom2 (Tuple[float, float]): Min and max percentage of the bond constituted by p atomic orbitals - on the second atom. - d_character_atom1 (Tuple[float, float]): Min and max percentage of the bond constituted by d atomic orbitals - on the first atom. - d_character_atom2 (Tuple[float, float]): Min and max percentage of the bond constituted by d atomic orbitals - on the second atom. - f_character_atom1 (Tuple[float, float]): Min and max percentage of the bond constituted by f atomic orbitals - on the first atom. - f_character_atom2 (Tuple[float, float]): Min and max percentage of the bond constituted by f atomic orbitals - on the second atom. - polarization_atom1 (Tuple[float, float]): Min and max fraction of electrons in the bond donated by the - first atom. - polarization_atom2 (Tuple[float, float]): Min and max fraction of electrons in the bond donated by the - second atom. - bond_occupancy (Tuple[float, float]): Min and max number of electrons in the bond. - electron_type_interaction (str): Should alpha ('alpha'), beta ('beta'), or all electrons (None) be - considered in a query of orbital interactions? - donor_type (str): Type of donor orbital, e.g. 'BD' for bonding or 'RY*' for anti-Rydberg - acceptor_type (str): Type of acceptor orbital, e.g. 'BD' for bonding or 'RY*' for anti-Rydberg - perturbation_energy (Tuple[float, float]): Min and max perturbation energy of the interaction - energy_difference (Tuple[float, float]): Min and max energy difference between interacting orbitals - fock_element (Tuple[float, float]): Min and max interaction Fock matrix element - num_chunks (int): Maximum number of chunks of data to yield. None will yield all possible. - chunk_size (int): Number of data entries per chunk. - all_fields (bool): Whether to return all fields in the document. Defaults to True. - fields (List[str]): List of fields in MoleculeDoc to return data for. - Default is "molecule_id", "property_id", "solvent", "method", "last_updated" - if all_fields is False. - - Returns: - ([RedoxDoc]) List of molecule redox documents - """ - query_params: dict = {} - - min_max = [ - "core_electrons", - "valence_electrons", - "rydberg_electrons", - "total_electrons", - "s_character", - "p_character", - "d_character", - "f_character", - "lp_occupancy", - "s_character_atom1", - "s_character_atom2", - "p_character_atom1", - "p_character_atom2", - "d_character_atom1", - "d_character_atom2", - "f_character_atom1", - "f_character_atom2", - "polarization_atom1", - "polarization_atom2", - "bond_occupancy", - "perturbation_energy", - "energy_difference", - "fock_element", - ] - - for param, value in locals().items(): - if param in min_max and value: - if isinstance(value, (int, float)): - value = (value, value) - query_params.update( - { - f"min_{param}": value[0], - f"max_{param}": value[1], - } - ) - - if molecule_ids: - if isinstance(molecule_ids, str): - molecule_ids = [molecule_ids] - - query_params.update({"molecule_ids": ",".join(molecule_ids)}) - - if property_ids: - if isinstance(property_ids, str): - property_ids = [property_ids] - - query_params.update({"property_ids": ",".join(property_ids)}) - - if charge: - query_params.update({"charge": charge}) - - if spin_multiplicity: - query_params.update({"spin_multiplicity": spin_multiplicity}) - - if level_of_theory: - query_params.update({"level_of_theory": level_of_theory}) - - if solvent: - query_params.update({"solvent": solvent}) - - if lot_solvent: - query_params.update({"lot_solvent": lot_solvent}) - - if formula: - if isinstance(formula, str): - formula = [formula] - - query_params.update({"formula": ",".join(formula)}) - - if chemsys: - if isinstance(chemsys, str): - chemsys = [chemsys] - - query_params.update({"chemsys": ",".join(chemsys)}) - - if elements: - query_params.update({"elements": ",".join(elements)}) - - if exclude_elements: - query_params.update({"exclude_elements": ",".join(exclude_elements)}) - - if electron_type_population: - query_params.update({"electron_type_population": electron_type_population}) - - if electron_type_lp: - query_params.update({"electron_type_lp": electron_type_lp}) - - if lp_type: - query_params.update({"lp_type": lp_type}) - - if electron_type_bond: - query_params.update({"electron_type_bond": electron_type_bond}) - - if bond_type: - query_params.update({"bond_type": bond_type}) - - if electron_type_interaction: - query_params.update( - {"electron_type_interaction": electron_type_interaction} - ) - - if donor_type: - query_params.update({"donor_type": donor_type}) - - if acceptor_type: - query_params.update({"acceptor_type": acceptor_type}) - - return super()._search( - num_chunks=num_chunks, - chunk_size=chunk_size, - all_fields=all_fields, - fields=fields, - **query_params, - ) diff --git a/mp_api/client/routes/molecules/partial_charges.py b/mp_api/client/routes/molecules/partial_charges.py deleted file mode 100644 index 7cca3313..00000000 --- a/mp_api/client/routes/molecules/partial_charges.py +++ /dev/null @@ -1,119 +0,0 @@ -from __future__ import annotations - -from emmet.core.molecules.atomic import PartialChargesDoc -from emmet.core.mpid import MPculeID - -from mp_api.client.core import BaseRester - - -class MoleculesPartialChargesRester(BaseRester): - suffix = "molecules/partial_charges" - document_model = PartialChargesDoc - primary_key = "property_id" - - def search( - self, - molecule_ids: MPculeID | list[MPculeID] | None = None, - property_ids: str | list[str] | None = None, - method: str | None = None, - charge: int | None = None, - spin_multiplicity: int | None = None, - level_of_theory: str | None = None, - solvent: str | None = None, - lot_solvent: str | None = None, - formula: str | list[str] | None = None, - elements: list[str] | None = None, - exclude_elements: list[str] | None = None, - chemsys: str | list[str] | None = None, - num_chunks: int | None = None, - chunk_size: int = 1000, - all_fields: bool = True, - fields: list[str] | None = None, - ): - """Query molecules partial charges docs using a variety of search criteria. - - Arguments: - molecule_ids (MPculeID, List[MPculeID]): List of Materials Project Molecule IDs (MPculeIDs) to return data - for. - property_ids (str, List[str]): List of property IDs to return data for. - method (str): Method used to generate bonding data - (e.g. "nbo", "critic2", "mulliken", or "resp") - charge (Tuple[int, int]): Minimum and maximum charge for the molecule. - spin_multiplicity (Tuple[int, int]): Minimum and maximum spin for the molecule. - level_of_theory (str): Desired level of theory (e.g. "wB97X-V/def2-TZVPPD/SMD") - solvent (str): Desired solvent (e.g. "SOLVENT=WATER") - lot_solvent (str): Desired combination of level of theory and solvent - (e.g. "wB97X-V/def2-TZVPPD/SMD(SOLVENT=THF)") - formula (str, List[str]): An alphabetical formula or list of formulas - (e.g. "C2 Li2 O4", ["C2 H4", "C2 H6"]). - elements (List[str]): A list of elements. - exclude_elements (List(str)): List of elements to exclude. - chemsys (str, List[str]): A chemical system, list of chemical systems - (e.g., Li-C-O, [C-O-H-N, Li-N]). - num_chunks (int): Maximum number of chunks of data to yield. None will yield all possible. - chunk_size (int): Number of data entries per chunk. - all_fields (bool): Whether to return all fields in the document. Defaults to True. - fields (List[str]): List of fields in MoleculeDoc to return data for. - Default is "molecule_id", "property_id", "solvent", "method", "last_updated" - if all_fields is False. - - Returns: - ([PartialChargesDoc]) List of partial charges documents - """ - query_params: dict = {} - - if molecule_ids: - if isinstance(molecule_ids, str): - molecule_ids = [molecule_ids] - - query_params.update({"molecule_ids": ",".join(molecule_ids)}) - - if property_ids: - if isinstance(property_ids, str): - property_ids = [property_ids] - - query_params.update({"property_ids": ",".join(property_ids)}) - - if method: - query_params.update({"method": method}) - - if charge: - query_params.update({"charge": charge}) - - if spin_multiplicity: - query_params.update({"spin_multiplicity": spin_multiplicity}) - - if level_of_theory: - query_params.update({"level_of_theory": level_of_theory}) - - if solvent: - query_params.update({"solvent": solvent}) - - if lot_solvent: - query_params.update({"lot_solvent": lot_solvent}) - - if formula: - if isinstance(formula, str): - formula = [formula] - - query_params.update({"formula": ",".join(formula)}) - - if chemsys: - if isinstance(chemsys, str): - chemsys = [chemsys] - - query_params.update({"chemsys": ",".join(chemsys)}) - - if elements: - query_params.update({"elements": ",".join(elements)}) - - if exclude_elements: - query_params.update({"exclude_elements": ",".join(exclude_elements)}) - - return super()._search( - num_chunks=num_chunks, - chunk_size=chunk_size, - all_fields=all_fields, - fields=fields, - **query_params, - ) diff --git a/mp_api/client/routes/molecules/partial_spins.py b/mp_api/client/routes/molecules/partial_spins.py deleted file mode 100644 index 50b128ff..00000000 --- a/mp_api/client/routes/molecules/partial_spins.py +++ /dev/null @@ -1,119 +0,0 @@ -from __future__ import annotations - -from emmet.core.molecules.atomic import PartialSpinsDoc -from emmet.core.mpid import MPculeID - -from mp_api.client.core import BaseRester - - -class MoleculesPartialSpinsRester(BaseRester): - suffix = "molecules/partial_spins" - document_model = PartialSpinsDoc - primary_key = "property_id" - - def search( - self, - molecule_ids: MPculeID | list[MPculeID] | None = None, - property_ids: str | list[str] | None = None, - method: str | None = None, - charge: int | None = None, - spin_multiplicity: int | None = None, - level_of_theory: str | None = None, - solvent: str | None = None, - lot_solvent: str | None = None, - formula: str | list[str] | None = None, - elements: list[str] | None = None, - exclude_elements: list[str] | None = None, - chemsys: str | list[str] | None = None, - num_chunks: int | None = None, - chunk_size: int = 1000, - all_fields: bool = True, - fields: list[str] | None = None, - ): - """Query molecules partial spins docs using a variety of search criteria. - - Arguments: - molecule_ids (MPculeID, List[MPculeID]): List of Materials Project Molecule IDs (MPculeIDs) to return data - for. - property_ids (str, List[str]): List of property IDs to return data for. - method (str): Method used to generate bonding data - (e.g. "nbo" or "mulliken") - charge (Tuple[int, int]): Minimum and maximum charge for the molecule. - spin_multiplicity (Tuple[int, int]): Minimum and maximum spin for the molecule. - level_of_theory (str): Desired level of theory (e.g. "wB97X-V/def2-TZVPPD/SMD") - solvent (str): Desired solvent (e.g. "SOLVENT=WATER") - lot_solvent (str): Desired combination of level of theory and solvent - (e.g. "wB97X-V/def2-TZVPPD/SMD(SOLVENT=THF)") - formula (str, List[str]): An alphabetical formula or list of formulas - (e.g. "C2 Li2 O4", ["C2 H4", "C2 H6"]). - elements (List[str]): A list of elements. - exclude_elements (List(str)): List of elements to exclude. - chemsys (str, List[str]): A chemical system, list of chemical systems - (e.g., Li-C-O, [C-O-H-N, Li-N]). - num_chunks (int): Maximum number of chunks of data to yield. None will yield all possible. - chunk_size (int): Number of data entries per chunk. - all_fields (bool): Whether to return all fields in the document. Defaults to True. - fields (List[str]): List of fields in MoleculeDoc to return data for. - Default is "molecule_id", "property_id", "solvent", "method", "last_updated" - if all_fields is False. - - Returns: - ([PartialSpinsDoc]) List of partial charges documents - """ - query_params: dict = {} - - if molecule_ids: - if isinstance(molecule_ids, str): - molecule_ids = [molecule_ids] - - query_params.update({"molecule_ids": ",".join(molecule_ids)}) - - if property_ids: - if isinstance(property_ids, str): - property_ids = [property_ids] - - query_params.update({"property_ids": ",".join(property_ids)}) - - if method: - query_params.update({"method": method}) - - if charge: - query_params.update({"charge": charge}) - - if spin_multiplicity: - query_params.update({"spin_multiplicity": spin_multiplicity}) - - if level_of_theory: - query_params.update({"level_of_theory": level_of_theory}) - - if solvent: - query_params.update({"solvent": solvent}) - - if lot_solvent: - query_params.update({"lot_solvent": lot_solvent}) - - if formula: - if isinstance(formula, str): - formula = [formula] - - query_params.update({"formula": ",".join(formula)}) - - if chemsys: - if isinstance(chemsys, str): - chemsys = [chemsys] - - query_params.update({"chemsys": ",".join(chemsys)}) - - if elements: - query_params.update({"elements": ",".join(elements)}) - - if exclude_elements: - query_params.update({"exclude_elements": ",".join(exclude_elements)}) - - return super()._search( - num_chunks=num_chunks, - chunk_size=chunk_size, - all_fields=all_fields, - fields=fields, - **query_params, - ) diff --git a/mp_api/client/routes/molecules/redox.py b/mp_api/client/routes/molecules/redox.py deleted file mode 100644 index d60d091c..00000000 --- a/mp_api/client/routes/molecules/redox.py +++ /dev/null @@ -1,171 +0,0 @@ -from __future__ import annotations - -from emmet.core.molecules.redox import RedoxDoc -from emmet.core.mpid import MPculeID - -from mp_api.client.core import BaseRester - - -class MoleculesRedoxRester(BaseRester): - suffix = "molecules/redox" - document_model = RedoxDoc - primary_key = "property_id" - - def search( - self, - molecule_ids: MPculeID | list[MPculeID] | None = None, - property_ids: str | list[str] | None = None, - charge: int | None = None, - spin_multiplicity: int | None = None, - level_of_theory: str | None = None, - solvent: str | None = None, - lot_solvent: str | None = None, - formula: str | list[str] | None = None, - elements: list[str] | None = None, - exclude_elements: list[str] | None = None, - chemsys: str | list[str] | None = None, - electrode: str | None = None, - min_reduction_potential: float | None = None, - max_reduction_potential: float | None = None, - min_oxidation_potential: float | None = None, - max_oxidation_potential: float | None = None, - electron_affinity: tuple[float, float] | None = None, - ionization_energy: tuple[float, float] | None = None, - reduction_energy: tuple[float, float] | None = None, - reduction_free_energy: tuple[float, float] | None = None, - oxidation_energy: tuple[float, float] | None = None, - oxidation_free_energy: tuple[float, float] | None = None, - num_chunks: int | None = None, - chunk_size: int = 1000, - all_fields: bool = True, - fields: list[str] | None = None, - ): - """Query molecules redox docs using a variety of search criteria. - - Arguments: - molecule_ids (MPculeID, List[MPculeID]): List of Materials Project Molecule IDs (MPculeIDs) to return data - for. - property_ids (str, List[str]): List of property IDs to return data for. - charge (Tuple[int, int]): Minimum and maximum charge for the molecule. - spin_multiplicity (Tuple[int, int]): Minimum and maximum spin for the molecule. - level_of_theory (str): Desired level of theory (e.g. "wB97X-V/def2-TZVPPD/SMD") - solvent (str): Desired solvent (e.g. "SOLVENT=WATER") - lot_solvent (str): Desired combination of level of theory and solvent - (e.g. "wB97X-V/def2-TZVPPD/SMD(SOLVENT=THF)") - formula (str, List[str]): An alphabetical formula or list of formulas - (e.g. "C2 Li2 O4", ["C2 H4", "C2 H6"]). - elements (List[str]): A list of elements. - exclude_elements (List(str)): List of elements to exclude. - chemsys (str, List[str]): A chemical system, list of chemical systems - (e.g., Li-C-O, [C-O-H-N, Li-N]). - electrode (str): For redox potential queries, a string representation of the reference - electrode (currently accepted: "H", "Li", "Mg", "Ca") - min_reduction_potential (float): Minimum reduction potential considered - max_reduction_potential (float): Maximum reduction potential considered - min_oxidation_potential (float): Minimum oxidation potential considered - max_oxidation_potential (float): Maximum oxidation potential considered - electron_affinity (Tuple[float, float]): Minimum and maximum electron affinities - ionization_energy (Tuple[float, float]): Minimum and maximum ionization energies - reduction_energy (Tuple[float, float]): Minimum and maximum reduction energies - reduction_free_energy (Tuple[float, float]): Minimum and maximum reduction free energies - oxidation_energy (Tuple[float, float]): Minimum and maximum oxidation energies - oxidation_free_energy (Tuple[float, float]): Minimum and maximum oxidation free energies - num_chunks (int): Maximum number of chunks of data to yield. None will yield all possible. - chunk_size (int): Number of data entries per chunk. - all_fields (bool): Whether to return all fields in the document. Defaults to True. - fields (List[str]): List of fields in MoleculeDoc to return data for. - Default is "molecule_id", "property_id", "solvent", "method", "last_updated" - if all_fields is False. - - Returns: - ([RedoxDoc]) List of molecule redox documents - """ - query_params: dict = {} - - min_max = [ - "electron_affinity", - "ionization_energy", - "reduction_energy", - "reduction_free_energy", - "oxidation_energy", - "oxidation_free_energy", - ] - - for param, value in locals().items(): - if param in min_max and value: - if isinstance(value, (int, float)): - value = (value, value) - query_params.update( - { - f"{param}_min": value[0], - f"{param}_max": value[1], - } - ) - - if molecule_ids: - if isinstance(molecule_ids, str): - molecule_ids = [molecule_ids] - - query_params.update({"molecule_ids": ",".join(molecule_ids)}) - - if property_ids: - if isinstance(property_ids, str): - property_ids = [property_ids] - - query_params.update({"property_ids": ",".join(property_ids)}) - - if charge: - query_params.update({"charge": charge}) - - if spin_multiplicity: - query_params.update({"spin_multiplicity": spin_multiplicity}) - - if level_of_theory: - query_params.update({"level_of_theory": level_of_theory}) - - if solvent: - query_params.update({"solvent": solvent}) - - if lot_solvent: - query_params.update({"lot_solvent": lot_solvent}) - - if formula: - if isinstance(formula, str): - formula = [formula] - - query_params.update({"formula": ",".join(formula)}) - - if chemsys: - if isinstance(chemsys, str): - chemsys = [chemsys] - - query_params.update({"chemsys": ",".join(chemsys)}) - - if elements: - query_params.update({"elements": ",".join(elements)}) - - if exclude_elements: - query_params.update({"exclude_elements": ",".join(exclude_elements)}) - - if electrode: - query_params.update({"electrode": electrode}) - - if min_reduction_potential: - query_params.update({"min_reduction_potential": min_reduction_potential}) - - if max_reduction_potential: - query_params.update({"max_reduction_potential": max_reduction_potential}) - - if min_oxidation_potential: - query_params.update({"min_oxidation_potential": min_oxidation_potential}) - - if max_oxidation_potential: - query_params.update({"max_oxidation_potential": max_oxidation_potential}) - - return super()._search( - num_chunks=num_chunks, - chunk_size=chunk_size, - all_fields=all_fields, - fields=fields, - **query_params, - ) diff --git a/mp_api/client/routes/molecules/summary.py b/mp_api/client/routes/molecules/summary.py index 9b2481f8..471fd84a 100644 --- a/mp_api/client/routes/molecules/summary.py +++ b/mp_api/client/routes/molecules/summary.py @@ -19,17 +19,11 @@ def search( spin_multiplicity: int | None = None, nelements: tuple[int, int] | None = None, chemsys: str | list[str] | None = None, - # deprecated: bool | None = None, elements: list[str] | None = None, exclude_elements: list[str] | None = None, formula: str | list[str] | None = None, has_props: list[HasProps] | None = None, - molecule_ids: list[MPculeID] | None = None, - # has_solvent: Optional[Union[str, List[str]]] = None, - # has_level_of_theory: Optional[Union[str, List[str]]] = None, - # has_lot_solvent: Optional[Union[str, List[str]]] = None, - # with_solvent: Optional[str] = None, - # num_sites: Optional[Tuple[int, int]] = None, + molecule_ids: str | list[str | MPculeID] | None = None, num_chunks: int | None = None, chunk_size: int = 1000, all_fields: bool = True, @@ -41,15 +35,6 @@ def search( charge (int): Minimum and maximum charge for the molecule. spin_multiplicity (int): Minimum and maximum spin for the molecule. nelements (Tuple[int, int]): Minimum and maximum number of elements - # has_solvent (str, List[str]): Whether the molecule has properties calculated in - # solvents (e.g., "SOLVENT=THF", ["SOLVENT=WATER", "VACUUM"]) - # has_level_of_theory (str, List[str]): Whether the molecule has properties calculated - # using a particular level of theory (e.g. "wB97M-V/def2-SVPD/SMD", - # ["wB97X-V/def2-TZVPPD/SMD", "wB97M-V/def2-QZVPPD/SMD"]) - # has_lot_solvent (str, List[str]): Whether the molecule has properties calculated - # using a particular combination of level of theory and solvent (e.g. - # "wB97X-V/def2-SVPD/SMD(SOLVENT=THF)", - # ["wB97X-V/def2-TZVPPD/SMD(VACUUM)", "wB97M-V/def2-QZVPPD/SMD(SOLVENT=WATER)"]) chemsys (str, List[str]): A chemical system, list of chemical systems (e.g., Li-C-O, [C-O-H-N, Li-N]). #deprecated (bool): Whether the material is tagged as deprecated. @@ -58,7 +43,8 @@ def search( formula (str, List[str]): An alphabetical formula or list of formulas (e.g. "C2 Li2 O4", ["C2 H4", "C2 H6"]). has_props: (List[HasProps]): The calculated properties available for the material. - molecule_ids (List[MPculeID]): List of Materials Project Molecule IDs (MPculeIDs) to return data for. + molecule_ids (str or MPculeID, or list[str | MPculeID]): + (List of) Materials Project Molecule IDs (MPculeIDs) to return data for. num_chunks (int): Maximum number of chunks of data to yield. None will yield all possible. chunk_size (int): Number of data entries per chunk. all_fields (bool): Whether to return all fields in the document. Defaults to True. @@ -90,6 +76,8 @@ def search( ) if molecule_ids: + if isinstance(molecule_ids, str | MPculeID): + molecule_ids = [molecule_ids] query_params.update({"molecule_ids": ",".join(molecule_ids)}) if charge: @@ -98,9 +86,6 @@ def search( if spin_multiplicity: query_params.update({"spin_multiplicity": spin_multiplicity}) - # if deprecated is not None: - # query_params.update({"deprecated": deprecated}) - if formula: if isinstance(formula, str): formula = [formula] diff --git a/mp_api/client/routes/molecules/tasks.py b/mp_api/client/routes/molecules/tasks.py deleted file mode 100644 index ead97e04..00000000 --- a/mp_api/client/routes/molecules/tasks.py +++ /dev/null @@ -1,72 +0,0 @@ -from __future__ import annotations - -from emmet.core.qchem.task import TaskDocument - -from mp_api.client.core import BaseRester -from mp_api.client.core.utils import validate_ids - - -class MoleculesTaskRester(BaseRester): - suffix = "molecules/tasks" - document_model = TaskDocument - primary_key = "task_id" - - # TODO: get_trajectory method (once PR in pymatgen) - - def search( - self, - task_ids: list[str] | None = None, - chemsys: str | list[str] | None = None, - elements: list[str] | None = None, - exclude_elements: list[str] | None = None, - formula: str | list[str] | None = None, - num_chunks: int | None = None, - chunk_size: int = 1000, - all_fields: bool = True, - fields: list[str] | None = None, - ): - """Query core task docs using a variety of search criteria. - - Arguments: - task_ids (List[str]): List of Materials Project IDs to return data for. - chemsys (str, List[str]): A chemical system or list of chemical systems - (e.g., C-H-O, [C-Li-O, Li-O]). - elements (List[str]): A list of elements. - exclude_elements (List[str]): A list of elements to exclude. - formula (str, List[str]): An alphabetical formula (e.g. "C1 Li2 O3" or ["C2 H4", "C2 H6"]). - num_chunks (int): Maximum number of chunks of data to yield. None will yield all possible. - chunk_size (int): Number of data entries per chunk. Max size is 100. - all_fields (bool): Whether to return all fields in the document. Defaults to True. - fields (List[str]): List of fields in TaskDocument to return data for. - Default is material_id, last_updated, and formula_pretty if all_fields is False. - - Returns: - ([TaskDocument]) List of task documents - """ - query_params: dict = {} - - if task_ids: - query_params.update({"task_ids": ",".join(validate_ids(task_ids))}) - - if formula: - query_params.update({"formula": formula}) - - if elements: - query_params.update({"elements": ",".join(elements)}) - - if exclude_elements: - query_params.update({"exclude_elements": ",".join(exclude_elements)}) - - if chemsys: - if isinstance(chemsys, str): - chemsys = [chemsys] - - query_params.update({"chemsys": ",".join(chemsys)}) - - return super()._search( - num_chunks=num_chunks, - chunk_size=chunk_size, - all_fields=all_fields, - fields=fields, - **query_params, - ) diff --git a/mp_api/client/routes/molecules/thermo.py b/mp_api/client/routes/molecules/thermo.py deleted file mode 100644 index 9a0f96ad..00000000 --- a/mp_api/client/routes/molecules/thermo.py +++ /dev/null @@ -1,185 +0,0 @@ -from __future__ import annotations - -from emmet.core.molecules.thermo import MoleculeThermoDoc -from emmet.core.mpid import MPculeID - -from mp_api.client.core import BaseRester - - -class MoleculesThermoRester(BaseRester): - suffix = "molecules/thermo" - document_model = MoleculeThermoDoc - primary_key = "property_id" - - def search( - self, - molecule_ids: MPculeID | list[MPculeID] | None = None, - property_ids: str | list[str] | None = None, - charge: int | None = None, - spin_multiplicity: int | None = None, - level_of_theory: str | None = None, - solvent: str | None = None, - lot_solvent: str | None = None, - correction_level_of_theory: str | None = None, - correction_solvent: str | None = None, - correction_lot_solvent: str | None = None, - combined_lot_solvent: str | None = None, - electronic_energy: tuple[float, float] | None = None, - zero_point_energy: tuple[float, float] | None = None, - total_enthalpy: tuple[float, float] | None = None, - total_entropy: tuple[float, float] | None = None, - translational_enthalpy: tuple[float, float] | None = None, - rotational_enthalpy: tuple[float, float] | None = None, - vibrational_enthalpy: tuple[float, float] | None = None, - translational_entropy: tuple[float, float] | None = None, - rotational_entropy: tuple[float, float] | None = None, - vibrational_entropy: tuple[float, float] | None = None, - free_energy: tuple[float, float] | None = None, - formula: str | list[str] | None = None, - elements: list[str] | None = None, - exclude_elements: list[str] | None = None, - chemsys: str | list[str] | None = None, - num_chunks: int | None = None, - chunk_size: int = 1000, - all_fields: bool = True, - fields: list[str] | None = None, - ): - """Query molecules thermo docs using a variety of search criteria. - - Arguments: - molecule_ids (MPculeID, List[MPculeID]): List of Materials Project Molecule IDs (MPculeIDs) to return data - for. - property_ids (str, List[str]): List of property IDs to return data for. - charge (Tuple[int, int]): Minimum and maximum charge for the molecule. - spin_multiplicity (Tuple[int, int]): Minimum and maximum spin for the molecule. - level_of_theory (str): Desired level of theory (e.g. "wB97X-V/def2-TZVPPD/SMD") - solvent (str): Desired solvent (e.g. "SOLVENT=WATER") - lot_solvent (str): Desired combination of level of theory and solvent - (e.g. "wB97X-V/def2-TZVPPD/SMD(SOLVENT=THF)") - correction_level_of_theory (str): Desired correction level of theory (e.g. "wB97X-V/def2-TZVPPD/SMD") - correction_solvent (str): Desired correction solvent (e.g. "SOLVENT=WATER") - correction_lot_solvent (str): Desired correction combination of level of theory and solvent - (e.g. "wB97X-V/def2-TZVPPD/SMD(SOLVENT=THF)") - combined_lot_solvent (str): Desired combination of level of theory and solvent including both main - thermo calculation and single-point energy correction - (e.g. "wB97X-D/def2-SVPD/VACUUM//wB97X-V/def2-TZVPPD/SMD(SOLVENT=THF)") - electronic_energy (Tuple[float, float]): Minimum and maximum electronic energy - zero_point_energy (Tuple[float, float]): Minimum and maximum zero-point energy - total_enthalpy (Tuple[float, float]): Minimum and maximum total enthalpy - total_entropy (Tuple[float, float]): Minimum and maximum total entropy - translational_enthalpy (Tuple[float, float]): Minimum and maximum translational enthalpy - rotational_enthalpy (Tuple[float, float]): Minimum and maximum rotational enthalpy - vibrational_enthalpy (Tuple[float, float]): Minimum and maximum vibrational enthalpy - translational_entropy (Tuple[float, float]): Minimum and maximum translational enthalpy - rotational_entropy (Tuple[float, float]): Minimum and maximum rotational enthalpy - vibrational_entropy (Tuple[float, float]): Minimum and maximum vibrational enthalpy - free_energy (Tuple[float, float]): Minimum and maximum free energy - formula (str, List[str]): An alphabetical formula or list of formulas - (e.g. "C2 Li2 O4", ["C2 H4", "C2 H6"]). - elements (List[str]): A list of elements. - exclude_elements (List(str)): List of elements to exclude. - chemsys (str, List[str]): A chemical system, list of chemical systems - (e.g., Li-C-O, [C-O-H-N, Li-N]). - num_chunks (int): Maximum number of chunks of data to yield. None will yield all possible. - chunk_size (int): Number of data entries per chunk. - all_fields (bool): Whether to return all fields in the document. Defaults to True. - fields (List[str]): List of fields in MoleculeDoc to return data for. - Default is "molecule_id", "property_id", "solvent", "method", "last_updated" - if all_fields is False. - - Returns: - ([MoleculeThermoDoc]) List of molecule thermo documents - """ - query_params: dict = {} - - min_max = [ - "electronic_energy", - "zero_point_energy", - "total_enthalpy", - "total_entropy", - "translational_enthalpy", - "rotational_enthalpy", - "vibrational_enthalpy", - "translational_entropy", - "rotational_entropy", - "vibrational_entropy", - "free_energy", - ] - - for param, value in locals().items(): - if param in min_max and value: - if isinstance(value, (int, float)): - value = (value, value) - query_params.update( - { - f"{param}_min": value[0], - f"{param}_max": value[1], - } - ) - - if molecule_ids: - if isinstance(molecule_ids, str): - molecule_ids = [molecule_ids] - - query_params.update({"molecule_ids": ",".join(molecule_ids)}) - - if property_ids: - if isinstance(property_ids, str): - property_ids = [property_ids] - - query_params.update({"property_ids": ",".join(property_ids)}) - - if charge: - query_params.update({"charge": charge}) - - if spin_multiplicity: - query_params.update({"spin_multiplicity": spin_multiplicity}) - - if level_of_theory: - query_params.update({"level_of_theory": level_of_theory}) - - if solvent: - query_params.update({"solvent": solvent}) - - if lot_solvent: - query_params.update({"lot_solvent": lot_solvent}) - - if correction_level_of_theory: - query_params.update( - {"correction_level_of_theory": correction_level_of_theory} - ) - - if correction_solvent: - query_params.update({"correction_solvent": correction_solvent}) - - if correction_lot_solvent: - query_params.update({"correction_lot_solvent": correction_lot_solvent}) - - if combined_lot_solvent: - query_params.update({"combined_lot_solvent": combined_lot_solvent}) - - if formula: - if isinstance(formula, str): - formula = [formula] - - query_params.update({"formula": ",".join(formula)}) - - if chemsys: - if isinstance(chemsys, str): - chemsys = [chemsys] - - query_params.update({"chemsys": ",".join(chemsys)}) - - if elements: - query_params.update({"elements": ",".join(elements)}) - - if exclude_elements: - query_params.update({"exclude_elements": ",".join(exclude_elements)}) - - return super()._search( - num_chunks=num_chunks, - chunk_size=chunk_size, - all_fields=all_fields, - fields=fields, - **query_params, - ) diff --git a/mp_api/client/routes/molecules/vibrations.py b/mp_api/client/routes/molecules/vibrations.py deleted file mode 100644 index d72d8701..00000000 --- a/mp_api/client/routes/molecules/vibrations.py +++ /dev/null @@ -1,131 +0,0 @@ -from __future__ import annotations - -from emmet.core.molecules.vibration import VibrationDoc -from emmet.core.mpid import MPculeID - -from mp_api.client.core import BaseRester - - -class MoleculesVibrationRester(BaseRester): - suffix = "molecules/vibrations" - document_model = VibrationDoc - primary_key = "property_id" - - def search( - self, - molecule_ids: MPculeID | list[MPculeID] | None = None, - property_ids: str | list[str] | None = None, - charge: int | None = None, - spin_multiplicity: int | None = None, - level_of_theory: str | None = None, - solvent: str | None = None, - lot_solvent: str | None = None, - formula: str | list[str] | None = None, - elements: list[str] | None = None, - exclude_elements: list[str] | None = None, - chemsys: str | list[str] | None = None, - num_chunks: int | None = None, - chunk_size: int = 1000, - all_fields: bool = True, - fields: list[str] | None = None, - ): - """Query molecules vibration docs using a variety of search criteria. - - Arguments: - molecule_ids (MPculeID, List[MPculeID]): List of Materials Project Molecule IDs (MPculeIDs) to return data - for. - property_ids (str, List[str]): List of property IDs to return data for. - charge (Tuple[int, int]): Minimum and maximum charge for the molecule. - spin_multiplicity (Tuple[int, int]): Minimum and maximum spin for the molecule. - level_of_theory (str): Desired level of theory (e.g. "wB97X-V/def2-TZVPPD/SMD") - solvent (str): Desired solvent (e.g. "SOLVENT=WATER") - lot_solvent (str): Desired combination of level of theory and solvent - (e.g. "wB97X-V/def2-TZVPPD/SMD(SOLVENT=THF)") - correction_level_of_theory (str): Desired correction level of theory (e.g. "wB97X-V/def2-TZVPPD/SMD") - correction_solvent (str): Desired correction solvent (e.g. "SOLVENT=WATER") - correction_lot_solvent (str): Desired correction combination of level of theory and solvent - (e.g. "wB97X-V/def2-TZVPPD/SMD(SOLVENT=THF)") - combined_lot_solvent (str): Desired combination of level of theory and solvent including both main - thermo calculation and single-point energy correction - (e.g. "wB97X-D/def2-SVPD/VACUUM//wB97X-V/def2-TZVPPD/SMD(SOLVENT=THF)") - electronic_energy (Tuple[float, float]): Minimum and maximum electronic energy - zero_point_energy (Tuple[float, float]): Minimum and maximum zero-point energy - total_enthalpy (Tuple[float, float]): Minimum and maximum total enthalpy - total_entropy (Tuple[float, float]): Minimum and maximum total entropy - translational_enthalpy (Tuple[float, float]): Minimum and maximum translational enthalpy - rotational_enthalpy (Tuple[float, float]): Minimum and maximum rotational enthalpy - vibrational_enthalpy (Tuple[float, float]): Minimum and maximum vibrational enthalpy - translational_entropy (Tuple[float, float]): Minimum and maximum translational enthalpy - rotational_entropy (Tuple[float, float]): Minimum and maximum rotational enthalpy - vibrational_entropy (Tuple[float, float]): Minimum and maximum vibrational enthalpy - free_energy (Tuple[float, float]): Minimum and maximum free energy - formula (str, List[str]): An alphabetical formula or list of formulas - (e.g. "C2 Li2 O4", ["C2 H4", "C2 H6"]). - elements (List[str]): A list of elements. - exclude_elements (List(str)): List of elements to exclude. - chemsys (str, List[str]): A chemical system, list of chemical systems - (e.g., Li-C-O, [C-O-H-N, Li-N]). - num_chunks (int): Maximum number of chunks of data to yield. None will yield all possible. - chunk_size (int): Number of data entries per chunk. - all_fields (bool): Whether to return all fields in the document. Defaults to True. - fields (List[str]): List of fields in MoleculeDoc to return data for. - Default is "molecule_id", "property_id", "solvent", "method", "last_updated" - if all_fields is False. - - Returns: - ([VibrationDoc]) List of molecule vibration documents - """ - query_params: dict = {} - - if molecule_ids: - if isinstance(molecule_ids, str): - molecule_ids = [molecule_ids] - - query_params.update({"molecule_ids": ",".join(molecule_ids)}) - - if property_ids: - if isinstance(property_ids, str): - property_ids = [property_ids] - - query_params.update({"property_ids": ",".join(property_ids)}) - - if charge: - query_params.update({"charge": charge}) - - if spin_multiplicity: - query_params.update({"spin_multiplicity": spin_multiplicity}) - - if level_of_theory: - query_params.update({"level_of_theory": level_of_theory}) - - if solvent: - query_params.update({"solvent": solvent}) - - if lot_solvent: - query_params.update({"lot_solvent": lot_solvent}) - - if formula: - if isinstance(formula, str): - formula = [formula] - - query_params.update({"formula": ",".join(formula)}) - - if chemsys: - if isinstance(chemsys, str): - chemsys = [chemsys] - - query_params.update({"chemsys": ",".join(chemsys)}) - - if elements: - query_params.update({"elements": ",".join(elements)}) - - if exclude_elements: - query_params.update({"exclude_elements": ",".join(exclude_elements)}) - - return super()._search( - num_chunks=num_chunks, - chunk_size=chunk_size, - all_fields=all_fields, - fields=fields, - **query_params, - ) diff --git a/tests/client/molecules/test_molecules.py b/tests/client/molecules/test_molecules.py index f1bdc892..5d5cd098 100644 --- a/tests/client/molecules/test_molecules.py +++ b/tests/client/molecules/test_molecules.py @@ -1,18 +1,16 @@ """Test basic molecules functionality. -This class currently is non-functional, except as an access +This class currently is only functional as an access point for other resters, therefore we include only basic tests here. """ -from mp_api.client.routes.molecules.molecules import BaseMoleculeRester, MoleculeRester +from mp_api.client.routes.molecules.molecules import MoleculeRester from mp_api.client.routes.molecules import MOLECULES_RESTERS def test_molecule_rester(): with MoleculeRester() as rester: - assert set(dir(rester)) == set( - dir(BaseMoleculeRester) + list(MOLECULES_RESTERS) - ) + assert all(sub_rester in dir(rester) for sub_rester in MOLECULES_RESTERS) assert all( getattr(rester, k)._class_name == lazy_obj._class_name diff --git a/tests/client/molecules/test_summary.py b/tests/client/molecules/test_summary.py index 6bf966eb..3e9ddd2e 100644 --- a/tests/client/molecules/test_summary.py +++ b/tests/client/molecules/test_summary.py @@ -3,6 +3,7 @@ import pytest from emmet.core.molecules.summary import HasProps +from emmet.core.mpid import MPculeID from mp_api.client.routes.molecules.summary import MoleculesSummaryRester @@ -15,13 +16,17 @@ "all_fields", "fields", "exclude_elements", - "has_props", ] alt_name_dict = {"formula": "formula_alphabetical", "molecule_ids": "molecule_id"} custom_field_tests: dict = { - "molecule_ids": ["351ef090ebd90b661a4e1205756f6957-C1Mg1N2O1S1-m2-1"], + "molecule_ids": MPculeID("351ef090ebd90b661a4e1205756f6957-C1Mg1N2O1S1-m2-1"), + "molecule_ids": [ + "042b6da7a6eb790fd5038f3729ef715c-C5H8O3-m1-2", + "6fef3b0ef7f484b8c2dd0a0012bf7c70-C2H2O3P1-0-2", + "a3572e662965c190b2a085c6a67a8a03-C9H12-1-2", + ], "formula": "C2 H4", "chemsys": "C-H", "elements": ["P"], @@ -35,7 +40,6 @@ } -@pytest.mark.skip(reason="Temporary until data adjustments") @requires_api_key def test_client(): search_method = MoleculesSummaryRester().search diff --git a/tests/client/test_client.py b/tests/client/test_client.py index fda8323e..c02f63d2 100644 --- a/tests/client/test_client.py +++ b/tests/client/test_client.py @@ -48,17 +48,16 @@ # "summary", ] # temp -mpr = MPRester() - # Temporarily ignore molecules resters while molecules query operators are changed -resters_to_test = [ - rester - for rester in mpr._all_resters - if ( - "molecule" not in rester._class_name.lower() - and not (pmg_alloys is None and "alloys" in str(rester).lower()) - ) -] +with MPRester() as mpr: + resters_to_test = [ + rester + for rester in mpr._all_resters + if ( + "molecule" not in rester._class_name.lower() + and not (pmg_alloys is None and "alloys" in str(rester).lower()) + ) + ] @requires_api_key diff --git a/tests/client/test_core_client.py b/tests/client/test_core_client.py index 44ab70b6..89c83c67 100644 --- a/tests/client/test_core_client.py +++ b/tests/client/test_core_client.py @@ -1,7 +1,11 @@ import pytest +import json + from mp_api.client import MPRester from mp_api.client.core import BaseRester +from mp_api.client.core.exceptions import MPRestError, MPRestWarning +from mp_api.client.routes.materials.materials import MaterialsRester from .conftest import requires_api_key @@ -20,12 +24,6 @@ def mpr(): rester.session.close() -@requires_api_key -@pytest.mark.xfail -def test_post_fail(rester): - rester._post_resource({}, suburl="materials/find_structure") - - @requires_api_key def test_pagination(mpr): mpids = mpr.materials.search( @@ -71,3 +69,28 @@ def test_fields_not_requested_excludes_requested_fields(mpr): AttributeError, match="data is available but has not been requested in" ): deser_doc.tags + + with pytest.raises(AttributeError, match="object has no attribute 'fake_field'"): + deser_doc.fake_field + + assert all( + substr in str(deser_doc) + for substr in ("MPDataDoc", "CoreTaskDoc", "task_id", "Fields not requested") + ) + assert all( + substr in deser_doc.__repr__() + for substr in ("MPDataDoc", "CoreTaskDoc", "task_id", "fields_not_requested") + ) + assert deser_doc.dict() == deser_doc.model_dump() + assert isinstance(json.dumps(deser_doc.dict()), str) + + +def test_warnings_exceptions(): + with pytest.warns(MPRestWarning, match="Ignoring `monty_decode`"): + MaterialsRester(monty_decode=True) + + with pytest.raises(MPRestError, match="Chunk size must be greater than zero"): + MaterialsRester()._get_all_documents({}, chunk_size=-1) + + with pytest.raises(MPRestError, match="Number of chunks must be greater than zero"): + MaterialsRester()._get_all_documents({}, num_chunks=-1) diff --git a/tests/client/test_mprester.py b/tests/client/test_mprester.py index 0a9571c9..6b89577f 100644 --- a/tests/client/test_mprester.py +++ b/tests/client/test_mprester.py @@ -102,7 +102,9 @@ def test_get_structures(self, mpr): structs = mpr.get_structures("Mn-O", final=False) assert len(structs) > 0 - def test_find_structure(self, mpr): + def test_find_structure( + self, + ): cif_str = """# mp-111 data_Ne _symmetry_space_group_name_H-M 'P 1' @@ -131,20 +133,40 @@ def test_find_structure(self, mpr): _atom_site_occupancy Ne Ne0 1 0.00000000 0.00000000 -0.00000000 1 """ + struct_from_cif = CifParser.from_str(cif_str).parse_structures(primitive=True)[ + 0 + ] temp_file = NamedTemporaryFile(suffix=".cif") with open(temp_file.name, "wt") as f: f.write(cif_str) f.seek(0) - for struct_or_path in ( - temp_file.name, - CifParser.from_str(cif_str).parse_structures(primitive=True)[0], - ): - data = mpr.find_structure(struct_or_path) + for struct_or_path, use_document_model in [ + (temp_file.name, True), + (struct_from_cif, False), + ]: + with MPRester(use_document_model=use_document_model) as mpr: + data = mpr.find_structure(struct_or_path) assert isinstance(data, str) and data == "mp-111" f.close() + with pytest.raises(MPRestError, match="Provide filename or Structure object."): + mpr.find_structure(struct_from_cif.as_dict()) + + with pytest.raises(MPRestError, match="`allow_multiple_results` must be a"): + mpr.find_structure(struct_from_cif, allow_multiple_results=1.0) + + assert ( + len( + mpr.find_structure( + struct_from_cif.copy().replace_species({"Ne": "K"}), + allow_multiple_results=2, + ) + ) + <= 2 + ) + def test_get_bandstructure_by_material_id(self, mpr): bs = mpr.get_bandstructure_by_material_id("mp-149") assert isinstance(bs, BandStructureSymmLine) @@ -253,6 +275,11 @@ def test_get_entries_in_chemsys(self, mpr): for e in gibbs_entries: assert isinstance(e, GibbsComputedStructureEntry) + with pytest.raises( + MPRestError, match="Please specify fewer elements to query by" + ): + mpr.get_entries_in_chemsys([Element.from_Z(1 + i).name for i in range(10)]) + @pytest.mark.skipif( contribs_client is None, reason="`pip install mpcontribs-client` to use pourbaix functionality.", @@ -622,6 +649,72 @@ def test_oxygen_evolution_bad_input(self, mpr): with pytest.raises(ValueError, match="No available insertion electrode data"): _ = mpr.get_oxygen_evolution("mp-2207", "Al") - def test_monty_decode_warning(self): + def test_nomad_integration(self, mpr): + # No particular reason for this MPID other than that it exists in NOMAD. + target_mpid = "mp-10018" + with pytest.warns( + MPRestWarning, match="Full downloads of raw data are being transitioned" + ), pytest.warns( + MPRestWarning, match="the following ids are not found on NOMAD" + ): + calc_type_map, nomad_urls = mpr.get_download_info( + target_mpid, file_patterns=["some_pattern"] + ) + assert all( + isinstance(entry["task_id"], MPID) + and isinstance(entry["calc_type"], CalcType) + for entry in calc_type_map[target_mpid] + ) + assert all( + url.startswith("https://nomad-lab.eu/prod/rae/api/raw/query") + and "file_pattern=some_pattern" in url + for url in nomad_urls + ) + + calc_type_map, nomad_urls = mpr.get_download_info( + [MPID(target_mpid)], calc_types=["GGA Deformation"] + ) + assert all( + isinstance(entry["task_id"], MPID) + and entry["calc_type"].value == "GGA Deformation" + for entry in calc_type_map[target_mpid] + ) + assert all( + url.startswith( + "https://nomad-lab.eu/prod/rae/api/raw/query?external_id=" + ) + for url in nomad_urls + ) + + def test_warnings_exceptions(self, monkeypatch: pytest.MonkeyPatch): + from mp_api.client.core.settings import MAPI_CLIENT_SETTINGS + with pytest.warns(MPRestWarning, match="Ignoring `monty_decode`"): MPRester(monty_decode=False) + + with MPRester() as mpr: + with pytest.raises( + NotImplementedError, + match="The MPRester\(\).query method has been replaced", + ): + mpr.query(some_field=1.0) + + with pytest.warns( + MPRestWarning, match="No material found containing task mp-0" + ): + assert mpr.get_material_id_from_task_id("mp-0") is None + + for attr in mpr._deprecated_attributes: + with pytest.warns( + DeprecationWarning, match="Accessing.*data through MPRester\..*" + ): + getattr(mpr, attr, None) + + emmet_ver = mpr.get_emmet_version(mpr.endpoint) + monkeypatch.setattr( + MAPI_CLIENT_SETTINGS, "MIN_EMMET_VERSION", f"{emmet_ver.major + 1}.0.0" + ) + with pytest.warns( + MPRestWarning, match="The installed version of the mp-api" + ): + MPRester()