Skip to content

Commit 4d22e2d

Browse files
committed
Serve periodicity independently of composition
1 parent 78105ca commit 4d22e2d

2 files changed

Lines changed: 55 additions & 7 deletions

File tree

‎src/httk/atomistic/structure_entries.py‎

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ def _structures_definition() -> EntryTypeDefinition:
6868
'species_at_sites': 'species_at_sites',
6969
'species': 'species',
7070
'structure_features': 'structure_features',
71+
'nperiodic_dimensions': 'nperiodic_dimensions',
72+
'dimension_types': 'dimension_types',
7173
}
7274

7375
# Structural record keys that serve null for a None (structure-less) entry.
@@ -80,12 +82,16 @@ def _structures_definition() -> EntryTypeDefinition:
8082
'species_at_sites',
8183
'species',
8284
'structure_features',
85+
'nperiodic_dimensions',
86+
'dimension_types',
8387
)
8488

85-
# Standard composition properties auto-derived from each Structure (None-safe).
89+
# Standard *composition* properties auto-derived from each Structure. They serve null
90+
# together for a structure that is not fully ordered, because a formula in whole atoms is
91+
# not well defined for one. Periodicity is deliberately not among them: it has nothing to
92+
# do with composition, and bundling it here made a disordered alloy report its periodicity
93+
# as unknown.
8694
_AUTO_DERIVED_KEYS: tuple[str, ...] = (
87-
'nperiodic_dimensions',
88-
'dimension_types',
8995
'elements_ratios',
9096
'chemical_formula_reduced',
9197
'chemical_formula_anonymous',
@@ -144,8 +150,6 @@ def _derived_properties(structure: Any) -> dict[str, Any]:
144150
for position, amount in enumerate(anonymous_amounts)
145151
)
146152
return {
147-
'nperiodic_dimensions': 3,
148-
'dimension_types': [1, 1, 1],
149153
'elements_ratios': [counts[element] / total for element in elements_sorted],
150154
'chemical_formula_reduced': reduced_formula,
151155
'chemical_formula_anonymous': anonymous_formula,
@@ -273,6 +277,14 @@ def records(self, entry_type: str) -> Iterable[Mapping[str, Any]]:
273277
'species_at_sites': list(structure.species_at_sites),
274278
'species': species_dicts,
275279
'structure_features': features,
280+
# A Structure carries no notion of periodicity, so every one of them
281+
# is a fully periodic crystal. That is a limitation of the model
282+
# rather than a fact about the entry, and it is stated here, with the
283+
# other always-served structural facts, rather than among the
284+
# composition-derived properties where a disordered structure would
285+
# have nulled it.
286+
'nperiodic_dimensions': 3,
287+
'dimension_types': [1, 1, 1],
276288
}
277289
)
278290
record.update(_derived_properties(structure))

‎tests/test_structure_entries.py‎

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,8 +131,6 @@ def test_chemical_formula_and_ratios() -> None:
131131
assert record["chemical_formula_anonymous"] == "A3BC"
132132
assert record["chemical_formula_descriptive"] == "FeO3Sm"
133133
assert record["elements_ratios"] == [0.2, 0.6, 0.2]
134-
assert record["nperiodic_dimensions"] == 3
135-
assert record["dimension_types"] == [1, 1, 1]
136134

137135

138136
def test_null_structure_serves_null() -> None:
@@ -192,3 +190,41 @@ def test_registration_discovered_via_httk_core() -> None:
192190
provider = factory({"s-1": _nacl_like()})
193191
assert isinstance(provider, StructureEntryProvider)
194192
assert set(provider.entry_types()) == {"structures"}
193+
194+
195+
# --- periodicity ---
196+
197+
198+
def test_periodicity_is_served_whatever_the_composition() -> None:
199+
"""It is not a composition property, and must not null out with the formula.
200+
201+
A `Structure` carries no notion of periodicity, so every one of them is served as a
202+
fully periodic crystal. The bug was that these two travelled with the composition
203+
block, which serves null for any structure that is not fully ordered — so a disordered
204+
alloy, or a structure with attached atoms, reported its periodicity as *unknown* purely
205+
because a whole-atom formula could not be written for it.
206+
"""
207+
cell = [[4.0, 0.0, 0.0], [0.0, 4.0, 0.0], [0.0, 0.0, 4.0]]
208+
ordered = Species(name="Na", chemical_symbols=("Na",), concentration=(1.0,))
209+
disordered = Species(name="X", chemical_symbols=("Na", "K"), concentration=(0.5, 0.5))
210+
attached = Species(name="C", chemical_symbols=("C",), concentration=(1.0,), attached=("H",), nattached=(1,))
211+
212+
for label, species in (("ordered", ordered), ("disordered", disordered), ("attached", attached)):
213+
structure = Structure(cell, [[0, 0, 0]], [species], [species.name])
214+
(record,) = list(StructureEntryProvider({"x": structure}).records("structures"))
215+
assert record["nperiodic_dimensions"] == 3, label
216+
assert record["dimension_types"] == [1, 1, 1], label
217+
218+
# The composition properties still null out for those, which is correct.
219+
(record,) = list(
220+
StructureEntryProvider({"x": Structure(cell, [[0, 0, 0]], [disordered], ["X"])}).records("structures")
221+
)
222+
assert record["chemical_formula_reduced"] is None
223+
assert record["elements_ratios"] is None
224+
225+
226+
def test_an_entry_with_no_structure_still_serves_null_periodicity() -> None:
227+
"""There is no structure to describe, so unknown is the honest answer there."""
228+
(record,) = list(StructureEntryProvider({"empty": None}).records("structures"))
229+
assert record["nperiodic_dimensions"] is None
230+
assert record["dimension_types"] is None

0 commit comments

Comments
 (0)