# TopG API — brief for another agent Everything needed to call the TopG structure generator. Self-contained: paste this whole file. ## What it does TopG takes a **coordination query** — a description of each inequivalent metal site's local environment — and returns **complete crystal structures**: space group, Wyckoff positions, free coordinates and cell shape. It is a 4.5M-parameter conditional transformer. **No search or refinement is involved; the output is the model's raw emission.** It generates a **metal sublattice**, not a composition. Element identities in the returned CIF are arbitrary palette entries and carry no chemical meaning. ## Endpoint and auth - Space `RGPalgrave/TopG-demo` · base URL `https://rgpalgrave-topg-demo.hf.space` - Endpoint **`/generate`** — use this one. (`/run` is the web page's own button and takes UI-shaped arguments.) - **The Space is private, so every call needs a HuggingFace token** with read access to it. Pass it via the `HF_TOKEN` environment variable; do not hard-code it. ## Python ```python import json, os from gradio_client import Client # pip install gradio_client c = Client("RGPalgrave/TopG-demo", token=os.environ["HF_TOKEN"]) r = c.predict(json.dumps({ "sites": [ {"H": "4/mmm", "cs": [4, 2], "ratios": [1.0, 1.15], "orbit_size": 2}, {"H": "mmm", "cs": [2, 4], "ratios": [1.0, 1.02], "orbit_size": 2} ], "n_atoms": 4, "model": "H_pq0", "candidates": 8, "seed": 0, "score": True, "cif": True }), api_name="/generate") r = json.loads(r) if isinstance(r, str) else r ``` ## curl POST returns an `event_id`; GET streams the result. ```bash EV=$(curl -s -X POST "https://rgpalgrave-topg-demo.hf.space/gradio_api/call/generate" \ -H "Authorization: Bearer $HF_TOKEN" -H "Content-Type: application/json" \ -d '{"data":["{\"sites\":[{\"H\":\"m-3m\",\"cs\":[6,12],\"ratios\":[1.0,1.414],\"orbit_size\":4}],\"n_atoms\":4}"]}' \ | python -c "import sys,json;print(json.load(sys.stdin)['event_id'])") curl -s -N "https://rgpalgrave-topg-demo.hf.space/gradio_api/call/generate/$EV" \ -H "Authorization: Bearer $HF_TOKEN" | grep -m1 '^data:' ``` ## Request fields | field | required | meaning | |---|---|---| | `sites` | **yes** | one object per *inequivalent* site (below). 1–12 of them | | `n_atoms` | no | metal atoms in the **conventional** cell, 1–192. Defaults to the sum of `orbit_size`. **Orbit sizes must sum to it** — a mismatch is accepted but returns a `warning` and usually few or no structures | | `model` | no | `H_pq0` (default), `H_pq1`, `H_pq2`, `I_full0`, `I_full1`, `I_full2` | | `candidates` | no | beams to draw, default 8. More candidates → more distinct structures, linear cost | | `seed` | no | default 0 | | `score` | no | default `true`; scores each structure against the query. The slow part for large cells. `false` returns `best_rung: null` | | `cif` | no | default `true`; `false` omits CIF text | Each entry of `sites`: | field | meaning | |---|---| | `H` | site symmetry, Hermann–Mauguin (table below) | | `cs` | coordination sequence — metal neighbours in each successive shell, e.g. `[4, 2]` | | `ratios` | each shell's distance **relative to the nearest**, so `ratios[0]` is always `1.0` | | `orbit_size` | atoms this site contributes to the conventional cell | `cs` and `ratios` must be the same length: **1 to 6 shells**. Fewer than 6 is a *partial query* — supported, and easier for the model — but **use an `H_pq` model for it**. `I_full` is trained on full-depth queries only and collapses on short ones (~10% vs ~50% skeleton accuracy). ## Accepted site symmetries (36) Any symmetry may be used in any query; the row says where it actually occurs among the 230 space groups. | system | site symmetries | |---|---| | triclinic | `1` `-1` | | monoclinic | `1` `-1` `2` `m` `2/m` | | orthorhombic | `1` `-1` `2` `m` `2/m` `222` `2mm` `m2m` `mm2` `mmm` | | tetragonal | `1` `-1` `2` `m` `-4` `2/m` `222` `2mm` `4` `m2m` `-42m` `-4m2` `4/m` `422` `4mm` `mmm` `4/mmm` | | trigonal | `1` `-1` `2` `m` `3` `2/m` `-3` `32` `3m` `-3m` | | hexagonal | `1` `-1` `2` `m` `3` `2/m` `222` `2mm` `m2m` `mm2` `-3` `-6` `32` `3m` `6` `mmm` `-3m` `-62m` `-6m2` `6/m` `622` `6mm` `6/mmm` | | cubic | `1` `2` `m` `3` `-4` `2/m` `222` `2mm` `4` `mm2` `-3` `32` `3m` `-42m` `-4m2` `4/m` `422` `4mm` `mmm` `-3m` `23` `4/mmm` `-43m` `432` `m-3` `m-3m` | **Not usable:** `312` `31m` `321` `3m1` `-31m` `-3m1` — these are in the model's vocabulary but occur in no Wyckoff table, so no training example ever contained one. The API rejects them; use the form without the axis suffix (`321`→`32`, `3m1`→`3m`, `-3m1`→`-3m`). ## Response ```json {"ok": true, "query": {"n_atoms": 4, "k": 2, "depth": 2, "sites": [...]}, "candidates_built": 8, "distinct_structures": 2, "warning": null, "provenance": {"model": "H_pq0", "tokenizer_fingerprint": "e1db473d06e14c82", "device": "cpu", "note": "pure model output; no search or refinement"}, "structures": [ {"rank": 1, "space_group": 139, "crystal_system": "tetragonal", "orbits": "2a 2b", "cell_shape": "c_over_a=1.4714", "best_rung": "r0_2sh_noratio", "times_drawn": 6, "cif": "# generated using pymatgen..."} ]} ``` Errors never raise — they return `{"ok": false, "error": "..."}`, with `expected` carrying the schema when the payload was malformed. - `structures` are **deduplicated** and ranked: scored hits first, then by `times_drawn`. - `times_drawn` is how many of the `candidates` beams produced that same structure — a rough confidence signal, not a probability. - `cell_shape` lists only the **free** shape parameters of the crystal system (cubic none; tetragonal/trigonal/hexagonal `c_over_a`; orthorhombic 2, monoclinic 3, triclinic 5). **No absolute length is emitted** — a scale-free query cannot determine one, so the CIF's absolute cell size is a placeholder and only ratios are meaningful. - The **CIF is written in P1** with every atom explicit, so the file says `P 1` while `space_group` gives the real symmetry. The symmetry is in the coordinates. ## `best_rung` — how well a structure answers the query Nested criteria, each stricter than the last. `null` means it passed none. | rung | shells checked | distance tolerance | |---|---|---| | `r0_2sh_noratio` | 2 | ignored | | `r1_3sh_noratio` | 3 | ignored | | `r2_3sh_t04` | 3 | 4% | | `r2a_3sh_t08` | 3 | 8% | | `r5_6sh_noratio` | 6 | ignored | | **`r3_6sh_t04`** | **6** | **4%** — the project's historical criterion | | `r3a_6sh_t08` | 6 | 8% | | `r3b_6sh_t15` | 6 | 15% | ## How much to trust the output Measured on a frozen, skeleton-disjoint evaluation set; pure model, no search: - The **discrete** answer (space group + Wyckoff labels + N) is exactly right **~54–59%** of the time. - The **geometry** meets the strict `r3_6sh_t04` criterion **~2–3%** of the time, and essentially never above ~9 free parameters. **Treat returned coordinates as a starting point, not an answer.** - A *perfect* model would score **95.67%**, not 100% — that is the emission grid's own ceiling. - A structure passing no rung still answers the query approximately; `best_rung` says which criterion it actually meets. ## Gotchas - **`seed` reproduces within one deployment, not across environments.** The same seed on this Space and on a local checkout gives the same structures but different draw counts, because the Python and torch builds differ. Do not use `seed` as a portable identifier. - **The first call after the Space wakes is slow** — it downloads the checkpoint once, then caches. A small query (N ≤ 8, 8 candidates) then takes a few seconds. - Cost grows with **cell size**, not with hardware; the model is too small for a GPU to help. For N > 40 send `"score": false`. - Sites are **inequivalent sites, not atoms**: `k` is the number of orbits, `n_atoms` the number of atoms, and `orbit_size` links them. Model card: `RGPalgrave/TopG` · code and full record: