|
2 | 2 | from __future__ import annotations |
3 | 3 |
|
4 | 4 | from datetime import datetime |
5 | | -import json |
6 | | -from typing import Literal |
| 5 | +from typing import Literal, Any |
7 | 6 |
|
8 | 7 | import plotly.graph_objects as plotly_go |
9 | 8 |
|
|
22 | 21 | from emmet.core.thermo import ThermoType |
23 | 22 | from emmet.core.vasp.calc_types import CalcType |
24 | 23 | from emmet.core.xas import Edge, Type |
25 | | -from pymatgen.analysis.phase_diagram import PhaseDiagram |
26 | 24 | from pymatgen.analysis.magnetism.analyzer import Ordering |
27 | 25 | from pymatgen.core.periodic_table import Element |
| 26 | +from pymatgen.core.composition import Composition |
28 | 27 | from pymatgen.core.structure import Structure |
29 | 28 | from pymatgen.electronic_structure.core import OrbitalType, Spin |
30 | 29 | from pymatgen.entries.computed_entries import ComputedEntry |
|
35 | 34 | class MPMcpTools(_NeedsMPClient): |
36 | 35 | """Define tools needed for the MP MCP client.""" |
37 | 36 |
|
| 37 | + def get_structure_by_material_id( |
| 38 | + self, |
| 39 | + material_id: str, |
| 40 | + structure_format: Literal["json", "poscar", "cif"], |
| 41 | + ) -> dict[str, Any] | str: |
| 42 | + """Find a structure in the Materials Project by its identifier/ID. |
| 43 | +
|
| 44 | + Return type changes based on format: |
| 45 | + structure_format = "json": |
| 46 | + Returns the JSON representation of a pymatgen structure object. |
| 47 | + structure_format = "poscar": |
| 48 | + Returns a VASP POSCAR-like representation |
| 49 | + structure_format = "cif": |
| 50 | + Returns a crystallographic information file (CIF) |
| 51 | + """ |
| 52 | + struct = self.client.get_structure_by_material_id( |
| 53 | + material_id=material_id, |
| 54 | + final=True, |
| 55 | + conventional_unit_cell=False, |
| 56 | + ) |
| 57 | + if structure_format == "json": |
| 58 | + return struct.as_dict() if hasattr(struct, "as_dict") else struct |
| 59 | + |
| 60 | + if isinstance(struct, dict): |
| 61 | + struct = Structure.from_dict(struct) |
| 62 | + return struct.to(fmt=structure_format) |
| 63 | + |
38 | 64 | def get_phase_diagram_from_elements( |
39 | 65 | self, |
40 | 66 | elements: list[str], |
41 | 67 | thermo_type: ThermoType | str = "GGA_GGA+U_R2SCAN", |
42 | 68 | ) -> plotly_go.Figure: |
| 69 | + """Find a thermodynamic phase diagram in the Materials Project by specified elements. |
| 70 | +
|
| 71 | + ### Examples: |
| 72 | + Given elements Na and Cl: |
| 73 | + ``` |
| 74 | + phase_diagram = MPMcpTools().get_phase_diagram_from_elements( |
| 75 | + elements = ["Na","Cl"], |
| 76 | + ) |
| 77 | + ``` |
| 78 | +
|
| 79 | + Given a chemical system, "K-P-O": |
| 80 | + ``` |
| 81 | + phase_diagrasm = MPMcpTools().get_phase_diagram_from_elements( |
| 82 | + elements = "K-P-O".split("-"), |
| 83 | + ) |
| 84 | + ``` |
| 85 | +
|
| 86 | + """ |
43 | 87 | pd = self.client.materials.thermo.get_phase_diagram_from_chemsys( |
44 | 88 | "-".join(elements), thermo_type |
45 | 89 | ) |
46 | 90 | return pd.get_plot() # has to be JSON serializable |
47 | 91 |
|
48 | | - def get_stability( |
| 92 | + def get_stability_or_energy_above_hull( |
49 | 93 | self, |
50 | | - composition: dict[str, float], |
| 94 | + formula: str, |
51 | 95 | energy: float, |
52 | | - run_type: Literal["GGA", "GGA+U", "R2SCAN"] | None = None, |
53 | | - thermo_type: str | ThermoType = "GGA_GGA+U", |
54 | | - ) -> list[dict]: |
55 | | - data = None |
56 | | - if run_type: |
57 | | - data = {"run_type": run_type} |
58 | | - return self.client.get_stability( |
59 | | - entries=ComputedEntry(composition, energy, data=data), |
60 | | - thermo_type=thermo_type, |
61 | | - ) |
| 96 | + run_type: Literal["GGA", "PBE", "GGA+U", "PBE+U", "R2SCAN"], |
| 97 | + ) -> float: |
| 98 | + """Get the stability of a particular material. |
| 99 | +
|
| 100 | + Given a material's formula and energy in eV, tells you the material's |
| 101 | + stability from the energy above the hull (positive for unstable, 0 for stable) |
| 102 | + in eV/atom. |
| 103 | +
|
| 104 | + The user must specify a particular functional: |
| 105 | + - PBE or GGA |
| 106 | + - PBE+U or GGA+U |
| 107 | + - R2SCAN |
| 108 | + this will add necessary corrections to the energy to compare with |
| 109 | + the Materials Project. |
| 110 | +
|
| 111 | + """ |
| 112 | + run_type = run_type.replace("PBE", "GGA") |
| 113 | + data = {"run_type": run_type} |
| 114 | + thermo_type = "GGA_GGA+U" if run_type in {"GGA", "GGA+U"} else "R2SCAN" |
| 115 | + |
| 116 | + try: |
| 117 | + stability = self.client.get_stability( |
| 118 | + entries=[ComputedEntry(Composition(formula), energy, data=data)], |
| 119 | + thermo_type=thermo_type, |
| 120 | + ) |
| 121 | + return stability[0]["e_above_hull"] |
| 122 | + except ValueError: |
| 123 | + return float("inf") |
62 | 124 |
|
63 | 125 | def get_absorption_data( |
64 | 126 | self, |
@@ -644,7 +706,7 @@ def get_material_data( |
644 | 706 | efermi=efermi, |
645 | 707 | elastic_anisotropy=elastic_anisotropy, |
646 | 708 | elements=elements, |
647 | | - energy_above_hull=tuple(float(x) for x in json.loads(energy_above_hull)), |
| 709 | + energy_above_hull=energy_above_hull, |
648 | 710 | equilibrium_reaction_energy=equilibrium_reaction_energy, |
649 | 711 | exclude_elements=exclude_elements, |
650 | 712 | formation_energy=formation_energy, |
@@ -964,15 +1026,6 @@ def get_entry_by_material_id( |
964 | 1026 | conventional_unit_cell=conventional_unit_cell, |
965 | 1027 | ) |
966 | 1028 |
|
967 | | - def get_structure_by_material_id( |
968 | | - self, material_id: str, final: bool = True, conventional_unit_cell: bool = False |
969 | | - ) -> list[dict]: |
970 | | - return self.client.get_structure_by_material_id( |
971 | | - material_id=material_id, |
972 | | - final=final, |
973 | | - conventional_unit_cell=conventional_unit_cell, |
974 | | - ) |
975 | | - |
976 | 1029 | def get_structures(self, chemsys_formula: str | list[str] = True) -> list[dict]: |
977 | 1030 | return self.client.get_structures(chemsys_formula=chemsys_formula) |
978 | 1031 |
|
|
0 commit comments