Analyzing the data

With runs collected into a store (see Storing data in a database), analysis is the step that turns records into results. In httk₂ this splits cleanly into two halves: curated scripts that a workflow package ships alongside its runs, and your own queries against the store using the search DSL and httk-analyse.

Workflow-provided analysis

A workflow package can declare curated postprocess scripts in its httk_workflow.toml under [workflow.postprocess.<NAME>] tables. Each names a provider-owned executable that runs once per selected collected job. You invoke one by its declared name:

$ httk workflow postprocess --workspace WORKSPACE --script relaxation-report

For example, a relaxation package might declare:

[workflow.postprocess.relaxation-report]
file = "scripts/relaxation_report"
description = "write a text and JSON relaxation summary"

In httk v1

Nothing like curated per-workflow postprocess scripts existed. Analysis code was shipped ad hoc next to the project — a loose script the author remembered to keep near the runs. In httk₂ the package that defines the runs also owns its standard summaries, so they travel with it.

Querying a store yourself

For your own analysis, open a store and build a query with store.searcher(). The DSL binds a record class to a variable with variable(), adds conditions with add(), names outputs in results(), and runs on iteration:

from httk.atomistic import UnitcellStructureRecord
from httk.store import Backend, SqlStore

store = SqlStore(Backend.sqlite("presentation.sqlite"))

search = store.searcher()
structure = search.variable(UnitcellStructureRecord)
search.add(structure.species_at_sites.has_any("Ca", "Ti"))

for row in search.results(structure=structure):
    print(len(row.structure.species_at_sites), "sites")

has_any / has_only express set membership on variable-length fields such as species_at_sites, and two variables of the same class self-join, so “another record with the same spacegroup” is expressible directly.

In httk v1

Analysis was always a hand-written script re-querying httk.db, with searcher joins like search.add(search_total_energy.structure == search_struct) and search.add_all(search_struct.formula_symbols.is_in('O', 'Ca', 'Ti')), feeding the result into matplotlib. The searcher DSL survives in httk-store in recognizable form — variable/add/output — so this is the most familiar corner of httk₂ for a v1 user.

If you would rather write a query as an OPTIMADE filter string — the same text a remote client sends in a URL — httk.store.query.optimade_filters translates one against a store’s schema, so _httk_custom_symbols HAS "O" runs as an ordinary search with no HTTP server in sight.

Phase diagrams and hulls

httk-analyse provides the materials-science PhaseDiagram. Build it from compositions or from collected structures, then read the stable set and the per-atom energy above the hull:

from httk.analyse.matsci import PhaseDiagram

diagram = PhaseDiagram.from_compositions(
    [{"A": 1}, {"B": 1}, {"A": 1, "B": 1}],
    [0.0, 0.0, -2.0],
    ids=["A", "B", "AB"],
)
assert tuple(diagram.hull_indices) == (0, 1, 2)
assert diagram.energy_above_hull[2] == 0.0

ax = diagram.plot()

from_structures derives compositions from collected structures instead. Plotting is an explicit step: plot() returns a Matplotlib axes you extend or save. For non-composition problems, httk.analyse.generic.LowerConvexHull is the underlying dimension-agnostic hull, exposing hull_indices and value_above_hull.

In httk v1

The phase diagram was httk.atomistic.StructurePhaseDiagram.create(structures, energies), and you displayed it with pd.vis.show() — visualization lived inside the structure classes (httk.atomistic.vis). In httk₂ it is httk.analyse.matsci.PhaseDiagram with a plain .plot(); visualization no longer lives inside the structure classes.