Serve data over OPTIMADE

This example starts with CIF files and a JSON table of results, stores both in SQLite, and serves structures and _httk_records over OPTIMADE. Each result has a formation energy and a relationship to its structure, using the same typed-record pattern as altermagnets.

Use Python 3.12 or newer and install the three modules (also included in the httk2 metapackage):

python -m pip install 'httk-atomistic[default]' 'httk-store[db]' httk-serve

The EntryRecord, DataEntryRecord, Property, and entry_record helpers in the scripts below are currently unreleased. Use matching development checkouts of httk-core and httk-store when trying this example.

Prepare the input files

Create this directory; results.sqlite will be generated by the import:

materials/
  result_record.py
  build_db.py
  serve_db.py
  results.json
  cifs/
    NaCl.cif
    MgO.cif

Put your own CIF files in cifs/. For a complete runnable example, save the following two files. Each lists the eight sites of a conventional rock-salt cell explicitly, with the identity symmetry operation.

cifs/NaCl.cif:

data_NaCl
_cell_length_a 5.64
_cell_length_b 5.64
_cell_length_c 5.64
_cell_angle_alpha 90
_cell_angle_beta 90
_cell_angle_gamma 90
_space_group_IT_number 1
loop_
_space_group_symop_operation_xyz
'x,y,z'
loop_
_atom_site_label
_atom_site_type_symbol
_atom_site_fract_x
_atom_site_fract_y
_atom_site_fract_z
Na1 Na 0 0 0
Na2 Na 0.5 0.5 0
Na3 Na 0.5 0 0.5
Na4 Na 0 0.5 0.5
Cl1 Cl 0.5 0.5 0.5
Cl2 Cl 0 0 0.5
Cl3 Cl 0 0.5 0
Cl4 Cl 0.5 0 0

cifs/MgO.cif:

data_MgO
_cell_length_a 4.21
_cell_length_b 4.21
_cell_length_c 4.21
_cell_angle_alpha 90
_cell_angle_beta 90
_cell_angle_gamma 90
_space_group_IT_number 1
loop_
_space_group_symop_operation_xyz
'x,y,z'
loop_
_atom_site_label
_atom_site_type_symbol
_atom_site_fract_x
_atom_site_fract_y
_atom_site_fract_z
Mg1 Mg 0 0 0
Mg2 Mg 0.5 0.5 0
Mg3 Mg 0.5 0 0.5
Mg4 Mg 0 0.5 0.5
O1 O 0.5 0.5 0.5
O2 O 0 0 0.5
O3 O 0 0.5 0
O4 O 0.5 0 0

Save this as results.json. These are invented demonstration energies in eV per atom, not calculated or measured values. Each cif names a file relative to cifs/.

[
  {"cif": "NaCl.cif", "formation_energy": -1.2},
  {"cif": "MgO.cif", "formation_energy": -0.8}
]

Define, import, and serve the records

Save this as result_record.py. The class only says what a result contains: the Property metadata gives the energy its meaning and unit, and the structure field becomes an OPTIMADE relationship.

from typing import Annotated

from httk.atomistic import UnitcellStructureRecord
from httk.core import DataEntryRecord, Property, entry_record


@entry_record("example.result")
class Result(DataEntryRecord):
    formation_energy: Annotated[
        float,
        Property(
            unit="eV",
            description="Formation energy per atom relative to elemental reference phases.",
        ),
    ]
    structure: UnitcellStructureRecord

Save this as build_db.py:

import json
from pathlib import Path

from httk.atomistic import UnitcellStructureRecord, UnitcellStructureView
from httk.core import load
from httk.store import EntryIdScheme, SqliteStore
from result_record import Result


store = SqliteStore(
    "results.sqlite", records=[Result], entry_ids=EntryIdScheme("example", "1")
)
for row in json.loads(Path("results.json").read_text()):
    sid = store.save(UnitcellStructureView(load(Path("cifs") / row["cif"])))
    structure = store.fetch(UnitcellStructureRecord, sid)
    store.save(Result(row["formation_energy"], structure))
store.close()

Save this as serve_db.py:

from httk.serve.optimade import serve
from httk.store import SqliteStore
from result_record import Result


store = SqliteStore("results.sqlite", records=[Result])
serve(store, port=8080)
store.close()

Run the import once, then start the API:

python build_db.py
python serve_db.py

The server reads the persisted database directly; it does not need the JSON or CIF files. Repeating the build deduplicates unchanged data. The custom energy attribute is published as _httk_custom_formation_energy.

Query the API and include the linked structures

In another terminal:

curl http://127.0.0.1:8080/v1/info/_httk_records
curl http://127.0.0.1:8080/v1/structures
curl --get http://127.0.0.1:8080/v1/_httk_records \
  --data-urlencode 'filter=_httk_custom_formation_energy < -1' \
  --data-urlencode 'include=structures'

The last request selects the NaCl result. Its attributes._httk_custom_formation_energy is -1.2, and its relationships.structures points to the saved structure. The included array contains that structure’s lattice, sites, species, and formula. Visit /v1/info/_httk_records to see the energy property’s definition.

The API needs a running Python service, even if its companion website is published on a static host such as GitHub Pages. For more on database queries, ASGI deployment, and production serving, see the serve documentation and the store database documentation.