Skip to content

Commit 7a20d8f

Browse files
remove contribs only schemas
1 parent 3c8d4db commit 7a20d8f

2 files changed

Lines changed: 1 addition & 410 deletions

File tree

emmet-core/emmet/core/ml.py

Lines changed: 0 additions & 361 deletions
Original file line numberDiff line numberDiff line change
@@ -2,36 +2,19 @@
22

33
from __future__ import annotations
44

5-
from functools import cached_property
6-
from typing import TYPE_CHECKING
7-
8-
import numpy as np
95
from pydantic import (
10-
BaseModel,
116
Field,
127
field_validator,
138
)
149
from pymatgen.analysis.elasticity import ElasticTensor
15-
from pymatgen.core import Element, Structure
1610

1711
from emmet.core.elasticity import (
1812
BulkModulus,
1913
ElasticityDoc,
2014
ElasticTensorDoc,
2115
ShearModulus,
2216
)
23-
from emmet.core.math import Matrix3D, Vector3D, Vector6D, matrix_3x3_to_voigt
24-
from emmet.core.structure import StructureMetadata
25-
from emmet.core.tasks import TaskDoc
26-
from emmet.core.types.pymatgen_types.composition_adapter import CompositionType
27-
from emmet.core.types.pymatgen_types.element_adapter import ElementType
2817
from emmet.core.types.pymatgen_types.structure_adapter import StructureType
29-
from emmet.core.types.typing import IdentifierType
30-
from emmet.core.vasp.calc_types import RunType as VaspRunType
31-
32-
if TYPE_CHECKING:
33-
34-
from typing_extensions import Self
3518

3619

3720
class MLDoc(ElasticityDoc):
@@ -134,347 +117,3 @@ def shear_vrh_no_suffix(cls, new_key, values):
134117
"""Map field shear_modulus_vrh to shear_modulus."""
135118
val = values.get("shear_modulus_vrh", new_key)
136119
return ShearModulus(vrh=val)
137-
138-
139-
class MLTrainDoc(StructureMetadata, extra="allow"): # type: ignore[call-arg]
140-
"""Generic schema for ML training data."""
141-
142-
cell: Matrix3D | None = Field(
143-
None,
144-
description="The 3x3 matrix of cell/lattice vectors, such that a is the first row, b the second, and c the third.",
145-
)
146-
147-
atomic_numbers: list[int] | None = Field(
148-
None,
149-
description="The list of proton numbers at each site. Should be the same length as `cart_coords`",
150-
)
151-
152-
cart_coords: list[Vector3D] | None = Field(
153-
None,
154-
description="The list of Cartesian coordinates of each atom. Should be the same length as `atomic_numbers`.",
155-
)
156-
157-
magmoms: list[float] | None = Field(
158-
None, description="The list of on-site magnetic moments."
159-
)
160-
161-
energy: float | None = Field(
162-
None, description="The total energy associated with this structure."
163-
)
164-
165-
forces: list[Vector3D] | None = Field(
166-
None,
167-
description="The interatomic forces corresponding to each site in the structure.",
168-
)
169-
170-
abs_forces: list[float] | None = Field(
171-
None, description="The magnitude of the interatomic force on each site."
172-
)
173-
174-
stress: Vector6D | None = Field(
175-
None,
176-
description="The components of the symmetric stress tensor in Voigt notation (xx, yy, zz, yz, xz, xy).",
177-
)
178-
179-
stress_matrix: Matrix3D | None = Field(
180-
None,
181-
description="The 3x3 stress tensor. Use this if the tensor is unphysically non-symmetric.",
182-
)
183-
184-
bandgap: float | None = Field(None, description="The final DFT bandgap.")
185-
186-
elements: list[ElementType] | None = Field(
187-
None,
188-
description="List of unique elements in the material sorted alphabetically.",
189-
)
190-
191-
composition: CompositionType | None = Field(
192-
None, description="Full composition for the material."
193-
)
194-
195-
composition_reduced: CompositionType | None = Field(
196-
None,
197-
title="Reduced Composition",
198-
description="Simplified representation of the composition.",
199-
)
200-
201-
functional: VaspRunType | None = Field(
202-
None, description="The approximate functional used to generate this entry."
203-
)
204-
205-
bader_charges: list[float] | None = Field(
206-
None, description="Bader charges on each site of the structure."
207-
)
208-
bader_magmoms: list[float] | None = Field(
209-
None,
210-
description="Bader on-site magnetic moments for each site of the structure.",
211-
)
212-
213-
@cached_property
214-
def structure(self) -> Structure:
215-
"""Get the structure associated with this entry."""
216-
site_props = {"magmom": self.magmoms} if self.magmoms else None
217-
return Structure(
218-
np.array(self.cell),
219-
[Element.from_Z(z) for z in self.atomic_numbers], # type: ignore[union-attr]
220-
self.cart_coords, # type: ignore[arg-type]
221-
coords_are_cartesian=True,
222-
site_properties=site_props,
223-
)
224-
225-
@classmethod
226-
def from_structure(
227-
cls,
228-
meta_structure: Structure,
229-
fields: list[str] | None = None,
230-
**kwargs,
231-
) -> Self:
232-
"""
233-
Create an ML training document from an ordered structure and fields.
234-
235-
This method mostly exists to ensure that the structure field is
236-
set because `meta_structure` does not populate it automatically.
237-
238-
Parameters
239-
-----------
240-
meta_structure : Structure
241-
An ordered structure
242-
fields : list of str or None
243-
Additional fields in the document to populate
244-
**kwargs
245-
Any other fields / constructor kwargs
246-
"""
247-
if not meta_structure.is_ordered:
248-
raise ValueError(
249-
f"{cls.__name__} only supports ordered structures at this time."
250-
)
251-
252-
if (forces := kwargs.get("forces")) is not None and kwargs.get(
253-
"abs_forces"
254-
) is None:
255-
kwargs["abs_forces"] = [np.linalg.norm(f) for f in forces]
256-
257-
if magmoms := meta_structure.site_properties.get("magmom"):
258-
kwargs["magmoms"] = magmoms
259-
260-
return super().from_structure(
261-
meta_structure=meta_structure,
262-
fields=fields,
263-
cell=meta_structure.lattice.matrix,
264-
atomic_numbers=[site.specie.Z for site in meta_structure],
265-
cart_coords=meta_structure.cart_coords,
266-
**kwargs,
267-
)
268-
269-
@classmethod
270-
def from_task_doc(
271-
cls,
272-
task_doc: TaskDoc,
273-
**kwargs,
274-
) -> list[Self]:
275-
"""Create a list of ML training documents from the ionic steps in a TaskDoc.
276-
277-
Parameters
278-
-----------
279-
task_doc : TaskDoc
280-
**kwargs
281-
Any kwargs to pass to `from_structure`.
282-
"""
283-
entries = []
284-
285-
for cr in task_doc.calcs_reversed[::-1]:
286-
nion = len(cr.output.ionic_steps)
287-
288-
for iion, ionic_step in enumerate(cr.output.ionic_steps):
289-
structure = Structure.from_dict(ionic_step.structure.as_dict())
290-
# these are fields that should only be set on the final frame of a calculation
291-
# also patch in magmoms because of how Calculation works
292-
last_step_kwargs = {}
293-
if iion == nion - 1:
294-
if magmom := cr.output.structure.site_properties.get("magmom"):
295-
structure.add_site_property("magmom", magmom)
296-
last_step_kwargs["bandgap"] = cr.output.bandgap
297-
if bader_analysis := cr.bader:
298-
for bk in (
299-
"charge",
300-
"magmom",
301-
):
302-
last_step_kwargs[f"bader_{bk}s"] = bader_analysis[bk]
303-
304-
if (_st := ionic_step.stress) is not None:
305-
st = np.array(_st)
306-
if np.allclose(st, st.T, rtol=1e-8):
307-
# Stress tensor is symmetric
308-
last_step_kwargs["stress"] = matrix_3x3_to_voigt(_st)
309-
else:
310-
# Stress tensor is non-symmetric
311-
last_step_kwargs["stress_matrix"] = _st
312-
313-
entries.append(
314-
cls.from_structure(
315-
meta_structure=structure,
316-
energy=ionic_step.e_0_energy,
317-
forces=ionic_step.forces,
318-
functional=cr.run_type,
319-
**last_step_kwargs,
320-
**kwargs,
321-
)
322-
)
323-
return entries
324-
325-
@cached_property
326-
def to_ase_atoms(self):
327-
"""Vestigial functionality to convert to ASE atoms.
328-
329-
NB: pymatgen depends on ASE optionally so this
330-
may...be...OK to include here?
331-
332-
Can cut this as needed.
333-
"""
334-
335-
try:
336-
from ase.calculators.singlepoint import SinglePointCalculator
337-
from ase import Atoms
338-
except ImportError:
339-
raise ImportError(
340-
"You must `pip install ase` to use the atoms functionality here!"
341-
)
342-
343-
atoms = Atoms(
344-
positions=self.cart_coords,
345-
numbers=self.atomic_numbers,
346-
cell=self.cell,
347-
)
348-
calc = SinglePointCalculator(
349-
atoms,
350-
**{
351-
k: getattr(self, k, None)
352-
for k in {"energy", "forces", "stress", "magmoms"}
353-
},
354-
)
355-
atoms.calc = calc
356-
return atoms
357-
358-
359-
class MatPESProvenanceDoc(BaseModel):
360-
"""Information regarding the origins of a MatPES structure."""
361-
362-
original_mp_id: IdentifierType | None = Field(
363-
None,
364-
description="MP identifier corresponding to the Materials Project structure from which this entry was sourced from.",
365-
)
366-
materials_project_version: str | None = Field(
367-
None,
368-
description="The version of the Materials Project from which the struture was sourced.",
369-
)
370-
md_ensemble: str | None = Field(
371-
None,
372-
description="The molecular dynamics ensemble used to generate this structure.",
373-
)
374-
md_temperature: float | None = Field(
375-
None,
376-
description="If a float, the temperature in Kelvin at which MLMD was performed.",
377-
)
378-
md_pressure: float | None = Field(
379-
None,
380-
description="If a float, the pressure in atmosphere at which MLMD was performed.",
381-
)
382-
md_step: int | None = Field(
383-
None,
384-
description="The step in the MD simulation from which the structure was sampled.",
385-
)
386-
mlip_name: str | None = Field(
387-
None, description="The name of the ML potential used to perform MLMD."
388-
)
389-
390-
391-
class MatPESTrainDoc(MLTrainDoc):
392-
"""
393-
Schema for VASP data in the Materials Potential Energy Surface (MatPES) effort.
394-
395-
This schema is used in the data entries for MatPES v2025.1,
396-
which can be downloaded either:
397-
- On [MPContribs](https://materialsproject-contribs.s3.amazonaws.com/index.html#MatPES_2025_1/)
398-
- or on [the site]
399-
"""
400-
401-
matpes_id: str | None = Field(None, description="MatPES identifier.")
402-
403-
formation_energy_per_atom: float | None = Field(
404-
None,
405-
description="The uncorrected formation enthalpy per atom at zero pressure and temperature.",
406-
)
407-
cohesive_energy_per_atom: float | None = Field(
408-
None, description="The uncorrected cohesive energy per atom."
409-
)
410-
411-
provenance: MatPESProvenanceDoc | None = Field(
412-
None, description="Information about the provenance of the structure."
413-
)
414-
415-
@property
416-
def pressure(self) -> float | None:
417-
"""Return the pressure from the DFT stress tensor."""
418-
return sum(self.stress[:3]) / 3.0 if self.stress else None
419-
420-
421-
class MPtrjProvenance(BaseModel):
422-
"""Metadata for MPtrj entries."""
423-
424-
material_id: IdentifierType | None = Field(
425-
None, description="The Materials Project (summary) ID for this material."
426-
)
427-
task_id: IdentifierType | None = Field(
428-
None, description="The Materials Project (summary) ID for this material."
429-
)
430-
calcs_reversed_index: int | None = Field(
431-
None, description="The index of the reversed calculations, if applicable."
432-
)
433-
ionic_step_index: int | None = Field(
434-
None, description="The index of the ionic step, if applicable."
435-
)
436-
437-
438-
class MPtrjTrainDoc(MLTrainDoc):
439-
"""Schematize MPtrj data."""
440-
441-
energy: float | None = Field(
442-
None, description="The total uncorrected energy associated with this structure."
443-
)
444-
445-
cohesive_energy_per_atom: float | None = Field(
446-
None, description="The uncorrected cohesive energy per atom of this material."
447-
)
448-
449-
corrected_cohesive_energy_per_atom: float | None = Field(
450-
None,
451-
description=(
452-
"The corrected cohesive energy per atom of this material, "
453-
"using the Materials Project GGA / GGA+U mixing scheme."
454-
),
455-
)
456-
457-
provenance: MPtrjProvenance | None = Field(
458-
None, description="Metadata for this frame."
459-
)
460-
461-
462-
class MPAloeTrainDoc(MatPESTrainDoc):
463-
"""Schematize MP-ALOE data."""
464-
465-
mp_aloe_id: str | None = Field(
466-
None, description="The identifier of this entry in MP-ALOE."
467-
)
468-
ionic_step_number: int | None = Field(
469-
None, description="The ionic step index of this frame."
470-
)
471-
prototype_number: int | None = Field(
472-
None, description="The index of the prototype structure used in generation."
473-
)
474-
is_charge_balanced: bool | None = Field(
475-
None, description="Whether the structure is likely charge balanced."
476-
)
477-
has_overlapping_pseudo_cores: bool | None = Field(
478-
None,
479-
description="Whether the pseudopotential cores overlap for at least one set of nearest neighbors.",
480-
)

0 commit comments

Comments
 (0)