From b8c047108ebbd5792f9a3cd189045a455476bae4 Mon Sep 17 00:00:00 2001 From: Jason Munro Date: Thu, 23 Sep 2021 12:07:39 -0700 Subject: [PATCH] General upgrades (#390) * Formula to criteria wildcard bug fix * Add formula to criteria test * Add exclude elements to mprester * Increase default rate limit setting * Default deprecation query to false and update epts * Remove wulff endpoint * Remove wulff import * Fix potcar deserialization in task doc * Bug and test fixes * Temp xfails included --- app.py | 15 +----- src/mp_api/core/settings.py | 2 +- src/mp_api/matproj.py | 3 ++ src/mp_api/routes/__init__.py | 1 - .../routes/electronic_structure/resources.py | 4 ++ .../routes/materials/query_operators.py | 2 +- src/mp_api/routes/materials/utils.py | 14 +++--- src/mp_api/routes/provenance/resources.py | 3 +- src/mp_api/routes/summary/client.py | 5 ++ src/mp_api/routes/tasks/models.py | 13 ++++- src/mp_api/routes/wulff/__init__.py | 0 src/mp_api/routes/wulff/client.py | 9 ---- src/mp_api/routes/wulff/client.pyi | 13 ----- src/mp_api/routes/wulff/models.py | 50 ------------------- src/mp_api/routes/wulff/resources.py | 20 -------- tests/materials/test_utils.py | 1 + tests/test_mprester.py | 16 +++--- 17 files changed, 45 insertions(+), 126 deletions(-) delete mode 100644 src/mp_api/routes/wulff/__init__.py delete mode 100644 src/mp_api/routes/wulff/client.py delete mode 100644 src/mp_api/routes/wulff/client.pyi delete mode 100644 src/mp_api/routes/wulff/models.py delete mode 100644 src/mp_api/routes/wulff/resources.py diff --git a/app.py b/app.py index 5eb04e08..d1003154 100644 --- a/app.py +++ b/app.py @@ -34,7 +34,6 @@ surface_props_store_json = os.environ.get( "SURFACE_PROPS_STORE", "surface_props_store.json" ) -wulff_store_json = os.environ.get("WULFF_STORE", "wulff_store.json") robocrys_store_json = os.environ.get("ROBOCRYS_STORE", "robocrys_store.json") synth_store_json = os.environ.get("SYNTH_STORE", "synth_store.json") insertion_electrodes_store_json = os.environ.get( @@ -181,13 +180,6 @@ collection_name="surface_properties", ) - wulff_store = MongoURIStore( - uri=f"mongodb+srv://{db_uri}", - database="mp_core", - key="task_id", - collection_name="wulff", - ) - robo_store = MongoURIStore( uri=f"mongodb+srv://{db_uri}", database="mp_core", @@ -322,14 +314,13 @@ doi_store = loadfn(doi_store_json) substrates_store = loadfn(substrates_store_json) surface_props_store = loadfn(surface_props_store_json) - wulff_store = loadfn(wulff_store_json) robo_store = loadfn(robocrys_store_json) synth_store = loadfn(synth_store_json) insertion_electrodes_store = loadfn(insertion_electrodes_store_json) molecules_store = loadfn(molecules_store_json) oxi_states_store = loadfn(oxi_states_store_json) provenance_store = loadfn(provenance_store_json) - ssummary_store = loadfn(summary_store_json) + summary_store = loadfn(summary_store_json) es_store = loadfn(es_store_json) @@ -450,10 +441,6 @@ resources.update({"surface_properties": [surface_props_resource(surface_props_store)]}) -# Wulff -from mp_api.routes.wulff.resources import wulff_resource - -resources.update({"wulff": [wulff_resource(wulff_store)]}) # Robocrystallographer from mp_api.routes.robocrys.resources import robo_resource, robo_search_resource diff --git a/src/mp_api/core/settings.py b/src/mp_api/core/settings.py index 1a2afa84..63903e85 100644 --- a/src/mp_api/core/settings.py +++ b/src/mp_api/core/settings.py @@ -23,7 +23,7 @@ class MAPISettings(BaseSettings): db_version: str = Field("2021_prerelease", description="Database version") requests_per_min: int = Field( - 60, description="Number of requests per minute to for rate limit." + 100, description="Number of requests per minute to for rate limit." ) class Config: diff --git a/src/mp_api/matproj.py b/src/mp_api/matproj.py index a3866872..fbcd835e 100644 --- a/src/mp_api/matproj.py +++ b/src/mp_api/matproj.py @@ -470,6 +470,7 @@ def query( self, material_ids: Optional[List[MPID]] = None, chemsys_formula: Optional[str] = None, + exclude_elements: Optional[List[str]] = None, nsites: Optional[Tuple[int, int]] = None, volume: Optional[Tuple[float, float]] = None, density: Optional[Tuple[float, float]] = None, @@ -530,6 +531,7 @@ def query( chemsys_formula (str): A chemical system (e.g., Li-Fe-O), or formula including anonomyzed formula or wild cards (e.g., Fe2O3, ABO3, Si*). + exclude_elements (List(str)): List of elements to exclude. crystal_system (CrystalSystem): Crystal system of material. spacegroup_number (int): Space group number of material. spacegroup_symbol (str): Space group symbol of the material in international short symbol notation. @@ -599,6 +601,7 @@ def query( return self.summary.search_summary_docs( # type: ignore material_ids=material_ids, chemsys_formula=chemsys_formula, + exclude_elements=exclude_elements, nsites=nsites, volume=volume, density=density, diff --git a/src/mp_api/routes/__init__.py b/src/mp_api/routes/__init__.py index 35c3190e..7d1d67a5 100644 --- a/src/mp_api/routes/__init__.py +++ b/src/mp_api/routes/__init__.py @@ -7,7 +7,6 @@ from mp_api.routes.grain_boundary.client import GrainBoundaryRester from mp_api.routes.substrates.client import SubstratesRester from mp_api.routes.surface_properties.client import SurfacePropertiesRester -from mp_api.routes.wulff.client import WulffRester from mp_api.routes.phonon.client import PhononRester from mp_api.routes.elasticity.client import ElasticityRester from mp_api.routes.thermo.client import ThermoRester diff --git a/src/mp_api/routes/electronic_structure/resources.py b/src/mp_api/routes/electronic_structure/resources.py index 965f76a4..7b3916a6 100644 --- a/src/mp_api/routes/electronic_structure/resources.py +++ b/src/mp_api/routes/electronic_structure/resources.py @@ -6,6 +6,7 @@ from mp_api.routes.materials.query_operators import ( ElementsQuery, FormulaQuery, + DeprecationQuery, ) from mp_api.routes.electronic_structure.query_operators import ( @@ -26,6 +27,7 @@ def es_resource(es_store): FormulaQuery(), ElementsQuery(), NumericQuery(model=ElectronicStructureDoc), + DeprecationQuery(), SortQuery(), PaginationQuery(), SparseFieldsQuery( @@ -45,6 +47,7 @@ def bs_resource(es_store): ElectronicStructureDoc, query_operators=[ BSDataQuery(), + DeprecationQuery(), SortQuery(), PaginationQuery(), SparseFieldsQuery( @@ -84,6 +87,7 @@ def dos_resource(es_store): ElectronicStructureDoc, query_operators=[ DOSDataQuery(), + DeprecationQuery(), SortQuery(), PaginationQuery(), SparseFieldsQuery( diff --git a/src/mp_api/routes/materials/query_operators.py b/src/mp_api/routes/materials/query_operators.py index e090dd6b..e98c515b 100644 --- a/src/mp_api/routes/materials/query_operators.py +++ b/src/mp_api/routes/materials/query_operators.py @@ -82,7 +82,7 @@ class DeprecationQuery(QueryOperator): def query( self, deprecated: Optional[bool] = Query( - None, description="Whether the material is marked as deprecated", + False, description="Whether the material is marked as deprecated", ), ) -> STORE_PARAMS: diff --git a/src/mp_api/routes/materials/utils.py b/src/mp_api/routes/materials/utils.py index 1fe968f0..8d78b20a 100644 --- a/src/mp_api/routes/materials/utils.py +++ b/src/mp_api/routes/materials/utils.py @@ -22,6 +22,10 @@ def formula_to_criteria(formula: str) -> Dict: if "*" in eles: crit["nelements"] = len(eles) crit["elements"] = {"$all": [ele for ele in eles if ele != "*"]} + + if crit["elements"]["$all"] == []: + del crit["elements"] + return crit else: chemsys = "-".join(sorted(eles)) @@ -34,17 +38,11 @@ def formula_to_criteria(formula: str) -> Dict: formula_dummies = formula.replace("*", "{}").format(*dummies[:nstars]) - integer_formula = Composition(formula_dummies).get_integer_formula_and_factor()[ - 0 - ] + integer_formula = Composition(formula_dummies).get_integer_formula_and_factor()[0] comp = Composition(integer_formula).reduced_composition crit = dict() crit["formula_anonymous"] = comp.anonymized_formula - real_elts = [ - str(e) - for e in comp.elements - if not e.as_dict().get("element", "A") in dummies - ] + real_elts = [str(e) for e in comp.elements if not e.as_dict().get("element", "A") in dummies] for el, n in comp.to_reduced_dict.items(): if el in real_elts: diff --git a/src/mp_api/routes/provenance/resources.py b/src/mp_api/routes/provenance/resources.py index 7df5d0da..65a1409a 100644 --- a/src/mp_api/routes/provenance/resources.py +++ b/src/mp_api/routes/provenance/resources.py @@ -1,5 +1,6 @@ from maggma.api.resource import ReadOnlyResource from maggma.api.query_operator import PaginationQuery +from mp_api.routes.materials.query_operators import DeprecationQuery from emmet.core.provenance import ProvenanceDoc @@ -8,7 +9,7 @@ def provenance_resource(provenance_store): resource = ReadOnlyResource( provenance_store, ProvenanceDoc, - query_operators=[PaginationQuery()], + query_operators=[DeprecationQuery(), PaginationQuery()], tags=["Provenance"], disable_validation=True, enable_default_search=False, diff --git a/src/mp_api/routes/summary/client.py b/src/mp_api/routes/summary/client.py index 907e163d..bf79aeaf 100644 --- a/src/mp_api/routes/summary/client.py +++ b/src/mp_api/routes/summary/client.py @@ -18,6 +18,7 @@ def search_summary_docs( self, material_ids: Optional[List[MPID]] = None, chemsys_formula: Optional[str] = None, + exclude_elements: Optional[List[str]] = None, nsites: Optional[Tuple[int, int]] = None, volume: Optional[Tuple[float, float]] = None, density: Optional[Tuple[float, float]] = None, @@ -78,6 +79,7 @@ def search_summary_docs( chemsys_formula (str): A chemical system (e.g., Li-Fe-O), or formula including anonomyzed formula or wild cards (e.g., Fe2O3, ABO3, Si*). + exclude_elements (List(str)): List of elements to exclude. crystal_system (CrystalSystem): Crystal system of material. spacegroup_number (int): Space group number of material. spacegroup_symbol (str): Space group symbol of the material in international short symbol notation. @@ -199,6 +201,9 @@ def search_summary_docs( if chemsys_formula: query_params.update({"formula": chemsys_formula}) + if exclude_elements is not None: + query_params.update({"exclude_elements": ",".join(exclude_elements)}) + query_params.update( { "crystal_system": crystal_system, diff --git a/src/mp_api/routes/tasks/models.py b/src/mp_api/routes/tasks/models.py index 8a4c8dc4..53ddfdca 100644 --- a/src/mp_api/routes/tasks/models.py +++ b/src/mp_api/routes/tasks/models.py @@ -1,6 +1,7 @@ from datetime import datetime from enum import Enum -from typing import List +from json import decoder +from typing import List, Dict from monty.json import MontyDecoder from pydantic import BaseModel, Field, validator @@ -8,7 +9,7 @@ from pymatgen.core.structure import Structure from pymatgen.core.composition import Composition from pymatgen.core.periodic_table import Element -from pymatgen.io.vasp import Incar, Poscar, Kpoints, Potcar +from pymatgen.io.vasp import Incar, Poscar, Kpoints from pymatgen.core.trajectory import Trajectory monty_decoder = MontyDecoder() @@ -30,6 +31,14 @@ class TaskType(str, Enum): GGA_U_Structure_Optimization = "GGA+U Structure Optimization" +class Potcar(BaseModel): + functional: str = Field(None, description="Functional type use in the calculation.") + + symbols: List[str] = Field( + None, description="List of VASP potcar symbols used in the calculation." + ) + + class OrigInputs(BaseModel): incar: Incar = Field( None, description="Pymatgen object representing the INCAR file", diff --git a/src/mp_api/routes/wulff/__init__.py b/src/mp_api/routes/wulff/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/src/mp_api/routes/wulff/client.py b/src/mp_api/routes/wulff/client.py deleted file mode 100644 index 80c76c16..00000000 --- a/src/mp_api/routes/wulff/client.py +++ /dev/null @@ -1,9 +0,0 @@ -from mp_api.core.client import BaseRester -from mp_api.routes.wulff.models import WulffDoc - - -class WulffRester(BaseRester): - - suffix = "wulff" - document_model = WulffDoc # type: ignore - primary_key = "task_id" diff --git a/src/mp_api/routes/wulff/client.pyi b/src/mp_api/routes/wulff/client.pyi deleted file mode 100644 index 7796e16c..00000000 --- a/src/mp_api/routes/wulff/client.pyi +++ /dev/null @@ -1,13 +0,0 @@ -from typing import List, Optional -from mp_api.routes.wulff.models import WulffDoc - - -class WulffRester: - - def get_document_by_id( - self, - document_id: str, - fields: Optional[List[str]] = None, - monty_decode: bool = True, - ) -> WulffDoc: - ... diff --git a/src/mp_api/routes/wulff/models.py b/src/mp_api/routes/wulff/models.py deleted file mode 100644 index 3b21f55f..00000000 --- a/src/mp_api/routes/wulff/models.py +++ /dev/null @@ -1,50 +0,0 @@ -from typing import List - -from pydantic import BaseModel, Field, validator - - -class ImageEntry(BaseModel): - """ - Model for an high resolution image entry in SurfacePropDoc - """ - - miller_index: List[int] = Field( - None, description="Miller index of surface.", - ) - - image: bytes = Field( - None, description="High resolution image data.", - ) - - # Make sure that the datetime field is properly formatted - @validator("image", pre=True) - def image_bytes_ok(cls, v): - return str(v) - - -class WulffDoc(BaseModel): - """ - Model for a document containing wulff construction image data. - """ - - hi_res_images: List[ImageEntry] = Field( - None, description="List of individual surface image data.", - ) - - pretty_formula: str = Field( - None, description="Reduced formula of the material.", - ) - - thumbnail: str = Field( - None, description="Thumbnail image data.", - ) - - task_id: str = Field( - None, - description="The Materials Project ID of the material. This comes in the form: mp-******", - ) - - # Make sure that the datetime field is properly formatted - @validator("thumbnail", pre=True) - def thumbnail_bytes_ok(cls, v): - return str(v) diff --git a/src/mp_api/routes/wulff/resources.py b/src/mp_api/routes/wulff/resources.py deleted file mode 100644 index c5735f1b..00000000 --- a/src/mp_api/routes/wulff/resources.py +++ /dev/null @@ -1,20 +0,0 @@ -from maggma.api.resource import ReadOnlyResource -from mp_api.routes.wulff.models import WulffDoc - -from maggma.api.query_operator import PaginationQuery, SparseFieldsQuery - - -def wulff_resource(wulff_store): - resource = ReadOnlyResource( - wulff_store, - WulffDoc, - query_operators=[ - PaginationQuery(), - SparseFieldsQuery(WulffDoc, default_fields=["task_id"]), - ], - tags=["Surface Properties"], - enable_default_search=False, - disable_validation=True, - ) - - return resource diff --git a/tests/materials/test_utils.py b/tests/materials/test_utils.py index ff80704c..be7471d2 100644 --- a/tests/materials/test_utils.py +++ b/tests/materials/test_utils.py @@ -19,3 +19,4 @@ def test_formula_to_criteria(): # Chemsys assert formula_to_criteria("Si-O") == {"chemsys": "O-Si"} assert formula_to_criteria("Si-*") == {"elements": {"$all": ["Si"]}, "nelements": 2} + assert formula_to_criteria("*-*-*") == {"nelements": 3} diff --git a/tests/test_mprester.py b/tests/test_mprester.py index 94ee18a6..e17b8c89 100644 --- a/tests/test_mprester.py +++ b/tests/test_mprester.py @@ -34,8 +34,8 @@ def mpr(): ) class TestMPRester: def test_get_structure_by_material_id(self, mpr): - s1 = mpr.get_structure_by_material_id("mp-1") - assert s1.formula == "Cs2" + s1 = mpr.get_structure_by_material_id("mp-149") + assert s1.formula == "Si2" s1 = mpr.get_structure_by_material_id("mp-4163", conventional_unit_cell=True) assert s1.formula == "Ca12 Ti8 O28" @@ -47,7 +47,6 @@ def test_get_structure_by_material_id(self, mpr): with pytest.warns(UserWarning): mpr.get_structure_by_material_id("mp-698856") - @pytest.mark.xfail # temp xfail until deployment def test_get_database_version(self, mpr): db_version = mpr.get_database_version() assert db_version == MAPISettings().db_version @@ -55,6 +54,7 @@ def test_get_database_version(self, mpr): def test_get_materials_id_from_task_id(self, mpr): assert mpr.get_materials_id_from_task_id("mp-540081") == "mp-19017" + @pytest.mark.xfail # temp xfail while data is fixed def test_get_materials_id_references(self, mpr): data = mpr.get_materials_id_references("mp-123") assert len(data) > 5 @@ -132,8 +132,9 @@ def test_get_phonon_data_by_material_id(self, mpr): dos = mpr.get_phonon_dos_by_material_id("mp-11659") assert isinstance(dos, PhononDos) + @pytest.mark.xfail # temp xfail while data is fixes def test_get_charge_density_data(self, mpr): - task_ids = mpr.get_charge_density_calculation_ids_from_material_id("mp-149") + task_ids = mpr.get_charge_density_calculation_ids_from_material_id("mp-13") assert len(task_ids) > 0 vasp_calc_details = mpr.get_charge_density_calculation_details( @@ -144,7 +145,7 @@ def test_get_charge_density_data(self, mpr): chgcar = mpr.get_charge_density_from_calculation_id(task_ids[0]["task_id"]) assert isinstance(chgcar, Chgcar) - chgcar = mpr.get_charge_density_by_material_id("mp-149") + chgcar = mpr.get_charge_density_by_material_id("mp-13") assert isinstance(chgcar, Chgcar) def test_get_substrates(self, mpr): @@ -154,7 +155,7 @@ def test_get_substrates(self, mpr): def test_get_surface_data(self, mpr): data = mpr.get_surface_data("mp-126") # Pt - one_surf = mpr.get_surface_data("mp-129", miller_index=[-2, -3, 1]) + one_surf = mpr.get_surface_data("mp-129", miller_index=[1, 2, 3]) assert one_surf["surface_energy"] == pytest.approx(2.99156963) assert one_surf["miller_index"] == pytest.approx([3, 2, 1]) assert "surfaces" in data @@ -185,6 +186,7 @@ def test_get_wulff_shape(self, mpr): ws = mpr.get_wulff_shape("mp-126") assert isinstance(ws, WulffShape) + @pytest.mark.xfail # temp xfail while data is fixed def test_query(self, mpr): excluded_params = [ @@ -199,6 +201,7 @@ def test_query(self, mpr): alt_name_dict = { "material_ids": "material_id", "chemsys_formula": "formula_pretty", + "exclude_elements": "formula_pretty", "piezoelectric_modulus": "e_ij_max", "crystal_system": "symmetry", "spacegroup_symbol": "symmetry", @@ -217,6 +220,7 @@ def test_query(self, mpr): custom_field_tests = { "material_ids": ["mp-149"], "chemsys_formula": "SiO2", + "exclude_elements": ["Si"], "crystal_system": CrystalSystem.cubic, "spacegroup_number": 38, "spacegroup_symbol": "Amm2",