Skip to content

aiida

Convert an OPTIMADE structure, in the format of StructureResource to an AiiDA StructureData Node.

For more information on the AiiDA code see their website.

This conversion function relies on the aiida-core package.

AIIDA_NOT_FOUND = 'AiiDA not found, cannot convert structure to an AiiDA StructureData' module-attribute

__all__ = ('get_aiida_structure_data') module-attribute

AdapterPackageNotFound

Bases: OptimadeWarning

The package for an adapter cannot be found.

Source code in optimade/adapters/warnings.py
6
7
class AdapterPackageNotFound(OptimadeWarning):
    """The package for an adapter cannot be found."""

ConversionWarning

Bases: OptimadeWarning

A non-critical error/fallback/choice happened during conversion of an entry to format.

Source code in optimade/adapters/warnings.py
10
11
class ConversionWarning(OptimadeWarning):
    """A non-critical error/fallback/choice happened during conversion of an entry to format."""

OptimadeStructure

Bases: EntryResource

Representing a structure.

Source code in optimade/models/structures.py
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
class StructureResource(EntryResource):
    """Representing a structure."""

    type: Annotated[
        Literal["structures"],
        StrictField(
            description="""The name of the type of an entry.

- **Type**: string.

- **Requirements/Conventions**:
    - **Support**: MUST be supported by all implementations, MUST NOT be `null`.
    - **Query**: MUST be a queryable property with support for all mandatory filter features.
    - **Response**: REQUIRED in the response.
    - MUST be an existing entry type.
    - The entry of type `<type>` and ID `<id>` MUST be returned in response to a request for `/<type>/<id>` under the versioned base URL.

- **Examples**:
    - `"structures"`""",
            pattern="^structures$",
            support=SupportLevel.MUST,
            queryable=SupportLevel.MUST,
        ),
    ] = "structures"

    attributes: StructureResourceAttributes

OptimadeStructureSpecies

Bases: BaseModel

A list describing the species of the sites of this structure.

Species can represent pure chemical elements, virtual-crystal atoms representing a statistical occupation of a given site by multiple chemical elements, and/or a location to which there are attached atoms, i.e., atoms whose precise location are unknown beyond that they are attached to that position (frequently used to indicate hydrogen atoms attached to another element, e.g., a carbon with three attached hydrogens might represent a methyl group, -CH3).

  • Examples:
    • [ {"name": "Ti", "chemical_symbols": ["Ti"], "concentration": [1.0]} ]: any site with this species is occupied by a Ti atom.
    • [ {"name": "Ti", "chemical_symbols": ["Ti", "vacancy"], "concentration": [0.9, 0.1]} ]: any site with this species is occupied by a Ti atom with 90 % probability, and has a vacancy with 10 % probability.
    • [ {"name": "BaCa", "chemical_symbols": ["vacancy", "Ba", "Ca"], "concentration": [0.05, 0.45, 0.5], "mass": [0.0, 137.327, 40.078]} ]: any site with this species is occupied by a Ba atom with 45 % probability, a Ca atom with 50 % probability, and by a vacancy with 5 % probability. The mass of this site is (on average) 88.5 a.m.u.
    • [ {"name": "C12", "chemical_symbols": ["C"], "concentration": [1.0], "mass": [12.0]} ]: any site with this species is occupied by a carbon isotope with mass 12.
    • [ {"name": "C13", "chemical_symbols": ["C"], "concentration": [1.0], "mass": [13.0]} ]: any site with this species is occupied by a carbon isotope with mass 13.
    • [ {"name": "CH3", "chemical_symbols": ["C"], "concentration": [1.0], "attached": ["H"], "nattached": [3]} ]: any site with this species is occupied by a methyl group, -CH3, which is represented without specifying precise positions of the hydrogen atoms.
Source code in optimade/models/structures.py
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
class Species(BaseModel):
    """A list describing the species of the sites of this structure.

    Species can represent pure chemical elements, virtual-crystal atoms representing a
    statistical occupation of a given site by multiple chemical elements, and/or a
    location to which there are attached atoms, i.e., atoms whose precise location are
    unknown beyond that they are attached to that position (frequently used to indicate
    hydrogen atoms attached to another element, e.g., a carbon with three attached
    hydrogens might represent a methyl group, -CH3).

    - **Examples**:
        - `[ {"name": "Ti", "chemical_symbols": ["Ti"], "concentration": [1.0]} ]`: any site with this species is occupied by a Ti atom.
        - `[ {"name": "Ti", "chemical_symbols": ["Ti", "vacancy"], "concentration": [0.9, 0.1]} ]`: any site with this species is occupied by a Ti atom with 90 % probability, and has a vacancy with 10 % probability.
        - `[ {"name": "BaCa", "chemical_symbols": ["vacancy", "Ba", "Ca"], "concentration": [0.05, 0.45, 0.5], "mass": [0.0, 137.327, 40.078]} ]`: any site with this species is occupied by a Ba atom with 45 % probability, a Ca atom with 50 % probability, and by a vacancy with 5 % probability. The mass of this site is (on average) 88.5 a.m.u.
        - `[ {"name": "C12", "chemical_symbols": ["C"], "concentration": [1.0], "mass": [12.0]} ]`: any site with this species is occupied by a carbon isotope with mass 12.
        - `[ {"name": "C13", "chemical_symbols": ["C"], "concentration": [1.0], "mass": [13.0]} ]`: any site with this species is occupied by a carbon isotope with mass 13.
        - `[ {"name": "CH3", "chemical_symbols": ["C"], "concentration": [1.0], "attached": ["H"], "nattached": [3]} ]`: any site with this species is occupied by a methyl group, -CH3, which is represented without specifying precise positions of the hydrogen atoms.

    """

    name: Annotated[
        str,
        OptimadeField(
            description="""Gives the name of the species; the **name** value MUST be unique in the `species` list.""",
            support=SupportLevel.MUST,
            queryable=SupportLevel.OPTIONAL,
        ),
    ]

    chemical_symbols: Annotated[
        list[ChemicalSymbol],
        OptimadeField(
            description="""MUST be a list of strings of all chemical elements composing this species. Each item of the list MUST be one of the following:

- a valid chemical-element symbol, or
- the special value `"X"` to represent a non-chemical element, or
- the special value `"vacancy"` to represent that this site has a non-zero probability of having a vacancy (the respective probability is indicated in the `concentration` list, see below).

If any one entry in the `species` list has a `chemical_symbols` list that is longer than 1 element, the correct flag MUST be set in the list `structure_features`.""",
            support=SupportLevel.MUST,
            queryable=SupportLevel.OPTIONAL,
        ),
    ]

    concentration: Annotated[
        list[float],
        OptimadeField(
            description="""MUST be a list of floats, with same length as `chemical_symbols`. The numbers represent the relative concentration of the corresponding chemical symbol in this species. The numbers SHOULD sum to one. Cases in which the numbers do not sum to one typically fall only in the following two categories:

- Numerical errors when representing float numbers in fixed precision, e.g. for two chemical symbols with concentrations `1/3` and `2/3`, the concentration might look something like `[0.33333333333, 0.66666666666]`. If the client is aware that the sum is not one because of numerical precision, it can renormalize the values so that the sum is exactly one.
- Experimental errors in the data present in the database. In this case, it is the responsibility of the client to decide how to process the data.

Note that concentrations are uncorrelated between different site (even of the same species).""",
            support=SupportLevel.MUST,
            queryable=SupportLevel.OPTIONAL,
        ),
    ]

    mass: Annotated[
        Optional[list[float]],
        OptimadeField(
            description="""If present MUST be a list of floats expressed in a.m.u.
Elements denoting vacancies MUST have masses equal to 0.""",
            unit="a.m.u.",
            support=SupportLevel.OPTIONAL,
            queryable=SupportLevel.OPTIONAL,
        ),
    ] = None

    original_name: Annotated[
        Optional[str],
        OptimadeField(
            description="""Can be any valid Unicode string, and SHOULD contain (if specified) the name of the species that is used internally in the source database.

Note: With regards to "source database", we refer to the immediate source being queried via the OPTIMADE API implementation.""",
            support=SupportLevel.OPTIONAL,
            queryable=SupportLevel.OPTIONAL,
        ),
    ] = None

    attached: Annotated[
        Optional[list[str]],
        OptimadeField(
            description="""If provided MUST be a list of length 1 or more of strings of chemical symbols for the elements attached to this site, or "X" for a non-chemical element.""",
            support=SupportLevel.OPTIONAL,
            queryable=SupportLevel.OPTIONAL,
        ),
    ] = None

    nattached: Annotated[
        Optional[list[int]],
        OptimadeField(
            description="""If provided MUST be a list of length 1 or more of integers indicating the number of attached atoms of the kind specified in the value of the :field:`attached` key.""",
            support=SupportLevel.OPTIONAL,
            queryable=SupportLevel.OPTIONAL,
        ),
    ] = None

    @field_validator("concentration", "mass", mode="after")
    def validate_concentration_and_mass(
        cls, value: Optional[list[float]], info: "ValidationInfo"
    ) -> Optional[list[float]]:
        if not value:
            return value

        if info.data.get("chemical_symbols"):
            if len(value) != len(info.data["chemical_symbols"]):
                raise ValueError(
                    f"Length of concentration ({len(value)}) MUST equal length of "
                    f"chemical_symbols ({len(info.data['chemical_symbols'])})"
                )
            return value

        raise ValueError(
            f"Could not validate {info.field_name!r} as 'chemical_symbols' is missing/invalid."
        )

    @field_validator("attached", "nattached", mode="after")
    @classmethod
    def validate_minimum_list_length(
        cls, value: Optional[Union[list[str], list[int]]]
    ) -> Optional[Union[list[str], list[int]]]:
        if value is not None and len(value) < 1:
            raise ValueError(
                "The list's length MUST be 1 or more, instead it was found to be "
                f"{len(value)}"
            )
        return value

    @model_validator(mode="after")
    def attached_nattached_mutually_exclusive(self) -> "Species":
        if (self.attached is None and self.nattached is not None) or (
            self.attached is not None and self.nattached is None
        ):
            raise ValueError(
                f"Either both or none of attached ({self.attached}) and nattached "
                f"({self.nattached}) MUST be set."
            )

        if (
            self.attached is not None
            and self.nattached is not None
            and len(self.attached) != len(self.nattached)
        ):
            raise ValueError(
                f"attached ({self.attached}) and nattached ({self.nattached}) MUST be "
                "lists of equal length."
            )

        return self

get_aiida_structure_data(optimade_structure)

Get AiiDA StructureData from OPTIMADE structure.

Parameters:

Name Type Description Default
optimade_structure StructureResource

OPTIMADE structure.

required

Returns:

Type Description
StructureData

AiiDA StructureData Node.

Source code in optimade/adapters/structures/aiida.py
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
def get_aiida_structure_data(optimade_structure: OptimadeStructure) -> StructureData:
    """Get AiiDA `StructureData` from OPTIMADE structure.

    Parameters:
        optimade_structure: OPTIMADE structure.

    Returns:
        AiiDA `StructureData` Node.

    """
    if "optimade.adapters" in repr(globals().get("StructureData")):
        warn(AIIDA_NOT_FOUND, AdapterPackageNotFound)
        return None

    attributes = optimade_structure.attributes

    # Convert null/None values to float("nan")
    lattice_vectors, adjust_cell = pad_cell(attributes.lattice_vectors)  # type: ignore[arg-type]
    structure = StructureData(cell=lattice_vectors)

    # If species not provided, infer data from species_at_sites
    species: Optional[list[OptimadeStructureSpecies]] = attributes.species
    if not species:
        species = species_from_species_at_sites(attributes.species_at_sites)  # type: ignore[arg-type]

    # Add Kinds
    for kind in species:
        symbols = []
        concentration = []
        mass = 0.0
        for index, chemical_symbol in enumerate(kind.chemical_symbols):
            # NOTE: The non-chemical element identifier "X" is identical to how AiiDA handles this,
            # so it will be treated the same as any other true chemical identifier.
            if chemical_symbol == "vacancy":
                # Skip. This is how AiiDA handles vacancies;
                # to not include them, while keeping the concentration in a site less than 1.
                continue
            else:
                symbols.append(chemical_symbol)
                concentration.append(kind.concentration[index])

                # AiiDA needs a definition for the mass, and for it to be > 0
                # mass is OPTIONAL for OPTIMADE structures
                if kind.mass:
                    mass += kind.concentration[index] * kind.mass[index]

        if not mass:
            warn(
                f"No mass defined for <species(name={kind.name!r})>, will default to setting mass to 1.0.",
                ConversionWarning,
            )

        structure.append_kind(
            Kind(
                symbols=symbols, weights=concentration, mass=mass or 1.0, name=kind.name
            )
        )

    # Add Sites
    for index in range(attributes.nsites):  # type: ignore[arg-type]
        # range() to ensure 1-to-1 between kind and site
        structure.append_site(
            Site(
                kind_name=attributes.species_at_sites[index],  # type: ignore[index]
                position=attributes.cartesian_site_positions[index],  # type: ignore[index]
            )
        )

    if adjust_cell:
        structure._adjust_default_cell(
            pbc=[bool(dim.value) for dim in attributes.dimension_types]  # type: ignore[union-attr]
        )

    return structure

pad_cell(lattice_vectors, padding=None)

Turn any null/None values into a float in given tuple of lattice_vectors.

Parameters:

Name Type Description Default
lattice_vectors tuple[Vector3D, Vector3D, Vector3D]

A 3x3 cartesian cell. This is the lattice_vectors attribute.

required
padding Optional[float]

A value with which null or None values should be replaced.

None

Returns:

Type Description
tuple

The possibly redacted/padded lattice_vectors and a bool declaring whether or not

tuple

the value has been redacted/padded or not, i.e., whether it contained null or None

tuple

values.

Source code in optimade/adapters/structures/utils.py
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
def pad_cell(
    lattice_vectors: tuple[Vector3D, Vector3D, Vector3D],
    padding: Optional[float] = None,
) -> tuple:  # Setting this properly makes MkDocs fail.
    """Turn any `null`/`None` values into a `float` in given `tuple` of
    [`lattice_vectors`][optimade.models.structures.StructureResourceAttributes.lattice_vectors].

    Parameters:
        lattice_vectors: A 3x3 cartesian cell. This is the
            [`lattice_vectors`][optimade.models.structures.StructureResourceAttributes.lattice_vectors]
            attribute.
        padding: A value with which `null` or `None` values should be replaced.

    Returns:
        The possibly redacted/padded `lattice_vectors` and a `bool` declaring whether or not
        the value has been redacted/padded or not, i.e., whether it contained `null` or `None`
        values.

    """
    return _pad_iter_of_iters(
        iterable=lattice_vectors,
        padding=padding,
        outer=tuple,
        inner=tuple,
    )

species_from_species_at_sites(species_at_sites)

When a list of species dictionaries is not provided, this function can be used to infer the species from the provided species_at_sites.

In this use case, species_at_sites is assumed to provide a list of element symbols, and refers to situations with no mixed occupancy, i.e., the constructed species list will contain all unique species with concentration equal to 1 and the species_at_site tag will be used as the chemical symbol.

Parameters:

Name Type Description Default
species_at_sites list[str]

The list found under the species_at_sites field.

required

Returns:

Type Description
list[Species]

An OPTIMADE species list.

Source code in optimade/adapters/structures/utils.py
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
def species_from_species_at_sites(
    species_at_sites: list[str],
) -> list[OptimadeStructureSpecies]:
    """When a list of species dictionaries is not provided, this function
    can be used to infer the species from the provided species_at_sites.

    In this use case, species_at_sites is assumed to provide a list of
    element symbols, and refers to situations with no mixed occupancy, i.e.,
    the constructed species list will contain all unique species with
    concentration equal to 1 and the species_at_site tag will be used as
    the chemical symbol.

    Parameters:
        species_at_sites: The list found under the species_at_sites field.

    Returns:
        An OPTIMADE species list.

    """
    return [
        OptimadeStructureSpecies(name=_, concentration=[1.0], chemical_symbols=[_])
        for _ in set(species_at_sites)
    ]