Skip to content
Open
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
22 changes: 22 additions & 0 deletions openapi/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -5416,6 +5416,28 @@
"x-optimade-support": "should",
"x-optimade-unit": "\u00c5"
},
"fractional_site_positions": {
"anyOf": [
{
"items": {
"items": {
"type": "number"
},
"type": "array",
"maxItems": 3,
"minItems": 3
},
"type": "array"
},
{
"type": "null"
}
],
"title": "Fractional Site Positions",
"description": "Fractional coordinates (positions) of each site in the structure.\n A site is usually used to describe positions of atoms; what atoms can be encountered at a given site is conveyed by the :property:`species_at_sites` property, and the species themselves are described in the :property:`species` property.\n Site coordinates MAY be given as `cartesian_site_positions`_, `fractional_site_positions`_, or both.\n When symmetry operations given in `space_group_symmetry_operations_xyz`_ are applied, they MUST be applied to coordinates given in the `fractional_site_positions`_ array.\n\n- **Type**: list of list of floats\n\n- **Requirements/Conventions**:\n - **Support**: SHOULD be supported by all implementations, i.e., SHOULD NOT be :val:`null`.\n - **Query**: Support for queries on this property is OPTIONAL.\n If supported, filters MAY support only a subset of comparison operators.\n - It MUST be a list of length equal to the number of sites in the structure, where every element is a list of the three fractional coordinates of a site expressed as float values in the fractions of the unit cell vectors given by the `lattice_vectors`_ property.\n - An entry MAY have multiple sites at the same site position (for a relevant use of this, see e.g., the property `assemblies`_).\n - **Note**: Since both `cartesian_site_positions`_ and the `fractional_site_positions`_ always describe the same sites, they MUST always have the same number of elements, equal to the number of elements in the `species_at_sites`_ array.\n\n- **Examples**:\n\n - `[[0,0,0],[0,0,0.2]]` indicates a structure with two sites, one sitting at the origin and one along the third unit cell axis (*c*-axis), 1/5-th (0.2) of the vector *c* in the direction of the vector *c* from the origin.",
"x-optimade-queryable": "optional",
"x-optimade-support": "should"
},
"nsites": {
"anyOf": [
{
Expand Down
1 change: 1 addition & 0 deletions optimade/adapters/structures/ase.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ def from_ase_atoms(atoms: Atoms) -> StructureResourceAttributes:

attributes = {}
attributes["cartesian_site_positions"] = atoms.positions.tolist()
attributes["fractional_site_positions"] = atoms.get_scaled_positions().tolist()
attributes["lattice_vectors"] = atoms.cell.tolist()
attributes["species_at_sites"] = atoms.get_chemical_symbols()
attributes["elements_ratios"] = elements_ratios_from_species_at_sites(
Expand Down
6 changes: 2 additions & 4 deletions optimade/adapters/structures/cif.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,16 +93,14 @@ def get_cif(

# Since some structure viewers are having issues with cartesian coordinates,
# we calculate the fractional coordinates if this is a 3D structure and we have all the necessary information.
if not hasattr(attributes, "fractional_site_positions"):
if not getattr(attributes, "fractional_site_positions", None):
attributes.fractional_site_positions = fractional_coordinates(
cell=attributes.lattice_vectors, # type:ignore[arg-type]
cartesian_positions=attributes.cartesian_site_positions, # type:ignore[arg-type]
)

# NOTE: This is otherwise a bit ahead of its time, since this OPTIMADE property is part of an open PR.
# See https://github.com/Materials-Consortia/OPTIMADE/pull/206
coord_type = (
"fract" if hasattr(attributes, "fractional_site_positions") else "Cartn"
"fract" if getattr(attributes, "fractional_site_positions", None) else "Cartn"
)

cif += (
Expand Down
4 changes: 2 additions & 2 deletions optimade/adapters/structures/proteindatabank.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ def get_pdbx_mmcif(

# Since some structure viewers are having issues with cartesian coordinates,
# we calculate the fractional coordinates if this is a 3D structure and we have all the necessary information.
if not hasattr(attributes, "fractional_site_positions"):
if not getattr(attributes, "fractional_site_positions", None):
attributes.fractional_site_positions = fractional_coordinates(
cell=attributes.lattice_vectors, # type: ignore[arg-type]
cartesian_positions=attributes.cartesian_site_positions, # type: ignore[arg-type]
Expand Down Expand Up @@ -136,7 +136,7 @@ def get_pdbx_mmcif(
# NOTE: This is otherwise a bit ahead of its time, since this OPTIMADE property is part of an open PR.
# See https://github.com/Materials-Consortia/OPTIMADE/pull/206
coord_type = (
"fract" if hasattr(attributes, "fractional_site_positions") else "Cartn"
"fract" if getattr(attributes, "fractional_site_positions", None) else "Cartn"
)

cif += (
Expand Down
139 changes: 139 additions & 0 deletions optimade/models/structures.py
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,9 @@ def check_self_consistency(self) -> "Assembly":
CORRELATED_STRUCTURE_FIELDS = (
{"dimension_types", "nperiodic_dimensions"},
{"cartesian_site_positions", "species_at_sites"},
{"fractional_site_positions", "species_at_sites"},
{"nsites", "cartesian_site_positions"},
{"nsites", "fractional_site_positions"},
{"species_at_sites", "species"},
)

Expand Down Expand Up @@ -738,6 +740,87 @@ class StructureResourceAttributes(EntryResourceAttributes):
),
] = None

fractional_site_positions: Annotated[
list[Vector3D] | None,
OptimadeField(
description="""Fractional coordinates (positions) of each site in the structure.
A site is usually used to describe positions of atoms; what atoms can be encountered at a given site is conveyed by the `species_at_sites` property, and the species themselves are described in the `species` property.
Site coordinates MAY be given as `cartesian_site_positions`_, `fractional_site_positions`_, or both.
When symmetry operations given in `space_group_symmetry_operations_xyz`_ are applied, they MUST be applied to coordinates given in the `fractional_site_positions`_ array.

- **Type**: list of list of floats

- **Requirements/Conventions**:
- **Support**: SHOULD be supported by all implementations, i.e., SHOULD NOT be `null`.
- **Query**: Support for queries on this property is OPTIONAL.
If supported, filters MAY support only a subset of comparison operators.
- It MUST be a list of length equal to the number of sites in the structure, where every element is a list of the three fractional coordinates of a site expressed as float values in the fractions of the unit cell vectors given by the `lattice_vectors`_ property.
- An entry MAY have multiple sites at the same site position (for a relevant use of this, see e.g., the property `assemblies`_).
- **Note**: Since both `cartesian_site_positions`_ and the `fractional_site_positions`_ always describe the same sites, they MUST always have the same number of elements, equal to the number of elements in the `species_at_sites`_ array.

- **Examples**:

- `[[0,0,0],[0,0,0.2]]` indicates a structure with two sites, one sitting at the origin and one along the third unit cell axis (*c*-axis), 1/5-th (0.2) of the vector *c* in the direction of the vector *c* from the origin.""",
support=SupportLevel.SHOULD,
queryable=SupportLevel.OPTIONAL,
),
] = None

site_coordinate_span: Annotated[
str | None,
OptimadeField(
description="""Indicates the extent of the material (crystal) described in the response.
In particular, properties `cartesian_site_positions` and `fractional_site_positions` MUST contain all sites *belonging* to the described extent.
- **Type**: string
- **Requirements/conventions**:

- **Support**: MUST be supported by all implementations if coordinates in `fractional_site_positions` are returned.
It SHOULD be supported if coordinates in `cartesian_site_positions` are returned.
- **Query**: Support for queries on this property is OPTIONAL.

- The value of this property MUST be one of the following:

- `"fundamental_domain"`: means that sites described in the response span a fundamental domain (Vinberg, 1994; European Mathematical Society, 2020) of a periodic system.
When a server indicates this span in the response, it MUST provide those sites that enable reconstruction of the whole periodic system by applying symmetry operations from `space_group_symmetry_operations_xyz` property and then applying translations given by `lattice_vectors`.
The fundamental domain does not need to be a connected space region.
- `"asymmetric_unit"`: all sites are in a simply connected space region that is a fundamental domain, as per IUCr Online Dictionary of Crystallography definition (IUCr, 2017).
- `"molecular_fundamental_domain"`: a fundamental domain where all atoms connected by covalent or donor-acceptor coordination bonds are adjacent to each other, placed at a bond distance.
- `"molecular_asymmetric_unit"`: an asymmetric unit (a simply connected fundamental domain) where all atoms bound by covalent or donor-acceptor coordination bonds are adjacent to each other, placed at a bond distance.
- `"unit_cell"`: a full unit cell of a periodic system (crystal), i.e., any repeating unit chosen by the server defined by the property `lattice_vectors`.
For this span, the server MUST provide a set of sites in the response that can be used to reconstruct the whole periodic system (crystal) by simply applying translations from the `lattice_vectors` property to those sites.
- `"molecular_unit_cell"`: same as `"unit_cell"`, but in addition places atoms that are bound by covalent or coordination bonds at a bond distance from each other.
- `"molecular_entities"`: sets of atoms that are bound by covalent or coordination bonds, as per IUPAC definition of a 'molecular entity'.
This set of sites MAY be larger than a fundamental domain.
- `"other"`: any other collection of sites that does not fit the enumerated values above.
- `null`: if omitted or `null`, the default value of `site_coordinate_span` is `unit_cell`.
This is the assumed behavior of all main implementations before the `site_coordinate_span` definition was introduced.

- **Note**: In all cases it is RECOMMENDED that only the minimal set of the sites that is needed to reconstruct the whole material is provided.
For example, for the 'unit_cell' span the server SHOULD NOT return sites that can be obtained from other returned sites through the translations given in `lattice_vectors`; only a non-redundant set of sites SHOULD be provided.

- **Bibliographic References**:

E.B. Vinberg (originator). (1994) Encyclopedia of Mathematics. ISBN [1402006098](https://isbnsearch.org/isbn/1402006098), URL: https://encyclopediaofmath.org/index.php?title=Fundamental_domain&oldid=13590 [accessed 2025-04-30T08:55+03:00].

European Mathematical Society (2020). Fundamental domain. Encyclopedia of Mathematics. URL: http://encyclopediaofmath.org/index.php?title=Fundamental_domain&oldid=47023 [accessed 2025-04-30T08:53+03:00].

IUCr (2017). Asymmetric unit. Online Dictionary of Crystallography, URL: https://dictionary.iucr.org/Asymmetric_unit [accessed 2025-04-30T09:01+03:00].""",
support=SupportLevel.SHOULD,
queryable=SupportLevel.OPTIONAL,
),
] = None

site_coordinate_span_description: Annotated[
str | None,
OptimadeField(
description="""Human-readable semi-formal characterization of the coordinate span when the `site_coordinate_span` property has value `"other"`.
- **Type**: string
- **Example**: `"Two fullerene molecules with a VdW contact."`""",
support=SupportLevel.SHOULD,
queryable=SupportLevel.OPTIONAL,
),
] = None

nsites: Annotated[
int | None,
OptimadeField(
Expand Down Expand Up @@ -997,6 +1080,53 @@ class StructureResourceAttributes(EntryResourceAttributes):
),
]

optimization_type: Annotated[
str | None,
OptimadeField(
title="Optimization Type",
description="""A string that classifies the type of optimization that has resulted in the structural data.

If the property is `null` or omitted, no information is provided about the type of optimization used to obtain the structural data.

If present and not `null`, the property SHOULD take one of the following values:

* `experimental`: the structure results from an optimization or refinement process part of an experimental technique, e.g., minimization of the discrepancy between observed and predicted scattered amplitudes from diffraction data.

* `hybrid`: the structure is the result of the combination of an experiment and further optimization based on a reasonable theoretical energy model so that it remains a fair representation of the original experimental structure.
For example, experimental structures relaxed using *ab initio* calculations are in this category.
Structures where the experimental coordinates are kept, but one or more elements are substituted for other elements, are not included in this category.

* `global`: the structure has been optimized using a theoretical technique based on a reasonable energy model in a way that takes into account the global energy surface.
The structure has been optimized into the global energy minimum or into a local minimum within an energy range of the global minimum commonly considered for potential metastability (typically on the scale of 100 meV/atom).
A common technique for this type of optimization is to construct the convex hull of thermodynamical stability from the known minima and dismiss structures outside the relevant energy range.

* `local`: the structure has been optimized using a theoretical technique based on a reasonable energy model into a local minimum of the energy surface.
For example, structures relaxed using *ab initio* calculations without consideration of the energy of other minima in configuration space qualify for this category.

* `none`: the structure has not undergone an optimization process and is thus, in some sense, arbitrary.
Structures of this kind can come from, e.g., randomly generated coordinates or non-equilibrium snapshots.

* `indeterminate`: the database declares that the type of optimization used for this specific entry cannot be determined, e.g., because that information is missing.
This value represents a stronger statement (that the database knows that the type of optimization is not known) than an omitted classification (i.e., the field is missing or has the value `null`) which communicates that the property is unknown only in the sense discussed in the section `Properties with an unknown value`.)

* `other`: the structure is the result of some optimization process, but none of the other categories correctly represents the type of optimization used.

Other strings prefixed by a database-specific prefix, e.g., `_exmpl_optimized_on_fixed_grid`, SHOULD NOT be used.
Other non-standard strings MUST NOT be used.
Clients encountering unrecognized strings SHOULD treat them to mean the same as the field having the value `"other"`.

Structures produced by AI models and other techniques that have been reasonably tested to reliably generate results equivalent to structural optimization using energy models SHOULD be classified the same as if that type of energy model had been used.

**Explained examples**:

- For a structure entry directly encoding structural information obtained from a neutron diffraction experiment: `"experimental"`.

- For a structure entry that encodes the structural information from a theoretical relaxation of an `"experimental"` entry using computational software that implements density functional theory: `"hybrid"`.""",
support=SupportLevel.OPTIONAL,
queryable=SupportLevel.OPTIONAL,
),
] = None

@model_validator(mode="after")
def warn_on_missing_correlated_fields(self) -> "StructureResourceAttributes":
"""Emit warnings if a field takes a null value when a value
Expand Down Expand Up @@ -1179,6 +1309,15 @@ def validate_nsites(self) -> "StructureResourceAttributes":
"cartesian_site_positions (value: "
f"{len(self.cartesian_site_positions)})"
)

if self.fractional_site_positions and self.nsites != len(
self.fractional_site_positions
):
raise ValueError(
f"nsites (value: {self.nsites}) MUST equal length of "
"fractional_site_positions (value: "
f"{len(self.fractional_site_positions)})"
)
return self

@model_validator(mode="after")
Expand Down
4 changes: 2 additions & 2 deletions optimade/server/data/test_data.jsonl

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions optimade/server/mappers/structures.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ class StructureMapper(BaseResourceMapper):
("elements", "nelements"),
("elements_ratios", "nelements"),
("cartesian_site_positions", "nsites"),
("fractional_site_positions", "nsites"),
("species_at_sites", "nsites"),
)
ENTRY_RESOURCE_CLASS = StructureResource
12 changes: 9 additions & 3 deletions tests/adapters/structures/test_structures.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,17 +190,23 @@ def compare_lossy_conversion(
"attached",
"immutable_id",
"species",
"fractional_site_positions",
"site_coordinate_span",
"site_coordinate_span_description",
"space_group_symmetry_operations_xyz",
"space_group_symbol_hall",
"space_group_symbol_hermann_mauguin",
"space_group_symbol_hermann_mauguin_extended",
"space_group_it_number",
"optimization_type",
)
array_keys = (
"cartesian_site_positions",
"fractional_site_positions",
"lattice_vectors",
)
array_keys = ("cartesian_site_positions", "lattice_vectors")

for k in reconverted_structure_attributes:
if k not in lossy_keys:
if k not in lossy_keys and k in structure_attributes:
if k in array_keys and np is not None:
np.testing.assert_almost_equal(
reconverted_structure_attributes[k], structure_attributes[k]
Expand Down
7 changes: 6 additions & 1 deletion tests/models/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,12 @@ def good_structure() -> dict:
"dimension_types": [1, 1, 1],
"nperiodic_dimensions": 3,
"lattice_vectors": [[4.0, 0.0, 0.0], [0.0, 4.0, 0.0], [0.0, 1.0, 4.0]],
"cartesian_site_positions": [[0, 0, 0], [0, 0, 0], [0, 0, 0]],
"cartesian_site_positions": [[0, 0, 0], [2.0, 2.0, 0.0], [0.0, 0.5, 2.0]],
"fractional_site_positions": [
[0.0, 0.0, 0.0],
[0.5, 0.5, 0.0],
[0.0, 0.0, 0.5],
],
"species": [
{"name": "Si", "chemical_symbols": ["Si"], "concentration": [1.0]},
{"name": "Ge", "chemical_symbols": ["Ge"], "concentration": [1.0]},
Expand Down
Loading
Loading