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.
Files changed (108) hide show
  1. meidnet_matter-0.6.1/LICENSE +21 -0
  2. meidnet_matter-0.6.1/PKG-INFO +166 -0
  3. meidnet_matter-0.6.1/README.md +119 -0
  4. meidnet_matter-0.6.1/matter/__init__.py +10 -0
  5. meidnet_matter-0.6.1/matter/api/__init__.py +0 -0
  6. meidnet_matter-0.6.1/matter/api/deps.py +34 -0
  7. meidnet_matter-0.6.1/matter/api/errors.py +99 -0
  8. meidnet_matter-0.6.1/matter/api/router.py +33 -0
  9. meidnet_matter-0.6.1/matter/api/routes_generate.py +88 -0
  10. meidnet_matter-0.6.1/matter/api/routes_pipeline.py +42 -0
  11. meidnet_matter-0.6.1/matter/api/routes_read.py +75 -0
  12. meidnet_matter-0.6.1/matter/api/routes_readiness.py +18 -0
  13. meidnet_matter-0.6.1/matter/api/routes_runs.py +174 -0
  14. meidnet_matter-0.6.1/matter/api/routes_schema.py +25 -0
  15. meidnet_matter-0.6.1/matter/api/routes_studies.py +47 -0
  16. meidnet_matter-0.6.1/matter/app.py +94 -0
  17. meidnet_matter-0.6.1/matter/backends/__init__.py +0 -0
  18. meidnet_matter-0.6.1/matter/backends/base.py +69 -0
  19. meidnet_matter-0.6.1/matter/backends/meidnet_backend.py +58 -0
  20. meidnet_matter-0.6.1/matter/cli.py +69 -0
  21. meidnet_matter-0.6.1/matter/demo_build.py +400 -0
  22. meidnet_matter-0.6.1/matter/jsonsafe.py +34 -0
  23. meidnet_matter-0.6.1/matter/schemas/__init__.py +0 -0
  24. meidnet_matter-0.6.1/matter/schemas/candidate.py +219 -0
  25. meidnet_matter-0.6.1/matter/schemas/generation.py +59 -0
  26. meidnet_matter-0.6.1/matter/schemas/goal.py +84 -0
  27. meidnet_matter-0.6.1/matter/services/__init__.py +0 -0
  28. meidnet_matter-0.6.1/matter/services/artefacts.py +108 -0
  29. meidnet_matter-0.6.1/matter/services/checkpoints.py +86 -0
  30. meidnet_matter-0.6.1/matter/services/container.py +97 -0
  31. meidnet_matter-0.6.1/matter/services/enrichment.py +315 -0
  32. meidnet_matter-0.6.1/matter/services/export.py +168 -0
  33. meidnet_matter-0.6.1/matter/services/families.py +67 -0
  34. meidnet_matter-0.6.1/matter/services/generation.py +278 -0
  35. meidnet_matter-0.6.1/matter/services/goals.py +240 -0
  36. meidnet_matter-0.6.1/matter/services/jobs.py +81 -0
  37. meidnet_matter-0.6.1/matter/services/judge.py +74 -0
  38. meidnet_matter-0.6.1/matter/services/latents.py +57 -0
  39. meidnet_matter-0.6.1/matter/services/manifest.py +73 -0
  40. meidnet_matter-0.6.1/matter/services/novelty.py +50 -0
  41. meidnet_matter-0.6.1/matter/services/pipeline_blocks.py +82 -0
  42. meidnet_matter-0.6.1/matter/services/readiness.py +405 -0
  43. meidnet_matter-0.6.1/matter/services/registry.py +77 -0
  44. meidnet_matter-0.6.1/matter/services/runs.py +247 -0
  45. meidnet_matter-0.6.1/matter/services/studies.py +61 -0
  46. meidnet_matter-0.6.1/matter/services/support.py +102 -0
  47. meidnet_matter-0.6.1/matter/settings.py +80 -0
  48. meidnet_matter-0.6.1/matter/static/assets/CandidateDetail-BqiNpujp.js +1 -0
  49. meidnet_matter-0.6.1/matter/static/assets/CandidatePage-DkSYSUGt.js +1 -0
  50. meidnet_matter-0.6.1/matter/static/assets/CellViewer-uGz-OQCZ.js +1 -0
  51. meidnet_matter-0.6.1/matter/static/assets/ComingNext-DWJyJnZ_.js +1 -0
  52. meidnet_matter-0.6.1/matter/static/assets/Compare-3KoBHM7L.js +1 -0
  53. meidnet_matter-0.6.1/matter/static/assets/CreateProject-Dq502gk7.js +1 -0
  54. meidnet_matter-0.6.1/matter/static/assets/Explorer-C2wjb9pR.js +1 -0
  55. meidnet_matter-0.6.1/matter/static/assets/Flow-Xh6Ing8B.js +1 -0
  56. meidnet_matter-0.6.1/matter/static/assets/Goal-DVF6bS_M.js +1 -0
  57. meidnet_matter-0.6.1/matter/static/assets/Home-Dalx-9os.js +1 -0
  58. meidnet_matter-0.6.1/matter/static/assets/Landing-CrKrk_YV.js +1 -0
  59. meidnet_matter-0.6.1/matter/static/assets/LiveOnlyPage-Dx-LKZ2L.js +1 -0
  60. meidnet_matter-0.6.1/matter/static/assets/Method-B4fLbGFt.js +10 -0
  61. meidnet_matter-0.6.1/matter/static/assets/NotFound-dUJkEBAw.js +1 -0
  62. meidnet_matter-0.6.1/matter/static/assets/Pipeline-D63C_uOU.js +1 -0
  63. meidnet_matter-0.6.1/matter/static/assets/PipelineBlock-vFIiSNLW.js +1 -0
  64. meidnet_matter-0.6.1/matter/static/assets/Play-B7TzF3AE.js +1 -0
  65. meidnet_matter-0.6.1/matter/static/assets/PlayJob-CMpMeVkH.js +1 -0
  66. meidnet_matter-0.6.1/matter/static/assets/Privacy-DxIp5Gz5.js +1 -0
  67. meidnet_matter-0.6.1/matter/static/assets/Readiness-D6XYxBgo.js +1 -0
  68. meidnet_matter-0.6.1/matter/static/assets/Runs-vEmbIL88.js +1 -0
  69. meidnet_matter-0.6.1/matter/static/assets/Studies-DP2-k-1Y.js +1 -0
  70. meidnet_matter-0.6.1/matter/static/assets/Study-CVy-yiYD.js +1 -0
  71. meidnet_matter-0.6.1/matter/static/assets/Validate-GmD8JXu0.js +3 -0
  72. meidnet_matter-0.6.1/matter/static/assets/components-DL-WsPWG.js +1 -0
  73. meidnet_matter-0.6.1/matter/static/assets/goalStore-jO6sRghi.js +1 -0
  74. meidnet_matter-0.6.1/matter/static/assets/index-BVXe3TnV.css +1 -0
  75. meidnet_matter-0.6.1/matter/static/assets/index-BcbUwdSt.js +3 -0
  76. meidnet_matter-0.6.1/matter/static/assets/index-DC_MKBTO.js +10 -0
  77. meidnet_matter-0.6.1/matter/static/assets/landing-B3ddNw34.js +1 -0
  78. meidnet_matter-0.6.1/matter/static/assets/periodic-DuMLU4Sx.js +1 -0
  79. meidnet_matter-0.6.1/matter/static/assets/react-D0F8gdN_.js +3 -0
  80. meidnet_matter-0.6.1/matter/static/assets/research-BifopWLj.js +1 -0
  81. meidnet_matter-0.6.1/matter/static/assets/research-Demqquip.js +1 -0
  82. meidnet_matter-0.6.1/matter/static/favicon.svg +1 -0
  83. meidnet_matter-0.6.1/matter/static/index.html +32 -0
  84. meidnet_matter-0.6.1/matter/static/og.png +0 -0
  85. meidnet_matter-0.6.1/matter/version.py +78 -0
  86. meidnet_matter-0.6.1/meidnet_matter.egg-info/PKG-INFO +166 -0
  87. meidnet_matter-0.6.1/meidnet_matter.egg-info/SOURCES.txt +106 -0
  88. meidnet_matter-0.6.1/meidnet_matter.egg-info/dependency_links.txt +1 -0
  89. meidnet_matter-0.6.1/meidnet_matter.egg-info/entry_points.txt +2 -0
  90. meidnet_matter-0.6.1/meidnet_matter.egg-info/requires.txt +22 -0
  91. meidnet_matter-0.6.1/meidnet_matter.egg-info/top_level.txt +1 -0
  92. meidnet_matter-0.6.1/pyproject.toml +74 -0
  93. meidnet_matter-0.6.1/setup.cfg +4 -0
  94. meidnet_matter-0.6.1/tests/test_candidate_record.py +90 -0
  95. meidnet_matter-0.6.1/tests/test_demo_artefacts.py +98 -0
  96. meidnet_matter-0.6.1/tests/test_deploy.py +162 -0
  97. meidnet_matter-0.6.1/tests/test_generate.py +63 -0
  98. meidnet_matter-0.6.1/tests/test_goals.py +91 -0
  99. meidnet_matter-0.6.1/tests/test_health_version.py +41 -0
  100. meidnet_matter-0.6.1/tests/test_hull_mlip.py +100 -0
  101. meidnet_matter-0.6.1/tests/test_jsonsafe.py +21 -0
  102. meidnet_matter-0.6.1/tests/test_method_commands.py +61 -0
  103. meidnet_matter-0.6.1/tests/test_mirror.py +61 -0
  104. meidnet_matter-0.6.1/tests/test_readiness.py +95 -0
  105. meidnet_matter-0.6.1/tests/test_research_routes.py +98 -0
  106. meidnet_matter-0.6.1/tests/test_search.py +173 -0
  107. meidnet_matter-0.6.1/tests/test_static.py +30 -0
  108. 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
+ [![ci](https://github.com/ABnano/MEIDNet-Matter/actions/workflows/ci.yml/badge.svg)](https://github.com/ABnano/MEIDNet-Matter/actions/workflows/ci.yml)
57
+ [![Space](https://img.shields.io/badge/%F0%9F%A4%97%20Space-MEIDNet--Matter-4f46e5)](https://huggingface.co/spaces/Babu09/MEIDNet-Matter)
58
+ [![PyPI](https://img.shields.io/pypi/v/meidnet-matter?label=PyPI&color=4f46e5)](https://pypi.org/project/meidnet-matter/)
59
+ [![engine](https://img.shields.io/badge/engine-meidnet%202.4.0.dev2-9333ea)](https://pypi.org/project/meidnet/)
60
+ [![licence](https://img.shields.io/badge/licence-MIT-0a7d0a)](https://github.com/ABnano/MEIDNet-Matter/blob/main/LICENSE)
61
+ [![paper](https://img.shields.io/badge/npj%20Comput.%20Mater.-2026-1c5cab)](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
+ [![ci](https://github.com/ABnano/MEIDNet-Matter/actions/workflows/ci.yml/badge.svg)](https://github.com/ABnano/MEIDNet-Matter/actions/workflows/ci.yml)
10
+ [![Space](https://img.shields.io/badge/%F0%9F%A4%97%20Space-MEIDNet--Matter-4f46e5)](https://huggingface.co/spaces/Babu09/MEIDNet-Matter)
11
+ [![PyPI](https://img.shields.io/pypi/v/meidnet-matter?label=PyPI&color=4f46e5)](https://pypi.org/project/meidnet-matter/)
12
+ [![engine](https://img.shields.io/badge/engine-meidnet%202.4.0.dev2-9333ea)](https://pypi.org/project/meidnet/)
13
+ [![licence](https://img.shields.io/badge/licence-MIT-0a7d0a)](https://github.com/ABnano/MEIDNet-Matter/blob/main/LICENSE)
14
+ [![paper](https://img.shields.io/badge/npj%20Comput.%20Mater.-2026-1c5cab)](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}"'})