Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions express/properties/non_scalar/non_scalar_property_context.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
from typing import Any, Type

from mat3ra.esse.utils import validate_and_clean

from express.parsers import BaseParser
from express.properties.non_scalar import NonScalarProperty


class NonScalarPropertyFromContext(NonScalarProperty):
def __init__(
self,
name: str,
parser: Type[BaseParser],
data: Any = None,
context: dict[str, Any] | None = None,
context_key: str | None = None,
*args,
**kwargs,
):
super().__init__(name, parser, *args, **kwargs)
if data is not None:
self.data = data
elif "value" in kwargs:
self.data = kwargs["value"]
else:
self.data = context[context_key or name]

def _serialize(self):
if isinstance(self.data, dict):
return {"name": self.name, **self.data}
return {"name": self.name, "values": self.data}

def serialize_and_validate(self):
instance = self._serialize()
result = validate_and_clean(instance, self.schema)
if not result["is_valid"]:
raise result["errors"][0]
return instance
3 changes: 3 additions & 0 deletions express/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@
"total_energy_contributions": {
"reference": "express.properties.non_scalar.total_energy_contributions.TotalEnergyContributions"
},
"formation_energy_contributions": {
"reference": "express.properties.non_scalar.non_scalar_property_context.NonScalarPropertyFromContext"
},
"material": {"reference": "express.properties.material.Material"},
"symmetry": {"reference": "express.properties.non_scalar.symmetry.Symmetry"},
"workflow:pyml_predict": {"reference": "express.properties.workflow.PyMLTrainAndPredictWorkflow"},
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ classifiers = [
dependencies = [
"pymatgen>=2023.8.10",
"ase>=3.17.0",
"mat3ra-esse>=2026.5.30.post0",
"mat3ra-esse>=2026.6.12",
"jarvis-tools>=2023.12.12",
# To avoid module 'numpy.linalg._umath_linalg' has no attribute '_ilp64' in Colab
"numpy>=1.24.4,<2",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
from tests.unit import UnitTestBase

from express.properties.non_scalar.non_scalar_property_context import NonScalarPropertyFromContext

COMPOUND_CONTRIBUTION = {
"formula": "SiC",
"n_atoms": 4,
"is_elemental": False,
"total_energy": -520.003969643439,
"total_energy_per_atom": -130.00099241085975,
"precision_value": 8192,
"precision_metric": "KPPRA",
}
SILICON_CONTRIBUTION = {
"formula": "Si",
"n_atoms": 2,
"is_elemental": True,
"total_energy": -261.003969643439,
"total_energy_per_atom": -130.5019848217195,
"precision_value": 8192,
"precision_metric": "KPPRA",
}
FORMATION_ENERGY_CONTRIBUTIONS_VALUES = [COMPOUND_CONTRIBUTION, SILICON_CONTRIBUTION]
FORMATION_ENERGY_CONTRIBUTIONS = {
"name": "formation_energy_contributions",
"values": FORMATION_ENERGY_CONTRIBUTIONS_VALUES,
}
COMPOUND_CONTRIBUTION_WITHOUT_PRECISION = {
"formula": "SiC",
"n_atoms": 4,
"is_elemental": False,
"total_energy": -520.003969643439,
"total_energy_per_atom": -130.00099241085975,
}
DEFAULT_PRECISION_VALUE = -1
DEFAULT_PRECISION_METRIC = "unknown"
COMPOUND_CONTRIBUTION_WITH_DEFAULT_PRECISION = {
**COMPOUND_CONTRIBUTION_WITHOUT_PRECISION,
"precision_value": DEFAULT_PRECISION_VALUE,
"precision_metric": DEFAULT_PRECISION_METRIC,
}


class FormationEnergyContributionsTest(UnitTestBase):
def setUp(self):
super().setUp()

def tearDown(self):
super().tearDown()

def test_formation_energy_contributions_from_context_values(self):
property_ = NonScalarPropertyFromContext(
"formation_energy_contributions",
None,
data=FORMATION_ENERGY_CONTRIBUTIONS_VALUES,
)

self.assertDeepAlmostEqual(property_.serialize_and_validate(), FORMATION_ENERGY_CONTRIBUTIONS)

def test_formation_energy_contributions_from_context_data(self):
property_ = NonScalarPropertyFromContext(
"formation_energy_contributions",
None,
data={"values": FORMATION_ENERGY_CONTRIBUTIONS_VALUES},
)

self.assertDeepAlmostEqual(property_.serialize_and_validate(), FORMATION_ENERGY_CONTRIBUTIONS)

def test_formation_energy_contributions_from_value_alias(self):
property_ = NonScalarPropertyFromContext(
"formation_energy_contributions",
None,
value=FORMATION_ENERGY_CONTRIBUTIONS_VALUES,
)

self.assertDeepAlmostEqual(property_.serialize_and_validate(), FORMATION_ENERGY_CONTRIBUTIONS)

def test_formation_energy_contributions_applies_schema_defaults(self):
property_ = NonScalarPropertyFromContext(
"formation_energy_contributions",
None,
data=[COMPOUND_CONTRIBUTION_WITHOUT_PRECISION],
)

self.assertDeepAlmostEqual(
property_.serialize_and_validate(),
{
"name": "formation_energy_contributions",
"values": [COMPOUND_CONTRIBUTION_WITH_DEFAULT_PRECISION],
},
)

def test_formation_energy_contributions_from_context_key(self):
property_ = NonScalarPropertyFromContext(
"formation_energy_contributions",
None,
context={"FORMATION_ENERGY_REFERENCES": FORMATION_ENERGY_CONTRIBUTIONS_VALUES},
context_key="FORMATION_ENERGY_REFERENCES",
)

self.assertDeepAlmostEqual(property_.serialize_and_validate(), FORMATION_ENERGY_CONTRIBUTIONS)
Loading