meidnet-matter 0.6.1__tar.gz
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.
- meidnet_matter-0.6.1/LICENSE +21 -0
- meidnet_matter-0.6.1/PKG-INFO +166 -0
- meidnet_matter-0.6.1/README.md +119 -0
- meidnet_matter-0.6.1/matter/__init__.py +10 -0
- meidnet_matter-0.6.1/matter/api/__init__.py +0 -0
- meidnet_matter-0.6.1/matter/api/deps.py +34 -0
- meidnet_matter-0.6.1/matter/api/errors.py +99 -0
- meidnet_matter-0.6.1/matter/api/router.py +33 -0
- meidnet_matter-0.6.1/matter/api/routes_generate.py +88 -0
- meidnet_matter-0.6.1/matter/api/routes_pipeline.py +42 -0
- meidnet_matter-0.6.1/matter/api/routes_read.py +75 -0
- meidnet_matter-0.6.1/matter/api/routes_readiness.py +18 -0
- meidnet_matter-0.6.1/matter/api/routes_runs.py +174 -0
- meidnet_matter-0.6.1/matter/api/routes_schema.py +25 -0
- meidnet_matter-0.6.1/matter/api/routes_studies.py +47 -0
- meidnet_matter-0.6.1/matter/app.py +94 -0
- meidnet_matter-0.6.1/matter/backends/__init__.py +0 -0
- meidnet_matter-0.6.1/matter/backends/base.py +69 -0
- meidnet_matter-0.6.1/matter/backends/meidnet_backend.py +58 -0
- meidnet_matter-0.6.1/matter/cli.py +69 -0
- meidnet_matter-0.6.1/matter/demo_build.py +400 -0
- meidnet_matter-0.6.1/matter/jsonsafe.py +34 -0
- meidnet_matter-0.6.1/matter/schemas/__init__.py +0 -0
- meidnet_matter-0.6.1/matter/schemas/candidate.py +219 -0
- meidnet_matter-0.6.1/matter/schemas/generation.py +59 -0
- meidnet_matter-0.6.1/matter/schemas/goal.py +84 -0
- meidnet_matter-0.6.1/matter/services/__init__.py +0 -0
- meidnet_matter-0.6.1/matter/services/artefacts.py +108 -0
- meidnet_matter-0.6.1/matter/services/checkpoints.py +86 -0
- meidnet_matter-0.6.1/matter/services/container.py +97 -0
- meidnet_matter-0.6.1/matter/services/enrichment.py +315 -0
- meidnet_matter-0.6.1/matter/services/export.py +168 -0
- meidnet_matter-0.6.1/matter/services/families.py +67 -0
- meidnet_matter-0.6.1/matter/services/generation.py +278 -0
- meidnet_matter-0.6.1/matter/services/goals.py +240 -0
- meidnet_matter-0.6.1/matter/services/jobs.py +81 -0
- meidnet_matter-0.6.1/matter/services/judge.py +74 -0
- meidnet_matter-0.6.1/matter/services/latents.py +57 -0
- meidnet_matter-0.6.1/matter/services/manifest.py +73 -0
- meidnet_matter-0.6.1/matter/services/novelty.py +50 -0
- meidnet_matter-0.6.1/matter/services/pipeline_blocks.py +82 -0
- meidnet_matter-0.6.1/matter/services/readiness.py +405 -0
- meidnet_matter-0.6.1/matter/services/registry.py +77 -0
- meidnet_matter-0.6.1/matter/services/runs.py +247 -0
- meidnet_matter-0.6.1/matter/services/studies.py +61 -0
- meidnet_matter-0.6.1/matter/services/support.py +102 -0
- meidnet_matter-0.6.1/matter/settings.py +80 -0
- meidnet_matter-0.6.1/matter/static/assets/CandidateDetail-BqiNpujp.js +1 -0
- meidnet_matter-0.6.1/matter/static/assets/CandidatePage-DkSYSUGt.js +1 -0
- meidnet_matter-0.6.1/matter/static/assets/CellViewer-uGz-OQCZ.js +1 -0
- meidnet_matter-0.6.1/matter/static/assets/ComingNext-DWJyJnZ_.js +1 -0
- meidnet_matter-0.6.1/matter/static/assets/Compare-3KoBHM7L.js +1 -0
- meidnet_matter-0.6.1/matter/static/assets/CreateProject-Dq502gk7.js +1 -0
- meidnet_matter-0.6.1/matter/static/assets/Explorer-C2wjb9pR.js +1 -0
- meidnet_matter-0.6.1/matter/static/assets/Flow-Xh6Ing8B.js +1 -0
- meidnet_matter-0.6.1/matter/static/assets/Goal-DVF6bS_M.js +1 -0
- meidnet_matter-0.6.1/matter/static/assets/Home-Dalx-9os.js +1 -0
- meidnet_matter-0.6.1/matter/static/assets/Landing-CrKrk_YV.js +1 -0
- meidnet_matter-0.6.1/matter/static/assets/LiveOnlyPage-Dx-LKZ2L.js +1 -0
- meidnet_matter-0.6.1/matter/static/assets/Method-B4fLbGFt.js +10 -0
- meidnet_matter-0.6.1/matter/static/assets/NotFound-dUJkEBAw.js +1 -0
- meidnet_matter-0.6.1/matter/static/assets/Pipeline-D63C_uOU.js +1 -0
- meidnet_matter-0.6.1/matter/static/assets/PipelineBlock-vFIiSNLW.js +1 -0
- meidnet_matter-0.6.1/matter/static/assets/Play-B7TzF3AE.js +1 -0
- meidnet_matter-0.6.1/matter/static/assets/PlayJob-CMpMeVkH.js +1 -0
- meidnet_matter-0.6.1/matter/static/assets/Privacy-DxIp5Gz5.js +1 -0
- meidnet_matter-0.6.1/matter/static/assets/Readiness-D6XYxBgo.js +1 -0
- meidnet_matter-0.6.1/matter/static/assets/Runs-vEmbIL88.js +1 -0
- meidnet_matter-0.6.1/matter/static/assets/Studies-DP2-k-1Y.js +1 -0
- meidnet_matter-0.6.1/matter/static/assets/Study-CVy-yiYD.js +1 -0
- meidnet_matter-0.6.1/matter/static/assets/Validate-GmD8JXu0.js +3 -0
- meidnet_matter-0.6.1/matter/static/assets/components-DL-WsPWG.js +1 -0
- meidnet_matter-0.6.1/matter/static/assets/goalStore-jO6sRghi.js +1 -0
- meidnet_matter-0.6.1/matter/static/assets/index-BVXe3TnV.css +1 -0
- meidnet_matter-0.6.1/matter/static/assets/index-BcbUwdSt.js +3 -0
- meidnet_matter-0.6.1/matter/static/assets/index-DC_MKBTO.js +10 -0
- meidnet_matter-0.6.1/matter/static/assets/landing-B3ddNw34.js +1 -0
- meidnet_matter-0.6.1/matter/static/assets/periodic-DuMLU4Sx.js +1 -0
- meidnet_matter-0.6.1/matter/static/assets/react-D0F8gdN_.js +3 -0
- meidnet_matter-0.6.1/matter/static/assets/research-BifopWLj.js +1 -0
- meidnet_matter-0.6.1/matter/static/assets/research-Demqquip.js +1 -0
- meidnet_matter-0.6.1/matter/static/favicon.svg +1 -0
- meidnet_matter-0.6.1/matter/static/index.html +32 -0
- meidnet_matter-0.6.1/matter/static/og.png +0 -0
- meidnet_matter-0.6.1/matter/version.py +78 -0
- meidnet_matter-0.6.1/meidnet_matter.egg-info/PKG-INFO +166 -0
- meidnet_matter-0.6.1/meidnet_matter.egg-info/SOURCES.txt +106 -0
- meidnet_matter-0.6.1/meidnet_matter.egg-info/dependency_links.txt +1 -0
- meidnet_matter-0.6.1/meidnet_matter.egg-info/entry_points.txt +2 -0
- meidnet_matter-0.6.1/meidnet_matter.egg-info/requires.txt +22 -0
- meidnet_matter-0.6.1/meidnet_matter.egg-info/top_level.txt +1 -0
- meidnet_matter-0.6.1/pyproject.toml +74 -0
- meidnet_matter-0.6.1/setup.cfg +4 -0
- meidnet_matter-0.6.1/tests/test_candidate_record.py +90 -0
- meidnet_matter-0.6.1/tests/test_demo_artefacts.py +98 -0
- meidnet_matter-0.6.1/tests/test_deploy.py +162 -0
- meidnet_matter-0.6.1/tests/test_generate.py +63 -0
- meidnet_matter-0.6.1/tests/test_goals.py +91 -0
- meidnet_matter-0.6.1/tests/test_health_version.py +41 -0
- meidnet_matter-0.6.1/tests/test_hull_mlip.py +100 -0
- meidnet_matter-0.6.1/tests/test_jsonsafe.py +21 -0
- meidnet_matter-0.6.1/tests/test_method_commands.py +61 -0
- meidnet_matter-0.6.1/tests/test_mirror.py +61 -0
- meidnet_matter-0.6.1/tests/test_readiness.py +95 -0
- meidnet_matter-0.6.1/tests/test_research_routes.py +98 -0
- meidnet_matter-0.6.1/tests/test_search.py +173 -0
- meidnet_matter-0.6.1/tests/test_static.py +30 -0
- meidnet_matter-0.6.1/tests/test_support.py +51 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Anand Babu
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: meidnet-matter
|
|
3
|
+
Version: 0.6.1
|
|
4
|
+
Summary: MEIDNet Matter: multimodal inverse design for materials discovery, from your materials data to candidate structures
|
|
5
|
+
Author: Anand Babu
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://babu09-meidnet-matter.hf.space/
|
|
8
|
+
Project-URL: Source, https://github.com/ABnano/MEIDNet-Matter
|
|
9
|
+
Project-URL: Issues, https://github.com/ABnano/MEIDNet-Matter/issues
|
|
10
|
+
Project-URL: Engine, https://github.com/ABnano/MEIDNet
|
|
11
|
+
Project-URL: Models, https://huggingface.co/Babu09/MEIDNet
|
|
12
|
+
Project-URL: Paper, https://doi.org/10.1038/s41524-026-02153-3
|
|
13
|
+
Keywords: materials,inverse design,multimodal learning,crystal structures,perovskite,generative model
|
|
14
|
+
Classifier: Development Status :: 3 - Alpha
|
|
15
|
+
Classifier: Intended Audience :: Science/Research
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Framework :: FastAPI
|
|
22
|
+
Classifier: Topic :: Scientific/Engineering :: Chemistry
|
|
23
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
24
|
+
Requires-Python: >=3.10
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
License-File: LICENSE
|
|
27
|
+
Requires-Dist: meidnet<3,>=2.4.0.dev0
|
|
28
|
+
Requires-Dist: fastapi>=0.115
|
|
29
|
+
Requires-Dist: uvicorn>=0.30
|
|
30
|
+
Requires-Dist: pydantic>=2.5
|
|
31
|
+
Requires-Dist: numpy>=1.24
|
|
32
|
+
Requires-Dist: pyyaml>=6.0
|
|
33
|
+
Requires-Dist: python-multipart>=0.0.9
|
|
34
|
+
Requires-Dist: pandas>=2.2
|
|
35
|
+
Requires-Dist: scipy>=1.11
|
|
36
|
+
Requires-Dist: spglib>=2.5
|
|
37
|
+
Requires-Dist: ase>=3.23
|
|
38
|
+
Provides-Extra: dev
|
|
39
|
+
Requires-Dist: pytest>=8; extra == "dev"
|
|
40
|
+
Requires-Dist: httpx>=0.27; extra == "dev"
|
|
41
|
+
Provides-Extra: judge
|
|
42
|
+
Requires-Dist: matgl==4.0.3; extra == "judge"
|
|
43
|
+
Requires-Dist: torch-geometric>=2.6; extra == "judge"
|
|
44
|
+
Provides-Extra: deploy
|
|
45
|
+
Requires-Dist: huggingface_hub>=0.25; extra == "deploy"
|
|
46
|
+
Dynamic: license-file
|
|
47
|
+
|
|
48
|
+
<p align="center"><img src="https://raw.githubusercontent.com/ABnano/MEIDNet-Matter/main/docs/assets/screenshot.png" alt="MEIDNet Matter: the readiness report and the candidates of a search" width="820"></p>
|
|
49
|
+
|
|
50
|
+
# MEIDNet Matter
|
|
51
|
+
|
|
52
|
+
**From your materials data to candidate structures.**
|
|
53
|
+
|
|
54
|
+
MEIDNet Matter is a multimodal inverse-design workbench for crystalline materials. A researcher brings crystal structures and properties; Matter reports what the data and the model support (the Design Readiness report), then runs a constrained, property-conditioned search and returns candidate structures with their evidence and provenance. The scientific engine is [MEIDNet](https://github.com/ABnano/MEIDNet), imported as a package; the sibling platform [MEIDNet Prism](https://babu09-meidnet.hf.space/) is where to learn the method, reproduce the results and benchmark models.
|
|
55
|
+
|
|
56
|
+
[](https://github.com/ABnano/MEIDNet-Matter/actions/workflows/ci.yml)
|
|
57
|
+
[](https://huggingface.co/spaces/Babu09/MEIDNet-Matter)
|
|
58
|
+
[](https://pypi.org/project/meidnet-matter/)
|
|
59
|
+
[](https://pypi.org/project/meidnet/)
|
|
60
|
+
[](https://github.com/ABnano/MEIDNet-Matter/blob/main/LICENSE)
|
|
61
|
+
[](https://doi.org/10.1038/s41524-026-02153-3)
|
|
62
|
+
|
|
63
|
+
Matter currently searches property-conditioned candidates within supported structural families. Free-geometry crystal generation is planned as additional design backends mature.
|
|
64
|
+
|
|
65
|
+
## Try it
|
|
66
|
+
|
|
67
|
+
**Live:** https://babu09-meidnet-matter.hf.space/ — open the Perov-5 demo project, set a band-gap target, exclude lead, read the readiness report, run the search (about a minute on the shared CPU), open a candidate, download its CIF or the whole run bundle.
|
|
68
|
+
|
|
69
|
+
**Mirror for any network:** https://abnano.github.io/MEIDNet-Matter/ — the same site on GitHub Pages, for networks that block
|
|
70
|
+
`*.hf.space` (public Wi-Fi often does: the Space then shows a grey page). Everything that reads works there (studies, the
|
|
71
|
+
staged pipeline with its code, the method, checkpoints, every structure file); generation and the demo search point to the
|
|
72
|
+
Space, and the mirror says whether the Space is reachable from your network. It is rebuilt for every release
|
|
73
|
+
(`.github/workflows/pages.yml`: `npm run build:mirror`, then `scripts/build_mirror.py`). Prism has its own mirror at
|
|
74
|
+
https://abnano.github.io/MEIDNet/.
|
|
75
|
+
|
|
76
|
+
## What it does, in this version
|
|
77
|
+
|
|
78
|
+
The home page opens on a worked result: a real accepted structure with both of its readings, a small requested-versus-delivered
|
|
79
|
+
plot, an animated six-step walkthrough, and a rolling strip of the structures the generator delivered. From there: **Pipeline**
|
|
80
|
+
(the ten blocks with their bands and code), **Studies** (Perov-5, Materials Project perovskites, an external upload, MP-20, with
|
|
81
|
+
checkpoints), **Generate** (live band-gap generation on MP-20 with two independent readings per cell, 3D cards and an evidence
|
|
82
|
+
map) and **Method** (mechanism, strengths, limits, and the commands that run the same stages on your own data: whatever route produced your candidates, one check gives them the qualified judge, relaxation by two potentials, both readings again on the relaxed cells, the energy above the hull with one potential for every phase, novelty, and a class: new, rediscovered, or contradicted by your data's own value). The Perov-5
|
|
83
|
+
demo project below is the original flow and is kept as it was, with one change: every candidate now shows the
|
|
84
|
+
**structure-based prediction** first and the **search value** beside it, and says which of the two supports the target.
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
| Step | What you get |
|
|
88
|
+
|---|---|
|
|
89
|
+
| **Goal** | Target value, range or bound per property; family and variant; excluded elements and presets; the chemistry rules with their limits; the search budget. |
|
|
90
|
+
| **Readiness** | Six indicators before any search: prediction error of each targeted property on the evaluation split (a published-model diagnostic when the checkpoint saw that split in training), cross-modal retrieval in both directions, what the decoder recovers, where the target sits in the training distribution and how much data lies around it, how many structures share the target window, and whether the family's elements occur in the data. One verdict; a "not recommended" target can still be searched in exploratory mode. |
|
|
91
|
+
| **Candidates** | A search with the engine; every candidate carries the structure-based prediction and the search value of each property, each with its domain status, and what the evidence supports, the rules with values and windows, the encoder's own prediction of the composition and whether it agrees with the search's, the nearest training materials with their DFT values, the training data around the prediction, whether the dataset already holds it, the stability stage, and a "why" sentence that repeats these facts. Table, cards and map; filters; comparison side by side. |
|
|
92
|
+
| **One target, many structures** | When the search has finished, candidates are grouped into clusters of similar encoder latents (cosine ≥ 0.9). The cards view shows the clusters; the "Prioritise" control orders candidates by target accuracy, diversity (one per cluster first), stability, novelty, search score, encoder agreement or order found. |
|
|
93
|
+
| **Validation ladder** | Six stages: Generated · Chemistry checked · MLIP screened · DFT relaxed · DFT property confirmed · Experimentally tested. Every candidate records the highest stage it reached and the result of each stage; this version records stages 0 and 1, the later ones are your own steps and keep their place in the record. |
|
|
94
|
+
| **Export** | CIF per candidate, the candidate table, the candidate record (versioned: `meidnet-matter/candidate-record/1`, JSON Schema at `/api/schema/candidate-record`), and the run bundle: goal, engine configuration, metrics, readiness, candidates, CIFs, `targets.csv`, the record schema, a manifest with file hashes. |
|
|
95
|
+
| **Score on Prism** | The bundle's `cifs/` and `targets.csv` are the input of `meidnet score` (MEIDNet 2.3.1 or later): validity, uniqueness, novelty, diversity, distribution and the conditional metrics, named as in LeMat-GenBench. The export page gives the commands; [the metrics are defined on Prism](https://babu09-meidnet.hf.space/docs/benchmarks/compatibility.html). |
|
|
96
|
+
|
|
97
|
+
The demo project: cubic ABX₃ perovskites of the Perov-5 dataset, the published MEIDNet model, the direct band gap and the formation enthalpy. By the engine's thresholds the published model is weak on both properties on held-out data, so the demo opens its searches in exploratory mode by design; [docs/scientific-scope.md](https://github.com/ABnano/MEIDNet-Matter/blob/main/docs/scientific-scope.md) has the numbers and what the evidence shows instead.
|
|
98
|
+
|
|
99
|
+
Coming next (Phase 1): upload your own property table and CIF files, a data-quality report, training in the browser, readiness on your own held-out data, the same search with your model; then imports from Materials Project and NOMAD, a bring-your-own-model backend, and synthesis context linked from existing resources. [docs/data-format.md](https://github.com/ABnano/MEIDNet-Matter/blob/main/docs/data-format.md) describes the upload layout, the candidate record and `targets.csv`.
|
|
100
|
+
|
|
101
|
+
Matter is one half of the MEIDNet ecosystem: [MEIDNet Prism](https://babu09-meidnet.hf.space/) is where the method is learned, benchmarked and developed ([the ecosystem](https://babu09-meidnet.hf.space/docs/ecosystem.html)); Matter is where a dataset becomes candidates.
|
|
102
|
+
|
|
103
|
+
## Run it yourself
|
|
104
|
+
|
|
105
|
+
Supported: Linux, macOS, and Windows through WSL (Windows 11 with Smart App Control blocks unsigned wheels one module at
|
|
106
|
+
a time, so use WSL there). One command installs the engine and the application with the judge from PyPI, with CPU torch
|
|
107
|
+
(the default Linux build pulls about 2.5 GB of GPU libraries), pinned to the versions this release was tested with; the
|
|
108
|
+
last line proves the install:
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
pip install --extra-index-url https://download.pytorch.org/whl/cpu \
|
|
112
|
+
-c https://github.com/ABnano/MEIDNet-Matter/releases/latest/download/constraints.txt "meidnet-matter[judge]"
|
|
113
|
+
python -c "import meidnet, matter, matgl; print(meidnet.__version__, matter.__version__)" && meidnet --version
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
On a slow or flaky network, [scripts/install.sh](https://github.com/ABnano/MEIDNet-Matter/blob/main/scripts/install.sh)
|
|
117
|
+
does the same with retries, a fresh virtual environment and the check (`bash scripts/install.sh`; `MATTER_SOURCE=release`
|
|
118
|
+
takes the wheels of the latest release instead of PyPI). The same wheels are attached to every release, for an index
|
|
119
|
+
mirror or an offline machine:
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
pip install --extra-index-url https://download.pytorch.org/whl/cpu \
|
|
123
|
+
"meidnet @ https://github.com/ABnano/MEIDNet-Matter/releases/latest/download/meidnet-2.4.0.dev2-py3-none-any.whl" \
|
|
124
|
+
"meidnet-matter[judge] @ https://github.com/ABnano/MEIDNet-Matter/releases/latest/download/meidnet_matter-0.6.1-py3-none-any.whl"
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
`constraints.txt` is written by the release workflow from its own install check (Linux, Python 3.12); on another Python
|
|
128
|
+
drop the `-c` line. The engine on PyPI, `meidnet` 2.4.0.dev2, is the snapshot in `engine/`, published from this repository
|
|
129
|
+
until the upstream MEIDNet release 2.4.0 replaces it.
|
|
130
|
+
|
|
131
|
+
From a clone instead (the engine first, so that nothing older is fetched from PyPI):
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
git clone https://github.com/ABnano/MEIDNet-Matter && cd MEIDNet-Matter
|
|
135
|
+
pip install --extra-index-url https://download.pytorch.org/whl/cpu ./engine && pip install -e ".[dev,judge]"
|
|
136
|
+
python scripts/fetch_assets.py # the checkpoints the studies use, each verified against its checksum
|
|
137
|
+
cd frontend && npm ci && npm run build && cd ..
|
|
138
|
+
python -m uvicorn matter.app:app --port 8000 # http://127.0.0.1:8000/
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
The potentials used for relaxation (TensorNet, CHGNet) are fetched from the Hugging Face Hub on first use; the classic
|
|
142
|
+
transfer is used by default because the Hub's Xet transfer can stall silently on some networks (`HF_HUB_DISABLE_XET`).
|
|
143
|
+
The known-good versions of every dependency are the ones in [deploy/requirements.txt](https://github.com/ABnano/MEIDNet-Matter/blob/main/deploy/requirements.txt), which the
|
|
144
|
+
public Space runs.
|
|
145
|
+
|
|
146
|
+
Nothing leaves your computer. Details, the Docker image and the Space deploy: [docs/deployment.md](https://github.com/ABnano/MEIDNet-Matter/blob/main/docs/deployment.md). What happens to your data on the shared Space: [docs/privacy.md](https://github.com/ABnano/MEIDNet-Matter/blob/main/docs/privacy.md).
|
|
147
|
+
|
|
148
|
+
## How it works
|
|
149
|
+
|
|
150
|
+
Data → readiness → shared latent space → search → candidates. MEIDNet learns one latent space shared by crystal structures and their properties and searches it for compositions that hit property targets while obeying the chemistry rules of a material family; Matter adds the readiness report before the search, the evidence next to every candidate, the run manifest, and the interface. The backend (`matter/`, FastAPI) calls the `meidnet` package through one backend interface so that other design engines can be added; the frontend (`frontend/`, React + TypeScript) is built into `matter/static` and served by the backend.
|
|
151
|
+
|
|
152
|
+
## Development
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
python -m pytest -q # backend; -m "not slow" skips the model and the tiny search
|
|
156
|
+
cd frontend && npm run typecheck && npm test && npm run lint # frontend
|
|
157
|
+
node scripts/smoke_browser.mjs http://127.0.0.1:8000 build/smoke # the browser walk-through against a running server
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Citation
|
|
161
|
+
|
|
162
|
+
If you use MEIDNet Matter, please cite the MEIDNet paper and the software ([CITATION.cff](https://github.com/ABnano/MEIDNet-Matter/blob/main/CITATION.cff)):
|
|
163
|
+
|
|
164
|
+
> A. Babu, R. Almeida Gouvêa, P. Vandergheynst, G.-M. Rignanese, MEIDNet: Multimodal generative AI framework for inverse materials design, npj Computational Materials 12, 287 (2026). doi:10.1038/s41524-026-02153-3
|
|
165
|
+
|
|
166
|
+
Data: Perov-5 (Castelli et al. 2012) in the CDVAE split (Xie et al. 2022). Licence: MIT. Author: Anand Babu.
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
<p align="center"><img src="https://raw.githubusercontent.com/ABnano/MEIDNet-Matter/main/docs/assets/screenshot.png" alt="MEIDNet Matter: the readiness report and the candidates of a search" width="820"></p>
|
|
2
|
+
|
|
3
|
+
# MEIDNet Matter
|
|
4
|
+
|
|
5
|
+
**From your materials data to candidate structures.**
|
|
6
|
+
|
|
7
|
+
MEIDNet Matter is a multimodal inverse-design workbench for crystalline materials. A researcher brings crystal structures and properties; Matter reports what the data and the model support (the Design Readiness report), then runs a constrained, property-conditioned search and returns candidate structures with their evidence and provenance. The scientific engine is [MEIDNet](https://github.com/ABnano/MEIDNet), imported as a package; the sibling platform [MEIDNet Prism](https://babu09-meidnet.hf.space/) is where to learn the method, reproduce the results and benchmark models.
|
|
8
|
+
|
|
9
|
+
[](https://github.com/ABnano/MEIDNet-Matter/actions/workflows/ci.yml)
|
|
10
|
+
[](https://huggingface.co/spaces/Babu09/MEIDNet-Matter)
|
|
11
|
+
[](https://pypi.org/project/meidnet-matter/)
|
|
12
|
+
[](https://pypi.org/project/meidnet/)
|
|
13
|
+
[](https://github.com/ABnano/MEIDNet-Matter/blob/main/LICENSE)
|
|
14
|
+
[](https://doi.org/10.1038/s41524-026-02153-3)
|
|
15
|
+
|
|
16
|
+
Matter currently searches property-conditioned candidates within supported structural families. Free-geometry crystal generation is planned as additional design backends mature.
|
|
17
|
+
|
|
18
|
+
## Try it
|
|
19
|
+
|
|
20
|
+
**Live:** https://babu09-meidnet-matter.hf.space/ — open the Perov-5 demo project, set a band-gap target, exclude lead, read the readiness report, run the search (about a minute on the shared CPU), open a candidate, download its CIF or the whole run bundle.
|
|
21
|
+
|
|
22
|
+
**Mirror for any network:** https://abnano.github.io/MEIDNet-Matter/ — the same site on GitHub Pages, for networks that block
|
|
23
|
+
`*.hf.space` (public Wi-Fi often does: the Space then shows a grey page). Everything that reads works there (studies, the
|
|
24
|
+
staged pipeline with its code, the method, checkpoints, every structure file); generation and the demo search point to the
|
|
25
|
+
Space, and the mirror says whether the Space is reachable from your network. It is rebuilt for every release
|
|
26
|
+
(`.github/workflows/pages.yml`: `npm run build:mirror`, then `scripts/build_mirror.py`). Prism has its own mirror at
|
|
27
|
+
https://abnano.github.io/MEIDNet/.
|
|
28
|
+
|
|
29
|
+
## What it does, in this version
|
|
30
|
+
|
|
31
|
+
The home page opens on a worked result: a real accepted structure with both of its readings, a small requested-versus-delivered
|
|
32
|
+
plot, an animated six-step walkthrough, and a rolling strip of the structures the generator delivered. From there: **Pipeline**
|
|
33
|
+
(the ten blocks with their bands and code), **Studies** (Perov-5, Materials Project perovskites, an external upload, MP-20, with
|
|
34
|
+
checkpoints), **Generate** (live band-gap generation on MP-20 with two independent readings per cell, 3D cards and an evidence
|
|
35
|
+
map) and **Method** (mechanism, strengths, limits, and the commands that run the same stages on your own data: whatever route produced your candidates, one check gives them the qualified judge, relaxation by two potentials, both readings again on the relaxed cells, the energy above the hull with one potential for every phase, novelty, and a class: new, rediscovered, or contradicted by your data's own value). The Perov-5
|
|
36
|
+
demo project below is the original flow and is kept as it was, with one change: every candidate now shows the
|
|
37
|
+
**structure-based prediction** first and the **search value** beside it, and says which of the two supports the target.
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
| Step | What you get |
|
|
41
|
+
|---|---|
|
|
42
|
+
| **Goal** | Target value, range or bound per property; family and variant; excluded elements and presets; the chemistry rules with their limits; the search budget. |
|
|
43
|
+
| **Readiness** | Six indicators before any search: prediction error of each targeted property on the evaluation split (a published-model diagnostic when the checkpoint saw that split in training), cross-modal retrieval in both directions, what the decoder recovers, where the target sits in the training distribution and how much data lies around it, how many structures share the target window, and whether the family's elements occur in the data. One verdict; a "not recommended" target can still be searched in exploratory mode. |
|
|
44
|
+
| **Candidates** | A search with the engine; every candidate carries the structure-based prediction and the search value of each property, each with its domain status, and what the evidence supports, the rules with values and windows, the encoder's own prediction of the composition and whether it agrees with the search's, the nearest training materials with their DFT values, the training data around the prediction, whether the dataset already holds it, the stability stage, and a "why" sentence that repeats these facts. Table, cards and map; filters; comparison side by side. |
|
|
45
|
+
| **One target, many structures** | When the search has finished, candidates are grouped into clusters of similar encoder latents (cosine ≥ 0.9). The cards view shows the clusters; the "Prioritise" control orders candidates by target accuracy, diversity (one per cluster first), stability, novelty, search score, encoder agreement or order found. |
|
|
46
|
+
| **Validation ladder** | Six stages: Generated · Chemistry checked · MLIP screened · DFT relaxed · DFT property confirmed · Experimentally tested. Every candidate records the highest stage it reached and the result of each stage; this version records stages 0 and 1, the later ones are your own steps and keep their place in the record. |
|
|
47
|
+
| **Export** | CIF per candidate, the candidate table, the candidate record (versioned: `meidnet-matter/candidate-record/1`, JSON Schema at `/api/schema/candidate-record`), and the run bundle: goal, engine configuration, metrics, readiness, candidates, CIFs, `targets.csv`, the record schema, a manifest with file hashes. |
|
|
48
|
+
| **Score on Prism** | The bundle's `cifs/` and `targets.csv` are the input of `meidnet score` (MEIDNet 2.3.1 or later): validity, uniqueness, novelty, diversity, distribution and the conditional metrics, named as in LeMat-GenBench. The export page gives the commands; [the metrics are defined on Prism](https://babu09-meidnet.hf.space/docs/benchmarks/compatibility.html). |
|
|
49
|
+
|
|
50
|
+
The demo project: cubic ABX₃ perovskites of the Perov-5 dataset, the published MEIDNet model, the direct band gap and the formation enthalpy. By the engine's thresholds the published model is weak on both properties on held-out data, so the demo opens its searches in exploratory mode by design; [docs/scientific-scope.md](https://github.com/ABnano/MEIDNet-Matter/blob/main/docs/scientific-scope.md) has the numbers and what the evidence shows instead.
|
|
51
|
+
|
|
52
|
+
Coming next (Phase 1): upload your own property table and CIF files, a data-quality report, training in the browser, readiness on your own held-out data, the same search with your model; then imports from Materials Project and NOMAD, a bring-your-own-model backend, and synthesis context linked from existing resources. [docs/data-format.md](https://github.com/ABnano/MEIDNet-Matter/blob/main/docs/data-format.md) describes the upload layout, the candidate record and `targets.csv`.
|
|
53
|
+
|
|
54
|
+
Matter is one half of the MEIDNet ecosystem: [MEIDNet Prism](https://babu09-meidnet.hf.space/) is where the method is learned, benchmarked and developed ([the ecosystem](https://babu09-meidnet.hf.space/docs/ecosystem.html)); Matter is where a dataset becomes candidates.
|
|
55
|
+
|
|
56
|
+
## Run it yourself
|
|
57
|
+
|
|
58
|
+
Supported: Linux, macOS, and Windows through WSL (Windows 11 with Smart App Control blocks unsigned wheels one module at
|
|
59
|
+
a time, so use WSL there). One command installs the engine and the application with the judge from PyPI, with CPU torch
|
|
60
|
+
(the default Linux build pulls about 2.5 GB of GPU libraries), pinned to the versions this release was tested with; the
|
|
61
|
+
last line proves the install:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
pip install --extra-index-url https://download.pytorch.org/whl/cpu \
|
|
65
|
+
-c https://github.com/ABnano/MEIDNet-Matter/releases/latest/download/constraints.txt "meidnet-matter[judge]"
|
|
66
|
+
python -c "import meidnet, matter, matgl; print(meidnet.__version__, matter.__version__)" && meidnet --version
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
On a slow or flaky network, [scripts/install.sh](https://github.com/ABnano/MEIDNet-Matter/blob/main/scripts/install.sh)
|
|
70
|
+
does the same with retries, a fresh virtual environment and the check (`bash scripts/install.sh`; `MATTER_SOURCE=release`
|
|
71
|
+
takes the wheels of the latest release instead of PyPI). The same wheels are attached to every release, for an index
|
|
72
|
+
mirror or an offline machine:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
pip install --extra-index-url https://download.pytorch.org/whl/cpu \
|
|
76
|
+
"meidnet @ https://github.com/ABnano/MEIDNet-Matter/releases/latest/download/meidnet-2.4.0.dev2-py3-none-any.whl" \
|
|
77
|
+
"meidnet-matter[judge] @ https://github.com/ABnano/MEIDNet-Matter/releases/latest/download/meidnet_matter-0.6.1-py3-none-any.whl"
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
`constraints.txt` is written by the release workflow from its own install check (Linux, Python 3.12); on another Python
|
|
81
|
+
drop the `-c` line. The engine on PyPI, `meidnet` 2.4.0.dev2, is the snapshot in `engine/`, published from this repository
|
|
82
|
+
until the upstream MEIDNet release 2.4.0 replaces it.
|
|
83
|
+
|
|
84
|
+
From a clone instead (the engine first, so that nothing older is fetched from PyPI):
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
git clone https://github.com/ABnano/MEIDNet-Matter && cd MEIDNet-Matter
|
|
88
|
+
pip install --extra-index-url https://download.pytorch.org/whl/cpu ./engine && pip install -e ".[dev,judge]"
|
|
89
|
+
python scripts/fetch_assets.py # the checkpoints the studies use, each verified against its checksum
|
|
90
|
+
cd frontend && npm ci && npm run build && cd ..
|
|
91
|
+
python -m uvicorn matter.app:app --port 8000 # http://127.0.0.1:8000/
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
The potentials used for relaxation (TensorNet, CHGNet) are fetched from the Hugging Face Hub on first use; the classic
|
|
95
|
+
transfer is used by default because the Hub's Xet transfer can stall silently on some networks (`HF_HUB_DISABLE_XET`).
|
|
96
|
+
The known-good versions of every dependency are the ones in [deploy/requirements.txt](https://github.com/ABnano/MEIDNet-Matter/blob/main/deploy/requirements.txt), which the
|
|
97
|
+
public Space runs.
|
|
98
|
+
|
|
99
|
+
Nothing leaves your computer. Details, the Docker image and the Space deploy: [docs/deployment.md](https://github.com/ABnano/MEIDNet-Matter/blob/main/docs/deployment.md). What happens to your data on the shared Space: [docs/privacy.md](https://github.com/ABnano/MEIDNet-Matter/blob/main/docs/privacy.md).
|
|
100
|
+
|
|
101
|
+
## How it works
|
|
102
|
+
|
|
103
|
+
Data → readiness → shared latent space → search → candidates. MEIDNet learns one latent space shared by crystal structures and their properties and searches it for compositions that hit property targets while obeying the chemistry rules of a material family; Matter adds the readiness report before the search, the evidence next to every candidate, the run manifest, and the interface. The backend (`matter/`, FastAPI) calls the `meidnet` package through one backend interface so that other design engines can be added; the frontend (`frontend/`, React + TypeScript) is built into `matter/static` and served by the backend.
|
|
104
|
+
|
|
105
|
+
## Development
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
python -m pytest -q # backend; -m "not slow" skips the model and the tiny search
|
|
109
|
+
cd frontend && npm run typecheck && npm test && npm run lint # frontend
|
|
110
|
+
node scripts/smoke_browser.mjs http://127.0.0.1:8000 build/smoke # the browser walk-through against a running server
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Citation
|
|
114
|
+
|
|
115
|
+
If you use MEIDNet Matter, please cite the MEIDNet paper and the software ([CITATION.cff](https://github.com/ABnano/MEIDNet-Matter/blob/main/CITATION.cff)):
|
|
116
|
+
|
|
117
|
+
> A. Babu, R. Almeida Gouvêa, P. Vandergheynst, G.-M. Rignanese, MEIDNet: Multimodal generative AI framework for inverse materials design, npj Computational Materials 12, 287 (2026). doi:10.1038/s41524-026-02153-3
|
|
118
|
+
|
|
119
|
+
Data: Perov-5 (Castelli et al. 2012) in the CDVAE split (Xie et al. 2022). Licence: MIT. Author: Anand Babu.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MEIDNet Matter: from your materials data to candidate structures.
|
|
3
|
+
|
|
4
|
+
A researcher brings crystal structures and properties. Matter reports what the data and the model support (the
|
|
5
|
+
Design Readiness report), then runs a constrained, property-conditioned search and returns candidate structures
|
|
6
|
+
with their evidence and provenance. The scientific engine is the ``meidnet`` package; Matter imports it and never
|
|
7
|
+
copies it.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
__version__ = "0.6.1"
|
|
File without changes
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""Request-scoped helpers for the routes."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import re
|
|
5
|
+
|
|
6
|
+
from fastapi import Header, Request
|
|
7
|
+
|
|
8
|
+
from matter.api.errors import ApiError
|
|
9
|
+
|
|
10
|
+
SESSION_RE = re.compile(r"^[A-Za-z0-9_-]{4,64}$")
|
|
11
|
+
RESERVED_SESSIONS = ("local", "localtab")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def get_services(request: Request):
|
|
15
|
+
services = getattr(request.app.state, "services", None)
|
|
16
|
+
if services is None:
|
|
17
|
+
raise ApiError("server_error", "the application has not finished starting", status=503)
|
|
18
|
+
return services
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def session_id(request: Request, x_matter_session: str | None = Header(default=None), mutating: bool = False) -> str:
|
|
22
|
+
"""The visitor's session id: the X-Matter-Session header. Locally it may be absent ("local"); on a shared host a
|
|
23
|
+
mutating request needs one, and the shared defaults are refused."""
|
|
24
|
+
settings = request.app.state.settings
|
|
25
|
+
sid = (x_matter_session or "").strip()
|
|
26
|
+
if not sid:
|
|
27
|
+
if settings.public and mutating:
|
|
28
|
+
raise ApiError("validation_error", "the X-Matter-Session header is needed on this shared server", status=400)
|
|
29
|
+
return "local"
|
|
30
|
+
if not SESSION_RE.match(sid):
|
|
31
|
+
raise ApiError("validation_error", "X-Matter-Session must be 4–64 letters, digits, '-' or '_'", status=400)
|
|
32
|
+
if settings.public and sid in RESERVED_SESSIONS:
|
|
33
|
+
raise ApiError("validation_error", f"'{sid}' is a reserved session id", status=400)
|
|
34
|
+
return sid
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"""One error shape for every non-2xx answer:
|
|
2
|
+
|
|
3
|
+
{"error": {"code": "...", "message": "plain sentence", "fields": [{"loc": "objectives.0.property", "msg": "..."}],
|
|
4
|
+
"retry_after_s": 30}}
|
|
5
|
+
|
|
6
|
+
Engine errors (ValueError, SystemExit, KeyError raised by meidnet) are the user's problem to fix and come back as 400
|
|
7
|
+
with the engine's own sentence. Anything unexpected is a 500; on a public host its text is hidden.
|
|
8
|
+
"""
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import traceback
|
|
12
|
+
|
|
13
|
+
from fastapi import FastAPI, Request
|
|
14
|
+
from fastapi.exceptions import RequestValidationError
|
|
15
|
+
from pydantic import ValidationError
|
|
16
|
+
from starlette.exceptions import HTTPException as StarletteHTTPException
|
|
17
|
+
|
|
18
|
+
from matter.jsonsafe import MatterJSONResponse
|
|
19
|
+
from matter.settings import Settings
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class ApiError(Exception):
|
|
23
|
+
"""An error with a code and a status, raised by services and turned into the envelope here."""
|
|
24
|
+
|
|
25
|
+
def __init__(self, code: str, message: str, status: int = 400, fields: list | None = None, retry_after_s: int | None = None):
|
|
26
|
+
super().__init__(message)
|
|
27
|
+
self.code, self.message, self.status, self.fields, self.retry_after_s = code, message, status, fields or [], retry_after_s
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class NotAvailableInPhase(ApiError):
|
|
31
|
+
def __init__(self, phase: int, what: str):
|
|
32
|
+
super().__init__("not_available_in_phase", f"{what} arrives in Phase {phase} of MEIDNet Matter", status=501)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def envelope(code: str, message: str, status: int, fields=None, retry_after_s=None) -> MatterJSONResponse:
|
|
36
|
+
body = {"error": {"code": code, "message": message}}
|
|
37
|
+
if fields:
|
|
38
|
+
body["error"]["fields"] = fields
|
|
39
|
+
if retry_after_s is not None:
|
|
40
|
+
body["error"]["retry_after_s"] = retry_after_s
|
|
41
|
+
headers = {"Retry-After": str(retry_after_s)} if retry_after_s else None
|
|
42
|
+
return MatterJSONResponse(body, status_code=status, headers=headers)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _loc(loc) -> str:
|
|
46
|
+
return ".".join(str(x) for x in loc if x not in ("body",))
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class EngineExitMiddleware:
|
|
50
|
+
"""meidnet reports user errors with SystemExit, a BaseException that Starlette's handlers cannot register for;
|
|
51
|
+
this turns it into the 400 envelope before it reaches the server."""
|
|
52
|
+
|
|
53
|
+
def __init__(self, app):
|
|
54
|
+
self.app = app
|
|
55
|
+
|
|
56
|
+
async def __call__(self, scope, receive, send):
|
|
57
|
+
if scope["type"] != "http":
|
|
58
|
+
return await self.app(scope, receive, send)
|
|
59
|
+
try:
|
|
60
|
+
await self.app(scope, receive, send)
|
|
61
|
+
except SystemExit as exc:
|
|
62
|
+
response = envelope("engine_error", str(exc.code if exc.code is not None else exc), 400)
|
|
63
|
+
await response(scope, receive, send)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def install_error_handlers(app: FastAPI, settings: Settings) -> None:
|
|
67
|
+
app.add_middleware(EngineExitMiddleware)
|
|
68
|
+
|
|
69
|
+
@app.exception_handler(ApiError)
|
|
70
|
+
async def _api_error(request: Request, exc: ApiError):
|
|
71
|
+
return envelope(exc.code, exc.message, exc.status, exc.fields, exc.retry_after_s)
|
|
72
|
+
|
|
73
|
+
@app.exception_handler(RequestValidationError)
|
|
74
|
+
async def _request_validation(request: Request, exc: RequestValidationError):
|
|
75
|
+
fields = [{"loc": _loc(e.get("loc", ())), "msg": e.get("msg", "")} for e in exc.errors()]
|
|
76
|
+
return envelope("validation_error", "the request does not match the API schema", 422, fields)
|
|
77
|
+
|
|
78
|
+
@app.exception_handler(ValidationError)
|
|
79
|
+
async def _pydantic_validation(request: Request, exc: ValidationError):
|
|
80
|
+
fields = [{"loc": _loc(e.get("loc", ())), "msg": e.get("msg", "")} for e in exc.errors()]
|
|
81
|
+
return envelope("validation_error", "a setting has an invalid value", 422, fields)
|
|
82
|
+
|
|
83
|
+
@app.exception_handler(StarletteHTTPException)
|
|
84
|
+
async def _http(request: Request, exc: StarletteHTTPException):
|
|
85
|
+
code = {404: "not_found", 405: "method_not_allowed"}.get(exc.status_code, "http_error")
|
|
86
|
+
return envelope(code, str(exc.detail), exc.status_code)
|
|
87
|
+
|
|
88
|
+
@app.exception_handler(ValueError)
|
|
89
|
+
async def _value_error(request: Request, exc: ValueError):
|
|
90
|
+
return envelope("engine_error", str(exc), 400)
|
|
91
|
+
|
|
92
|
+
@app.exception_handler(KeyError)
|
|
93
|
+
async def _key_error(request: Request, exc: KeyError):
|
|
94
|
+
return envelope("engine_error", str(exc.args[0]) if exc.args else "unknown key", 400)
|
|
95
|
+
|
|
96
|
+
@app.exception_handler(Exception)
|
|
97
|
+
async def _unexpected(request: Request, exc: Exception):
|
|
98
|
+
message = "server error" if settings.public else "".join(traceback.format_exception(type(exc), exc, exc.__traceback__))
|
|
99
|
+
return envelope("server_error", message, 500)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""All API routes, mounted under /api by matter.app."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from fastapi import APIRouter, Request
|
|
5
|
+
|
|
6
|
+
from matter.api import routes_generate, routes_pipeline, routes_read, routes_readiness, routes_runs, routes_schema, routes_studies
|
|
7
|
+
from matter.api.errors import NotAvailableInPhase
|
|
8
|
+
from matter.version import build_info
|
|
9
|
+
|
|
10
|
+
api_router = APIRouter()
|
|
11
|
+
api_router.include_router(routes_read.router)
|
|
12
|
+
api_router.include_router(routes_readiness.router)
|
|
13
|
+
api_router.include_router(routes_runs.router)
|
|
14
|
+
api_router.include_router(routes_schema.router)
|
|
15
|
+
api_router.include_router(routes_pipeline.router)
|
|
16
|
+
api_router.include_router(routes_studies.router)
|
|
17
|
+
api_router.include_router(routes_generate.router)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@api_router.get("/version", summary="What is running: versions, git commit, mode")
|
|
21
|
+
def version(request: Request) -> dict:
|
|
22
|
+
settings = request.app.state.settings
|
|
23
|
+
return build_info(settings.build_info, settings.public)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@api_router.post("/datasets/upload", summary="Phase 1: upload your own dataset", status_code=501)
|
|
27
|
+
def upload_dataset():
|
|
28
|
+
raise NotAvailableInPhase(1, "Uploading your own dataset")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@api_router.post("/models/train", summary="Phase 1: train a model on your dataset", status_code=501)
|
|
32
|
+
def train_model():
|
|
33
|
+
raise NotAvailableInPhase(1, "Training a model")
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"""Generation jobs: a band-gap request in, structures with two judgements out."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import io
|
|
5
|
+
import os
|
|
6
|
+
import zipfile
|
|
7
|
+
|
|
8
|
+
from fastapi import APIRouter, Depends, Header, Request
|
|
9
|
+
from fastapi.responses import FileResponse, StreamingResponse
|
|
10
|
+
|
|
11
|
+
from matter.api.deps import get_services, session_id
|
|
12
|
+
from matter.api.errors import ApiError
|
|
13
|
+
from matter.schemas.generation import GENERATION_SCHEMA_ID, GenerateRequest, result_schema
|
|
14
|
+
from matter.services import generation as GEN
|
|
15
|
+
|
|
16
|
+
router = APIRouter(tags=["generate"])
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@router.post("/generate", status_code=201, summary="Start a generation job for one or more band-gap targets")
|
|
20
|
+
def create(body: GenerateRequest, request: Request, x_matter_session: str | None = Header(default=None), services=Depends(get_services)) -> dict:
|
|
21
|
+
sid = session_id(request, x_matter_session, mutating=True)
|
|
22
|
+
settings = services.settings
|
|
23
|
+
if settings.public:
|
|
24
|
+
services.generations.cleanup()
|
|
25
|
+
req, notes = GEN.validate_request(body, settings.public, services.catalog)
|
|
26
|
+
if not services.catalog.available(req["model_id"]):
|
|
27
|
+
raise ApiError("model_unavailable", f"{req['model_id']} is not present on this server", status=503)
|
|
28
|
+
est = int(8 + 1.5 * sum(req["per_target"] for _ in req["targets"]) * (1 + len(req["targets"])))
|
|
29
|
+
job = services.generations.create(sid, req, notes, services.catalog.info(req["model_id"]), services.judge.describe(), est)
|
|
30
|
+
try:
|
|
31
|
+
services.jobs.start(job["job_id"], sid, GEN.execute_generation, job, services, max_seconds=GEN.GEN_PUBLIC_LIMITS["seconds"])
|
|
32
|
+
except ApiError:
|
|
33
|
+
services.generations.remove(job["job_id"])
|
|
34
|
+
raise
|
|
35
|
+
return GEN.summary(job)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@router.get("/generate", summary="This session's generation jobs")
|
|
39
|
+
def list_jobs(request: Request, x_matter_session: str | None = Header(default=None), services=Depends(get_services)) -> list[dict]:
|
|
40
|
+
sid = session_id(request, x_matter_session)
|
|
41
|
+
return services.generations.list(sid if services.settings.public else None)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@router.get("/generate/{job_id}", summary="A job's status (default) or its whole record (view=full)")
|
|
45
|
+
def get_job(job_id: str, view: str = "status", services=Depends(get_services)) -> dict:
|
|
46
|
+
job = services.generations.get(job_id)
|
|
47
|
+
if view == "full":
|
|
48
|
+
return {k: v for k, v in job.items() if not k.startswith("_")}
|
|
49
|
+
return GEN.status_view(job)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@router.post("/generate/{job_id}/stop", summary="Ask a running job to stop after the current draw")
|
|
53
|
+
def stop(job_id: str, services=Depends(get_services)) -> dict:
|
|
54
|
+
job = services.generations.get(job_id)
|
|
55
|
+
stopping = services.jobs.stop(job_id)
|
|
56
|
+
return {"ok": True, "stopping": stopping, "status": job["status"]}
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@router.get("/generate/{job_id}/candidates/{candidate_id}/cif", summary="One generated cell")
|
|
60
|
+
def cif(job_id: str, candidate_id: str, services=Depends(get_services)):
|
|
61
|
+
job = services.generations.get(job_id)
|
|
62
|
+
for c in job.get("candidates", []):
|
|
63
|
+
if c["candidate_id"] == candidate_id:
|
|
64
|
+
path = os.path.join(services.generations.path(job_id), c["file"])
|
|
65
|
+
if os.path.isfile(path):
|
|
66
|
+
return FileResponse(path, media_type="chemical/x-cif", filename=f"{c['formula']}_{candidate_id}.cif")
|
|
67
|
+
raise ApiError("not_found", f"no candidate {candidate_id} in {job_id}", status=404)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@router.get("/generate/{job_id}/export/cifs.zip", summary="Every generated cell, the table, the record and the relax command")
|
|
71
|
+
def export_zip(job_id: str, services=Depends(get_services)):
|
|
72
|
+
job = services.generations.get(job_id)
|
|
73
|
+
if job["status"] in ("queued", "running"):
|
|
74
|
+
raise ApiError("busy", "the job is still running; download when it has finished", status=409, retry_after_s=15)
|
|
75
|
+
folder = services.generations.path(job_id)
|
|
76
|
+
buf = io.BytesIO()
|
|
77
|
+
with zipfile.ZipFile(buf, "w", zipfile.ZIP_DEFLATED) as z:
|
|
78
|
+
for root, _dirs, files in os.walk(folder):
|
|
79
|
+
for name in files:
|
|
80
|
+
p = os.path.join(root, name)
|
|
81
|
+
z.write(p, os.path.relpath(p, folder))
|
|
82
|
+
buf.seek(0)
|
|
83
|
+
return StreamingResponse(buf, media_type="application/zip", headers={"Content-Disposition": f'attachment; filename="{job_id}.zip"'})
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@router.get("/schema/generation-result", summary="The JSON Schema of a generation record")
|
|
87
|
+
def schema() -> dict:
|
|
88
|
+
return {"schema_id": GENERATION_SCHEMA_ID, **result_schema()}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""The staged pipeline: the ten blocks, their metrics and bands, and the source of every component (read-only)."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from fastapi import APIRouter, Depends
|
|
5
|
+
from fastapi.responses import PlainTextResponse
|
|
6
|
+
|
|
7
|
+
from matter.api.deps import get_services
|
|
8
|
+
from matter.api.errors import ApiError
|
|
9
|
+
from matter.services.pipeline_blocks import blocks_payload, component_list, component_source
|
|
10
|
+
|
|
11
|
+
router = APIRouter(prefix="/pipeline", tags=["pipeline"])
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _research_file(services) -> str | None:
|
|
15
|
+
studies = getattr(services, "studies", None)
|
|
16
|
+
return studies.pipeline_blocks_file() if studies is not None else None
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@router.get("/blocks", summary="The ten blocks with their metrics, bands, components and per-dataset verdicts")
|
|
20
|
+
def blocks(services=Depends(get_services)) -> dict:
|
|
21
|
+
return blocks_payload(_research_file(services))
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@router.get("/blocks/{block_id}", summary="One block")
|
|
25
|
+
def block(block_id: str, services=Depends(get_services)) -> dict:
|
|
26
|
+
bid = (block_id or "").upper()
|
|
27
|
+
for b in blocks_payload(_research_file(services))["blocks"]:
|
|
28
|
+
if b["id"] == bid:
|
|
29
|
+
return b
|
|
30
|
+
raise ApiError("not_found", f"no block {block_id!r}", status=404)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@router.get("/components", summary="Every component with its size, checksum and where it can run")
|
|
34
|
+
def components() -> list[dict]:
|
|
35
|
+
return component_list()
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@router.get("/components/{name}", summary="The source of one component, as plain text", response_class=PlainTextResponse)
|
|
39
|
+
def component(name: str):
|
|
40
|
+
text, meta = component_source(name)
|
|
41
|
+
return PlainTextResponse(text, headers={"X-Matter-Sha256": meta["sha256"], "Cache-Control": "no-cache",
|
|
42
|
+
"Content-Disposition": f'inline; filename="{name}"'})
|