mattergraph-api 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mattergraph_api/__init__.py +1 -0
- mattergraph_api/config.py +15 -0
- mattergraph_api/db/migrations/.gitkeep +1 -0
- mattergraph_api/db/models.py +6 -0
- mattergraph_api/db/session.py +4 -0
- mattergraph_api/main.py +77 -0
- mattergraph_api/py.typed +0 -0
- mattergraph_api/routes/__init__.py +1 -0
- mattergraph_api/routes/datasets.py +184 -0
- mattergraph_api/routes/demo.py +116 -0
- mattergraph_api/routes/materials.py +38 -0
- mattergraph_api/routes/scores.py +80 -0
- mattergraph_api/routes/search.py +26 -0
- mattergraph_api/routes/simulations.py +46 -0
- mattergraph_api/routes/workflows.py +18 -0
- mattergraph_api/services/__init__.py +1 -0
- mattergraph_api/services/dataset_registry.py +201 -0
- mattergraph_api/services/demo_service.py +482 -0
- mattergraph_api/services/store_service.py +38 -0
- mattergraph_api/services/workflow_service.py +195 -0
- mattergraph_api-0.1.0.dist-info/METADATA +60 -0
- mattergraph_api-0.1.0.dist-info/RECORD +23 -0
- mattergraph_api-0.1.0.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from mattergraph import MaterialStore
|
|
7
|
+
|
|
8
|
+
from mattergraph_api.services.dataset_registry import dataset_registry
|
|
9
|
+
from mattergraph_api.services.demo_service import get_demo_store
|
|
10
|
+
|
|
11
|
+
_store: MaterialStore | None = None
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _resolve_demo_path() -> Path | None:
|
|
15
|
+
env = os.environ.get("MATTERGRAPH_DEMO_DATA")
|
|
16
|
+
if env:
|
|
17
|
+
return Path(env)
|
|
18
|
+
return None
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def get_store() -> MaterialStore:
|
|
22
|
+
global _store # noqa: PLW0603
|
|
23
|
+
if _store is None:
|
|
24
|
+
p = _resolve_demo_path()
|
|
25
|
+
_store = MaterialStore.from_jsonl(p) if p is not None and p.is_file() else get_demo_store()
|
|
26
|
+
return _store
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def resolve_store(dataset_id: str | None = None) -> MaterialStore:
|
|
30
|
+
"""Return the bundled store or lazily materialize one selected local dataset."""
|
|
31
|
+
if dataset_id is None:
|
|
32
|
+
return get_store()
|
|
33
|
+
return dataset_registry.materialize(dataset_id)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def reset_store(s: MaterialStore) -> None:
|
|
37
|
+
global _store # noqa: PLW0603
|
|
38
|
+
_store = s
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, Field
|
|
6
|
+
|
|
7
|
+
from mattergraph_api.services.demo_service import (
|
|
8
|
+
FIXTURE_DISCLAIMER,
|
|
9
|
+
FIXTURE_RELATIVE_PATH,
|
|
10
|
+
RUN_ID,
|
|
11
|
+
SLICE_NAME,
|
|
12
|
+
TARGET,
|
|
13
|
+
WORKFLOW_VERSION,
|
|
14
|
+
get_demo_dataset,
|
|
15
|
+
get_demo_manifest,
|
|
16
|
+
get_filtered_demo_dataset,
|
|
17
|
+
graph_summary,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class CandidateSliceSummary(BaseModel):
|
|
22
|
+
slice_id: str
|
|
23
|
+
slice_name: str
|
|
24
|
+
target: str
|
|
25
|
+
input_count: int
|
|
26
|
+
output_count: int
|
|
27
|
+
removed_count: int
|
|
28
|
+
filter_steps: list[dict[str, Any]] = Field(default_factory=list)
|
|
29
|
+
report: dict[str, Any] = Field(default_factory=dict)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class GraphPreview(BaseModel):
|
|
33
|
+
material_id: str
|
|
34
|
+
formula: str
|
|
35
|
+
node_count: int
|
|
36
|
+
edge_count: int
|
|
37
|
+
node_feature_shape: list[int]
|
|
38
|
+
edge_feature_shape: list[int]
|
|
39
|
+
global_features: dict[str, float | int]
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class GraphExportSummary(BaseModel):
|
|
43
|
+
included_count: int
|
|
44
|
+
excluded_count: int
|
|
45
|
+
previews: list[GraphPreview] = Field(default_factory=list)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class BenchmarkSummary(BaseModel):
|
|
49
|
+
target: str
|
|
50
|
+
row_count: int
|
|
51
|
+
columns: list[str]
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class BenchmarkPreviewRow(BaseModel):
|
|
55
|
+
material_id: str
|
|
56
|
+
formula: str
|
|
57
|
+
target: float | str | None
|
|
58
|
+
density: float | str | None = None
|
|
59
|
+
energy_above_hull: float | str | None = None
|
|
60
|
+
max_force: float | str | None = None
|
|
61
|
+
nsites: int | None = None
|
|
62
|
+
nelements: int | None = None
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class WorkflowProvenance(BaseModel):
|
|
66
|
+
fixture_path: str
|
|
67
|
+
loader: str
|
|
68
|
+
workflow_version: str
|
|
69
|
+
run_id: str
|
|
70
|
+
fixture_kind: str = "checksummed_real_snapshot"
|
|
71
|
+
disclaimer: str
|
|
72
|
+
upstream_revision: str
|
|
73
|
+
hull_revision: str
|
|
74
|
+
license: str
|
|
75
|
+
citation_doi: str
|
|
76
|
+
snapshot_sha256: str
|
|
77
|
+
field_sources: dict[str, str]
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class LeMaterialDemoWorkflowResponse(BaseModel):
|
|
81
|
+
workflow_id: str
|
|
82
|
+
source_dataset: str
|
|
83
|
+
source_subset: str
|
|
84
|
+
schema_report: dict[str, Any]
|
|
85
|
+
candidate_slice: CandidateSliceSummary
|
|
86
|
+
graph_export: GraphExportSummary
|
|
87
|
+
benchmark: BenchmarkSummary
|
|
88
|
+
benchmark_preview: list[BenchmarkPreviewRow]
|
|
89
|
+
provenance: WorkflowProvenance
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def build_lematerial_demo_workflow() -> LeMaterialDemoWorkflowResponse:
|
|
93
|
+
dataset = get_demo_dataset()
|
|
94
|
+
manifest = get_demo_manifest()
|
|
95
|
+
filtered = get_filtered_demo_dataset()
|
|
96
|
+
candidate_slice = filtered.create_slice(SLICE_NAME, target=TARGET)
|
|
97
|
+
slice_report = candidate_slice.report()
|
|
98
|
+
graph_export = filtered.to_graphs()
|
|
99
|
+
benchmark_frame = filtered.to_benchmark_frame(target=TARGET)
|
|
100
|
+
store = filtered.to_material_store()
|
|
101
|
+
graph_previews = []
|
|
102
|
+
for material in store.materials:
|
|
103
|
+
if material.structure is None:
|
|
104
|
+
continue
|
|
105
|
+
summary = graph_summary(material)
|
|
106
|
+
graph_previews.append(
|
|
107
|
+
GraphPreview(
|
|
108
|
+
material_id=material.material_id,
|
|
109
|
+
formula=material.formula,
|
|
110
|
+
node_count=len(summary["nodes"]),
|
|
111
|
+
edge_count=summary["edge_count"],
|
|
112
|
+
node_feature_shape=summary["node_feature_shape"],
|
|
113
|
+
edge_feature_shape=summary["edge_feature_shape"],
|
|
114
|
+
global_features=summary["global_features"],
|
|
115
|
+
)
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
return LeMaterialDemoWorkflowResponse(
|
|
119
|
+
workflow_id="lematerial_bulk_demo",
|
|
120
|
+
source_dataset=dataset.source_dataset,
|
|
121
|
+
source_subset=dataset.source_subset or "",
|
|
122
|
+
schema_report=dataset.schema_report(),
|
|
123
|
+
candidate_slice=CandidateSliceSummary(
|
|
124
|
+
slice_id=candidate_slice.slice_id,
|
|
125
|
+
slice_name=candidate_slice.slice_name,
|
|
126
|
+
target=candidate_slice.target or TARGET,
|
|
127
|
+
input_count=candidate_slice.input_count,
|
|
128
|
+
output_count=candidate_slice.output_count,
|
|
129
|
+
removed_count=slice_report["removed_count"],
|
|
130
|
+
filter_steps=slice_report["filter_steps"],
|
|
131
|
+
report=slice_report,
|
|
132
|
+
),
|
|
133
|
+
graph_export=GraphExportSummary(
|
|
134
|
+
included_count=graph_export.included_count,
|
|
135
|
+
excluded_count=graph_export.excluded_count,
|
|
136
|
+
previews=graph_previews,
|
|
137
|
+
),
|
|
138
|
+
benchmark=BenchmarkSummary(
|
|
139
|
+
target=TARGET,
|
|
140
|
+
row_count=len(benchmark_frame),
|
|
141
|
+
columns=[str(column) for column in benchmark_frame.columns],
|
|
142
|
+
),
|
|
143
|
+
benchmark_preview=_benchmark_preview(benchmark_frame.to_dict(orient="records")),
|
|
144
|
+
provenance=WorkflowProvenance(
|
|
145
|
+
fixture_path=FIXTURE_RELATIVE_PATH,
|
|
146
|
+
loader="LeMatBulk.from_records",
|
|
147
|
+
workflow_version=WORKFLOW_VERSION,
|
|
148
|
+
run_id=RUN_ID,
|
|
149
|
+
disclaimer=FIXTURE_DISCLAIMER,
|
|
150
|
+
upstream_revision=manifest["upstream_revision"],
|
|
151
|
+
hull_revision=manifest["hull_revision"],
|
|
152
|
+
license=manifest["license"],
|
|
153
|
+
citation_doi=manifest["citation_doi"],
|
|
154
|
+
snapshot_sha256=manifest["snapshot_sha256"],
|
|
155
|
+
field_sources=manifest["field_sources"],
|
|
156
|
+
),
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def _benchmark_preview(records: list[dict[str, Any]]) -> list[BenchmarkPreviewRow]:
|
|
161
|
+
preview: list[BenchmarkPreviewRow] = []
|
|
162
|
+
for row in records[:3]:
|
|
163
|
+
preview.append(
|
|
164
|
+
BenchmarkPreviewRow(
|
|
165
|
+
material_id=str(row["material_id"]),
|
|
166
|
+
formula=str(row["formula"]),
|
|
167
|
+
target=_json_scalar(row.get("target")),
|
|
168
|
+
density=_json_scalar(row.get("density")),
|
|
169
|
+
energy_above_hull=_json_scalar(row.get("energy_above_hull")),
|
|
170
|
+
max_force=_json_scalar(row.get("max_force")),
|
|
171
|
+
nsites=_json_int(row.get("nsites")),
|
|
172
|
+
nelements=_json_int(row.get("nelements")),
|
|
173
|
+
)
|
|
174
|
+
)
|
|
175
|
+
return preview
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def _json_scalar(value: Any) -> float | str | None:
|
|
179
|
+
if value is None:
|
|
180
|
+
return None
|
|
181
|
+
if hasattr(value, "item") and callable(value.item):
|
|
182
|
+
value = value.item()
|
|
183
|
+
if isinstance(value, (int, float)):
|
|
184
|
+
return float(value)
|
|
185
|
+
if isinstance(value, str):
|
|
186
|
+
return value
|
|
187
|
+
return str(value)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def _json_int(value: Any) -> int | None:
|
|
191
|
+
if value is None:
|
|
192
|
+
return None
|
|
193
|
+
if hasattr(value, "item") and callable(value.item):
|
|
194
|
+
value = value.item()
|
|
195
|
+
return int(value)
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mattergraph-api
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: FastAPI service for the MatterGraph demo and integrations.
|
|
5
|
+
Project-URL: Homepage, https://github.com/cyrusmo/MatterGraph
|
|
6
|
+
Project-URL: Repository, https://github.com/cyrusmo/MatterGraph
|
|
7
|
+
Project-URL: Issues, https://github.com/cyrusmo/MatterGraph/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/cyrusmo/MatterGraph/blob/main/CHANGELOG.md
|
|
9
|
+
Author: MatterGraph contributors
|
|
10
|
+
License-Expression: Apache-2.0
|
|
11
|
+
Keywords: api,fastapi,materials-informatics,materials-science
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Framework :: FastAPI
|
|
14
|
+
Classifier: Intended Audience :: Science/Research
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Topic :: Scientific/Engineering :: Chemistry
|
|
21
|
+
Classifier: Topic :: Scientific/Engineering :: Physics
|
|
22
|
+
Requires-Python: >=3.10
|
|
23
|
+
Requires-Dist: fastapi>=0.110
|
|
24
|
+
Requires-Dist: mattergraph-connectors~=0.1.0
|
|
25
|
+
Requires-Dist: mattergraph-core~=0.1.0
|
|
26
|
+
Requires-Dist: mattergraph-sim~=0.1.0
|
|
27
|
+
Requires-Dist: pydantic-settings>=2.2
|
|
28
|
+
Requires-Dist: pydantic>=2.5
|
|
29
|
+
Requires-Dist: pymatgen>=2024.1.1
|
|
30
|
+
Requires-Dist: uvicorn[standard]>=0.29.0
|
|
31
|
+
Description-Content-Type: text/markdown
|
|
32
|
+
|
|
33
|
+
# mattergraph-api
|
|
34
|
+
|
|
35
|
+
FastAPI demo service for [MatterGraph](https://github.com/cyrusmo/MatterGraph).
|
|
36
|
+
|
|
37
|
+
This is a **demonstration surface**, not a production service: storage is an in-memory store loaded from a JSONL fixture, and the persistence layer under `mattergraph_api/db/` is a placeholder for installations that need one.
|
|
38
|
+
|
|
39
|
+
## Routes
|
|
40
|
+
|
|
41
|
+
| Route | Purpose |
|
|
42
|
+
|---|---|
|
|
43
|
+
| `GET /health` | Liveness |
|
|
44
|
+
| `GET /materials`, `GET /materials/{mid}` | Browse normalized records |
|
|
45
|
+
| `GET /search?element=` | Filter by element |
|
|
46
|
+
| `POST /scores/rank` | Rank candidates with objectives, constraints, and weights |
|
|
47
|
+
| `POST /simulations/ase/relax` | Run an ASE relaxation (503 if `ase` is unavailable) |
|
|
48
|
+
| `GET /workflows/lematerial/demo` | End-to-end LeMat-Bulk screening walkthrough |
|
|
49
|
+
|
|
50
|
+
## Install and run
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
pip install mattergraph-api
|
|
54
|
+
export MATTERGRAPH_DEMO_DATA=data/demo/materials_sample.jsonl
|
|
55
|
+
uvicorn mattergraph_api.main:app --host 0.0.0.0 --port 8000
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## License
|
|
59
|
+
|
|
60
|
+
Apache-2.0
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
mattergraph_api/__init__.py,sha256=qN508bKRh8SnCQlEo0KhUM8ljqMAxUZidtD5eUT4xoo,36
|
|
2
|
+
mattergraph_api/config.py,sha256=bWh9w62w6AmMEgw8vm5T_BxMJfUAVQ69r_AmH1OGBxY,345
|
|
3
|
+
mattergraph_api/main.py,sha256=wice7kvb33fTKxwICzB0Pbinzld8zjRh4U5uBX03KvI,2456
|
|
4
|
+
mattergraph_api/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
+
mattergraph_api/db/models.py,sha256=zGD09I9cfIPHNcBjujAIA6C4ms4kLCkb4y0k_AUdpaM,186
|
|
6
|
+
mattergraph_api/db/session.py,sha256=-XJBvw5CX_Z9ikDV3o50ADAmfs93U4mgEHZNslNdJJY,123
|
|
7
|
+
mattergraph_api/db/migrations/.gitkeep,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
8
|
+
mattergraph_api/routes/__init__.py,sha256=B_IMQ-HRZVaz2X6OonhdX1ksEtIi2xOmtBA7Dsm3wlU,18
|
|
9
|
+
mattergraph_api/routes/datasets.py,sha256=zmqRgouvIvS4_mr-Tb6Npigz0tZK0iwGhZHhuIYg-Ak,6121
|
|
10
|
+
mattergraph_api/routes/demo.py,sha256=LE8MWid8P7IeeQo-kRajisp4996uW3u5kbC9e6EqfRM,3590
|
|
11
|
+
mattergraph_api/routes/materials.py,sha256=P8b2-6_Ou_RwwQRqVKV7vgwCqX-AgN1puDcrbe5idq8,1237
|
|
12
|
+
mattergraph_api/routes/scores.py,sha256=Oha4esye1pGXFh0jWHrHq04LS0dL_bdXUSuV8Fggj24,2330
|
|
13
|
+
mattergraph_api/routes/search.py,sha256=H5bBj44z70KaWgcVQXiQhj2v4ycEhLbQcO3sDew1__I,629
|
|
14
|
+
mattergraph_api/routes/simulations.py,sha256=jkV6XBe5lHnAvveMCJRwf6E8aKFq4KePaK8ttoCqfcY,1487
|
|
15
|
+
mattergraph_api/routes/workflows.py,sha256=ahDdEKL5umhZLCYGp3E8S6KrR8RtyaiAFS8dcYWxa5Q,421
|
|
16
|
+
mattergraph_api/services/__init__.py,sha256=rEGKUGgqrs_kAq_6WCLg6nrd-WvFuVQ1XBApyYdRxEY,38
|
|
17
|
+
mattergraph_api/services/dataset_registry.py,sha256=z3kRqfmE2MAG6kXJilS2CisoVDE1TBAkgLaEhFfLNVg,6536
|
|
18
|
+
mattergraph_api/services/demo_service.py,sha256=zIvmC2zKIju_GNnBlRGiGlG_LnTdSLuEXT1VIdSzpAU,14322
|
|
19
|
+
mattergraph_api/services/store_service.py,sha256=FH3cWuP3FmRrsaZ2iF5j7pFcDMI-tMcLorL-1dUkopY,992
|
|
20
|
+
mattergraph_api/services/workflow_service.py,sha256=Y3Su9rduO-0R2Vtwv_5t0mxUcDUDEJe3mskdXg3_jA4,5609
|
|
21
|
+
mattergraph_api-0.1.0.dist-info/METADATA,sha256=k0V4T823vZXot93DEyeoXGvT-URgBHRfmaXSs0ghEtI,2357
|
|
22
|
+
mattergraph_api-0.1.0.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
|
|
23
|
+
mattergraph_api-0.1.0.dist-info/RECORD,,
|