Skip to content

Commit

Permalink
Merge pull request #222 from materialsproject/robocrys
Browse files Browse the repository at this point in the history
Robocrystallographer document
  • Loading branch information
shyamd authored Jul 13, 2021
2 parents b081d3f + 14d8032 commit fe6da76
Show file tree
Hide file tree
Showing 10 changed files with 176 additions and 15 deletions.
43 changes: 43 additions & 0 deletions emmet-builders/emmet/builders/materials/robocrys.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
from typing import Dict, Optional, List
from maggma.builders.map_builder import MapBuilder
from maggma.core import Store

from pymatgen.core.structure import Structure
from emmet.core.robocrys import RobocrystallogapherDoc
from emmet.core.utils import jsanitize


class RobocrystallographerBuilder(MapBuilder):
def __init__(
self,
oxidation_states: Store,
robocrys: Store,
query: Optional[Dict] = None,
**kwargs
):
self.oxidation_states = oxidation_states
self.robocrys = robocrys
self.kwargs = kwargs

self.robocrys.key = "material_id"
self.oxidation_states.key = "material_id"

super().__init__(
source=oxidation_states,
target=robocrys,
query=query,
projection=["material_id", "structure"],
**kwargs
)

def unary_function(self, item):
structure = Structure.from_dict(item["structure"])
mpid = item["material_id"]

doc = RobocrystallogapherDoc.from_structure(
structure=structure,
material_id=mpid,
fields=[],
)

return jsanitize(doc.dict(), allow_bson=True)
7 changes: 2 additions & 5 deletions emmet-core/emmet/core/material_property.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ class PropertyDoc(StructureMetadata):
)

warnings: Sequence[str] = Field(
None, description="Any warnings related to this property"
[], description="Any warnings related to this property"
)

@classmethod
Expand All @@ -50,8 +50,5 @@ def from_structure( # type: ignore[override]
"""

return super().from_structure( # type: ignore
structure=structure,
material_id=material_id,
include_structure=False,
**kwargs
structure=structure, material_id=material_id, **kwargs
)
6 changes: 5 additions & 1 deletion emmet-core/emmet/core/oxidation_states.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,5 +90,9 @@ def from_structure(cls, structure: Structure, material_id: MPID, **kwargs): # t
raise e

return super().from_structure(
structure=structure, material_id=material_id, **d, **kwargs
structure=structure,
material_id=material_id,
include_structure=True,
**d,
**kwargs
)
72 changes: 72 additions & 0 deletions emmet-core/emmet/core/robocrys.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
from typing import Union

from pydantic import BaseModel, Field
from pymatgen.core.structure import Structure
from robocrys import StructureCondenser, StructureDescriber
from robocrys import __version__ as __robocrys_version__

from emmet.core.material_property import PropertyDoc
from emmet.core.mpid import MPID


class MineralData(BaseModel):
"""
Model for mineral data in the condensed structure robocrystallographer field
"""

type: Union[str, None] = Field(
description="Mineral type.",
)

name: str = Field(None, description="The mineral name if found")


class CondensedStructureData(BaseModel):
"""
Model for data in the condensed structure robocrystallographer field
More details: https://hackingmaterials.lbl.gov/robocrystallographer/format.html
"""

mineral: MineralData = Field(
description="Matched mineral data for the material.",
)

dimensionality: int = Field(
description="Dimensionality of the material.",
)


class RobocrystallogapherDoc(PropertyDoc):
"""
This document contains the descriptive data from robocrystallographer
for a material:
Structural features, mineral prototypes, dimensionality, ...
"""

description: str = Field(
description="Decription text from robocrytallographer.",
)

condensed_structure: CondensedStructureData = Field(
description="Condensed structure data from robocrytallographer.",
)

robocrys_version: str = Field(
__robocrys_version__,
description="The version of Robocrystallographer used to generate this document",
)

@classmethod
def from_structure(cls, structure: Structure, material_id: MPID, **kwargs): # type: ignore[override]
condensed_structure = StructureCondenser().condense_structure(structure)
description = StructureDescriber(
describe_symmetry_labels=False, fmt="unicode", return_parts=False
).describe(condensed_structure=condensed_structure)

return cls(
structure=structure,
material_id=material_id,
condensed_structure=condensed_structure,
description=description,
**kwargs
)
15 changes: 8 additions & 7 deletions emmet-core/emmet/core/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
from typing import Dict, List, Type, TypeVar, Union

import requests
from monty.json import MontyDecoder
from pydantic import BaseSettings, Field, root_validator
from pydantic.class_validators import validator
from pydantic.types import PyObject

DEFAULT_CONFIG_FILE_PATH = str(Path.home().joinpath(".emmet.json"))
Expand Down Expand Up @@ -118,15 +120,14 @@ def autoload(cls: Type[S], settings: Union[None, dict, S]) -> S:
return cls(**settings)
return settings

@validator("VASP_DEFAULT_INPUT_SETS", pre=True)
def convert_input_sets(cls, value):
if isinstance(value, dict):
return {k: MontyDecoder().process_decoded(v) for k, v in value.items()}
return value

def as_dict(self):
"""
HotPatch to enable serializing EmmetSettings via Monty
"""
return self.dict(exclude_unset=True, exclude_defaults=True)

@classmethod
def from_dict(cls: Type[S], settings: Dict) -> S:
"""
HotPatch to enable serializing EmmetSettings via Monty
"""
return cls(**settings)
6 changes: 5 additions & 1 deletion emmet-core/emmet/core/structure.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@
from typing import List, Optional, Type, TypeVar

from pydantic import BaseModel, Field
from pymatgen.core import Composition, Structure
from pymatgen.core.composition import Composition
from pymatgen.core.periodic_table import Element
from pymatgen.core.structure import Structure

from emmet.core.symmetry import SymmetryData

Expand All @@ -18,6 +19,9 @@ class StructureMetadata(BaseModel):
"""

# Structure metadata
structure: Optional[Structure] = Field(
None, description="The structure for this metadata"
)
nsites: int = Field(None, description="Total number of sites in the structure")
elements: List[Element] = Field(
None, description="List of elements in the material"
Expand Down
4 changes: 3 additions & 1 deletion emmet-core/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
pymatgen==2022.0.9
monty==v2021.3.3
monty==2021.6.10
pydantic==1.8.2
pybtex==0.24.0
typing-extensions==3.10.0.0
seekpath==2.0.1
setuptools-scm==6.0.1
robocrys==0.2.7
matminer==0.7.3
2 changes: 2 additions & 0 deletions emmet-core/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
"pydantic[email]~=1.8",
"pybtex~=0.24",
"typing-extensions>=3.7,<4.0",
"robocrys>=0.2.7",
"matminer>=0.7.3",
],
license="modified BSD",
zip_safe=False,
Expand Down
1 change: 1 addition & 0 deletions tests/emmet-core/test_oxidation_states.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,4 @@ def test_oxidation_state(structure: Structure):
print(f"Should work : {structure.composition}")
doc = OxidationStateDoc.from_structure(structure, material_id=33)
assert doc is not None
assert doc.structure is not None
35 changes: 35 additions & 0 deletions tests/emmet-core/test_robocrys.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import pytest
from pymatgen.core import Structure
from pymatgen.util.testing import PymatgenTest

from emmet.core.robocrys import RobocrystallogapherDoc

test_structures = {
name: struc.get_reduced_structure()
for name, struc in PymatgenTest.TEST_STRUCTURES.items()
if name
in [
"SiO2",
"Li2O",
"LiFePO4",
"TlBiSe2",
"K2O2",
"Li3V2(PO4)3",
"CsCl",
"Li2O2",
"NaFePO4",
"Pb2TiZrO6",
"SrTiO3",
"TiO2",
"BaNiO3",
"VO2",
]
}


@pytest.mark.parametrize("structure", test_structures.values())
def test_robocrys(structure: Structure):
"""Very simple test to make sure this actually works"""
print(f"Should work : {structure.composition}")
doc = RobocrystallogapherDoc.from_structure(structure, material_id=33)
assert doc is not None

0 comments on commit fe6da76

Please sign in to comment.