Skip to content

adapter

EntryAdapter

Base class for lazy resource entry adapters.

Attributes:

Name Type Description
ENTRY_RESOURCE type[EntryResource]

Entry resource to store entry as.

_type_converters dict[str, Callable]

Dictionary of valid conversion types for entry.

_type_ingesters dict[str, Callable]

Dictionary of valid ingestion types mapped to ingestion functions.

_type_ingesters_by_type dict[str, type]

Dictionary mapping the keys of _type_ingesters to data types that can be ingested.

as_<_type_converters> dict[str, type]

Convert entry to a type listed in _type_converters.

from_<_type_converters> dict[str, type]

Convert an external type to the corresponding OPTIMADE model.

Source code in optimade/adapters/base.py
 29
 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
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
214
215
216
217
218
class EntryAdapter:
    """
    Base class for lazy resource entry adapters.

    Attributes:
        ENTRY_RESOURCE: Entry resource to store entry as.
        _type_converters: Dictionary of valid conversion types for entry.
        _type_ingesters: Dictionary of valid ingestion types mapped to ingestion functions.
        _type_ingesters_by_type: Dictionary mapping the keys of `_type_ingesters` to data
            types that can be ingested.
        as_<_type_converters>: Convert entry to a type listed in `_type_converters`.
        from_<_type_converters>: Convert an external type to the corresponding OPTIMADE model.

    """

    ENTRY_RESOURCE: type[EntryResource] = EntryResource
    _type_converters: dict[str, Callable] = {}
    _type_ingesters: dict[str, Callable] = {}
    _type_ingesters_by_type: dict[str, type] = {}

    def __init__(self, entry: dict[str, Any]) -> None:
        """
        Parameters:
            entry (dict): A JSON OPTIMADE single resource entry.
        """
        self._converted: dict[str, Any] = {}

        self._entry = self.ENTRY_RESOURCE(**entry)

        # Note that these return also the default values for otherwise non-provided properties.
        self._common_converters = {
            # Return JSON serialized string, see https://pydantic-docs.helpmanual.io/usage/exporting_models/#modeljson
            "json": self.entry.model_dump_json,
            # Return Python dict, see https://pydantic-docs.helpmanual.io/usage/exporting_models/#modeldict
            "dict": self.entry.model_dump,
        }

    @property
    def entry(self) -> EntryResource:
        """Get OPTIMADE entry.

        Returns:
            The entry resource.

        """
        return self._entry

    def convert(self, format: str) -> Any:
        """Convert OPTIMADE entry to desired format.

        Parameters:
            format (str): Type or format to which the entry should be converted.

        Raises:
            AttributeError: If `format` can not be found in `_type_converters` or `_common_converters`.

        Returns:
            The converted entry according to the desired format or type.

        """
        if (
            format not in self._type_converters
            and format not in self._common_converters
        ):
            raise AttributeError(
                f"Non-valid entry type to convert to: {format}\nValid entry types: "
                f"{tuple(self._type_converters.keys()) + tuple(self._common_converters.keys())}"
            )

        if self._converted.get(format, None) is None:
            if format in self._type_converters:
                self._converted[format] = self._type_converters[format](self.entry)
            else:
                self._converted[format] = self._common_converters[format]()

        return self._converted[format]

    @classmethod
    def ingest_from(cls, data: Any, format: Optional[str] = None) -> Any:
        """Convert desired format to OPTIMADE format.

        Parameters:
            data (Any): The data to convert.
            format (str): Type or format to which the entry should be converted.

        Raises:
            AttributeError: If `format` can not be found in `_type_ingesters`.

        Returns:
            The ingested Structure.

        """

        if format is None:
            for key, instance_type in cls._type_ingesters_by_type.items():
                if isinstance(data, instance_type):
                    format = key
                    break

            else:
                raise AttributeError(
                    f"Non entry type to data of type {type(data)} from.\n"
                    f"Valid entry types: {tuple(cls._type_ingesters.keys())}"
                )

        if format not in cls._type_ingesters:
            raise AttributeError(
                f"Non-valid entry type to ingest from: {format}\n"
                f"Valid entry types: {tuple(cls._type_ingesters.keys())}"
            )

        return cls(
            {
                "attributes": cls._type_ingesters[format](data).model_dump(),
                "id": "",
                "type": "structures",
            }
        )

    @staticmethod
    def _get_model_attributes(
        starting_instances: Union[tuple[BaseModel, ...], list[BaseModel]], name: str
    ) -> Any:
        """Helper method for retrieving the OPTIMADE model's attribute, supporting "."-nested attributes"""
        for res in starting_instances:
            nested_attributes = name.split(".")
            for nested_attribute in nested_attributes:
                if nested_attribute in getattr(res, "model_fields", {}):
                    res = getattr(res, nested_attribute)
                else:
                    res = None
                    break
            if res is not None:
                return res
        raise AttributeError

    def __getattr__(self, name: str) -> Any:
        """Get converted entry or attribute from OPTIMADE entry.

        Support any level of "."-nested OPTIMADE `ENTRY_RESOURCE` attributes, e.g.,
        `attributes.species` for [`StuctureResource`][optimade.models.structures.StructureResource].

        Note:
            All nested attributes must individually be subclasses of `pydantic.BaseModel`,
            i.e., one can not access nested attributes in lists by passing a "."-nested `name` to this method,
            e.g., `attributes.species.name` or `attributes.species[0].name` will not work for variable `name`.

        Order:

        - Try to return converted entry if using `as_<_type_converters key>`.
        - Try to return OPTIMADE `ENTRY_RESOURCE` (nested) attribute.
        - Try to return OPTIMADE `ENTRY_RESOURCE.attributes` (nested) attribute.
        - Raise `AttributeError`.

        Parameters:
            name (str): Requested attribute.

        Raises:
            AttributeError: If the requested attribute is not recognized.
                See above for the description of the order in which an attribute is tested for validity.

        """
        # as_<entry_type>
        if name.startswith("as_"):
            entry_type = "_".join(name.split("_")[1:])
            return self.convert(entry_type)

        # Try returning ENTRY_RESOURCE attribute
        try:
            res = self._get_model_attributes((self.entry, self.entry.attributes), name)
        except AttributeError:
            pass
        else:
            return res

        # Non-valid attribute
        _entry_resource_name = re.match(
            r"(<class ')([a-zA-Z_]+\.)*([a-zA-Z_]+)('>)", str(self.ENTRY_RESOURCE)
        )
        entry_resource_name = (
            _entry_resource_name.group(3)
            if _entry_resource_name is not None
            else "UNKNOWN RESOURCE"
        )
        raise AttributeError(
            f"Unknown attribute: {name}\n"
            "If you want to get a converted entry as <entry_type> use `as_<entry_type>`, "
            f"where `<entry_type>` is one of {tuple(self._type_converters.keys()) + tuple(self._common_converters.keys())}\n"
            f"Otherwise, you can try to retrieve an OPTIMADE {entry_resource_name} attribute or property."
        )

entry: EntryResource property

Get OPTIMADE entry.

Returns:

Type Description
EntryResource

The entry resource.

__getattr__(name)

Get converted entry or attribute from OPTIMADE entry.

Support any level of "."-nested OPTIMADE ENTRY_RESOURCE attributes, e.g., attributes.species for StuctureResource.

Note

All nested attributes must individually be subclasses of pydantic.BaseModel, i.e., one can not access nested attributes in lists by passing a "."-nested name to this method, e.g., attributes.species.name or attributes.species[0].name will not work for variable name.

Order:

  • Try to return converted entry if using as_<_type_converters key>.
  • Try to return OPTIMADE ENTRY_RESOURCE (nested) attribute.
  • Try to return OPTIMADE ENTRY_RESOURCE.attributes (nested) attribute.
  • Raise AttributeError.

Parameters:

Name Type Description Default
name str

Requested attribute.

required

Raises:

Type Description
AttributeError

If the requested attribute is not recognized. See above for the description of the order in which an attribute is tested for validity.

Source code in optimade/adapters/base.py
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
214
215
216
217
218
def __getattr__(self, name: str) -> Any:
    """Get converted entry or attribute from OPTIMADE entry.

    Support any level of "."-nested OPTIMADE `ENTRY_RESOURCE` attributes, e.g.,
    `attributes.species` for [`StuctureResource`][optimade.models.structures.StructureResource].

    Note:
        All nested attributes must individually be subclasses of `pydantic.BaseModel`,
        i.e., one can not access nested attributes in lists by passing a "."-nested `name` to this method,
        e.g., `attributes.species.name` or `attributes.species[0].name` will not work for variable `name`.

    Order:

    - Try to return converted entry if using `as_<_type_converters key>`.
    - Try to return OPTIMADE `ENTRY_RESOURCE` (nested) attribute.
    - Try to return OPTIMADE `ENTRY_RESOURCE.attributes` (nested) attribute.
    - Raise `AttributeError`.

    Parameters:
        name (str): Requested attribute.

    Raises:
        AttributeError: If the requested attribute is not recognized.
            See above for the description of the order in which an attribute is tested for validity.

    """
    # as_<entry_type>
    if name.startswith("as_"):
        entry_type = "_".join(name.split("_")[1:])
        return self.convert(entry_type)

    # Try returning ENTRY_RESOURCE attribute
    try:
        res = self._get_model_attributes((self.entry, self.entry.attributes), name)
    except AttributeError:
        pass
    else:
        return res

    # Non-valid attribute
    _entry_resource_name = re.match(
        r"(<class ')([a-zA-Z_]+\.)*([a-zA-Z_]+)('>)", str(self.ENTRY_RESOURCE)
    )
    entry_resource_name = (
        _entry_resource_name.group(3)
        if _entry_resource_name is not None
        else "UNKNOWN RESOURCE"
    )
    raise AttributeError(
        f"Unknown attribute: {name}\n"
        "If you want to get a converted entry as <entry_type> use `as_<entry_type>`, "
        f"where `<entry_type>` is one of {tuple(self._type_converters.keys()) + tuple(self._common_converters.keys())}\n"
        f"Otherwise, you can try to retrieve an OPTIMADE {entry_resource_name} attribute or property."
    )

__init__(entry)

Parameters:

Name Type Description Default
entry dict

A JSON OPTIMADE single resource entry.

required
Source code in optimade/adapters/base.py
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
def __init__(self, entry: dict[str, Any]) -> None:
    """
    Parameters:
        entry (dict): A JSON OPTIMADE single resource entry.
    """
    self._converted: dict[str, Any] = {}

    self._entry = self.ENTRY_RESOURCE(**entry)

    # Note that these return also the default values for otherwise non-provided properties.
    self._common_converters = {
        # Return JSON serialized string, see https://pydantic-docs.helpmanual.io/usage/exporting_models/#modeljson
        "json": self.entry.model_dump_json,
        # Return Python dict, see https://pydantic-docs.helpmanual.io/usage/exporting_models/#modeldict
        "dict": self.entry.model_dump,
    }

convert(format)

Convert OPTIMADE entry to desired format.

Parameters:

Name Type Description Default
format str

Type or format to which the entry should be converted.

required

Raises:

Type Description
AttributeError

If format can not be found in _type_converters or _common_converters.

Returns:

Type Description
Any

The converted entry according to the desired format or type.

Source code in optimade/adapters/base.py
 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
def convert(self, format: str) -> Any:
    """Convert OPTIMADE entry to desired format.

    Parameters:
        format (str): Type or format to which the entry should be converted.

    Raises:
        AttributeError: If `format` can not be found in `_type_converters` or `_common_converters`.

    Returns:
        The converted entry according to the desired format or type.

    """
    if (
        format not in self._type_converters
        and format not in self._common_converters
    ):
        raise AttributeError(
            f"Non-valid entry type to convert to: {format}\nValid entry types: "
            f"{tuple(self._type_converters.keys()) + tuple(self._common_converters.keys())}"
        )

    if self._converted.get(format, None) is None:
        if format in self._type_converters:
            self._converted[format] = self._type_converters[format](self.entry)
        else:
            self._converted[format] = self._common_converters[format]()

    return self._converted[format]

ingest_from(data, format=None) classmethod

Convert desired format to OPTIMADE format.

Parameters:

Name Type Description Default
data Any

The data to convert.

required
format str

Type or format to which the entry should be converted.

None

Raises:

Type Description
AttributeError

If format can not be found in _type_ingesters.

Returns:

Type Description
Any

The ingested Structure.

Source code in optimade/adapters/base.py
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
@classmethod
def ingest_from(cls, data: Any, format: Optional[str] = None) -> Any:
    """Convert desired format to OPTIMADE format.

    Parameters:
        data (Any): The data to convert.
        format (str): Type or format to which the entry should be converted.

    Raises:
        AttributeError: If `format` can not be found in `_type_ingesters`.

    Returns:
        The ingested Structure.

    """

    if format is None:
        for key, instance_type in cls._type_ingesters_by_type.items():
            if isinstance(data, instance_type):
                format = key
                break

        else:
            raise AttributeError(
                f"Non entry type to data of type {type(data)} from.\n"
                f"Valid entry types: {tuple(cls._type_ingesters.keys())}"
            )

    if format not in cls._type_ingesters:
        raise AttributeError(
            f"Non-valid entry type to ingest from: {format}\n"
            f"Valid entry types: {tuple(cls._type_ingesters.keys())}"
        )

    return cls(
        {
            "attributes": cls._type_ingesters[format](data).model_dump(),
            "id": "",
            "type": "structures",
        }
    )

Structure

Bases: EntryAdapter

Lazy structure resource converter.

Go to EntryAdapter to see the full list of methods and properties.

Attributes:

Name Type Description
ENTRY_RESOURCE type[StructureResource]

This adapter stores entry resources as StructureResources.

_type_converters dict[str, Callable]

Dictionary of valid conversion types for entry.

Currently available types:

  • aiida_structuredata
  • ase
  • cif
  • pdb
  • pdbx_mmcif
  • pymatgen
  • jarvis
_type_ingesters dict[str, Callable]

Dictionary of valid ingesters.

as_<_type_converters> dict[str, Callable]

Convert entry to a type listed in _type_converters.

from_<_type_converters> dict[str, Callable]

Convert an external type to an OPTIMADE StructureResourceAttributes model.

Source code in optimade/adapters/structures/adapter.py
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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
class Structure(EntryAdapter):
    """
    Lazy structure resource converter.

    Go to [`EntryAdapter`][optimade.adapters.base.EntryAdapter] to see the full list of methods
    and properties.

    Attributes:
        ENTRY_RESOURCE: This adapter stores entry resources as
            [`StructureResource`][optimade.models.structures.StructureResource]s.
        _type_converters: Dictionary of valid conversion types for entry.

            Currently available types:

            - `aiida_structuredata`
            - `ase`
            - `cif`
            - `pdb`
            - `pdbx_mmcif`
            - `pymatgen`
            - `jarvis`

        _type_ingesters: Dictionary of valid ingesters.

        as_<_type_converters>: Convert entry to a type listed in `_type_converters`.
        from_<_type_converters>: Convert an external type to an OPTIMADE
            [`StructureResourceAttributes`][optimade.models.structures.StructureResourceAttributes]
            model.

    """

    ENTRY_RESOURCE: type[StructureResource] = StructureResource
    _type_converters: dict[str, Callable] = {
        "aiida_structuredata": get_aiida_structure_data,
        "ase": get_ase_atoms,
        "cif": get_cif,
        "pdb": get_pdb,
        "pdbx_mmcif": get_pdbx_mmcif,
        "pymatgen": get_pymatgen,
        "jarvis": get_jarvis_atoms,
    }

    _type_ingesters: dict[str, Callable] = {
        "pymatgen": from_pymatgen,
        "ase": from_ase_atoms,
    }

    _type_ingesters_by_type: dict[str, type] = {
        "pymatgen": PymatgenStructure,
        "ase": ASEAtoms,
    }

entry: EntryResource property

Get OPTIMADE entry.

Returns:

Type Description
EntryResource

The entry resource.

__getattr__(name)

Get converted entry or attribute from OPTIMADE entry.

Support any level of "."-nested OPTIMADE ENTRY_RESOURCE attributes, e.g., attributes.species for StuctureResource.

Note

All nested attributes must individually be subclasses of pydantic.BaseModel, i.e., one can not access nested attributes in lists by passing a "."-nested name to this method, e.g., attributes.species.name or attributes.species[0].name will not work for variable name.

Order:

  • Try to return converted entry if using as_<_type_converters key>.
  • Try to return OPTIMADE ENTRY_RESOURCE (nested) attribute.
  • Try to return OPTIMADE ENTRY_RESOURCE.attributes (nested) attribute.
  • Raise AttributeError.

Parameters:

Name Type Description Default
name str

Requested attribute.

required

Raises:

Type Description
AttributeError

If the requested attribute is not recognized. See above for the description of the order in which an attribute is tested for validity.

Source code in optimade/adapters/base.py
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
214
215
216
217
218
def __getattr__(self, name: str) -> Any:
    """Get converted entry or attribute from OPTIMADE entry.

    Support any level of "."-nested OPTIMADE `ENTRY_RESOURCE` attributes, e.g.,
    `attributes.species` for [`StuctureResource`][optimade.models.structures.StructureResource].

    Note:
        All nested attributes must individually be subclasses of `pydantic.BaseModel`,
        i.e., one can not access nested attributes in lists by passing a "."-nested `name` to this method,
        e.g., `attributes.species.name` or `attributes.species[0].name` will not work for variable `name`.

    Order:

    - Try to return converted entry if using `as_<_type_converters key>`.
    - Try to return OPTIMADE `ENTRY_RESOURCE` (nested) attribute.
    - Try to return OPTIMADE `ENTRY_RESOURCE.attributes` (nested) attribute.
    - Raise `AttributeError`.

    Parameters:
        name (str): Requested attribute.

    Raises:
        AttributeError: If the requested attribute is not recognized.
            See above for the description of the order in which an attribute is tested for validity.

    """
    # as_<entry_type>
    if name.startswith("as_"):
        entry_type = "_".join(name.split("_")[1:])
        return self.convert(entry_type)

    # Try returning ENTRY_RESOURCE attribute
    try:
        res = self._get_model_attributes((self.entry, self.entry.attributes), name)
    except AttributeError:
        pass
    else:
        return res

    # Non-valid attribute
    _entry_resource_name = re.match(
        r"(<class ')([a-zA-Z_]+\.)*([a-zA-Z_]+)('>)", str(self.ENTRY_RESOURCE)
    )
    entry_resource_name = (
        _entry_resource_name.group(3)
        if _entry_resource_name is not None
        else "UNKNOWN RESOURCE"
    )
    raise AttributeError(
        f"Unknown attribute: {name}\n"
        "If you want to get a converted entry as <entry_type> use `as_<entry_type>`, "
        f"where `<entry_type>` is one of {tuple(self._type_converters.keys()) + tuple(self._common_converters.keys())}\n"
        f"Otherwise, you can try to retrieve an OPTIMADE {entry_resource_name} attribute or property."
    )

__init__(entry)

Parameters:

Name Type Description Default
entry dict

A JSON OPTIMADE single resource entry.

required
Source code in optimade/adapters/base.py
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
def __init__(self, entry: dict[str, Any]) -> None:
    """
    Parameters:
        entry (dict): A JSON OPTIMADE single resource entry.
    """
    self._converted: dict[str, Any] = {}

    self._entry = self.ENTRY_RESOURCE(**entry)

    # Note that these return also the default values for otherwise non-provided properties.
    self._common_converters = {
        # Return JSON serialized string, see https://pydantic-docs.helpmanual.io/usage/exporting_models/#modeljson
        "json": self.entry.model_dump_json,
        # Return Python dict, see https://pydantic-docs.helpmanual.io/usage/exporting_models/#modeldict
        "dict": self.entry.model_dump,
    }

convert(format)

Convert OPTIMADE entry to desired format.

Parameters:

Name Type Description Default
format str

Type or format to which the entry should be converted.

required

Raises:

Type Description
AttributeError

If format can not be found in _type_converters or _common_converters.

Returns:

Type Description
Any

The converted entry according to the desired format or type.

Source code in optimade/adapters/base.py
 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
def convert(self, format: str) -> Any:
    """Convert OPTIMADE entry to desired format.

    Parameters:
        format (str): Type or format to which the entry should be converted.

    Raises:
        AttributeError: If `format` can not be found in `_type_converters` or `_common_converters`.

    Returns:
        The converted entry according to the desired format or type.

    """
    if (
        format not in self._type_converters
        and format not in self._common_converters
    ):
        raise AttributeError(
            f"Non-valid entry type to convert to: {format}\nValid entry types: "
            f"{tuple(self._type_converters.keys()) + tuple(self._common_converters.keys())}"
        )

    if self._converted.get(format, None) is None:
        if format in self._type_converters:
            self._converted[format] = self._type_converters[format](self.entry)
        else:
            self._converted[format] = self._common_converters[format]()

    return self._converted[format]

ingest_from(data, format=None) classmethod

Convert desired format to OPTIMADE format.

Parameters:

Name Type Description Default
data Any

The data to convert.

required
format str

Type or format to which the entry should be converted.

None

Raises:

Type Description
AttributeError

If format can not be found in _type_ingesters.

Returns:

Type Description
Any

The ingested Structure.

Source code in optimade/adapters/base.py
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
@classmethod
def ingest_from(cls, data: Any, format: Optional[str] = None) -> Any:
    """Convert desired format to OPTIMADE format.

    Parameters:
        data (Any): The data to convert.
        format (str): Type or format to which the entry should be converted.

    Raises:
        AttributeError: If `format` can not be found in `_type_ingesters`.

    Returns:
        The ingested Structure.

    """

    if format is None:
        for key, instance_type in cls._type_ingesters_by_type.items():
            if isinstance(data, instance_type):
                format = key
                break

        else:
            raise AttributeError(
                f"Non entry type to data of type {type(data)} from.\n"
                f"Valid entry types: {tuple(cls._type_ingesters.keys())}"
            )

    if format not in cls._type_ingesters:
        raise AttributeError(
            f"Non-valid entry type to ingest from: {format}\n"
            f"Valid entry types: {tuple(cls._type_ingesters.keys())}"
        )

    return cls(
        {
            "attributes": cls._type_ingesters[format](data).model_dump(),
            "id": "",
            "type": "structures",
        }
    )

StructureResource

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

from_ase_atoms(atoms)

Convert an ASE Atoms object into an OPTIMADE StructureResourceAttributes model.

Parameters:

Name Type Description Default
atoms Atoms

The ASE Atoms object to convert.

required

Returns:

Type Description
StructureResourceAttributes

An OPTIMADE StructureResourceAttributes model, which can be converted to a raw Python dictionary with .model_dump() or to JSON with .model_dump_json().

Source code in optimade/adapters/structures/ase.py
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
def from_ase_atoms(atoms: Atoms) -> StructureResourceAttributes:
    """Convert an ASE `Atoms` object into an OPTIMADE `StructureResourceAttributes` model.

    Parameters:
        atoms: The ASE `Atoms` object to convert.

    Returns:
        An OPTIMADE `StructureResourceAttributes` model, which can be converted to a raw Python
            dictionary with `.model_dump()` or to JSON with `.model_dump_json()`.

    """
    if not isinstance(atoms, Atoms):
        raise RuntimeError(
            f"Cannot convert type {type(atoms)} into an OPTIMADE `StructureResourceAttributes` model."
        )

    attributes = {}
    attributes["cartesian_site_positions"] = atoms.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(
        attributes["species_at_sites"]
    )
    attributes["species"] = species_from_species_at_sites(
        attributes["species_at_sites"]
    )
    attributes["dimension_types"] = [int(_) for _ in atoms.pbc.tolist()]
    attributes["nperiodic_dimensions"] = sum(attributes["dimension_types"])
    attributes["nelements"] = len(attributes["species"])
    attributes["elements"] = sorted([_.name for _ in attributes["species"]])
    attributes["nsites"] = len(attributes["species_at_sites"])

    attributes["chemical_formula_descriptive"] = atoms.get_chemical_formula()
    attributes["chemical_formula_reduced"] = reduce_formula(
        atoms.get_chemical_formula()
    )
    attributes["chemical_formula_anonymous"] = anonymize_formula(
        attributes["chemical_formula_reduced"],
    )
    attributes["last_modified"] = None
    attributes["immutable_id"] = None
    attributes["structure_features"] = []

    for key in atoms.info:
        optimade_key = key.lower()
        if not key.startswith(f"_{EXTRA_FIELD_PREFIX}"):
            optimade_key = f"_{EXTRA_FIELD_PREFIX}_{optimade_key}"
        attributes[optimade_key] = atoms.info[key]

    return StructureResourceAttributes(**attributes)

from_pymatgen(pmg_structure)

Convert a pymatgen Structure (3D) into an OPTIMADE StructureResourceAttributes model.

Parameters:

Name Type Description Default
pmg_structure Structure

The pymatgen Structure to convert.

required

Returns:

Type Description
StructureResourceAttributes

An OPTIMADE StructureResourceAttributes model, which can be converted to a raw Python dictionary with .model_dump() or to JSON with .model_dump_json().

Source code in optimade/adapters/structures/pymatgen.py
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
def from_pymatgen(pmg_structure: Structure) -> StructureResourceAttributes:
    """Convert a pymatgen `Structure` (3D) into an OPTIMADE `StructureResourceAttributes` model.

    Parameters:
        pmg_structure: The pymatgen `Structure` to convert.

    Returns:
        An OPTIMADE `StructureResourceAttributes` model, which can be converted to a raw Python
            dictionary with `.model_dump()` or to JSON with `.model_dump_json()`.

    """

    if not isinstance(pmg_structure, Structure):
        raise RuntimeError(
            f"Cannot convert type {type(pmg_structure)} into an OPTIMADE `StructureResourceAttributes` model."
        )

    attributes = {}
    attributes["cartesian_site_positions"] = pmg_structure.lattice.get_cartesian_coords(
        pmg_structure.frac_coords
    ).tolist()
    attributes["lattice_vectors"] = pmg_structure.lattice.matrix.tolist()
    attributes["species_at_sites"] = [_.symbol for _ in pmg_structure.species]
    attributes["species"] = [
        {"name": _.symbol, "chemical_symbols": [_.symbol], "concentration": [1]}
        for _ in set(pmg_structure.composition.elements)
    ]
    attributes["dimension_types"] = [int(_) for _ in pmg_structure.lattice.pbc]
    attributes["nperiodic_dimensions"] = sum(attributes["dimension_types"])
    attributes["nelements"] = len(pmg_structure.composition.elements)
    attributes["chemical_formula_anonymous"] = anonymize_formula(
        pmg_structure.composition.formula
    )
    attributes["elements"] = sorted(
        [_.symbol for _ in pmg_structure.composition.elements]
    )
    attributes["chemical_formula_reduced"] = reduce_formula(
        pmg_structure.composition.formula
    )
    attributes["chemical_formula_descriptive"] = pmg_structure.composition.formula
    attributes["elements_ratios"] = [
        pmg_structure.composition.get_atomic_fraction(e) for e in attributes["elements"]
    ]
    attributes["nsites"] = len(attributes["species_at_sites"])

    attributes["last_modified"] = None
    attributes["immutable_id"] = None
    attributes["structure_features"] = []

    return StructureResourceAttributes(**attributes)

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

get_ase_atoms(optimade_structure)

Get ASE Atoms from OPTIMADE structure.

Caution

Cannot handle partial occupancies (this includes vacancies).

Parameters:

Name Type Description Default
optimade_structure StructureResource

OPTIMADE structure.

required

Returns:

Type Description
Atoms

ASE Atoms object.

Source code in optimade/adapters/structures/ase.py
 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
104
105
def get_ase_atoms(optimade_structure: OptimadeStructure) -> Atoms:
    """Get ASE `Atoms` from OPTIMADE structure.

    Caution:
        Cannot handle partial occupancies (this includes vacancies).

    Parameters:
        optimade_structure: OPTIMADE structure.

    Returns:
        ASE `Atoms` object.

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

    attributes = optimade_structure.attributes

    # Cannot handle partial occupancies
    if StructureFeatures.DISORDER in attributes.structure_features:
        raise ConversionError(
            "ASE cannot handle structures with partial occupancies, sorry."
        )

    species = attributes.species
    # If species is missing, infer data from species_at_sites
    if not species:
        species = species_from_species_at_sites(attributes.species_at_sites)  # type: ignore[arg-type]

    optimade_species: dict[str, OptimadeStructureSpecies] = {_.name: _ for _ in species}

    # Since we've made sure there are no species with more than 1 chemical symbol,
    # asking for index 0 will always work.
    if "X" in [specie.chemical_symbols[0] for specie in optimade_species.values()]:
        raise ConversionError(
            "ASE cannot handle structures with unknown ('X') chemical symbols, sorry."
        )

    atoms = []
    for site_number in range(attributes.nsites):  # type: ignore[arg-type]
        species_name = attributes.species_at_sites[site_number]  # type: ignore[index]
        site = attributes.cartesian_site_positions[site_number]  # type: ignore[index]

        current_species = optimade_species[species_name]

        # Argument above about chemical symbols also holds here
        mass = None
        if current_species.mass:
            mass = current_species.mass[0]

        atoms.append(
            Atom(symbol=current_species.chemical_symbols[0], position=site, mass=mass)
        )

    info = {}
    for key in attributes.model_dump().keys():
        if key.startswith("_"):
            ase_key = key
            if key.startswith(f"_{EXTRA_FIELD_PREFIX}_"):
                ase_key = "".join(key.split(f"_{EXTRA_FIELD_PREFIX}_")[1:])
            info[ase_key] = getattr(attributes, key)

    return Atoms(
        symbols=atoms,
        cell=attributes.lattice_vectors,
        pbc=attributes.dimension_types,
        info=info if info else None,
    )

get_cif(optimade_structure)

Get CIF file as string from OPTIMADE structure.

Parameters:

Name Type Description Default
optimade_structure StructureResource

OPTIMADE structure.

required

Returns:

Type Description
str

The CIF file as a single Python str object.

Source code in optimade/adapters/structures/cif.py
 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
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
def get_cif(  # pylint: disable=too-many-locals,too-many-branches
    optimade_structure: OptimadeStructure,
) -> str:
    """Get CIF file as string from OPTIMADE structure.

    Parameters:
        optimade_structure: OPTIMADE structure.

    Returns:
        The CIF file as a single Python `str` object.

    """
    # NumPy is needed for calculations
    if globals().get("np", None) is None:
        warn(NUMPY_NOT_FOUND, AdapterPackageNotFound)
        return None  # type: ignore[return-value]

    cif = """#
# Created from an OPTIMADE structure.
#
# See https://www.optimade.org and/or
# https://github.com/Materials-Consortia/OPTIMADE for more information.
#
"""

    cif += f"data_{optimade_structure.id}\n\n"

    attributes = optimade_structure.attributes

    # Do this only if there's three non-zero lattice vectors
    # NOTE: This also negates handling of lattice_vectors with null/None values
    if valid_lattice_vector(attributes.lattice_vectors):  # type:ignore[arg-type]
        a_vector, b_vector, c_vector, alpha, beta, gamma = cell_to_cellpar(
            attributes.lattice_vectors  # type: ignore[arg-type]
        )

        cif += (
            f"_cell_length_a                    {a_vector:g}\n"
            f"_cell_length_b                    {b_vector:g}\n"
            f"_cell_length_c                    {c_vector:g}\n"
            f"_cell_angle_alpha                 {alpha:g}\n"
            f"_cell_angle_beta                  {beta:g}\n"
            f"_cell_angle_gamma                 {gamma:g}\n\n"
        )
        cif += (
            "_symmetry_space_group_name_H-M    'P 1'\n"
            "_symmetry_int_tables_number       1\n\n"
            "loop_\n"
            "  _symmetry_equiv_pos_as_xyz\n"
            "  'x, y, z'\n\n"
        )

        # 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"):
            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"
    )

    cif += (
        "loop_\n"
        "  _atom_site_type_symbol\n"  # species.chemical_symbols
        "  _atom_site_label\n"  # species.name + unique int
        "  _atom_site_occupancy\n"  # species.concentration
        f"  _atom_site_{coord_type}_x\n"  # cartesian_site_positions
        f"  _atom_site_{coord_type}_y\n"  # cartesian_site_positions
        f"  _atom_site_{coord_type}_z\n"  # cartesian_site_positions
        "  _atom_site_thermal_displace_type\n"  # Set to 'Biso'
        "  _atom_site_B_iso_or_equiv\n"  # Set to 1.0:f
    )

    if coord_type == "fract":
        sites = attributes.fractional_site_positions
    else:
        sites = attributes.cartesian_site_positions

    species: dict[str, OptimadeStructureSpecies] = {
        species.name: species for species in attributes.species  # type: ignore[union-attr]
    }

    symbol_occurences: dict[str, int] = {}
    for site_number in range(attributes.nsites):  # type: ignore[arg-type]
        species_name = attributes.species_at_sites[site_number]  # type: ignore[index]
        site = sites[site_number]

        current_species = species[species_name]

        for index, symbol in enumerate(current_species.chemical_symbols):
            if symbol == "vacancy":
                continue

            if symbol in symbol_occurences:
                symbol_occurences[symbol] += 1
            else:
                symbol_occurences[symbol] = 1
            label = f"{symbol}{symbol_occurences[symbol]}"

            cif += (
                f"  {symbol} {label} {current_species.concentration[index]:6.4f} {site[0]:8.5f}  "
                f"{site[1]:8.5f}  {site[2]:8.5f}  {'Biso':4}  {'1.000':6}\n"
            )

    return cif

get_jarvis_atoms(optimade_structure)

Get jarvis Atoms from OPTIMADE structure.

Caution

Cannot handle partial occupancies.

Parameters:

Name Type Description Default
optimade_structure StructureResource

OPTIMADE structure.

required

Returns:

Type Description
Atoms

A jarvis Atoms object.

Source code in optimade/adapters/structures/jarvis.py
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
def get_jarvis_atoms(optimade_structure: OptimadeStructure) -> Atoms:
    """Get jarvis `Atoms` from OPTIMADE structure.

    Caution:
        Cannot handle partial occupancies.

    Parameters:
        optimade_structure: OPTIMADE structure.

    Returns:
        A jarvis `Atoms` object.

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

    attributes = optimade_structure.attributes

    # Cannot handle partial occupancies
    if StructureFeatures.DISORDER in attributes.structure_features:
        raise ConversionError(
            "jarvis-tools cannot handle structures with partial occupancies."
        )

    return Atoms(
        lattice_mat=attributes.lattice_vectors,
        elements=[specie.name for specie in attributes.species],  # type: ignore[union-attr]
        coords=attributes.cartesian_site_positions,
        cartesian=True,
    )

get_pdb(optimade_structure)

Write Protein Data Bank (PDB) structure in the old PDB format from OPTIMADE structure.

Parameters:

Name Type Description Default
optimade_structure StructureResource

OPTIMADE structure.

required

Returns:

Type Description
str

A PDB file as a single Python str object.

Source code in optimade/adapters/structures/proteindatabank.py
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
def get_pdb(  # pylint: disable=too-many-locals
    optimade_structure: OptimadeStructure,
) -> str:
    """Write Protein Data Bank (PDB) structure in the old PDB format from OPTIMADE structure.

    Parameters:
        optimade_structure: OPTIMADE structure.

    Returns:
        A PDB file as a single Python `str` object.

    """
    if globals().get("np", None) is None:
        warn(NUMPY_NOT_FOUND, AdapterPackageNotFound)
        return None  # type: ignore[return-value]

    pdb = ""

    attributes = optimade_structure.attributes

    rotation = None
    if valid_lattice_vector(attributes.lattice_vectors):  # type: ignore[arg-type]
        currentcell = np.asarray(attributes.lattice_vectors)
        cellpar = cell_to_cellpar(currentcell)
        exportedcell = cellpar_to_cell(cellpar)
        rotation = np.linalg.solve(currentcell, exportedcell)
        # Setting Z-value = 1 and using P1 since we have all atoms defined explicitly
        Z = 1
        spacegroup = "P 1"
        pdb += (
            f"CRYST1{cellpar[0]:9.3f}{cellpar[1]:9.3f}{cellpar[2]:8.3f}"
            f"{cellpar[3]:7.2f}{cellpar[4]:7.2f}{cellpar[5]:7.2f} {spacegroup:11s}{Z:4d}\n"
        )

        for i, vector in enumerate(scaled_cell(currentcell)):
            pdb += f"SCALE{i + 1}    {vector[0]:10.6f}{vector[1]:10.6f}{vector[2]:10.6f}     {0:10.5f}\n"

    # There is a limit of 5 digit numbers in this field.
    pdb_maxnum = 100000
    bfactor = 1.0

    pdb += "MODEL     1\n"

    species: dict[str, OptimadeStructureSpecies] = {
        species.name: species
        for species in attributes.species  # type:ignore[union-attr]
    }

    sites = np.asarray(attributes.cartesian_site_positions)
    if rotation is not None:
        sites = sites.dot(rotation)

    for site_number in range(attributes.nsites):  # type: ignore[arg-type]
        species_name = attributes.species_at_sites[site_number]  # type: ignore[index]
        site = sites[site_number]

        current_species = species[species_name]

        for index, symbol in enumerate(current_species.chemical_symbols):
            if symbol == "vacancy":
                continue

            label = species_name
            if len(current_species.chemical_symbols) > 1:
                if (
                    "vacancy" in current_species.chemical_symbols
                    and len(current_species.chemical_symbols) == 2
                ):
                    pass
                else:
                    label = f"{symbol}{index + 1}"

            pdb += (
                f"ATOM  {site_number % pdb_maxnum:5d} {label:4} MOL     1    "
                f"{site[0]:8.3f}{site[1]:8.3f}{site[2]:8.3f}"
                f"{current_species.concentration[index]:6.2f}"
                f"{bfactor:6.2f}          {symbol.upper():2}  \n"
            )
    pdb += "ENDMDL\n"

    return pdb

get_pdbx_mmcif(optimade_structure)

Write Protein Data Bank (PDB) structure in the PDBx/mmCIF format from OPTIMADE structure.

Warning

The result of this function can currently not be parsed as a complete PDBx/mmCIF file.

Parameters:

Name Type Description Default
optimade_structure StructureResource

OPTIMADE structure.

required
Return

A modern PDBx/mmCIF file as a single Python str object.

Source code in optimade/adapters/structures/proteindatabank.py
 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
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
def get_pdbx_mmcif(  # pylint: disable=too-many-locals
    optimade_structure: OptimadeStructure,
) -> str:
    """Write Protein Data Bank (PDB) structure in the PDBx/mmCIF format from OPTIMADE structure.

    Warning:
        The result of this function can currently not be parsed as a complete PDBx/mmCIF file.

    Parameters:
        optimade_structure: OPTIMADE structure.

    Return:
        A modern PDBx/mmCIF file as a single Python `str` object.

    """
    if globals().get("np", None) is None:
        warn(NUMPY_NOT_FOUND, AdapterPackageNotFound)
        return None  # type: ignore[return-value]

    cif = """#
# Created from an OPTIMADE structure.
#
# See https://www.optimade.org and/or
# https://github.com/Materials-Consortia/OPTIMADE for more information.
#
# CIF 2.0 format, specifically mmCIF (PDBx).
# See http://mmcif.wwpdb.org for more information.
#
"""

    entry_id = f"{optimade_structure.type}{optimade_structure.id}"
    cif += f"data_{entry_id}\n_entry.id                         {entry_id}\n#\n"

    attributes = optimade_structure.attributes

    # Do this only if there's three non-zero lattice vectors
    if valid_lattice_vector(attributes.lattice_vectors):  # type: ignore[arg-type]
        a_vector, b_vector, c_vector, alpha, beta, gamma = cell_to_cellpar(
            attributes.lattice_vectors  # type: ignore[arg-type]
        )

        cif += (
            f"_cell.entry_id                    {entry_id}\n"
            f"_cell.length_a                    {a_vector:g}\n"
            f"_cell.length_b                    {b_vector:g}\n"
            f"_cell.length_c                    {c_vector:g}\n"
            f"_cell.angle_alpha                 {alpha:g}\n"
            f"_cell.angle_beta                  {beta:g}\n"
            f"_cell.angle_gamma                 {gamma:g}\n"
            "_cell.Z_PDB                       1\n#\n"
        )
        cif += (
            f"_symmetry.entry_id                {entry_id}\n"
            "_symmetry.space_group_name_H-M    'P 1'\n"
            "_symmetry.Int_Tables_number       1\n#\n"
        )

        # 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"):
            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: The following lines are perhaps needed to create a "valid" PDBx/mmCIF file.
    # However, at the same time, the information here is "default" and will for all structures "at this moment in time"
    # be the same. I.e., no information is gained by adding this now.
    # If it is found that they indeed are needed to create a "valid" PDBx/mmCIF file, they should be included in the output.
    # cif += (
    #     "loop_\n"
    #     "_struct_asym.id\n"
    #     "_struct_asym.entity_id\n"
    #     "A  1\n#\n"  # At this point, not using this feature.
    # )

    # cif += (
    #     "loop_\n"
    #     "_chem_comp.id\n"
    #     "X\n#\n"  # At this point, not using this feature.
    # )

    # cif += (
    #     "loop_\n"
    #     "_entity.id\n"
    #     "1\n#\n"  # At this point, not using this feature.
    # )

    # 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"
    )

    cif += (
        "loop_\n"
        "_atom_site.group_PDB\n"  # Always "ATOM"
        "_atom_site.id\n"  # number (1-counting)
        "_atom_site.type_symbol\n"  # species.chemical_symbols
        "_atom_site.label_atom_id\n"  # species.checmical_symbols symbol + number
        # For these next keys, see the comment above.
        # "_atom_site.label_asym_id\n"  # Will be set to "A" _struct_asym.id above
        # "_atom_site.label_comp_id\n"  # Will be set to "X" _chem_comp.id above
        # "_atom_site.label_entity_id\n"  # Will be set to "1" _entity.id above
        # "_atom_site.label_seq_id\n"
        "_atom_site.occupancy\n"  # species.concentration
        f"_atom_site.{coord_type}_x\n"  # cartesian_site_positions
        f"_atom_site.{coord_type}_y\n"  # cartesian_site_positions
        f"_atom_site.{coord_type}_z\n"  # cartesian_site_positions
        "_atom_site.thermal_displace_type\n"  # Set to 'Biso'
        "_atom_site.B_iso_or_equiv\n"  # Set to 1.0:f
    )

    if coord_type == "fract":
        sites = attributes.fractional_site_positions
    else:
        sites = attributes.cartesian_site_positions

    species: dict[str, OptimadeStructureSpecies] = {
        species.name: species for species in attributes.species  # type: ignore[union-attr]
    }

    for site_number in range(attributes.nsites):  # type: ignore[arg-type]
        species_name = attributes.species_at_sites[site_number]  # type: ignore[index]
        site = sites[site_number]

        current_species = species[species_name]

        for index, symbol in enumerate(current_species.chemical_symbols):
            if symbol == "vacancy":
                continue

            label = f"{species_name.upper()}{site_number + 1}"
            if len(current_species.chemical_symbols) > 1:
                if (
                    "vacancy" in current_species.chemical_symbols
                    and len(current_species.chemical_symbols) == 2
                ):
                    pass
                else:
                    label = f"{symbol.upper()}{index + 1}"

            cif += (
                f"ATOM  {site_number + 1:5d}  {symbol}  {label:8}  "
                f"{current_species.concentration[index]:6.4f}  {site[0]:8.5f}  "
                f"{site[1]:8.5f}  {site[2]:8.5f}  {'Biso':4}  {'1.000':6}\n"
            )

    return cif

get_pymatgen(optimade_structure)

Get pymatgen Structure or Molecule from OPTIMADE structure.

This function will return either a pymatgen Structure or Molecule based on the periodicity or periodic dimensionality of OPTIMADE structure.

For structures that are periodic in one or more dimensions, a pymatgen Structure is returned when valid lattice_vectors are given. This means, if the any of the values in the dimension_types attribute is 1s or if nperiodic_dimesions > 0.

Otherwise, a pymatgen Molecule is returned.

Parameters:

Name Type Description Default
optimade_structure StructureResource

OPTIMADE structure.

required

Returns:

Type Description
Union[Structure, Molecule]

A pymatgen Structure or Molecule based on the periodicity of the

Union[Structure, Molecule]

OPTIMADE structure.

Source code in optimade/adapters/structures/pymatgen.py
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
def get_pymatgen(optimade_structure: OptimadeStructure) -> Union[Structure, Molecule]:
    """Get pymatgen `Structure` or `Molecule` from OPTIMADE structure.

    This function will return either a pymatgen `Structure` or `Molecule` based
    on the periodicity or periodic dimensionality of OPTIMADE structure.

    For structures that are periodic in one or more dimensions, a pymatgen `Structure` is returned when valid lattice_vectors are given.
    This means, if the any of the values in the [`dimension_types`][optimade.models.structures.StructureResourceAttributes.dimension_types]
    attribute is `1`s or if [`nperiodic_dimesions`][optimade.models.structures.StructureResourceAttributes.nperiodic_dimensions] > 0.

    Otherwise, a pymatgen `Molecule` is returned.

    Parameters:
        optimade_structure: OPTIMADE structure.

    Returns:
        A pymatgen `Structure` or `Molecule` based on the periodicity of the
        OPTIMADE structure.

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

    if valid_lattice_vector(optimade_structure.attributes.lattice_vectors) and (  # type: ignore[arg-type]
        optimade_structure.attributes.nperiodic_dimensions > 0  # type: ignore[operator]
        or any(optimade_structure.attributes.dimension_types)  # type: ignore[arg-type]
    ):
        return _get_structure(optimade_structure)

    return _get_molecule(optimade_structure)