diff --git a/.gitattributes b/.gitattributes index 9b4d02a4dd30bc10f6ad38f2d31bf1ca64dfc333..2be9cb62c72552f5cab798c602a5ca85daad9753 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,4 @@ *.pth filter=lfs diff=lfs merge=lfs -text *.csv filter=lfs diff=lfs merge=lfs -text +site/assets/meidnet_tour.webm filter=lfs diff=lfs merge=lfs -text +site/assets/meidnet_tour_poster.png filter=lfs diff=lfs merge=lfs -text diff --git a/README.md b/README.md index 7534c5c59efde91d459f4e1bc7047e941ec58a6c..a75a9bc43b5c12d4d43c59f3440ae7792dfccccf 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ --- -title: MEIDNet Studio +title: MEIDNet Prism emoji: ๐Ÿ”ฎ colorFrom: indigo colorTo: purple @@ -7,7 +7,7 @@ sdk: docker app_port: 7860 pinned: true license: mit -short_description: Inverse design of crystals - live workbench and docs +short_description: Multimodal materials representation and inverse design tags: [materials, inverse-design, generative, crystal, perovskite] --- diff --git a/meidnet/data.py b/meidnet/data.py index 2908dc43bc63be24732641117210fc275ebda412..a4a138c064cef76d9d118e974a70b2b99ccdde5b 100644 --- a/meidnet/data.py +++ b/meidnet/data.py @@ -334,18 +334,24 @@ def load_records(df: pd.DataFrame, data_cfg, base_resolve, family=None, source: if len(s) > data_cfg.max_sites: report.skip(f"more than max_sites={data_cfg.max_sites} atoms", mid) continue - if align: - try: - s, _ = align_to_prototype(s, family, tol=data_cfg.prototype_tolerance) - report.aligned += 1 - except AlignmentError: - report.skip(f"does not match the {family.name} prototype", mid) - continue + if not s.is_ordered: # doped / solid-solution CIFs: one element per site is needed + report.skip("partially occupied site (doped or solid solution): one element per site is needed", mid) + continue try: + if align: + try: + s, _ = align_to_prototype(s, family, tol=data_cfg.prototype_tolerance) + report.aligned += 1 + except AlignmentError: + report.skip(f"does not match the {family.name} prototype", mid) + continue dense = featurize(s, data_cfg.max_sites, data_cfg.neighbor_cutoff) except ValueError: report.skip("unsupported species", mid) continue + except Exception as e: # one odd structure must not stop the whole table + report.skip(f"structure could not be processed ({type(e).__name__})", mid) + continue for el in s.composition.elements: report.elements[str(el)] += 1 records.append(Record(mid, dense, props, s.composition.reduced_formula, s if keep_structures else None)) diff --git a/meidnet/report.py b/meidnet/report.py index 1d7e514c9b40e9fd4c6ada7b49567dc1220ea25b..fb1c17206969d2f843c8c8bb6bc80afa6af9a760 100644 --- a/meidnet/report.py +++ b/meidnet/report.py @@ -282,7 +282,7 @@ def training_report(cfg, lm, history, train_set, val_set, path) -> str: body += (f"

Run details

" f"" f"" - f"
Training materials{len(train_set)}
Validation materials{0 if val_set is None else len(val_set)}
Epochs{cfg.training.epochs}
Time{human_time(history.get('seconds', 0))}
Model file{esc(lm.path)}
") + f"Model file{esc(os.path.basename(str(lm.path)))}") body += ("

Next step

Design candidates for your targets:

meidnet generate meidnet.yaml
" "

Or explore the design space interactively: meidnet studio meidnet.yaml.

") return write(path, page("Training report", f"How good is the model trained for {esc(cfg.name)}?", body, cfg.name)) diff --git a/meidnet/studio/chemiscope.py b/meidnet/studio/chemiscope.py index 17d3aacb8949e9c44cc78e995521b023fa0e01cd..a39de7fe7404036bd361fa2b32a658252042ae6a 100644 --- a/meidnet/studio/chemiscope.py +++ b/meidnet/studio/chemiscope.py @@ -123,8 +123,9 @@ def space_dataset(fam, space: dict, lm=None, max_structures: int = CHEMISCOPE_MA return {"meta": _meta(title, desc), "structures": structures, "properties": props, "settings": settings} -def candidates_dataset(cands: list[dict], fam, run_dir: str | None = None) -> dict: - """Candidates saved by a search (CIF files when available, else rebuilt on the prototype).""" +def candidates_dataset(cands: list[dict], fam, run_dir: str | None = None, lm=None) -> dict: + """Candidates saved by a search (CIF files when available, else rebuilt on the prototype). + With the model (``lm``) the predicted and target values carry their units and property names.""" from pymatgen.core import Structure structures = [] P = {"formula": [], "score": [], "round": [], "target_index": []} @@ -157,8 +158,19 @@ def candidates_dataset(cands: list[dict], fam, run_dir: str | None = None) -> di if f"rule_{r['name']}" not in rule_keys: rule_keys.append(f"rule_{r['name']}") n = len(structures) - props = {k: _prop(v) for k, v in P.items() if len(v) == n} - props["formula"]["description"] = "composition" + units = dict(zip(lm.stats.columns, lm.stats.units)) if lm else {} + labels = dict(zip(lm.stats.columns, lm.stats.labels)) if lm else {} + words = {"formula": "composition", "score": "distance to the target (lower is closer)", "round": "search round", + "target_index": "target number"} + + def describe(k): + for pre, word in (("pred_", "predicted"), ("target_", "target")): + if k.startswith(pre): + c = k[len(pre):] + return units.get(c, ""), f"{word} {labels.get(c, c)}" + return "", words.get(k, k) + + props = {k: _prop(v, *describe(k)) for k, v in P.items() if len(v) == n} x = pred_keys[0] if pred_keys else "score" y = pred_keys[1] if len(pred_keys) > 1 else "score" settings = {"map": {"x": {"property": x}, "y": {"property": y}, "color": {"property": "score", "palette": "viridis"}}, diff --git a/meidnet/studio/landing.html b/meidnet/studio/landing.html new file mode 100644 index 0000000000000000000000000000000000000000..ba5547d610187791c232b94a05f7aa12a2801fad --- /dev/null +++ b/meidnet/studio/landing.html @@ -0,0 +1,129 @@ + + + + + +MEIDNet Prism โ€” multimodal materials representation and inverse design + + + + + +
+ MEIDNet Prism + + +
+ +
+
+
+

MEIDNet Prism

+

Multimodal materials representation and inverse design

+

Bring your crystal structures and properties together in one shared representation. Explore it, design new materials from the properties you want, and benchmark the result.

+ +

Published in npj Computational Materials (2026) ยท doi 10.1038/s41524-026-02153-3 ยท GitHub ยท MIT licence

+
+ Crystal structure, band gap and formation enthalpy enter a prism and leave it as one shared representation; new materials come out of it. Your own property, XRD, DOS and spectra are drawn dashed: extensible. +
+ +
+ +
MEIDNet in one minute: data โ†’ model โ†’ family โ†’ rules โ†’ targets โ†’ search โ†’ candidates. The same tour opens on your first visit to the Studio.
+
+ +
+
Datayour materials
Modelshared space
+
Familycrystal type
Ruleschemistry
+
Targetswhat you want
Searchinverse design
+
Candidatesnew materials
+
+ +
+

Explore

Datasets, properties and modalities that work with MEIDNet today, and what is planned.

+
  • Perov-5: the published multimodal benchmark
  • Structure + any scalar properties: supported
  • XRD, DOS, spectra: extensible, planned
+

Datasets โ†’   Capabilities โ†’

+

Build

Upload a table of structures and properties, train in the browser, set targets and rules, run the search, inspect every candidate in 3D.

+
  • No code: seven coloured blocks, one YAML file
  • Researcher mode for the configuration as text
  • Colab notebooks and a command line for bigger runs
+

Launch Studio โ†’   Quickstart โ†’

+

Benchmark

Reproducible results per dataset, with an evidence ladder from โ€œgeneratedโ€ to โ€œexperimentally validatedโ€. Contribute yours through GitHub.

+
  • Published results of the paper
  • Rows re-run from this repository are marked verified
  • Never one leaderboard across different datasets
+

Benchmarks โ†’   Contribute โ†’

+
+ +
+ Published in npj Computational MaterialsOpen source, MITBit-exact regression against the paperโ€™s code + Three pretrained checkpointsMLIP stability screening (MACE)Runs on a laptop CPU +
+ +
+

Citation

+

A. Babu, R. A. Gouvรชa, P. Vandergheynst and G.-M. Rignanese, MEIDNet: Multimodal generative AI framework for inverse materials design, npj Computational Materials (2026). doi:10.1038/s41524-026-02153-3 ยท arXiv:2601.22009

+

+
@article{meidnet2026,
+  title   = {MEIDNet: Multimodal generative AI framework for inverse materials design},
+  author  = {Anand Babu and Rog{\'e}rio Almeida Gouv{\^e}a and Pierre Vandergheynst and Gian-Marco Rignanese},
+  journal = {npj Computational Materials},
+  year    = {2026},
+  doi     = {10.1038/s41524-026-02153-3}
+}
+
+ +
+ + + diff --git a/meidnet/studio/server.py b/meidnet/studio/server.py index e2ef305b4ec8c7fca1fc15c24775955ca81070ad..86b8ef09c654d18458a983aac19d84495a92a03c 100644 --- a/meidnet/studio/server.py +++ b/meidnet/studio/server.py @@ -300,6 +300,7 @@ class Studio: return { "version": __version__, "public": self.public, "limits": PUBLIC_LIMITS if self.public else None, "docs_url": "/docs/" if self.docs_dir else None, + "home_url": "/" if self.docs_dir else None, # the landing page exists in platform mode only "config": self._visible_config(cfg), "model": self._model_info(lm, own=self._own(s)), "ranges": property_ranges(lm), @@ -999,7 +1000,7 @@ class Studio: return {"available": False, "note": "run a search first"} name, variant = job.family or (self._base_cfg(s).generation.family, self._base_cfg(s).generation.variant) fam = load_family(self._family_src(name), variant=variant, default_variant=True) - return cs.candidates_dataset(job.candidates, fam, run_dir=job.run_dir) + return cs.candidates_dataset(job.candidates, fam, run_dir=job.run_dir, lm=self._model_for(s)) if s and s.check_info and s.cfg: if not any(getattr(r, "structure", None) is not None for r in s.check_info["records"]): return {"available": False, "note": "no usable structures in this table - see the Data block"} @@ -1062,6 +1063,13 @@ def _finite(obj): return obj +def _page(name: str) -> str: + """One of the two pages shipped with the package (studio.html, landing.html), read on every request so that + edits show without a restart.""" + with open(os.path.join(HERE, name), "r", encoding="utf-8") as f: + return f.read() + + class Handler(BaseHTTPRequestHandler): studio: Studio = None timeout = 120 # seconds a client may stay silent mid-request @@ -1115,8 +1123,17 @@ class Handler(BaseHTTPRequestHandler): s = self.studio try: if path in ("/", "/index.html"): - with open(os.path.join(HERE, "studio.html"), "r", encoding="utf-8") as f: - return self._send(200, f.read(), "text/html") + # platform mode (a docs site is served): the landing page; deep links keep working via /studio/ + if s.docs_dir and not any(k in q for k in ("panel", "explore", "tour")): + return self._send(200, _page("landing.html"), "text/html") + if s.docs_dir: + self.send_response(302) + self.send_header("Location", "/studio/" + ("?" + u.query if u.query else "")) + self.end_headers() + return + return self._send(200, _page("studio.html"), "text/html") + if path in ("/studio", "/studio/", "/studio/index.html"): + return self._send(200, _page("studio.html"), "text/html") if path == "/api/state": return self._send(200, s.state(sid)) if path == "/api/data": @@ -1250,9 +1267,10 @@ def serve(cfg: MEIDNetConfig | None, model_path: str | None = None, port: int = threading.Thread(target=warm_up, args=(Handler.studio,), daemon=True).start() threading.Thread(target=_clean_periodically, args=(Handler.studio,), daemon=True).start() url = f"http://{'127.0.0.1' if host in ('0.0.0.0', '') else host}:{port}/" - print(f"MEIDNet Studio {'(public mode) ' if public else ''}running at {url} (Ctrl+C to stop)", flush=True) + studio_url = url + ("studio/" if docs_dir else "") + print(f"MEIDNet Studio {'(public mode) ' if public else ''}running at {studio_url}" + (f" (home page {url}, docs {url}docs/)" if docs_dir else "") + " (Ctrl+C to stop)", flush=True) if open_browser and not public: - threading.Timer(0.8, lambda: webbrowser.open(url)).start() + threading.Timer(0.8, lambda: webbrowser.open(studio_url)).start() try: httpd.serve_forever() except KeyboardInterrupt: diff --git a/meidnet/studio/studio.html b/meidnet/studio/studio.html index b112bcc164727556b48462f61eb26c9ae368a059..176ebbc36757bb5fbba190ad2dc5cc94e3fb59ab 100644 --- a/meidnet/studio/studio.html +++ b/meidnet/studio/studio.html @@ -76,6 +76,8 @@ h2{font-size:21px;margin:0 0 4px;letter-spacing:-.01em}h3{font-size:15px;margin: .io .arr{display:flex;align-items:center;color:var(--accent);font-size:20px;background:none;border:none;padding:0} @media(max-width:700px){.io{grid-template-columns:1fr}.io .arr{display:none}} body:not(.beginner) .beginner-only{display:none} +body.beginner .researcher-only{display:none} /* the YAML drawer and the Python behind each block: Researcher mode */ +.home{text-decoration:none;font-size:20px;color:var(--c-candidates);font-weight:800;padding:0 4px} .help{border-left:3px solid var(--accent);background:var(--accent-soft);padding:6px 10px;margin:8px 0;color:var(--muted);font-size:13px;border-radius:0 8px 8px 0} .plain{border:1px dashed var(--accent);border-radius:10px;padding:8px 12px;margin:0 0 10px;font-size:13.5px} .muted{color:var(--muted)}.small{font-size:12.5px}.flag{color:#8a5a00}:root[data-theme="dark"] .flag{color:var(--warn)} @@ -141,19 +143,54 @@ body>div:empty:not([id]):not([class]){position:absolute;top:0;left:0;width:0;hei .byod{border-left:4px solid var(--c-data)}.map-form .pr{display:flex;gap:8px;align-items:center;margin:4px 0;flex-wrap:wrap} .loss{display:grid;grid-template-columns:1fr 1fr;gap:10px}@media(max-width:700px){.loss{grid-template-columns:1fr}} .cands{display:block} +/* tour: "How MEIDNet works" โ€” the stage is SVG drawn from a function of time (see the tour section of the script) */ +.tour{display:grid;grid-template-columns:minmax(0,1.75fr) minmax(240px,1fr);gap:16px;align-items:start;border-top:4px solid var(--tour-c,var(--c-family));--tour-c:var(--c-family)} +@media(max-width:860px){.tour{grid-template-columns:1fr}} +.tour-stage{width:100%;height:auto;display:block;border-radius:10px;background:var(--bg);border:1px solid var(--line)} +.tour-side .eyebrow{color:color-mix(in oklab,var(--tour-c) 70%,var(--ink))} +.tour-side h2{font-size:19px;margin:0 0 2px} +.tour-cap{font-size:16.5px;line-height:1.45;min-height:4.4em;margin:8px 0 10px} +.tour-ch{list-style:none;margin:0 0 10px;padding:0;display:flex;flex-wrap:wrap;gap:5px} +.tour-ch button{padding:2px 9px;font-size:12px;border-radius:999px;border-color:color-mix(in oklab,var(--cc) 45%,var(--line));color:color-mix(in oklab,var(--cc) 75%,var(--ink))} +.tour-ch button.on{background:var(--cc);border-color:var(--cc);color:#fff;font-weight:700} +.tour-ctl{display:flex;gap:8px;flex-wrap:wrap;align-items:center} +.tour-prog{height:6px;background:var(--line);border-radius:3px;overflow:hidden;margin:10px 0 2px;cursor:pointer} +.tour-prog i{display:block;height:100%;background:var(--tour-c);width:0} +.node.tour-focus .box{stroke:var(--nc);stroke-width:3;fill:color-mix(in oklab,var(--nc) 12%,var(--card));filter:drop-shadow(0 0 7px color-mix(in oklab,var(--nc) 65%,transparent))} +@media(max-width:600px){.tour-cap{font-size:15.5px;min-height:0}} +.tw-narrow .tw-ms text{display:none} /* on a phone the strip at the top of the stage keeps its colours, the caption has the words */
- MEIDNet Studioloadingโ€ฆ + MEIDNet Studioloadingโ€ฆ - + + - +
+
Change anything above or in a block โ€” what it does to the blocks after it appears here.
Flow log (0)
    @@ -256,7 +293,7 @@ function initPrefs(){ $('theme-btn').textContent = document.documentElement.dataset.theme==='dark' ? 'โ˜€๏ธ' : '๐ŸŒ™'; } function toggleTheme(){ const d=document.documentElement.dataset.theme==='dark'; document.documentElement.dataset.theme = d?'light':'dark'; try{localStorage.setItem('meidnet_theme', d?'light':'dark');}catch(e){} $('theme-btn').textContent = d?'๐ŸŒ™':'โ˜€๏ธ'; mountAll3d(); } -function setBeginner(on, silent){ document.body.classList.toggle('beginner', on); $('beg-switch').classList.toggle('on', on); try{localStorage.setItem('meidnet_beginner', on?'1':'0');}catch(e){} if(!silent){ renderLegend(); } } +function setBeginner(on, silent){ document.body.classList.toggle('beginner', on); $('beg-switch').classList.toggle('on', !on); try{localStorage.setItem('meidnet_beginner', on?'1':'0');}catch(e){} if(!silent){ renderLegend(); } } function midRange(p){ const r=S.state.ranges[p]; return r ? +((r[0]+r[1])/2).toFixed(2) : 0; } function uiFromConfig(g, props){ g = g || {}; @@ -307,6 +344,7 @@ function renderHeader(){ const links=[S.state.docs_url?`Docs`:'', 'Code', 'Paper'].filter(Boolean).join(' ยท '); const which = S.state.model.own ? 'your model' : (S.state.model.legacy ? 'published Perov-5 model' : S.state.model.path); $('hdr-sub').innerHTML = esc(which + ' ยท ' + props.map(p=>p.label).join(', ')) + '  ยท  ' + links; + $('home-link').hidden = !(S.state.home_url && !STATIC); } /* Load the family (or install one already fetched). Its rules always start from the family's own definition - @@ -630,7 +668,7 @@ function ioStrip(block){ } function behind(block){ const y=behindYaml(block), p=behindPython(block); - return `
    Behind the scenes: the YAML and Python of this block
    ${esc(y)}
    `; + return `
    Behind the scenes: the YAML and Python of this block
    ${esc(y)}
    `; } function behindTab(btn, which){ const d=btn.closest('.behind'); d.querySelectorAll('.tabs button').forEach(b=>b.classList.toggle('on', b===btn)); d.querySelectorAll('pre').forEach(p=>p.hidden = p.dataset.tab!==which); } function behindYaml(block){ @@ -924,7 +962,7 @@ function renderLive(){ L.innerHTML=b; } function renderLegend(){ const beg=document.body.classList.contains('beginner'); - $('legend').innerHTML = beg ? `

    Glossary

    Composition
    one element per site of the prototype, e.g. CsPbBrโ‚ƒ.
    Rule
    a hard check with a measured value and an allowed window.
    Target
    a property value you want; the model predicts how close each composition gets.
    Latent space
    the hidden coordinates where structures and properties meet; the search moves through it.
    Extrapolation
    a target outside the training range โ€” predictions there are guesses.

    Switch off Beginner mode in the header for a denser view.

    ` + $('legend').innerHTML = beg ? `

    Glossary

    Composition
    one element per site of the prototype, e.g. CsPbBrโ‚ƒ.
    Rule
    a hard check with a measured value and an allowed window.
    Target
    a property value you want; the model predicts how close each composition gets.
    Latent space
    the hidden coordinates where structures and properties meet; the search moves through it.
    Extrapolation
    a target outside the training range โ€” predictions there are guesses.

    Switch on Researcher mode in the header for the configuration as text and the Python behind each block.

    ` : `

    How to use

    1. Family: pick the crystal type; exclude elements.
    2. Rules: move limits; counts update live.
    3. Targets: set values; see the closest compositions.
    4. Search runs the latent optimisation; Candidates explains each result.
    5. Data/Model: upload your table, train, and design with your own properties.
    `; } /* โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• actions โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• */ @@ -1061,7 +1099,10 @@ function csDataset(what){ push('formula', c.formula); push('score', c.score); push('round', c.round); push('still_passes', candStillPasses(c)?1:0); for(const [k,v] of Object.entries(c.predictions||{})) push('pred_'+k, v); for(const [k,v] of Object.entries(c.target_values||{})) push('target_'+k, v); for(const r of c.constraint_results) if(r.value!==null) push('rule_'+r.name, r.value); }); - const props={}; for(const [k,v] of Object.entries(P)) if(v.length===structures.length) props[k]={target:'structure', values:v, units:'', description:k}; + const words={formula:'composition', score:'distance to the target (lower is closer)', round:'search round', still_passes:'1 if it passes the rules as they are now'}; + const describe=k=>{ const m=k.match(/^(pred|target)_(.+)$/); // the same words as meidnet.studio.chemiscope.candidates_dataset + return m ? {units:unit(m[2]).trim(), description:(m[1]==='pred'?'predicted ':'target ')+label(m[2])} : {units:'', description:words[k]||k}; }; + const props={}; for(const [k,v] of Object.entries(P)) if(v.length===structures.length) props[k]={target:'structure', values:v, ...describe(k)}; const pk=Object.keys(props).filter(k=>k.startsWith('pred_')); return {dataset:{meta:{name:'Candidates found by the search', description:`${structures.length} candidate(s) that passed every rule`, authors:['Anand Babu']}, structures, properties:props, settings:{map:{x:{property:pk[0]||'score'}, y:{property:pk[1]||'score'}, color:{property:'score', palette:'viridis'}}, structure:[{bonds:true, unitCell:true, supercell:[1,1,1], keepOrientation:true}]}}, index:idx}; @@ -1114,12 +1155,13 @@ function fallbackExplore(ds, sel){ let s=``; for(const t of ticks(xl,xh)) s+=`${fmt(t)}`; for(const t of ticks(yl,yh)) s+=`${fmt(t)}`; for(const r of rows) s+=`${esc(r.f)}`; - s+=`${esc(px)}${esc(py)}`; + const axis=k=>{ const p=ds.properties[k]||{}; return String(p.description||k).replace(/\s*\(structure encoder\)$/,'')+(p.units?' ('+p.units+')':''); }; // words, not column keys + s+=`${esc(axis(px))}${esc(axis(py))}`; fb.innerHTML=`

    The chemiscope viewer could not be loaded (offline?). Showing the built-in viewer: click a point, drag the cell to rotate.

    ${s}
    `; fbSelect(sel!==undefined?sel:0); } function fbSelect(i){ const ds=S.csData; if(!ds||!ds.structures[i]) return; const st=fromChemiscope(ds,i); S.cells['fb']=st; mountCell($('c3d-fb'), st, {title: ds.properties.formula?ds.properties.formula.values[i]:'', labels:true}); - $('fb-info').innerHTML = Object.entries(ds.properties).map(([k,p])=>`${esc(k)}: ${typeof p.values[i]==='number'?fmt(p.values[i]):esc(p.values[i])}${p.units?' '+esc(p.units):''}`).join(' ยท '); } + $('fb-info').innerHTML = Object.entries(ds.properties).map(([k,p])=>`${esc(k)}: ${typeof p.values[i]==='number'?fmt(p.values[i]):esc(p.values[i])}${p.units?' '+esc(p.units):''}`).join(' ยท '); } function closeExplore(){ $('explore').hidden=true; if(S.cs){ try{ S.cs.remove(); }catch(e){} S.cs=null; } } function downloadDataset(){ if(!S.csData) return; const a=document.createElement('a'); a.href=URL.createObjectURL(new Blob([JSON.stringify(S.csData)],{type:'application/json'})); a.download=`meidnet_${S.csWhat||'dataset'}_chemiscope.json`; a.click(); } @@ -1136,10 +1178,357 @@ function inspect(formula){ const r=S.fam.space.rows.find(x=>x.f===formula); if(! } function closeModal(){ const was=$('modal').classList.contains('open'); $('modal').classList.remove('open'); if(was && S.modalReturn && document.body.contains(S.modalReturn)) S.modalReturn.focus(); } + +/* โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• tour: the whole workflow in one minute โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• */ +/* The stage is SVG drawn from a pure function of time, tourFrame(ms). The page, the chapter jumps, the still frames + for reduced motion and the recorded video (tourRecord) all use the same frames. Scenes: TOUR_SCENES. */ +const TOUR = {ms:0, playing:false, last:0, drawn:0, raf:0, i:-1, total:0, visible:true, reduced:false, open:false}; +const TW = { + clamp:(x,a=0,b=1)=>Math.max(a,Math.min(b,x)), + seg:(t,a,b)=>Math.max(0,Math.min(1,(t-a)/(b-a))), // progress of t inside [a, b] + ease:x=>{ x=Math.max(0,Math.min(1,x)); return x<.5 ? 4*x*x*x : 1-Math.pow(-2*x+2,3)/2; }, + out:x=>{ x=Math.max(0,Math.min(1,x)); return 1-Math.pow(1-x,3); }, + pop:x=>{ x=Math.max(0,Math.min(1,x)); const c=1.70158; return 1+(c+1)*Math.pow(x-1,3)+c*Math.pow(x-1,2); }, + lerp:(a,b,x)=>a+(b-a)*x, + rng:seed=>()=>{ seed|=0; seed=seed+0x6D2B79F5|0; let t=Math.imul(seed^seed>>>15,1|seed); t=t+Math.imul(t^t>>>7,61|t)^t; return ((t^t>>>14)>>>0)/4294967296; }, +}; +const twC = id => BLOCKS.some(b=>b.id===id) ? `var(--c-${id})` : 'var(--c-family)'; +const twN = v => +(+v).toFixed(1); // short coordinates +/* drawing pieces (all return SVG markup) */ +function twText(x, y, str, o={}){ return `${esc(str)}`; } +function twRect(x, y, w, h, o={}){ return ``; } +function twChip(x, y, label, color, o={}){ const w=o.w||Math.max(44, 13*String(label).length+26), h=o.h||36; + return `${twRect(x-w/2, y-h/2, w, h, {r:h/2, fill:o.filled?color:'var(--card)', stroke:color, sw:2})}${twText(x, y+7.5, label, {anchor:'middle', size:o.size||20, weight:700, fill:o.filled?'#fff':color})}`; } +function twArrow(x1, y1, x2, y2, color, p=1, o={}){ if(p<=0) return ''; const x=TW.lerp(x1,x2,p), y=TW.lerp(y1,y2,p), a=Math.atan2(y2-y1,x2-x1), k=o.head||11; + return ``+ + ``; } +function twCheck(x, y, ok, p=1){ if(p<=0) return ''; const s=TW.pop(p); + return ``+ + (ok ? `` : ``)+''; } +/* a crystal from the built-in cell viewer, placed on the stage; elements = {A:'Cs', B:'Pb', X:'I'} or the family's first choice */ +function twCell(x, y, size, o={}){ + let st; + if(!o.names && S.fam && S.fam.sites){ const el={}; for(const [g,grp] of Object.entries(S.fam.groups)) el[g]=(o.elements&&o.elements[g])||grp.sample.find(e=>!S.ui||!S.ui.exclude.has(e))||grp.sample[0]; + const a=(S.fam.lattice&&S.fam.lattice.reference_a)||6; st=buildStruct(S.fam.sites.map(s=>el[s.group]), S.fam.sites.map(s=>s.frac), latticeMatrix(S.fam.lattice, a), S.fam.sites.map(s=>s.group)); } + if(o.names || !st){ const e=Object.assign({A:'Cs', B:'Pb', X:'I'}, o.elements||{}); // given names, or before the family has loaded: cubic ABX3 + const nm=o.names||[e.A,e.B,e.X,e.X,e.X]; + st=buildStruct(nm, [[0,0,0],[.5,.5,.5],[.5,.5,0],[.5,0,.5],[0,.5,.5]], latticeMatrix({lattice:'cubic'}, 6.3), ['A','B','X','X','X']); } + const svg=miniCell(st, {size:200, yaw:o.yaw??-0.55, pitch:o.pitch??0.35, labels:o.labels, title:o.title||''}); + return svg.replace('[^<]*<\/title>/, ''); } +/* the numbers the tour shows: live from the page when it has them, the published defaults before that */ +function tourLive(){ + const st=S.state, ev=S.ev, fam=S.fam, d=S.data; + const props=(st&&st.model&&st.model.properties&&st.model.properties.length) ? st.model.properties + : [{column:'heat_all', label:'Formation enthalpy', unit:'eV/atom'}, {column:'dir_gap', label:'Direct band gap', unit:'eV'}]; + const groups=fam&&fam.groups ? Object.entries(fam.groups).map(([g,grp])=>({g, sample:grp.sample.filter(e=>!S.ui||!S.ui.exclude.has(e))})) + : [{g:'A', sample:['Ba','Sr','Ca','Na','K','Rb','Cs','La','Ce','Pr','Nd','Sm','Eu','Gd','Tb','Dy','Ho','Er','Tm','Yb','Lu']}, // the halide variant + {g:'B', sample:['Mn','Fe','Co','Ni','Cu','Zn','Sn','Pb','Sc','Cr','V']}, {g:'X', sample:['F','Cl','Br','I']}]; + return {materials: d&&d.available ? d.kept : 11356, props, groups, + family: fam ? fam.title : 'Cubic ABX3 perovskite', comps: ev ? ev.rows.length : 924, pass: ev ? ev.alive.length : 27, + ranked: ev ? ev.ranked.slice(0,3).map(r=>r[1]) : [], target: S.ui ? S.ui.target : {}, objectives: S.ui ? S.ui.objectives : []}; +} +/* the seven-block strip at the top of the stage: where we are, and how far through this block */ +function tourMiniStrip(i, t){ + const cur=TOUR_SCENES[i].block, idx=BLOCKS.findIndex(b=>b.id===cur), W=126, G=8, x0=(960-(7*W+6*G))/2, y=12, H=36; + let s=''; + BLOCKS.forEach((b,k)=>{ const x=x0+k*(W+G), on=k===idx, done=(idx>=0&&k${twRect(x, y, W, H, {r:18, fill:on?twC(b.id):'var(--card)', stroke:twC(b.id), sw:on?0:2})}${twText(x+W/2, y+24.5, b.name, {anchor:'middle', size:17, weight:700, fill:on?'#fff':twC(b.id)})}`; + if(k<6) s+=``; }); + if(idx>=0){ const x=x0+idx*(W+G); s+=twRect(x+16, y+H+6, (W-32)*t, 4, {r:2, fill:twC(cur), stroke:'none', sw:0}); } + return s; +} +function tourAt(ms){ let a=0; const n=TOUR_SCENES.length; + for(let i=0;i' + body + ''; } +/* โ”€โ”€ player โ”€โ”€ */ +function tourDraw(){ const {i}=tourAt(TOUR.ms), sc=TOUR_SCENES[i], L=tourLive(); const stage=$('tour-stage'); + stage.classList.toggle('tw-narrow', stage.clientWidth>0 && stage.clientWidth<520); // phone: the strip keeps its colours, the caption has the words + stage.innerHTML=tourFrame(TOUR.ms); + $('tour-prog').firstElementChild.style.width=(100*TOUR.ms/TOUR.total).toFixed(2)+'%'; + if(i!==TOUR.i){ TOUR.i=i; const col=twC(sc.block); + $('tour').style.setProperty('--tour-c', col); + $('tour-eyebrow').textContent=`How it works ยท ${i+1} of ${TOUR_SCENES.length} ยท ${sc.title}`; + $('tour-cap').innerHTML=(BLOCKS.some(b=>b.id===sc.block)?`${esc(blockName(sc.block))}. `:'')+esc(tourCap(sc, L)); + document.querySelectorAll('#tour-ch button').forEach((b,k)=>{ b.classList.toggle('on', k===i); b.setAttribute('aria-current', k===i?'step':'false'); }); + const ob=$('tour-open-block'); const isBlock=BLOCKS.some(b=>b.id===sc.block); ob.hidden=!isBlock; if(isBlock) ob.textContent=`Open ${blockName(sc.block)} โ†“`; } + tourFocusNode(sc.block); +} +function tourFocusNode(block){ document.querySelectorAll('#pipe .node.tour-focus').forEach(n=>{ if(n.id!=='node-'+block) n.classList.remove('tour-focus'); }); + const n=$('node-'+block); if(n && TOUR.open) n.classList.add('tour-focus'); } +function tourLoop(now){ TOUR.raf=0; if(!TOUR.playing) return; + const dt=TOUR.last ? Math.min(120, now-TOUR.last) : 0; TOUR.last=now; + if(TOUR.visible && !document.hidden){ TOUR.ms=Math.min(TOUR.total, TOUR.ms+dt); + if(now-TOUR.drawn>=30 || TOUR.ms>=TOUR.total){ TOUR.drawn=now; tourDraw(); } // about 30 frames a second is plenty + if(TOUR.ms>=TOUR.total){ tourEnd(); return; } } + TOUR.raf=requestAnimationFrame(tourLoop); } +function tourPlay(){ if(TOUR.ms>=TOUR.total-1) TOUR.ms=0; TOUR.playing=true; TOUR.last=0; $('tour-play').textContent='โšโš Pause'; $('tour-play').setAttribute('aria-label','Pause the animation'); + if(!TOUR.raf) TOUR.raf=requestAnimationFrame(tourLoop); } +function tourPause(){ TOUR.playing=false; $('tour-play').textContent='โ–ถ Play'; $('tour-play').setAttribute('aria-label','Play the animation'); } +function tourToggle(){ TOUR.playing ? tourPause() : tourPlay(); } +function tourEnd(){ tourPause(); $('tour-play').textContent='โ†บ Play again'; tourSeen(); } +function tourSeen(){ try{ localStorage.setItem('meidnet_tour_seen','1'); }catch(e){} } +/* a chapter: from its start while playing; paused (or reduced motion) shows its finished picture */ +function tourGo(i){ i=Math.max(0, Math.min(TOUR_SCENES.length-1, i)); TOUR.ms=tourStart(i)+(TOUR.playing?0:TOUR_SCENES[i].ms-1); TOUR.i=-1; tourDraw(); } +function tourReplay(){ TOUR.ms=0; TOUR.i=-1; tourPlay(); tourDraw(); } +function tourSeekClick(e){ const r=$('tour-prog').getBoundingClientRect(); TOUR.ms=TW.clamp((e.clientX-r.left)/r.width)*TOUR.total; tourDraw(); } +function tourOpen(user){ const sec=$('tour'); sec.hidden=false; TOUR.open=true; TOUR.i=-1; + if(user){ TOUR.ms=0; sec.scrollIntoView({block:'nearest'}); } + if(TOUR.reduced && !user){ TOUR.ms=TOUR_SCENES[0].ms-1; tourPause(); tourDraw(); } // still pictures; Play animates + else { tourDraw(); tourPlay(); } + if(user) $('tour-play').focus(); } +function tourClose(){ tourPause(); $('tour').hidden=true; TOUR.open=false; tourSeen(); tourFocusNode(null); $('tour-btn').focus(); } +function tourOpenBlock(){ const b=TOUR_SCENES[TOUR.i].block; tourPause(); show(b); $('panel').scrollIntoView({behavior:'smooth', block:'start'}); } +function tourInit(){ + TOUR.total=TOUR_SCENES.reduce((a,s)=>a+s.ms, 0); + try{ TOUR.reduced=matchMedia('(prefers-reduced-motion: reduce)').matches; }catch(e){} + $('tour-ch').innerHTML=TOUR_SCENES.map((s,i)=>`
  1. `).join(''); + const stage=$('tour-stage'); + try{ new ResizeObserver(()=>stage.classList.toggle('tw-narrow', stage.clientWidth<520)).observe(stage); }catch(e){} + try{ new IntersectionObserver(es=>{ TOUR.visible=es[0].isIntersecting; }).observe($('tour')); }catch(e){} + $('tour').addEventListener('keydown', e=>{ if(/^(INPUT|SELECT|TEXTAREA)$/.test(e.target.tagName)) return; + if(e.key===' '||e.key==='k'){ if(e.target.tagName==='BUTTON' && e.key===' ') return; e.preventDefault(); tourToggle(); } + else if(e.key==='ArrowRight'){ e.preventDefault(); tourGo(TOUR.i+1); } else if(e.key==='ArrowLeft'){ e.preventDefault(); tourGo(TOUR.i-1); } + else if(e.key==='Escape'){ tourClose(); } }); + const qs=new URLSearchParams(location.search); let seen=false; try{ seen=localStorage.getItem('meidnet_tour_seen')==='1'; }catch(e){} + if(qs.get('tour')==='1' || (!seen && qs.get('tour')!=='0' && !qs.get('panel') && !qs.get('explore'))) tourOpen(false); +} +/* โ”€โ”€ video: the same frames with the caption drawn in; scripts/record_tour.mjs records them into docs/assets โ”€โ”€ */ +function tourWrap(str, n){ const out=[]; let line=''; for(const w of String(str).split(/\s+/)){ if((line+' '+w).trim().length>n && line){ out.push(line); line=w; } else line=(line+' '+w).trim(); } if(line) out.push(line); return out; } +function tourVideoSVG(ms){ + const cs=getComputedStyle(document.documentElement), vars=['--c-data','--c-model','--c-family','--c-rules','--c-targets','--c-search','--c-candidates','--good','--warn','--bad','--bg','--card','--ink','--muted','--faint','--line','--axis'] + .map(v=>`${v}:${cs.getPropertyValue(v).trim()}`).join(';'); + const {i}=tourAt(ms), sc=TOUR_SCENES[i], isBlock=BLOCKS.some(b=>b.id===sc.block), col=twC(sc.block); + const lines=tourWrap((isBlock?blockName(sc.block)+'. ':'')+tourCap(sc, tourLive()), 70).slice(0,3); + const capY=lines.length>2 ? 640 : 652; + return ``+ + `${tourFrame(ms)}`+ + ``+ + lines.map((l,k)=>`${esc(l)}`).join('')+ + `MEIDNet Studio ยท babu09-meidnet.hf.space`; } +/* โ”€โ”€ scenes: a start, one per block, an end. Numbers are live from the page once it has them. โ”€โ”€ */ +const twSub = f => String(f).replace(/\d/g, d=>'โ‚€โ‚โ‚‚โ‚ƒโ‚„โ‚…โ‚†โ‚‡โ‚ˆโ‚‰'[+d]); +const twOp = x => twN(TW.clamp(x)*100)/100; +const TW_SHORT = {charge_neutrality:'charges balance', tolerance_factor:'tolerance factor', octahedral_factor:'octahedral factor', + bond_window:'bond lengths', min_distance:'no overlaps', symmetry_refinement:'symmetry', property_window:'property window'}; +/* four real rows of the Perov-5 training table (A on the corner, B in the centre, X on the faces) */ +const TOUR_DATA_ROWS = [ + {f:'SrTiOโ‚ƒ', n:['Sr','Ti','O','O','O'], v:[-0.24, 4.3]}, {f:'KNbOโ‚ƒ', n:['K','Nb','O','O','O'], v:[-0.42, 4.1]}, + {f:'LaTiNOโ‚‚', n:['La','Ti','N','O','O'], v:[0.06, 2.5]}, {f:'SrZrNOF', n:['Sr','Zr','N','O','F'], v:[0.54, 3.2]}]; +/* the property the visitor wants: the band gap when it is an objective, else the first objective */ +function twWish(L){ const obs=L.objectives.length ? L.objectives : [{property:'dir_gap'}]; + const o=obs.find(x=>/gap/i.test(x.property))||obs[0], p=L.props.find(x=>x.column===o.property)||{label:'Band gap', unit:'eV'}; + const v=L.target[o.property]; return {label:p.label||o.property, unit:p.unit||'', value:v===undefined||v===''?1.5:+v, prop:o.property}; } +/* real compositions of the current space: the best-ranked passing ones and failing ones, each with the rule that stops it */ +function twPick(){ const ev=S.ev; if(!ev || !ev.rows.length) return null; const rules=activeRules(); + const first=r=>{ for(const ru of rules) if(!rulePass(ru.name, ru.params, r, ru.extra?null:ru.key)) return ru.key; return null; }; + const pass=ev.ranked.map(x=>x[1]).concat(ev.alive).filter((r,i,a)=>a.indexOf(r)===i).slice(0,6); + const fail=[], seen=new Set(); + for(const r of ev.rows){ if(r._ok) continue; const k=first(r); if(k && !seen.has(k)){ seen.add(k); fail.push({r, k}); } } + for(const r of ev.rows){ if(fail.length>=8) break; if(!r._ok && !fail.some(x=>x.r===r)) fail.push({r, k:first(r)}); } + return {pass, fail}; } +/* what the search found: the visitor's own search when there is one; before that, a run of the Studio defaults + (published model, halide, targets 2 eV / -0.1 eV/atom, seed 937; numbers vary slightly between machines) */ +const TOUR_EXAMPLE = [ + {f:'NaNiI3', e:{A:'Na', B:'Ni', X:'I'}, p:{dir_gap:1.97, heat_all:-0.05}, flags:[]}, + {f:'NaSnI3', e:{A:'Na', B:'Sn', X:'I'}, p:{dir_gap:1.98, heat_all:-1.37}, flags:['formation enthalpy outside the training range']}, + {f:'NaMnI3', e:{A:'Na', B:'Mn', X:'I'}, p:{dir_gap:2.37, heat_all:14.3}, flags:['formation enthalpy outside the training range']}]; +function twFound(L){ const fc=typeof foundCands==='function' ? foundCands() : []; + if(fc.length) return {kind:'yours', list:fc.slice(0,3).map(c=>({f:c.formula, e:c.elements, p:c.predictions||{}, t:c.target_values||L.target, flags:c.flags||[]}))}; + const cols=L.props.map(p=>p.column), dflt=!S.fam || (S.fam.name==='perovskite_abx3' && S.fam.variant==='halide'); + if(dflt && cols.includes('dir_gap') && cols.includes('heat_all')) return {kind:'example', list:TOUR_EXAMPLE.map(c=>Object.assign({t:{dir_gap:2, heat_all:-0.1}}, c))}; + const r=S.ev ? S.ev.ranked.slice(0,3).map(x=>x[1]) : []; + return {kind:'closest', list:r.map(x=>({f:x.f, e:x.e, p:x.p, t:L.target, flags:[]}))}; } +function twLabel(L, col){ const p=L.props.find(x=>x.column===col); return p ? (p.label||col) : col; } +function twUnit(L, col){ const p=L.props.find(x=>x.column===col); return p&&p.unit ? p.unit : ''; } +/* a composition chip: neutral while travelling, red when stopped, green when it passed */ +function twFormula(x, y, f, state, o={}){ const w=Math.max(70, 13.5*String(f).length+26), h=34; + const col=state==='fail'?'var(--bad)':state==='pass'?'var(--good)':'var(--axis)'; + return `${twRect(x-w/2, y-h/2, w, h, {r:h/2, fill:'var(--card)', stroke:col, sw:2.2})}${twText(x, y+7, twSub(f), {anchor:'middle', size:19, weight:700})}`; } + +const TOUR_SCENES = [ + {block:'intro', title:'Start', ms:4200, + cap:'Tell MEIDNet the properties you want and it suggests crystals that should have them. Here is how, block by block.', + draw:(t,L)=>{ const w=twWish(L), a=TW.out(TW.seg(t,0,.25)), pop=TW.pop(TW.seg(t,.45,.72)); + return ``+ + twRect(60,165,330,160,{r:18, fill:'color-mix(in oklab,var(--c-targets) 12%,var(--card))', stroke:twC('targets'), sw:2.5})+ + twText(86,206,'You want',{size:22, fill:'var(--muted)'})+twText(86,252,w.label,{size:28, weight:800, fill:twC('targets')})+ + twText(86,300,`โ‰ˆ ${fmt(w.value)} ${w.unit}`,{size:34, weight:800})+''+ + twText(500,222,'MEIDNet',{anchor:'middle', size:28, weight:800, fill:twC('family'), opacity:TW.seg(t,.25,.45)})+ + twArrow(405,245,602,245,twC('family'),TW.ease(TW.seg(t,.25,.5)),{sw:4.5, head:14})+ + ``+twCell(770,245,240,{yaw:-0.9+t*1.1, labels:true})+''+ + twText(480,422,'inverse design: from properties to crystals',{anchor:'middle', size:24, fill:'var(--muted)', opacity:TW.seg(t,.62,.85)}); }}, + + {block:'data', title:'Data', ms:6800, + cap:L=>`MEIDNet learns from known materials: a table in which each row is a crystal structure with its properties. Here, ${nfmt(L.materials)} materials.`, + draw:(t,L)=>{ const col=twC('data'); let s=twRect(36,76,610,356,{r:14}); + s+=twText(58,108,'structure',{size:16, weight:700, fill:'var(--muted)'})+twText(176,108,'formula',{size:16, weight:700, fill:'var(--muted)'})+ + twText(352,100,'formation enthalpy',{size:16, weight:700, fill:'var(--muted)'})+twText(352,120,'eV/atom',{size:14, fill:'var(--faint)'})+ + twText(528,100,'band gap',{size:16, weight:700, fill:'var(--muted)'})+twText(528,120,'eV',{size:14, fill:'var(--faint)'})+ + ``; + TOUR_DATA_ROWS.forEach((r,k)=>{ const p=TW.out(TW.seg(t,.06+k*.1,.28+k*.1)), y=136+k*64; + s+=``+twCell(95,y+32,62,{names:r.n, yaw:-0.62+0.25*t})+ + twText(176,y+40,r.f,{size:24, weight:700})+twText(352,y+40,fmt(r.v[0]),{size:24})+twText(528,y+40,fmt(r.v[1]),{size:24})+ + ``; }); + s+=twText(58,420,'four real rows of the Perov-5 training set',{size:14, fill:'var(--faint)', opacity:TW.seg(t,.4,.6)}); + s+=``+twRect(676,120,250,250,{r:18, fill:'color-mix(in oklab,var(--c-data) 10%,var(--card))', stroke:col, sw:2.5})+ + twText(801,222,nfmt(Math.round(L.materials*TW.ease(TW.seg(t,.45,.85)))),{anchor:'middle', size:50, weight:800, fill:col})+ + twText(801,262,'materials',{anchor:'middle', size:24, weight:700})+ + twText(801,304,`${L.props.length} properties each`,{anchor:'middle', size:20, fill:'var(--muted)', opacity:TW.seg(t,.7,.9)})+''; + return s; }}, + + {block:'model', title:'Model', ms:7600, + cap:'Two encoders place each crystal and its properties on one shared map. Training pulls the two points of the same material together.', + draw:(t,L)=>{ const col=twC('model'), a1=TW.ease(TW.seg(t,.05,.22)), a2=TW.ease(TW.seg(t,.2,.36)); + let s=twCell(92,158,104,{names:['Sr','Ti','O','O','O'], yaw:-0.55+0.4*t})+twText(92,232,'crystal',{anchor:'middle', size:17, weight:700, fill:'var(--muted)'}); + s+=twRect(38,296,108,66,{r:12})+twText(92,324,'4.3 eV',{anchor:'middle', size:20, weight:700})+twText(92,350,'โˆ’0.24 eV/atom',{anchor:'middle', size:13, fill:'var(--muted)'})+ + twText(92,388,'properties',{anchor:'middle', size:17, weight:700, fill:'var(--muted)'}); + s+=twArrow(150,158,204,158,col,a1)+twArrow(150,329,204,329,col,a1); + for(const [y,lab] of [[123,'crystal encoder'],[294,'property encoder']]) + s+=twRect(208,y,190,70,{r:14, fill:'color-mix(in oklab,var(--c-model) 12%,var(--card))', stroke:col, sw:2.5})+twText(303,y+42,lab,{anchor:'middle', size:19, weight:700, fill:col}); + s+=twArrow(402,158,478,205,col,a2)+twArrow(402,329,478,290,col,a2); + s+=twRect(482,78,450,354,{r:16, fill:'var(--bg)', stroke:'var(--line)'})+twText(502,108,'shared map',{size:18, weight:800, fill:'var(--muted)'})+twText(612,108,'(latent space)',{size:15, fill:'var(--faint)'}); + const R=TW.rng(7), c=TW.ease(TW.seg(t,.42,.86)), show=TW.seg(t,.3,.42); + for(let k=0;k<7;k++){ const px=540+R()*340, py=150+R()*215, ang=R()*6.283, d=48+R()*42; + const cx=px+Math.cos(ang)*d*(1-c), cy=py+Math.sin(ang)*d*(1-c), qx=px-Math.cos(ang)*d*(1-c), qy=py-Math.sin(ang)*d*(1-c); + s+=``+ + ``; } + s+=``+twText(522,416,'crystal',{size:15, fill:'var(--muted)'})+ + ``+twText(612,416,'its properties',{size:15, fill:'var(--muted)'})+ + twText(914,416,'pulled together',{anchor:'end', size:15, weight:700, fill:col, opacity:TW.seg(t,.8,.92)})+''; + return s; }}, + + {block:'family', title:'Family', ms:6800, + cap:L=>`You choose a crystal type, here the ${L.family.replace(/^([A-Z])(?=[a-z])/, c=>c.toLowerCase())}, and the elements allowed on each site: ${nfmt(L.comps)} compositions.`, + draw:(t,L)=>{ const col=twC('family'), gs=L.groups.slice(0,4), n=Math.max(1,gs.length), step=Math.min(5, Math.floor(t*7)), combo={}; + gs.forEach((g,j)=>{ const vis=g.sample.slice(0,6); combo[g.g]=vis[(step*(j+2)+j)%Math.max(1,vis.length)]; }); // only elements whose chips are shown + let s=twCell(225,236,290,{yaw:-0.9+1.2*t, labels:true, elements:combo})+twText(225,426,L.family.length>34?L.family.slice(0,33)+'โ€ฆ':L.family,{anchor:'middle', size:19, weight:800, fill:col}); + const x0=470, W=460; + gs.forEach((g,j)=>{ const x=x0+W/n*(j+.5), a=TW.out(TW.seg(t,.05+j*.08,.25+j*.08)); + s+=``+twChip(x,98,`${g.g} site`,col,{filled:true, size:18, h:34, w:Math.min(110, W/n-12)}); + g.sample.slice(0,6).forEach((el,m)=>{ s+=twChip(x,146+m*38,el,col,{filled:el===combo[g.g], size:18, h:32, w:72}); }); + if(g.sample.length>6) s+=twText(x,146+6*38-2,`+${g.sample.length-6} more`,{anchor:'middle', size:15, fill:'var(--muted)'}); + s+=''; }); + const sizes=L.groups.map(g=>g.sample.length), prod=sizes.reduce((a,b)=>a*b,1); // 21 ร— 11 ร— 4 = 924 when every filling is allowed + s+=twText(x0+W/2,426,`${prod===L.comps&&sizes.length>1?sizes.join(' ร— ')+' ':''}= ${nfmt(Math.round(L.comps*TW.ease(TW.seg(t,.55,.9))))} compositions`,{anchor:'middle', size:25, weight:800, fill:col, opacity:TW.seg(t,.5,.6)}); + return s; }}, + + {block:'rules', title:'Rules', ms:7600, + cap:L=>`Simple chemistry rules remove impossible combinations: charges must balance and ions must fit. ${nfmt(L.pass)} of ${nfmt(L.comps)} pass every rule.`, + draw:(t,L)=>{ const col=twC('rules'), pk=twPick(); + const keys=S.ev ? Object.keys(S.ev.lost).filter(k=>S.ev.lost[k]>0).slice(0,4) : ['charge_neutrality','bond_window','tolerance_factor','octahedral_factor']; + const gx=keys.map((k,j)=>285+j*150); let s=''; + s+=twText(36,104,nfmt(Math.round(TW.lerp(L.comps, L.pass, TW.ease(TW.seg(t,.12,.9))))),{size:34, weight:800})+twText(36,128,'compositions left',{size:15, fill:'var(--muted)'}); + keys.forEach((k,j)=>{ const x=gx[j], spec=S.ui&&S.ui.rules&&S.ui.rules[k], name=(spec&&spec.rule)||k, words=(TW_SHORT[name]||(S.fam?ruleTitle(k):k)).split(' '); + s+=twRect(x-9,176,18,164,{r:9, fill:'color-mix(in oklab,var(--c-rules) 35%,var(--card))', stroke:col, sw:2})+ + twText(x,136,words[0],{anchor:'middle', size:16, weight:700, fill:col})+(words.length>1?twText(x,156,words.slice(1).join(' '),{anchor:'middle', size:16, weight:700, fill:col}):'')+ + (S.ev?twText(x,370,'โˆ’'+nfmt(S.ev.lost[k]),{anchor:'middle', size:18, weight:800, fill:'var(--bad)', opacity:TW.seg(t,.25+j*.12,.35+j*.12)}):''); }); + s+=twText(880,136,'pass every',{anchor:'middle', size:16, weight:700, fill:'var(--good)'})+twText(880,156,'rule',{anchor:'middle', size:16, weight:700, fill:'var(--good)'}); + const chips=[]; + if(pk){ const fails=pk.fail.filter(x=>keys.includes(x.k)), P=pk.pass; + for(const it of [fails[0],P[0],fails[1],fails[2],P[1],fails[3],P[2],fails[4]]) if(it) chips.push(it.r ? {f:it.r.f, gate:keys.indexOf(it.k)} : {f:it.f, gate:-1}); } + else chips.push({f:'CsPbI3',gate:1},{f:'RbSnI3',gate:-1},{f:'NaFeCl3',gate:2},{f:'KMnBr3',gate:3},{f:'CsGeI3',gate:-1}); + let passed=0; const span=.34; + chips.forEach((c,k)=>{ const st=.04+k*.075, x0=60, x1=880, stop=c.gate>=0 ? gx[c.gate]-62 : x1; + const tStop=st+span*(stop-x0)/(x1-x0), lane=k%2 ? 290 : 232; if(t=0){ const f=TW.seg(t,tStop+.05,tStop+.16); + s+=``+twFormula(stop, lane, c.f, 'fail', {opacity:1-f})+(f<1?twCheck(stop+40, lane-18, false, TW.seg(t,tStop,tStop+.04)):'')+''; } + else { const slot=passed++, a=TW.ease(TW.seg(t,tStop,tStop+.06)); + s+=twFormula(880, TW.lerp(lane, 206+slot*46, a), c.f, 'pass')+twCheck(880+56, TW.lerp(lane,206+slot*46,a)-14, true, a); } }); + return s; }}, + + {block:'targets', title:'Targets', ms:7000, + cap:L=>{ const w=twWish(L); return `You say what you want, for example a ${w.label.toLowerCase()} of ${fmt(w.value)} ${w.unit}. The model predicts every composition and ranks the closest.`; }, + draw:(t,L)=>{ const col=twC('targets'), obs=L.objectives.length?L.objectives:[{property:'heat_all'},{property:'dir_gap'}], px=obs[0].property, py=obs[1]&&obs[1].property; + const rows=S.ev ? (S.ev.alive.length ? S.ev.alive : S.ev.rows.slice(0,40)) : []; + const R=TW.rng(5), pts=rows.length ? rows.map(r=>[r.p[px], py?r.p[py]:null, r]) : Array.from({length:24},()=>[R()*3, R()*3, null]); + const tx=L.target[px]!==undefined?+L.target[px]:1, ty=py&&L.target[py]!==undefined?+L.target[py]:1; + const xs=pts.map(p=>p[0]).filter(Number.isFinite).concat([tx]), ys=py?pts.map(p=>p[1]).filter(Number.isFinite).concat([ty]):[0,1]; + const pad=(a,b)=>{ const d=(b-a)||1; return [a-.1*d, b+.1*d]; }, [xl,xh]=pad(Math.min(...xs),Math.max(...xs)), [yl,yh]=pad(Math.min(...ys),Math.max(...ys)); + const X=v=>110+(v-xl)/(xh-xl)*510, Y=v=>py ? 392-(v-yl)/(yh-yl)*300 : 260; + let s=``+ + twText(365,424,`predicted ${twLabel(L,px)}${twUnit(L,px)?' ('+twUnit(L,px)+')':''}`,{anchor:'middle', size:16, fill:'var(--muted)'})+ + (py?`${esc('predicted '+twLabel(L,py)+(twUnit(L,py)?' ('+twUnit(L,py)+')':''))}`:''); + const ranked=S.ev ? S.ev.ranked.slice(0,3).map(x=>x[1]) : []; + pts.forEach((p,k)=>{ if(!Number.isFinite(p[0])||(py&&!Number.isFinite(p[1]))) return; const a=TW.seg(t,.02+k*.006,.12+k*.006); + if(!ranked.includes(p[2])) s+=``; }); + const m=TW.ease(TW.seg(t,.2,.45)), cx=TW.lerp(365,X(tx),m), cy=TW.lerp(242,Y(ty),m), ta=TW.seg(t,.18,.26); + s+=``+ + twText(Math.min(560,cx+20),Math.max(112,cy-20),'your target',{size:17, weight:800, fill:col, opacity:TW.seg(t,.4,.5)}); + ranked.forEach((r,k)=>{ const a=TW.pop(TW.seg(t,.5+k*.07,.62+k*.07)), x=X(r.p[px]), y=Y(py?r.p[py]:0); + s+=`${k+1}`; }); + s+=``+twRect(660,92,272,300,{r:14})+twText(680,124,'closest to your target',{size:17, weight:800, fill:col}); + (ranked.length?ranked:[{f:'โ€”',p:{}}]).forEach((r,k)=>{ const y=168+k*72, a=TW.seg(t,.6+k*.08,.7+k*.08); + s+=`${k+1}`+ + twText(720,y,twSub(r.f),{size:22, weight:800})+twText(720,y+24,obs.slice(0,2).map(o=>fmt(r.p[o.property])+(twUnit(L,o.property)?' '+twUnit(L,o.property):'')).join(' ยท '),{size:15, fill:'var(--muted)'})+''; }); + return s+''; }}, + + {block:'search', title:'Search', ms:8600, + cap:'Starting at your targetโ€™s point on the map, the search moves points until the model predicts your targets, turns each into a crystal and keeps those that pass every rule.', + draw:(t,L)=>{ const col=twC('search'), pk=twPick(), R=TW.rng(11), sx=370, sy=258; + let s=twRect(36,76,640,356,{r:16, fill:'var(--bg)', stroke:'var(--line)'})+twText(56,106,'shared map',{size:18, weight:800, fill:'var(--muted)'})+ + twRect(706,76,226,356,{r:16})+twText(726,106,'found',{size:19, weight:800, fill:col})+twText(726,128,'pass every rule',{size:15, fill:'var(--muted)'}); + for(let k=0;k<70;k++) s+=``; + s+=``+twText(sx,sy+42,'your target',{anchor:'middle', size:16, weight:800, fill:twC('targets')}); + const pass=twFound(L).list.slice(0,3), fail=pk?pk.fail.slice(0,5).map(x=>x.r):[]; // found = the real search results (or the default-run example) + const who=[0,1,0,0,1,0,1,0].map((p,k)=>p ? {r:pass[[1,4,6].indexOf(k)], ok:true} : {r:fail[[0,2,3,5,7].indexOf(k)], ok:false}); + const pos=(k,u)=>{ const th=k*0.785+0.3, r=(22+150*TW.ease(TW.seg(u,.08,.58))*(.55+.45*((k*37)%10)/10)); return [sx+r*Math.cos(th+.7*u)+8*Math.sin(u*9+k), sy+.72*r*Math.sin(th+.7*u)+6*Math.cos(u*7+k)]; }; + const appear=TW.seg(t,.03,.1); let slot=0; + who.forEach((w,k)=>{ const [x,y]=pos(k,Math.min(t,.6)); + if(t<.6){ let trail=''; for(let q=6;q>=0;q--){ const [a,b]=pos(k,Math.max(0,t-q*.018)); trail+=(q===6?'M':'L')+twN(a)+' '+twN(b); } + s+=``; return; } + const pop=TW.pop(TW.seg(t,.6,.68)), chk=TW.seg(t,.7,.76), fade=w.ok?0:TW.seg(t,.8,.88), fly=w.ok?TW.ease(TW.seg(t,.84,.97)):0; + const my=w.ok?slot++:0, X=TW.lerp(x,742,fly), Y=TW.lerp(y,178+my*88,fly); + const el=w.r ? w.r.e : null; + s+=``+twCell(X,Y,58,{elements:el||undefined, yaw:-0.55+t})+''+ + (chk>0?twCheck(X+24,Y-24,w.ok,chk):'')+''; }); + pass.forEach((r,k)=>{ if(r) s+=twText(782,185+k*88,twSub(r.f),{size:21, weight:800, opacity:TW.seg(t,.92,.99)}); }); + return s; }}, + + {block:'candidates', title:'Candidates', ms:7400, + cap:'Each new material comes with its crystal, the rules it passed and its predicted properties. Confirm the best ones with calculations or experiments.', + draw:(t,L)=>{ const col=twC('candidates'), obs=L.objectives.slice(0,2); + const F=twFound(L), cs=F.list.length ? F.list : TOUR_EXAMPLE.slice(0,1).map(c=>Object.assign({t:L.target}, c)); + let s=twText(932,75,{yours:'from your search', example:'example: a search with the Studio defaults', closest:'closest compositions - run a search for real candidates'}[F.kind],{anchor:'end', size:13, weight:700, fill:'var(--muted)', opacity:TW.seg(t,.1,.25)}); + cs.forEach((c,k)=>{ const x0=40+k*302, a=TW.out(TW.seg(t,.04+k*.12,.3+k*.12)); + s+=``+twRect(x0,82,282,300,{r:16, stroke:col, sw:2.2})+ + twCell(x0+66,150,112,{elements:c.e, yaw:-0.6+0.7*t})+twText(x0+130,134,twSub(c.f),{size:25, weight:800})+ + twText(x0+130,160,'โœ“ every rule',{size:16, weight:700, fill:'var(--good)'})+ + ((c.flags||[]).length ? twText(x0+130,182,'โš  extrapolating',{size:14, weight:700, fill:'color-mix(in oklab,var(--warn) 55%,var(--ink))'}) : ''); + obs.forEach((o,j)=>{ const v=c.p[o.property], tv=+(c.t||L.target)[o.property], y=222+j*78, u=twUnit(L,o.property), r=(S.state&&S.state.ranges&&S.state.ranges[o.property])||[Math.min(v,tv)-1, Math.max(v,tv)+1]; + const lo=Math.min(r[0],v,tv), hi=Math.max(r[1],v,tv), G=x=>x0+22+(x-lo)/((hi-lo)||1)*238; + s+=twText(x0+22,y,twLabel(L,o.property),{size:14, fill:'var(--muted)'})+ + (Number.isFinite(v)?twText(x0+22,y+22,`${fmt(v)}${u?' '+u:''}`,{size:17, weight:800}):'')+ + twRect(x0+22,y+32,238,7,{r:3.5, fill:'var(--line)', stroke:'none', sw:0})+ + (Number.isFinite(tv)?``+ + twText(TW.clamp(G(tv),x0+50,x0+232),y+60,`target ${fmt(tv)}`,{anchor:'middle', size:13, weight:700, fill:'var(--c-targets)'}):'')+ + (Number.isFinite(v)?``:''); }); + s+=''; }); + s+=twChip(480,416,'next: confirm with DFT or an experiment',col,{filled:true, size:19, h:38, w:470, opacity:TW.seg(t,.62,.74)}); + return s; }}, + + {block:'outro', title:'Your turn', ms:4400, + cap:'Your turn: open any block in the strip below, change something, and watch the effect flow through the next blocks. A good start: Targets, then Search.', + draw:(t)=>{ const a=TW.out(TW.seg(t,0,.25)); let s=twText(480,168,'Your turn',{anchor:'middle', size:48, weight:800, opacity:a})+ + twText(480,214,'Open any block below and change something:',{anchor:'middle', size:23, fill:'var(--muted)', opacity:TW.seg(t,.15,.35)})+ + twText(480,246,'the effect flows to the blocks after it.',{anchor:'middle', size:23, fill:'var(--muted)', opacity:TW.seg(t,.2,.4)}); + const wave=(t*2.2)%1; + BLOCKS.forEach((b,k)=>{ const x=165+k*105, on=Math.abs(wave*7-k-.5)<.8; + s+=``; + if(k<6) s+=``; }); + const bob=6*Math.sin(t*12); + s+=twArrow(480,356+bob,480,410+bob,'var(--c-family)',TW.seg(t,.45,.6),{sw:5, head:15})+twText(510,404+bob,'start here',{size:18, weight:800, fill:'var(--c-family)', opacity:TW.seg(t,.5,.65)}); + return s; }}, +]; document.addEventListener('keydown', e=>{ if(e.key==='Escape'){ closeModal(); closeYaml(); } const t=e.target.closest && e.target.closest('[data-show]'); // blocks and chain steps work from the keyboard too if(t && (e.key==='Enter'||e.key===' ')){ e.preventDefault(); show(t.dataset.show); } }); window.addEventListener('resize', ()=>{ clearTimeout(window._rz); window._rz=setTimeout(mountAll3d, 200); }); +tourInit(); // a first visit opens the one-minute animation above the workflow drawPipe(); // the workflow is visible at once; its numbers arrive with the data boot().catch(e=>{ renderChain([{block:'data', text:'Could not start: '+e.message}]); console.error(e); const lc=document.querySelector('.loading-card'); if(lc) lc.innerHTML=`

    The Studio could not start

    ${esc(e.message)}

    Reload the page in a minute. If it keeps failing, the server may be restarting or busy.

    `; }); diff --git a/site/404.html b/site/404.html index e0709b5f201bbb142f1467d67667cec3b8234505..924cb7b346b15389190fe2bec40c760b38934c6b 100644 --- a/site/404.html +++ b/site/404.html @@ -16,12 +16,12 @@ - + - MEIDNet + MEIDNet Prism @@ -47,6 +47,8 @@ + + @@ -81,9 +83,9 @@
    - - + + - - - - +
  2. + + + + + + How MEIDNet works + + - - - - + + + + + +
  3. + + + + + + + + + + +
  4. + - + + + + + Why you can trust (or not) a candidate + + -
  5. - - - - - - - +
  6. + + + + + + + + + +
  7. + + - Examples + Scope and roadmap @@ -1186,19 +1421,11 @@ - - - -
  8. - - diff --git a/site/assets/extra.css b/site/assets/extra.css new file mode 100644 index 0000000000000000000000000000000000000000..c8ee5767d4b18cf5c1bcd8d0f9d0669be4a3f3e2 --- /dev/null +++ b/site/assets/extra.css @@ -0,0 +1,7 @@ +/* MEIDNet docs: benchmark badges and dataset cards */ +.ladder{font-family:ui-monospace,SFMono-Regular,Consolas,monospace;letter-spacing:1px;color:#4a3aa7} +.bstatus{display:inline-block;border-radius:999px;padding:1px 9px;font-size:.78em;font-weight:700;border:1px solid} +.bstatus.sub{color:#52514e;border-color:#c3c2b7}.bstatus.pub{color:#1c5cab;border-color:#86b6ef}.bstatus.ver{color:#0a7d0a;border-color:#6fcf6f} +[data-md-color-scheme="slate"] .bstatus.sub{color:#c3c2b7} +.md-typeset .grid.cards>ul>li{border-top:4px solid #4f46e5} +.tour-video video{border:1px solid var(--md-default-fg-color--lightest)} diff --git a/site/assets/meidnet_tour.webm b/site/assets/meidnet_tour.webm new file mode 100644 index 0000000000000000000000000000000000000000..5b9e79406b5ad1e9f06e839ea79dd0b6b0ee0f95 --- /dev/null +++ b/site/assets/meidnet_tour.webm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e161b4bc69ef9789a8a85f1d8bb1142c20eba36cf03b55eaef1d21b6fe0a9d65 +size 10008532 diff --git a/site/assets/meidnet_tour_poster.png b/site/assets/meidnet_tour_poster.png new file mode 100644 index 0000000000000000000000000000000000000000..610332ca5267690e5249bf6e66fcd87ed2354599 --- /dev/null +++ b/site/assets/meidnet_tour_poster.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2a46aa993c62776cb6dc9c897cfad035317c19504d06b235f003c7cb59955269 +size 129635 diff --git a/site/assets/prism-mark.svg b/site/assets/prism-mark.svg new file mode 100644 index 0000000000000000000000000000000000000000..dba3e970cc069c0a02c92fde2898cb9994f01d53 --- /dev/null +++ b/site/assets/prism-mark.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/site/assets/prism.svg b/site/assets/prism.svg new file mode 100644 index 0000000000000000000000000000000000000000..8839dff3554c043b30ab5f6c30bc393cea609705 --- /dev/null +++ b/site/assets/prism.svg @@ -0,0 +1,37 @@ + + MEIDNet Prism + Crystal structures and material properties enter a prism and leave it as one shared representation, from which new materials are designed. + + + + + + + + + + + + Crystal structure + Band gap + Formation enthalpy + Your property + XRD ยท DOS ยท spectra (extensible) + + + + + + + + + shared representation + one 128-d latent space + new + materials + diff --git a/site/benchmarks/carbon24.html b/site/benchmarks/carbon24.html new file mode 100644 index 0000000000000000000000000000000000000000..dcabc702e38517251c827b39122a167239f929da --- /dev/null +++ b/site/benchmarks/carbon24.html @@ -0,0 +1,1925 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Carbon-24 - MEIDNet Prism + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    + +
    + + + + + + + + +

    Carbon-24

    +

    About 10,000 carbon structures with up to 24 atoms per cell (CDVAE split). In the MEIDNet paper it is the second structure-representation generalization test; no property modality and no checkpoint here.

    +

    10,153 rows ยท split: CDVAE: 6,091 train / 2,031 val / 2,031 test ยท role in the paper: published: structure-representation generalization (not a multimodal benchmark) ยท dataset source

    +
    +

    Read a row

    +

    Rows are compared only within this dataset and category. The ladder โ—โ—‹โ—‹โ—‹โ—‹ โ€ฆ โ—โ—โ—โ—โ— is the +evidence level (generated โ†’ ML filtered โ†’ MLIP validated โ†’ DFT validated โ†’ experimentally validated). +Published = taken from the paper; MEIDNet verified = reproduced here from the submitted configuration.

    +
    +

    No results yet. Contribute one.

    + + + + + + + + + + + + + +
    +
    + + + + + +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/site/benchmarks/index.html b/site/benchmarks/index.html new file mode 100644 index 0000000000000000000000000000000000000000..30e91af3e302e352a9120d36358dc267666bbe0b --- /dev/null +++ b/site/benchmarks/index.html @@ -0,0 +1,2046 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + MEIDNet Benchmarks - MEIDNet Prism + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    + +
    + + + + + + + + +

    MEIDNet Benchmarks

    +

    Reproducible results for multimodal materials representation and inverse design, one page per dataset. +Different datasets are different tasks, so there is no leaderboard across them: compare rows only within a page.

    +

    The evidence ladder

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    levelmeans
    โ—‹โ—‹โ—‹โ—‹โ—‹ generatedthe model produced it
    โ—โ—‹โ—‹โ—‹โ—‹ ML filteredit passed the family's rules and the model's own checks
    โ—โ—โ—‹โ—‹โ—‹ MLIP validatedrelaxed and found stable with a machine-learned potential (meidnet screen, MACE)
    โ—โ—โ—โ—‹โ—‹ DFT validatedconfirmed by density-functional calculations
    โ—โ—โ—โ—โ—‹ experimentally validatedsynthesised and measured
    +

    Each row also carries a status: Published (quoted from the paper), Community submitted (a pull request), +MEIDNet verified (reproduced here from the submitted configuration). How to contribute.

    +

    Datasets

    +
      +
    • Carbon-24 โ€” published: structure-representation generalization (not a multimodal benchmark) ยท 0 result(s), 0 verified
    • +
    • MP-20 โ€” published: structure-representation generalization (not a multimodal benchmark) ยท 0 result(s), 0 verified
    • +
    • Perov-5 โ€” the published multimodal benchmark: alignment, property reconstruction and inverse design ยท 3 result(s), 0 verified
    • +
    + + + + + + + + + + + + + +
    +
    + + + + + +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/site/benchmarks/mp20.html b/site/benchmarks/mp20.html new file mode 100644 index 0000000000000000000000000000000000000000..8299ad03a670d45527111ee511cc546f7c691ddc --- /dev/null +++ b/site/benchmarks/mp20.html @@ -0,0 +1,1925 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + MP-20 - MEIDNet Prism + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    + +
    + + + + + + + + +

    MP-20

    +

    About 45,000 Materials Project structures with at most 20 atoms per cell (CDVAE split). In the MEIDNet paper it tests how the crystal encoder and decoder generalise beyond perovskites; no property modality was aligned on it and no MP-20 checkpoint is shipped in this repository.

    +

    45,231 rows ยท split: CDVAE: 27,136 train / 9,047 val / 9,046 test ยท role in the paper: published: structure-representation generalization (not a multimodal benchmark) ยท dataset source

    +
    +

    Read a row

    +

    Rows are compared only within this dataset and category. The ladder โ—โ—‹โ—‹โ—‹โ—‹ โ€ฆ โ—โ—โ—โ—โ— is the +evidence level (generated โ†’ ML filtered โ†’ MLIP validated โ†’ DFT validated โ†’ experimentally validated). +Published = taken from the paper; MEIDNet verified = reproduced here from the submitted configuration.

    +
    +

    No results yet. Contribute one.

    + + + + + + + + + + + + + +
    +
    + + + + + +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/site/benchmarks/perov5.html b/site/benchmarks/perov5.html new file mode 100644 index 0000000000000000000000000000000000000000..f8c901a85536434e4273df684cdc2c18c94f2eef --- /dev/null +++ b/site/benchmarks/perov5.html @@ -0,0 +1,2110 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Perov-5 - MEIDNet Prism + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    + +
    + + + + + + + + +

    Perov-5

    +

    18,928 cubic ABX3 perovskites with relaxed structures, formation enthalpy (heat_all, eV/atom) and direct band gap (dir_gap, eV). The CDVAE split of the Castelli et al. (2012) perovskite set; the dataset behind the published MEIDNet model and the live Studio.

    +

    18,928 rows ยท split: CDVAE: 11,356 train / 3,785 val / 3,787 test ยท properties: heat_all, dir_gap ยท role in the paper: the published multimodal benchmark: alignment, property reconstruction and inverse design ยท dataset source

    +
    +

    Read a row

    +

    Rows are compared only within this dataset and category. The ladder โ—โ—‹โ—‹โ—‹โ—‹ โ€ฆ โ—โ—โ—โ—โ— is the +evidence level (generated โ†’ ML filtered โ†’ MLIP validated โ†’ DFT validated โ†’ experimentally validated). +Published = taken from the paper; MEIDNet verified = reproduced here from the submitted configuration.

    +
    +

    Representation quality

    + + + + + + + + + + + + + + + + + + + + + + + +
    resultmodelmodalitiescosine_matchedl2_matchedevidencestatus
    MEIDNet (paper): alignment of structure and property latents on Perov-5MEIDNet early fusion + curriculum (production model)structure, property:heat_all, property:dir_gap0.970.24โ—โ—‹โ—‹โ—‹โ—‹ generatedPublished (from the paper)
    +

    Conditional design

    + + + + + + + + + + + + + + + + + + + + + + + + + +
    resultmodelmodalitiesn_generatedn_sunsun_rateevidencestatus
    MEIDNet (paper): inverse design of perovskites from property targetsMEIDNet early fusion + curriculum (production model)structure, property:heat_all, property:dir_gap140 structures19 structures0.136โ—โ—โ—โ—โ—‹ DFT validatedPublished (from the paper)
    +

    Validation level

    + + + + + + + + + + + + + + + + + + + + + +
    resultmodelmodalitiesn_screenedevidencestatus
    MLIP screening of the shipped paper candidates (MACE-MP-0)MEIDNet early fusion + curriculum (production model)structure, property:heat_all, property:dir_gap26 structuresโ—โ—โ—โ—‹โ—‹ MLIP validatedCommunity submitted
    + + + + + + + + + + + + + +
    +
    + + + + + +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/site/benchmarks/results/paper-inverse-design.html b/site/benchmarks/results/paper-inverse-design.html new file mode 100644 index 0000000000000000000000000000000000000000..5b47acafcea8776642819dd8dd0f7fd784396960 --- /dev/null +++ b/site/benchmarks/results/paper-inverse-design.html @@ -0,0 +1,2016 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + MEIDNet (paper): inverse design of perovskites from property targets - MEIDNet Prism + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    + +
    + + + + + + + + +

    MEIDNet (paper): inverse design of perovskites from property targets

    +

    Published (from the paper) ยท โ—โ—โ—โ—โ—‹ DFT validated

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    DatasetPerov-5
    Modalitiesstructure, property:heat_all, property:dir_gap
    ModelMEIDNet early fusion + curriculum (production model) (MEIDNet 1.0 (paper), perovskite_abx3)
    CategoryConditional design
    Submitted byAnand Babu (UCLouvain) on 2026-05-29
    Configurationexamples/perov5/meidnet.yaml
    Checkpointdual_autoencoder_clip_earlyfusion_propertyaware_2k.pth
    +

    Results

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    metricvaluesplitnote
    n_generated140 structurescandidates generated from property targets in the campaign reported in the paper
    n_sun19 structuresstable, unique and novel after screening
    sun_rate0.13619 of 140
    +

    Validation

    +

    Evidence level: โ—โ—โ—โ—โ—‹ DFT validated.

    +

    Evidence:

    + +

    These numbers are quoted from the paper and have not been re-run from this repository.

    +

    Notes

    +

    Numbers quoted from the paper. The 26 CIFs in examples/perov5/paper_results/ are the candidates shipped with the paper; the MLIP screening row re-runs meidnet screen on them.

    + + + + + + + + + + + + + +
    +
    + + + + + +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/site/benchmarks/results/paper-representation.html b/site/benchmarks/results/paper-representation.html new file mode 100644 index 0000000000000000000000000000000000000000..416eb3db43b8724b2831f8ae4861882fef877bf8 --- /dev/null +++ b/site/benchmarks/results/paper-representation.html @@ -0,0 +1,2024 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + MEIDNet (paper): alignment of structure and property latents on Perov-5 - MEIDNet Prism + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    + +
    + + + + + + + + +

    MEIDNet (paper): alignment of structure and property latents on Perov-5

    +

    Published (from the paper) ยท โ—โ—‹โ—‹โ—‹โ—‹ generated

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    DatasetPerov-5
    Modalitiesstructure, property:heat_all, property:dir_gap
    ModelMEIDNet early fusion + curriculum (production model) (MEIDNet 1.0 (paper), perovskite_abx3)
    CategoryRepresentation quality
    Submitted byAnand Babu (UCLouvain) on 2026-05-29
    Configurationexamples/perov5/meidnet.yaml
    Checkpointdual_autoencoder_clip_earlyfusion_propertyaware_2k.pth
    +

    Results

    + + + + + + + + + + + + + + + + + + + + + + + +
    metricvaluesplitnote
    cosine_matched0.97validationmean cosine similarity between the structure and property latents of the same material, as reported in the paper
    l2_matched0.24validationmean L2 distance between the two latents of the same material, as reported in the paper
    +

    Validation

    +

    Evidence level: โ—โ—‹โ—‹โ—‹โ—‹ generated.

    +

    Evidence:

    + +

    Reproduce

    +

    Configuration examples/perov5/meidnet.yaml, split val:

    +
    python scripts/benchmarks.py reproduce paper-representation
    +
    +

    These numbers are quoted from the paper and have not been re-run from this repository.

    +

    Notes

    +

    Numbers quoted from the paper (npj Computational Materials, 2026). reproduce re-evaluates the shipped checkpoint on the CDVAE validation split and adds MAE, R2 and retrieval metrics; cosine_matched is compared with the quoted value.

    + + + + + + + + + + + + + +
    +
    + + + + + +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/site/benchmarks/results/paper-results-mace-screen.html b/site/benchmarks/results/paper-results-mace-screen.html new file mode 100644 index 0000000000000000000000000000000000000000..341c4dbd7e87108a22a02fc3fd1c4b695b00b707 --- /dev/null +++ b/site/benchmarks/results/paper-results-mace-screen.html @@ -0,0 +1,2021 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + MLIP screening of the shipped paper candidates (MACE-MP-0) - MEIDNet Prism + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    + +
    + + + + + + + + +

    MLIP screening of the shipped paper candidates (MACE-MP-0)

    +

    Community submitted ยท โ—โ—โ—โ—‹โ—‹ MLIP validated

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    DatasetPerov-5
    Modalitiesstructure, property:heat_all, property:dir_gap
    ModelMEIDNet early fusion + curriculum (production model) (MEIDNet 2.0.0, perovskite_abx3)
    CategoryValidation level
    Submitted byAnand Babu (UCLouvain) on 2026-10-03
    Configurationexamples/perov5/meidnet.yaml
    Checkpointdual_autoencoder_clip_earlyfusion_propertyaware_2k.pth
    Resourceslaptop CPU
    +

    Results

    + + + + + + + + + + + + + + + + + +
    metricvaluesplitnote
    n_screened26 structuresthe CIFs in examples/perov5/paper_results/
    +

    Validation

    +

    Evidence level: โ—โ—โ—โ—‹โ—‹ MLIP validated.

    +

    Evidence:

    +
      +
    • examples/perov5/paper_results/
    • +
    +

    Reproduce

    +

    Configuration examples/perov5/meidnet.yaml:

    +
    meidnet screen examples/perov5/paper_results --train-csv data/perov5/train.csv
    +
    +

    Notes

    +

    n_stable / n_unique / n_novel / n_sun are filled in by python scripts/benchmarks.py reproduce paper-results-mace-screen (MACE-MP-0 relaxation, about ten minutes on a CPU).

    + + + + + + + + + + + + + +
    +
    + + + + + +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/site/community/contribute.html b/site/community/contribute.html new file mode 100644 index 0000000000000000000000000000000000000000..dad84a5c2004ab7e76d00e98b60fa7b4a4fa50af --- /dev/null +++ b/site/community/contribute.html @@ -0,0 +1,2102 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Contribute a result - MEIDNet Prism + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    + +
    + + + + + + + + +

    Contribute a benchmark result

    +

    MEIDNet Benchmarks collect results per dataset, each with the evidence behind it. Anyone who runs MEIDNet on a +dataset โ€” the published Perov-5, or their own โ€” can submit a result. Submissions go through GitHub, so every +number has a public record and a reviewer.

    +

    What a submission is

    +

    One JSON file, benchmarks/submissions/<dataset>/<id>.json, that follows benchmarks/schema.json. The short +version:

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    fieldwhat to put
    datasetan id from benchmarks/datasets/ (perov5, โ€ฆ); a new dataset needs its own datasets/<id>.json with source, size and split
    modalitieswhat the model aligned: structure, property:<column> (one per property), later xrd, โ€ฆ
    modelMEIDNet version, family and variant, the configuration (meidnet.yaml), checkpoint link and its SHA-256
    categoryrepresentation_quality, conditional_design, efficiency or validation โ€” one category per file
    resultsmetric โ†’ {value, unit}; only the metric names allowed for that category (listed in the schema)
    validation_levelthe evidence ladder: generated โ†’ ml_filtered โ†’ mlip_validated โ†’ dft_validated โ†’ experimentally_validated
    evidencepublic links: CIF archive, stability.csv, DFT inputs/outputs, paper DOI
    reproduceconfiguration, seed and the exact command that produces the numbers
    statusalways community_submitted for a new file
    +

    The five things people get wrong

    +
      +
    1. Mixing categories in one file. A representation result (MAE, Rยฒ, retrieval) and a design result (SUN rate) + are two files.
    2. +
    3. A metric name that is not in the list. validate refuses it; the lists exist so that rows are comparable.
    4. +
    5. No split. State the split you evaluated on (or link a file of ids). A number on an unknown split is not comparable.
    6. +
    7. Evidence that is not public. A result without an inspectable artefact stays at generated.
    8. +
    9. Claiming a validation level the evidence does not support. dft_validated needs the DFT outputs, not the intention.
    10. +
    +

    How it works

    +
      +
    1. Copy benchmarks/submissions/_template.json, fill it in, run python scripts/benchmarks.py validate.
    2. +
    3. Open a pull request (the template has a checklist). CI runs validate.
    4. +
    5. Once merged, the result is rendered on the dataset's benchmark page and gets a permanent page of its own, + marked Community submitted.
    6. +
    7. The maintainer may run python scripts/benchmarks.py reproduce <id>: it re-runs the stated command from the + stated configuration and compares the numbers. If they agree within the tolerances, the row becomes + MEIDNet verified (a record in benchmarks/verified/, which only the maintainer can add).
    8. +
    +

    Verified means reproduced from the submitted configuration with this code. It does not mean the material is +real: that is what the validation level says.

    +

    If you prefer not to open a pull request, the issue template Benchmark submission asks for the same fields and the +maintainer converts it. Submissions are reviewed by the author of MEIDNet; there is no promised response time.

    + + + + + + + + + + + + + +
    +
    + + + + + +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/site/community/index.html b/site/community/index.html new file mode 100644 index 0000000000000000000000000000000000000000..08c07578cee3686c911d84003fc9570aafe89c36 --- /dev/null +++ b/site/community/index.html @@ -0,0 +1,1955 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Community results - MEIDNet Prism + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    + +
    + + + + + + + + +

    Community results

    +

    Every submitted result, newest first. Contribute yours.

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    dateresultdatasetcategoryevidencestatus
    2026-10-03MLIP screening of the shipped paper candidates (MACE-MP-0)Perov-5Validation levelโ—โ—โ—โ—‹โ—‹ MLIP validatedCommunity submitted
    2026-05-29MEIDNet (paper): inverse design of perovskites from property targetsPerov-5Conditional designโ—โ—โ—โ—โ—‹ DFT validatedPublished (from the paper)
    2026-05-29MEIDNet (paper): alignment of structure and property latents on Perov-5Perov-5Representation qualityโ—โ—‹โ—‹โ—‹โ—‹ generatedPublished (from the paper)
    + + + + + + + + + + + + + +
    +
    + + + + + +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/site/examples/double-perovskite.html b/site/examples/double-perovskite.html index b1722da9de79213c518253b81de85f78f91e943b..3d11fde68d27c2728cd3873b70c6eb57904de46c 100644 --- a/site/examples/double-perovskite.html +++ b/site/examples/double-perovskite.html @@ -22,12 +22,12 @@ - + - Double perovskites - MEIDNet + Double perovskites - MEIDNet Prism @@ -53,6 +53,8 @@ + + @@ -92,9 +94,9 @@
    - - + + - - - - - +
  9. + + + + + How MEIDNet works + + - - - - - - - + + + + + +
  10. + + + + + + + + + + +
  11. + - + + + + + Why you can trust (or not) a candidate + + -
  12. - - - - - - - +
  13. + + + + + + + + + +
  14. + + - Examples + Scope and roadmap @@ -1204,19 +1439,11 @@ - - - -
  15. - - diff --git a/site/examples/perov5.html b/site/examples/perov5.html index 96f6c586c68e7eb55937a3d7cd5382579edc9d12..0dbb1a462d63dcf110496eaab275b226bdcf1864 100644 --- a/site/examples/perov5.html +++ b/site/examples/perov5.html @@ -22,12 +22,12 @@ - + - Perov-5 (the paper) - MEIDNet + Perov-5 (the paper) - MEIDNet Prism @@ -53,6 +53,8 @@ + + @@ -92,9 +94,9 @@
    - - + + - - - - - +
  16. + + + + + How MEIDNet works + + - - - - - - - + + + + + +
  17. + + + + + + + + + + +
  18. + - + + + + + Why you can trust (or not) a candidate + + -
  19. - - - - - - - +
  20. + + + + + + + + + +
  21. + + - Examples + Scope and roadmap @@ -1204,19 +1439,11 @@ - - - -
  22. - - @@ -1706,7 +1993,7 @@ meidnet generate examples/perov5/m defaults: normalize: false for both properties and align_to_prototype: false (atoms in file order), and the 1200-epoch alignment ramp.

    The paper's generated structures

    -

    examples/perov5/paper_results/ holds the 27 CIFs (halide, oxide, chalcogenide) reported in the paper. +

    examples/perov5/paper_results/ holds the 26 CIFs (halide, oxide, chalcogenide) reported in the paper. meidnet screen examples/perov5/paper_results --train-csv data/perov5/train.csv recomputes their MACE stability.

    Regression against v1

    diff --git a/site/explore/capabilities.html b/site/explore/capabilities.html new file mode 100644 index 0000000000000000000000000000000000000000..586ad19d56e4a07b37eff06949b0875fb1e794b0 --- /dev/null +++ b/site/explore/capabilities.html @@ -0,0 +1,2248 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Capabilities (what works today) - MEIDNet Prism + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + + +
    +
    +
    + + + + + + + +
    + +
    + + + + + + + + +

    Capabilities: what works today

    +

    Generated from the code, so it cannot promise more than the code does. Supported = in this release; Community-tested = a benchmark result reproduced here names it; Planned = designed for, not implemented (roadmap).

    +

    Modalities

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    modalitystatushow
    Crystal structure (CIF, up to max_sites atoms, default 20)Supportedan equivariant graph encoder; structures are aligned to the family prototype
    Scalar properties (any number of numeric columns)Supportedone property encoder; every column becomes a target you can set
    Published properties: direct band gap (dir_gap), formation enthalpy (heat_all)Supportedthe shipped Perov-5 checkpoint
    Vector modalities: binned XRD, DOSPlannedan encoder per vector modality into the shared latent space
    Spectra (Raman, UV-Vis)Plannedas vector modalities
    Text (descriptions, synthesis)Planneda text encoder into the shared latent space
    Images (microscopy)Plannedan image encoder into the shared latent space
    +

    Fusion

    +

    One scheme: early fusion - the structure latent and the property latent of a material are averaged into the joint latent (meidnet/model.py), after a contrastive alignment whose weight ramps up over training.contrastive_warmup_epochs (the curriculum of the paper). There is no late-fusion option; a selector would be a fiction.

    +

    Input formats

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    formatnote
    CSVany delimiter pandas reads
    Excel (.xlsx, .xls)first sheet
    JSON tablerecords
    Parquetneeds the optional pyarrow
    CIF text in a columnone structure per row
    CIF files (.cif, or a .zip of them)named .cif
    +

    Material families

    + + + + + + + + + + + + + + + + + + + + +
    familyvariantssite groups
    double_perovskite_a2bbx6 - Rock-salt ordered double perovskite A2BB'X6 (Fm-3m)halide, oxideA, B1, B2, X
    perovskite_abx3 - Cubic ABX3 perovskite (Pm-3m)oxide, halide, chalcogenide, nitrideA, B, X
    +

    Any other prototype family can be described in a family file.

    +

    Rules (hard constraints)

    +
      +
    • bond_window - Sensible {from}โ€“{to} bonds
    • +
    • charge_neutrality - Charge balance
    • +
    • min_distance - No overlapping atoms
    • +
    • octahedral_factor - Octahedral factor
    • +
    • property_window - Predicted {property} window
    • +
    • tolerance_factor - Goldschmidt tolerance factor
    • +
    +

    Search terms (soft)

    +
      +
    • cubic_cell - Pulls the decoded cell towards a cube of the family's reference volume.
    • +
    • entropy_bonus - Rewards uncertainty early in a round so the search explores (fades to zero).
    • +
    • group_consistency - Sites of the same group should agree on their element (ramps up during a round).
    • +
    • prototype_alignment - Pulls decoded positions onto the prototype's site positions.
    • +
    • short_distance_penalty - Large penalty if any two decoded atoms come closer than min.
    • +
    • site_repulsion - Short-range exponential repulsion between decoded atoms.
    • +
    • site_separation - Keeps decoded atoms from collapsing onto each other (1/distance).
    • +
    • soft_charge - Expected total charge of the most likely composition should be zero (ramps up during a round).
    • +
    • tolerance_penalty - Soft version of the Goldschmidt tolerance-factor window, using the most likely elements.
    • +
    +

    After generation

    +
      +
    • meidnet screen: MACE-MP-0 relaxation and the stable / unique / novel (SUN) counts (optional pip install meidnet[stability]). DFT and experiments are yours: the benchmark evidence ladder records them.
    • +
    + + + + + + + + + + + + + +
    +
    + + + + + +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/site/explore/datasets.html b/site/explore/datasets.html new file mode 100644 index 0000000000000000000000000000000000000000..f7a07766563b72349061402bc1c0d9895d5c510f --- /dev/null +++ b/site/explore/datasets.html @@ -0,0 +1,2056 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Datasets - MEIDNet Prism + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    + +
    + + + + + + + + +

    Datasets

    +

    Datasets that work with MEIDNet, and what has been done with each. One card per dataset, never one score +across datasets: the tasks, sizes and properties differ, so results are only compared within a dataset +(see Benchmarks).

    +
    +
      +
    • +

      Perov-5 โ€” published multimodal benchmark

      +
      +

      18,928 cubic ABXโ‚ƒ perovskites (CDVAE split: 11,356 train / 3,785 validation / 3,787 test), each with a +relaxed structure, formation enthalpy (heat_all, eV/atom) and direct band gap (dir_gap, eV).

      +

      Modalities used in MEIDNet: crystal structure ยท electronic property (band gap) ยท thermodynamic property +(formation enthalpy).

      +

      What the paper does with it: multimodal alignment of the structure and property encoders, property +reconstruction, and the inverse-design demonstration (candidates generated from property targets, screened +with MACE and validated by DFT).

      +

      In this repository: meidnet download-data fetches it; the published checkpoint +checkpoints/dual_autoencoder_clip_earlyfusion_propertyaware_2k.pth was trained on it; it is the data behind +the live Studio.

      +

      Benchmark results ยท The paper's experiment ยท +Dataset source (CDVAE) ยท +Castelli et al. 2012, Xie et al. 2022

      +
    • +
    • +

      MP-20 โ€” published: structure-representation generalization

      +
      +

      ~45,000 structures from the Materials Project with at most 20 atoms per cell (CDVAE split).

      +

      Published MEIDNet use: generalization of the crystal encoder / decoder beyond perovskites (structure +representation and reconstruction). It is not a multimodal benchmark in the paper: no property modality was +aligned on it, and no MP-20 checkpoint is shipped here.

      +

      Benchmark results ยท Dataset source (CDVAE)

      +
    • +
    • +

      Carbon-24 โ€” published: structure-representation generalization

      +
      +

      ~10,000 carbon structures with up to 24 atoms per cell (CDVAE split).

      +

      Published MEIDNet use: the same structure-representation test as MP-20. Not a multimodal benchmark; no +Carbon-24 checkpoint is shipped here.

      +

      Benchmark results ยท Dataset source (CDVAE)

      +
    • +
    +
    +

    Your dataset

    +

    Any table with an id, one or more scalar property columns and a crystal structure per row (CIF text in a column +or one .cif file per id) can be used directly: upload it in the Studio +or run meidnet init โ†’ check โ†’ train โ†’ generate (bring your own dataset). +The structures must belong to one material family (prototype + site groups), which is +what the rules and the search need.

    +

    Datasets we would like to see benchmarked

    +

    Battery conductors, MOFs, 2D materials and spinels are natural next families. If you run MEIDNet on one of them, +contribute the result: the dataset gets its own benchmark page, and a verified row +once the result has been reproduced here.

    + + + + + + + + + + + + + +
    +
    + + + + + +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/site/index.html b/site/index.html index 540440b66508830bad8a4c31eb4425254d7acf30..966ebcb5a94354b572cd6156fd9d93bed20ac8bc 100644 --- a/site/index.html +++ b/site/index.html @@ -14,18 +14,18 @@ - + - + - MEIDNet + MEIDNet Prism @@ -51,6 +51,8 @@ + + @@ -76,7 +78,7 @@
    - + Skip to content @@ -90,9 +92,9 @@
    - - + + - - - - +
  23. + + + + + + How MEIDNet works + + - - - - + + + + + +
  24. + + + + + + + + + + +
  25. + - + + + + + Why you can trust (or not) a candidate + + -
  26. - - - - - - - +
  27. + + + + + + + + + +
  28. + + - Examples + Scope and roadmap @@ -1277,19 +1512,11 @@ - - - -
  29. - - @@ -1659,41 +1946,51 @@ -

    MEIDNet

    +

    MEIDNet Prism

    +

    โ† MEIDNet Prism home ยท the documentation of the MEIDNet framework and its platform.

    Design crystalline materials from target properties โ€” with your own data, your own rules, and no code to edit.

    MEIDNet learns one latent space shared by crystal structures and their properties, then searches it for new materials that hit property targets while obeying the chemical and structural rules of a material family. The published cubic-perovskite model (band gap + formation enthalpy) is one application of it; the framework accepts any table of structures and scalar properties and any prototype family you describe.

    +
    + +
    MEIDNet in one minute: data, model, family, rules, targets, search and candidates. The same tour opens + on a first visit to the live Studio (the โ€œโ–ถ How it worksโ€ button replays it).
    +
    +
    diff --git a/site/recipes/add-constraint.html b/site/recipes/add-constraint.html index 4407ab2a7868e3ba7d213407c17eb83f91fe0611..b2ae8e57832726cd0c83591c9885cb4050dbe83f 100644 --- a/site/recipes/add-constraint.html +++ b/site/recipes/add-constraint.html @@ -22,12 +22,12 @@ - + - Add a rule (constraint) - MEIDNet + Add a rule (constraint) - MEIDNet Prism @@ -53,6 +53,8 @@ + + @@ -92,9 +94,9 @@
    diff --git a/site/recipes/change-family.html b/site/recipes/change-family.html index e48d13d5173dedc7e1e7b55e07be633e0e8daefd..980677019b5e9c7b42f298f60ae381048b21fc34 100644 --- a/site/recipes/change-family.html +++ b/site/recipes/change-family.html @@ -22,12 +22,12 @@ - + - Change the material family - MEIDNet + Change the material family - MEIDNet Prism @@ -53,6 +53,8 @@ + + @@ -92,9 +94,9 @@
    diff --git a/site/recipes/screen.html b/site/recipes/screen.html index a6f2d663c3f30d78cce17642be70ecb27a63b85d..ba7f8c427faa074a0e85dbe0a45652fc32890d3b 100644 --- a/site/recipes/screen.html +++ b/site/recipes/screen.html @@ -22,12 +22,12 @@ - + - Screen stability with MACE - MEIDNet + Screen stability with MACE - MEIDNet Prism @@ -53,6 +53,8 @@ + + @@ -92,9 +94,9 @@
    - - + + - - - - + - - - - +
  30. + + + + + + + + + + Screen stability with MACE + + -
  31. + + + + + - - - - - -
  32. + + + + + + + + + +
  33. + + - Examples + How MEIDNet works @@ -1214,19 +1393,67 @@ - - + +
  34. + + + + + + + + + + +
  35. + -
  36. + + + + + - Examples + + + +
  37. + + + + + + + + Scope and roadmap - - diff --git a/site/reference/api.html b/site/reference/api.html index cca19211f39ba1622c93fd48112741e730c9fa59..9744a48f7c1f0e898409ed8d4c87cc4409a4f58e 100644 --- a/site/reference/api.html +++ b/site/reference/api.html @@ -22,12 +22,12 @@ - + - Python API - MEIDNet + Python API - MEIDNet Prism @@ -53,6 +53,8 @@ + + @@ -92,9 +94,9 @@
  38. + @@ -400,12 +403,12 @@ + + + + + + + + + + + + + + + + + + + + + +
  39. + + + + + + + + + + +
  40. + + + + + + + + + + + + + + + + + + + +
  41. + + + + + + + + + - -
  42. - - - - - + + - - - - - - +
  43. + - + + + + + Add a property + + -
  44. - - - - - - - +
  45. + + + + + + + + + +
  46. + + - Understand + Exclude or restrict elements @@ -1056,19 +1266,11 @@ - - - -
  47. - - - - - + + - - - - +
  48. + + + + + + How MEIDNet works + + - - - - + + + + + +
  49. + + + + + + + + + + +
  50. + - + + + + + Why you can trust (or not) a candidate + + -
  51. - - - - - - - +
  52. + + + + + + + + + +
  53. + + - Examples + Scope and roadmap @@ -1199,19 +1434,11 @@ - - - -
  54. - - @@ -1741,7 +2028,8 @@ dicts you can save with json.dump and open at chemiscope.org, or sh published_data_dataset, structure_to_chemiscope) ds = space_dataset(fam, space, lm, max_structures=2000) # design space: formula, site_*, pred_*, rule_*, passes_all -ds = candidates_dataset(candidate_dicts, fam, run_dir) # candidates of a run (reads their CIFs when present) +ds = candidates_dataset(candidate_dicts, fam, run_dir, lm=lm) # candidates of a run (reads their CIFs when present; + # lm adds units and property names) ds = records_dataset(info["records"], cfg.data.properties, source="my data") # needs check(cfg, keep_structures=True) structure_to_chemiscope(structure) # one pymatgen Structure โ†’ {size, names, x, y, z, cell}
    diff --git a/site/reference/cli.html b/site/reference/cli.html index ce51af187edebd729319754aaeb9610a91f474c6..c8a8df00c70ce9c93ae1aac172bf637c8c8bea31 100644 --- a/site/reference/cli.html +++ b/site/reference/cli.html @@ -22,12 +22,12 @@ - + - Command line - MEIDNet + Command line - MEIDNet Prism @@ -53,6 +53,8 @@ + + @@ -92,9 +94,9 @@
    - - + + - - - - +
  55. + + + + + + How MEIDNet works + + - - - - + + + + + +
  56. + + + + + + + + + + +
  57. + - + + + + + Why you can trust (or not) a candidate + + -
  58. - - - - - - - +
  59. + + + + + + + + + +
  60. + + - Examples + Scope and roadmap @@ -1199,19 +1434,11 @@ - - - -
  61. - - diff --git a/site/reference/config.html b/site/reference/config.html index cc139df082835ed0b3fef89cac5acf5e8173cad4..a94b52d9ae7f6222d773e690b86a10cda042ff45 100644 --- a/site/reference/config.html +++ b/site/reference/config.html @@ -22,12 +22,12 @@ - + - Configuration - MEIDNet + Configuration - MEIDNet Prism @@ -53,6 +53,8 @@ + + @@ -92,9 +94,9 @@
    - - + + - - - - +
  62. + + + + + + How MEIDNet works + + - - - - + + + + + +
  63. + + + + + + + + + + +
  64. + - + + + + + Why you can trust (or not) a candidate + + -
  65. - - - - - - - +
  66. + + + + + + + + + +
  67. + + - Examples + Scope and roadmap @@ -1199,19 +1434,11 @@ - - - -
  68. - - diff --git a/site/reference/families.html b/site/reference/families.html index 1f1446243f0aaa1c273892031a337f10e4e1cfef..bb6eb5d18c987446bd476471e89818fc67f33576 100644 --- a/site/reference/families.html +++ b/site/reference/families.html @@ -22,12 +22,12 @@ - + - Family files - MEIDNet + Family files - MEIDNet Prism @@ -53,6 +53,8 @@ + + @@ -92,9 +94,9 @@
    - - + + - - - - +
  69. + + + + + + How MEIDNet works + + - - - - + + + + + +
  70. + + + + + + + + + + +
  71. + - + + + + + Why you can trust (or not) a candidate + + -
  72. - - - - - - - +
  73. + + + + + + + + + +
  74. + + - Examples + Scope and roadmap @@ -1199,19 +1434,11 @@ - - - -
  75. - - diff --git a/site/reference/rules.html b/site/reference/rules.html index d2cb4bdfe95759b0476c8188c0dca0df7cb6f48f..9a81549aa4213405b929d7cad0292733c134413c 100644 --- a/site/reference/rules.html +++ b/site/reference/rules.html @@ -22,12 +22,12 @@ - + - Rules and search terms - MEIDNet + Rules and search terms - MEIDNet Prism @@ -53,6 +53,8 @@ + + @@ -92,9 +94,9 @@
    - - + + - - - - +
  76. + + + + + + How MEIDNet works + + - - - - + + + + + +
  77. + + + + + + + + + + +
  78. + - + + + + + Why you can trust (or not) a candidate + + -
  79. - - - - - - - +
  80. + + + + + + + + + +
  81. + + - Examples + Scope and roadmap @@ -1199,19 +1434,11 @@ - - - -
  82. - - diff --git a/site/research/paper.html b/site/research/paper.html index 4a553e44d067b29d401571a2425e200944734407..5acf4ec51cc5be07691d511391359dc4db3321ad 100644 --- a/site/research/paper.html +++ b/site/research/paper.html @@ -20,12 +20,12 @@ - + - Research - MEIDNet + Paper and citation - MEIDNet Prism @@ -51,6 +51,8 @@ + + @@ -90,9 +92,9 @@
    - - + + + - - - - +
  83. + + + + + How MEIDNet works + + - - - - + + + + + +
  84. + + + + + + + + + + +
  85. + - + + + + + Why you can trust (or not) a candidate + + -
  86. - - - - - - - +
  87. + + + + + + + + + +
  88. + + - Examples + Scope and roadmap @@ -1197,19 +1432,11 @@ - - - -
  89. - -
      + @@ -1481,6 +1708,63 @@ + + + + + + + + + + + + + +
    • + + + + + + + + + + +
    • + +
    diff --git a/site/search/search_index.json b/site/search/search_index.json index 16522da1f326c67b25870017507892ae71d44363..6b33f3f124bf078812a1cf11f22870fd8579ecad 100644 --- a/site/search/search_index.json +++ b/site/search/search_index.json @@ -1 +1 @@ -{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"],"fields":{"title":{"boost":1000.0},"text":{"boost":1.0},"tags":{"boost":1000000.0}}},"docs":[{"location":"index.html","title":"MEIDNet","text":"

    Design crystalline materials from target properties \u2014 with your own data, your own rules, and no code to edit.

    MEIDNet learns one latent space shared by crystal structures and their properties, then searches it for new materials that hit property targets while obeying the chemical and structural rules of a material family. The published cubic-perovskite model (band gap + formation enthalpy) is one application of it; the framework accepts any table of structures and scalar properties and any prototype family you describe.

    • :material-play-circle:{ .lg .middle } Try MEIDNet

      See what it does in 30 seconds: the live Studio needs nothing installed; meidnet demo runs on any laptop.

      :octicons-arrow-right-24: 5-minute quickstart

    • :material-database-import:{ .lg .middle } Use MEIDNet on my data

      A table with an id, properties and CIFs is all you need. Four commands take you from \"is my data usable?\" to candidates with explanations.

      :octicons-arrow-right-24: Bring your own dataset

    • :material-tune:{ .lg .middle } Change what it does

      Targets, elements, rules, family \u2014 all in one YAML file, or by moving sliders in MEIDNet Studio and exporting the file.

      :octicons-arrow-right-24: Recipes

    • :material-head-question:{ .lg .middle } Understand it

      How the two modalities are aligned, how the search works, what the reports mean, and where the limits are.

      :octicons-arrow-right-24: How MEIDNet works

    "},{"location":"index.html#the-workflow","title":"The workflow","text":"
       Data \u2500\u2500\u25ba Model \u2500\u2500\u25ba Family \u2500\u2500\u25ba Rules \u2500\u2500\u25ba Targets \u2500\u2500\u25ba Search \u2500\u2500\u25ba Candidates\n   table    shared    prototype  hard      property   latent     CIFs + report\n   + CIFs   latent    + sites    checks    goals      optimisation\n

    Each block is a setting in meidnet.yaml, a node in MEIDNet Studio \u2014 where changing one block immediately shows its effect on the ones after it \u2014 and a section in the HTML report each step writes.

    "},{"location":"index.html#honest-scope","title":"Honest scope","text":"
    • Generation works for prototype families: a fixed arrangement of sites whose occupants and cell size are chosen (ABX\u2083 perovskites, A\u2082BB\u2032X\u2086 double perovskites, and anything you describe the same way). MEIDNet does not invent new atomic arrangements. Details and roadmap.
    • Predicted properties are the model's estimates. The reports say when a prediction is an extrapolation. Confirm candidates with DFT or experiment; meidnet screen is a first filter.
    "},{"location":"index.html#paper","title":"Paper","text":"

    A. Babu, R. Almeida Gouv\u00eaa, P. Vandergheynst, G.-M. Rignanese, MEIDNet: Multimodal generative AI framework for inverse materials design, npj Computational Materials (2026). Citation and benchmarks \u00b7 code as published (v1.0).

    "},{"location":"examples/double-perovskite.html","title":"Double perovskites A\u2082BB\u2032X\u2086","text":"

    meidnet/families/double_perovskite_a2bbx6.yaml is a second family written with the same ingredients as the perovskite one. It shows what a family file can express:

    • a 10-site prototype in the rhombohedral primitive cell of an fcc lattice (lattice: fcc_primitive: edge a/\u221a2, angles 60\u00b0), A at (\u00bc,\u00bc,\u00bc) and (\u00be,\u00be,\u00be), B at the origin, B\u2032 at (\u00bd,\u00bd,\u00bd), six X;
    • four site groups with their own element lists and oxidation states (B: +1/+2, B\u2032: +3/+4);
    • a cell-size rule a = 2 (r_B + r_B\u2032 + 2 r_X) (one B\u2013X\u2013B\u2032 chain per half cell edge);
    • rules that average radii over several groups: tolerance_factor: {A: A, B: [B1, B2], X: X}.
    meidnet families double_perovskite_a2bbx6 --variant halide\n
    Rock-salt ordered double perovskite A2BB'X6 (Fm-3m)  [variant: halide]\n  formula pattern: A2B1B2X6   (10 atoms per cell)\n  A: 9 candidate elements - Large cation in the cuboctahedral cavity\n  B1: 15 candidate elements - First octahedral cation (usually +1 or +2)\n  B2: 20 candidate elements - Second octahedral cation (usually +3 or +4)\n  X: 4 candidate elements - Anion shared between neighbouring B and B' octahedra\n

    The halide variant has 10,800 compositions; 305 pass every rule (Cs\u2082AgBiI\u2086 among them). Candidates are built in the 10-atom primitive cell; the symmetry refinement step writes them out as the conventional 40-atom Fm-3m cell, which is what the CIF files contain.

    "},{"location":"examples/double-perovskite.html#using-it","title":"Using it","text":"

    A model for this family must be trained on double-perovskite structures aligned to the prototype:

    family: double_perovskite_a2bbx6\ndata:\n  table: double_perovskites.csv\n  properties: [{column: band_gap, unit: eV}]\n  align_to_prototype: true\ngeneration:\n  family: double_perovskite_a2bbx6\n  variant: halide\n  objectives: [{property: band_gap}]\n  targets: [{band_gap: 2.0}]\n

    The published checkpoint

    The Perov-5 model was trained on 5-atom ABX\u2083 cells. The Studio will score double-perovskite compositions with it (the encoder accepts any cell up to 20 atoms), but those predictions are far outside its training distribution \u2014 a demonstration of the mechanics, not a result.

    "},{"location":"examples/perov5.html","title":"Perov-5 \u2014 the paper's experiment","text":"

    The published MEIDNet model: cubic ABX\u2083 perovskites from the Perov-5 dataset (CDVAE split of Castelli et al.), properties formation enthalpy (heat_all, eV/atom) and direct band gap (dir_gap, eV).

    "},{"location":"examples/perov5.html#use-the-published-checkpoint","title":"Use the published checkpoint","text":"
    meidnet demo --family halide --band-gap 2.0            # or: oxide, chalcogenide, nitride\nmeidnet studio                                          # explore it live\n

    The three checkpoints from the paper are in checkpoints/; ..._propertyaware_2k.pth is the recommended one. meidnet info checkpoints/<file> describes any of them. They load into MEIDNet 2 unchanged.

    "},{"location":"examples/perov5.html#retrain-from-scratch","title":"Retrain from scratch","text":"
    meidnet download-data                       # train/val/test CSVs \u2192 data/perov5/ (18,928 rows, 18 MB)\nmeidnet train examples/perov5/meidnet.yaml  # 200 epochs \u2248 1 h on a laptop GPU\nmeidnet generate examples/perov5/meidnet.yaml\n

    examples/perov5/meidnet.yaml reproduces the paper's settings, including two that differ from MEIDNet 2's defaults: normalize: false for both properties and align_to_prototype: false (atoms in file order), and the 1200-epoch alignment ramp.

    "},{"location":"examples/perov5.html#the-papers-generated-structures","title":"The paper's generated structures","text":"

    examples/perov5/paper_results/ holds the 27 CIFs (halide, oxide, chalcogenide) reported in the paper. meidnet screen examples/perov5/paper_results --train-csv data/perov5/train.csv recomputes their MACE stability.

    "},{"location":"examples/perov5.html#regression-against-v1","title":"Regression against v1","text":"

    tests/legacy_v1/ contains the v1.0 code byte for byte. pytest -m slow runs it and MEIDNet 2 on the same targets and asserts identical CIFs, predictions and file names.

    "},{"location":"recipes/add-constraint.html","title":"Add a rule (constraint)","text":"

    A rule is a function that looks at a candidate and returns pass/fail, the measured value, the allowed window and a sentence. The search enforces it; every report and the Studio explain it.

    "},{"location":"recipes/add-constraint.html#1-write-it","title":"1. Write it","text":"

    my_rules.py:

    from meidnet.constraints import CONSTRAINTS\n\n@CONSTRAINTS.register(\"no_lead\", \"Rejects any composition containing Pb.\")\ndef no_lead(cand):\n    return cand.result(\"no_lead\", \"Pb\" not in cand.elements.values())\n\n@CONSTRAINTS.register(\"mass_window\", \"Mean atomic mass must lie between min and max (g/mol).\")\ndef mass_window(cand, min=0.0, max=200.0):\n    from pymatgen.core import Element\n    m = sum(Element(e).atomic_mass for e in cand.structure.species) / len(cand.structure)\n    return cand.result(\"mass_window\", min <= m <= max, float(m), (min, max), f\"mean mass {m:.1f}\")\n

    What a candidate gives you: cand.elements ({group: element}), cand.structure (pymatgen, after symmetry refinement), cand.raw (before), cand.lattice_a, cand.predictions ({property: value}), cand.radius(group).

    "},{"location":"recipes/add-constraint.html#2-register-the-file-and-name-the-rule","title":"2. Register the file and name the rule","text":"
    plugins: [my_rules.py]\ngeneration:\n  family: my_perovskite.yaml      # a copy of the built-in family with the rules appended:\n
    constraints:\n  - {name: charge_neutrality}\n  - ...\n  - {name: no_lead}\n  - {name: mass_window, min: 0, max: 120}\n
    "},{"location":"recipes/add-constraint.html#3-run","title":"3. Run","text":"

    meidnet generate meidnet.yaml. The candidate cards list No lead and mass window with the measured value; the funnel shows how many compositions each removed.

    A complete example lives in examples/custom_rules/ (a no toxic elements rule and a max cell edge rule), and notebook 03 does the same in Colab.

    "},{"location":"recipes/add-constraint.html#soft-terms","title":"Soft terms","text":"

    Rules decide acceptance. To steer the latent search instead (a penalty with a gradient), register a search term with @SEARCH_TERMS.register in meidnet.terms; it receives a SearchContext with the decoder's outputs. See rules and search terms.

    "},{"location":"recipes/add-property.html","title":"Add a property","text":"

    MEIDNet's property modality is a vector of any length. Adding a property is a data change, not a code change.

    1. Add the column to your table.
    2. List it under data.properties (label and unit are for the reports; normalize: true is the default and recommended when properties have different scales):

      data:\n  properties:\n    - {column: band_gap,     label: Band gap,          unit: eV}\n    - {column: formation_e,  label: Formation energy,  unit: eV/atom}\n    - {column: dielectric,   label: Dielectric constant}\n
    3. meidnet check \u2192 meidnet train. The training report rates each property separately.

    4. Use it in generation.objectives and targets.

    Properties with many missing values: rows with a missing value are skipped (the check report counts them). Fill them, or drop the property.

    The published model

    The Perov-5 checkpoint has exactly two properties (formation enthalpy, direct band gap) and cannot steer others; train your own model to add some.

    "},{"location":"recipes/change-family.html","title":"Change the material family","text":"

    A family file tells MEIDNet what kind of crystal to build. The built-in ones are meidnet/families/perovskite_abx3.yaml and double_perovskite_a2bbx6.yaml; meidnet families <name> prints a summary. To use your own, copy one, edit it, and point to it:

    generation:\n  family: my_family.yaml\n  variant: null          # or a variant you define\n
    "},{"location":"recipes/change-family.html#the-parts-of-a-family-file","title":"The parts of a family file","text":"
    name: spinel_ab2x4\ntitle: Normal spinel AB2X4 (Fd-3m, primitive cell)\n\nprototype:                    # WHERE atoms sit (fractional coordinates, in decoder slot order)\n  lattice: fcc_primitive      # cubic | fcc_primitive | bcc_primitive | explicit a,b,c,alpha,beta,gamma\n  reference_a: 8.1\n  sites:\n    - {group: A, frac: [0.125, 0.125, 0.125]}\n    - ...\n\ngroups:                       # WHO may sit there, and with which charges\n  A: {description: tetrahedral cation, elements: [Mg, Zn, Fe, Co, Ni, Mn], oxidation_states: {Mg: [2], ...}}\n  B: {...}\n  X: {...}\n\nsampling_order: [B, X, A]     # the LAST group is restricted to charge-balancing elements\n\nlattice:                      # HOW BIG the cell is\n  rule: bond_sum              # a = factor \u00d7 \u03a3 ionic radii of the listed groups (or rule: fixed)\n  groups: [B, X]\n  factor: 2.0\n  min: 3.0\n  max: 12.0\n\nvariants:                     # named sub-families: element subsets and rule parameters\n  oxide: {groups: {X: {elements: [O]}}, params: {tolerance_factor: {min: 0.8, max: 1.05}}}\n\nsearch_terms:                 # soft penalties during the latent search (see reference/rules)\n  - {name: site_separation, weight: 200}\n  - ...\n\nconstraints:                  # hard rules every saved candidate must pass\n  - {name: charge_neutrality}\n  - {name: min_distance, min: 0.8}\n  - ...\n
    "},{"location":"recipes/change-family.html#alignment-of-training-data","title":"Alignment of training data","text":"

    For generation to mean anything, the model must have been trained on structures whose atoms are in the prototype's site order. data.align_to_prototype: true (default) re-orders every training structure to match the family named in generation.family (or top-level family:); structures that do not fit are reported by meidnet check. Raise data.prototype_tolerance for distorted cells.

    "},{"location":"recipes/change-family.html#checking-a-family-file","title":"Checking a family file","text":"
    meidnet families my_family.yaml --variant oxide\nmeidnet space meidnet.yaml      # every composition with rule values \u2192 CSV\n

    Errors name the problem: unknown element symbols, a prototype group without a definition, an override that matches no rule, a group left empty after your filters.

    "},{"location":"recipes/change-family.html#worked-example","title":"Worked example","text":"

    The double perovskite page walks through a 10-site, non-cubic-primitive family written this way.

    "},{"location":"recipes/change-targets.html","title":"Change the target properties","text":""},{"location":"recipes/change-targets.html#a-different-value","title":"A different value","text":"
    generation:\n  targets:\n    - {band_gap: 1.2}\n    - {band_gap: 1.8}      # each entry is its own search\n
    "},{"location":"recipes/change-targets.html#a-different-kind-of-goal","title":"A different kind of goal","text":"

    loss says how a prediction is compared with the target:

    loss meaning typical use l2 squared distance (default) hit a value l1 absolute distance hit a value, less sensitive to outliers at_most only values above the target are penalised formation enthalpy \u2264 \u22120.1 eV/atom at_least only values below are penalised dielectric constant \u2265 20
    objectives:\n  - {property: band_gap,   loss: l2,       weight: 10000, select_weight: 1.0}\n  - {property: enthalpy,   loss: at_most,  weight: 6000,  select_weight: 0.4}\n

    weight acts during the latent search; select_weight when ranking finished candidates.

    "},{"location":"recipes/change-targets.html#a-different-property","title":"A different property","text":"

    The model knows the properties it was trained on (meidnet info model.pt lists them). To steer a property that is not in the model, add it to the data and retrain.

    "},{"location":"recipes/change-targets.html#in-the-studio","title":"In the Studio","text":"

    Drag the target slider; the predicted-closest table and the scatter update at once, and a warning appears if the target lies outside the training range. Export the YAML when you are happy.

    "},{"location":"recipes/elements.html","title":"Exclude or restrict elements","text":"
    generation:\n  exclude_elements: [Pb, Cd, Hg]           # never used, on any site\n  only_elements:                           # restrict a site group to a shortlist\n    B: [Ti, Zr, Hf, Sn]\n

    Both are applied to the family before the search starts, so the decoder cannot even propose an excluded element. The Studio's element chips do the same thing (click to toggle) and show the counts change.

    To change which elements a family allows at all, or their oxidation states, copy the family file and edit the groups: section \u2014 see Change the material family.

    "},{"location":"recipes/screen.html","title":"Screen stability with MACE","text":"
    pip install \"meidnet[stability]\"            # ase + mace-torch (~500 MB of weights on first use)\nmeidnet screen runs/my_oxides/generation --train-csv materials.csv\n

    For each CIF: relax cell and positions with the MACE-MP-0 universal potential, compute the formation energy per atom against elemental reference phases, and report

    • S stable \u2014 \u0394H_f \u2264 0.10 eV/atom (Materials Project's \"potentially synthesisable\" criterion; --threshold),
    • U unique \u2014 no structural duplicate among the candidates,
    • N novel \u2014 reduced formula absent from the training table.

    Results go to stability.csv in the folder. Structures with |\u0394H_f| > 5 eV/atom are flagged as artefacts and excluded from the rates. The reference phases and GGA-style O/N corrections are those of the paper.

    This is a first filter, not a verdict: MACE formation energies carry ~0.1 eV/atom uncertainty, and a cubic prototype may relax to a lower-symmetry structure.

    "},{"location":"reference/api.html","title":"Python API","text":"

    The command line is a thin layer over these functions; use them from notebooks or your own scripts.

    from meidnet.config import load_config, config_from_dict\nfrom meidnet.pipeline import check, train, generate\n\ncfg = load_config(\"meidnet.yaml\")\ninfo = check(cfg)                 # dict: report (counts, skip reasons), records, family, report_path\ninfo = check(cfg, keep_structures=True)   # records also keep their pymatgen Structure (record.structure)\nmodel_path = train(cfg)           # writes model.pt + training_report.html, returns the path\nresult = generate(cfg)            # GenerationResult: .saved (candidates), .targets (funnel logs)\n
    "},{"location":"reference/api.html#pieces","title":"Pieces","text":"
    from meidnet.checkpoint import load_checkpoint, describe, property_ranges\nlm = load_checkpoint(\"model.pt\")          # LoadedModel: .model, .stats (property names/units/normalisation)\nlm.model.encode_crystal(x)                # structure \u2192 latent (x from meidnet.data.featurize)\nlm.model.encode_properties(p)             # normalised property vector \u2192 latent\n\nfrom meidnet.family import load_family\nfam = load_family(\"perovskite_abx3\", variant=\"halide\", exclude=[\"Pb\"], overrides={\"tolerance_factor\": {\"max\": 1.0}})\n\nfrom meidnet.constraints import build_candidate, evaluate, CONSTRAINTS\ncand = evaluate(build_candidate(fam, {\"A\": \"Cs\", \"B\": \"Sn\", \"X\": \"Br\"}), fam.constraints)\ncand.passed, [r.to_dict() for r in cand.results]\n\nfrom meidnet.generate import Designer\nfrom meidnet.config import GenerationSection\ng = GenerationSection(family=\"perovskite_abx3\", variant=\"halide\",\n                      objectives=[{\"property\": \"dir_gap\"}], targets=[{\"dir_gap\": 2.0}], rounds=3, steps=200)\nres = Designer(lm, fam, g, on_saved=lambda c, t: print(c.formula)).run(\"out/\")\n\nfrom meidnet.designspace import enumerate_space, space_to_csv\nspace = enumerate_space(fam, lm)          # every composition: rule values + predictions\n

    Custom rules and search terms: CONSTRAINTS.register, meidnet.terms.SEARCH_TERMS.register.

    "},{"location":"reference/api.html#extra-rules-in-the-configuration","title":"Extra rules in the configuration","text":"

    generation.extra_constraints adds rules to the family's own for one run, without editing the family file \u2014 this is how the Studio saves a predicted-property window:

    generation:\n  family: perovskite_abx3\n  variant: halide\n  extra_constraints:\n    - {name: property_window, property: dir_gap, min: 1.5, max: 3.0}\n

    meidnet.pipeline.family_for(cfg, need_variant=True) returns the family a run uses, with these rules appended; generation.family / generation.variant take precedence over the top-level family.

    "},{"location":"reference/api.html#3d-datasets-chemiscope","title":"3D datasets (chemiscope)","text":"

    meidnet.studio.chemiscope turns MEIDNet objects into chemiscope datasets \u2014 plain dicts you can save with json.dump and open at chemiscope.org, or show in a notebook with chemiscope.show(...):

    from meidnet.studio.chemiscope import (space_dataset, candidates_dataset, records_dataset,\n                                       published_data_dataset, structure_to_chemiscope)\n\nds = space_dataset(fam, space, lm, max_structures=2000)   # design space: formula, site_*, pred_*, rule_*, passes_all\nds = candidates_dataset(candidate_dicts, fam, run_dir)    # candidates of a run (reads their CIFs when present)\nds = records_dataset(info[\"records\"], cfg.data.properties, source=\"my data\")   # needs check(cfg, keep_structures=True)\nstructure_to_chemiscope(structure)                         # one pymatgen Structure \u2192 {size, names, x, y, z, cell}\n

    candidate_dicts are saved candidates as dicts \u2014 [c.to_dict() for c in res.saved] after a Designer run, or [c for t in json.load(open(\"generation.json\"))[\"targets\"] for c in t[\"saved\"]] from a finished run. run_dir is the run folder that contains generation/ (cfg.out_dir after meidnet generate); where a CIF file is not found, the structure is rebuilt on the prototype from the candidate's elements.

    "},{"location":"reference/api.html#scripting-the-studio","title":"Scripting the Studio","text":"

    The Studio server is a small class you can drive without a browser \u2014 the 04 Colab notebook does exactly this. Every method takes the same JSON-like dicts the page sends; session keeps users (or experiments) apart.

    import base64\nfrom meidnet.studio.server import Studio\n\nst = Studio(None)                                   # the published model; or Studio(load_config(\"meidnet.yaml\"))\nsid = \"my-session\"\nst.state(sid)                                       # model, ranges, families, limits, session info\n\nb64 = base64.b64encode(open(\"materials.csv\", \"rb\").read()).decode()\nup = st.upload({\"session\": sid, \"files\": [{\"name\": \"materials.csv\", \"b64\": b64}]})\nup[\"suggest\"]                                       # suggested id / CIF / property columns\n\nst.check_data({\"session\": sid, \"id_column\": \"material_id\", \"cif_column\": \"cif\",\n               \"properties\": [{\"column\": \"dir_gap\", \"unit\": \"eV\"}],\n               \"family\": \"perovskite_abx3\", \"variant\": \"halide\"})\nst.start_train({\"session\": sid, \"epochs\": 10, \"batch_size\": 16})\nst.status(sid, \"train\")                             # poll: running, progress (epoch, loss, curve), done, error\nst.select_model(sid, \"published\")                   # or \"own\"\n\nst.family({\"session\": sid, \"family\": \"perovskite_abx3\", \"variant\": \"halide\"})   # design space, scored\nst.start_search({\"session\": sid, \"generation\": {\"per_target\": 2, \"rounds\": 2, \"steps\": 100}})\nst.status(sid, \"search\")                            # candidates as they are found\nst.chemiscope(sid, \"space\", \"perovskite_abx3\", \"halide\")   # or \"candidates\" / \"data\"\nst.validate({\"yaml\": \"generation:\\n  rounds: 2\\n\"})        # {ok, generation, notes} or {ok: False, errors}\nst.export({\"session\": sid, \"generation\": {}})              # the meidnet.yaml text\n
    "},{"location":"reference/cli.html","title":"Command line","text":"
    meidnet init            write a starter meidnet.yaml next to your data\nmeidnet check  CONFIG   is my data usable?            \u2192 check_report.html\nmeidnet train  CONFIG   learn the latent space        \u2192 model.pt, training_report.html\nmeidnet generate CONFIG design candidates             \u2192 CIFs, generation_report.html\nmeidnet studio [CONFIG] interactive workbench in your browser\nmeidnet space  CONFIG   every composition of the family with rule values and predictions \u2192 CSV\nmeidnet demo            quick demo with the published perovskite model\nmeidnet screen DIR      stability screening of CIFs with MACE (optional extra)\nmeidnet info MODEL      describe a checkpoint\nmeidnet families [NAME] list / describe material families\nmeidnet schema          JSON Schema of the config (for editors and the Studio)\nmeidnet download-data   fetch the Perov-5 dataset used in the paper\n

    meidnet <command> --help lists the options. Useful ones:

    init --template perov5 the paper's configuration instead of a blank one train --epochs N override training.epochs generate --quick few rounds and steps \u2014 a smoke test generate --model PATH use another checkpoint studio --export-static FILE.html self-contained Studio for a website studio --port 8765 --no-open choose the port; do not open a browser studio --public --host 0.0.0.0 host the Studio for other people: private session per visitor, capped budgets studio --run-root DIR --docs-dir site where sessions and searches are written; serve a built docs site at /docs/ space --chemiscope FILE.json also write the design space as a 3D dataset for chemiscope.org demo --family oxide --band-gap 3 --enthalpy -0.2 -n 5 demo targets

    If meidnet is not on your PATH, python -m meidnet.cli \u2026 is equivalent.

    "},{"location":"reference/config.html","title":"Configuration reference","text":"

    Every setting of meidnet.yaml, generated from the code (meidnet schema gives the JSON Schema). Unknown keys are rejected with a message naming the key, so typos cannot silently change a run.

    "},{"location":"reference/config.html#top-level","title":"Top level","text":"setting type default meaning name string meidnet_run Name of this project; outputs go to output_dir. description string Free text shown at the top of reports. output_dir string runs/{name} Where checkpoints, CIFs and reports are written. family string/null None Family used to align training structures (defaults to generation.family). model_path string/null None Existing checkpoint to generate from (skip training). Default: /model.pt plugins array Python files that register your own constraints or search terms (see 'Add a constraint'). data object/null None model section training section generation object/null None"},{"location":"reference/config.html#data","title":"data:","text":"setting type default meaning table string required Path to a CSV/Excel/JSON table with one row per material. id_column string material_id Column with a unique identifier for each material. cif_column string/null cif Column that contains the CIF text of each structure. Set to null if you use structures_dir. structures_dir string/null None Folder with one CIF file per material, named .cif (used when there is no cif_column). properties array required Numeric columns that form the property modality (any number, at least one). val_table string/null None Optional separate validation table with the same columns. val_fraction number 0.1 Fraction of rows held out for validation when no val_table is given (0 = no validation). max_sites integer 20 Largest number of atoms per cell. Bigger structures are skipped. neighbor_cutoff number 4.0 Distance (\u00c5) below which two atoms count as neighbours. align_to_prototype boolean True Re-order the atoms of every structure so they match the family prototype's site order (needed for meaningful generation). The published Perov-5 model was trained without it. prototype_tolerance number 0.15 How far (fraction of a cell edge) an atom may sit from its prototype site and still count as that site. Raise it to accept distorted structures; lower it to keep only ideal ones."},{"location":"reference/config.html#dataproperties","title":"data.properties[]","text":"setting type default meaning column string required Name of the column in your table that holds this property. label string/null None Human-readable name used in reports (defaults to the column name). unit string Unit shown in reports, e.g. 'eV' or 'eV/atom'. normalize boolean True Standardise the property to zero mean and unit spread before training. Keep this on when properties have very different scales."},{"location":"reference/config.html#model","title":"model:","text":"setting type default meaning latent_dim integer 128 Size of the shared latent space where structures and properties meet. node_hidden_dim integer 128 Width of the per-atom features inside the graph network. edge_dim integer 64 Width of the per-bond messages inside the graph network. species_embedding_dim integer 64 Size of the learned element embedding. property_hidden_dim integer 128 Width of the property encoder's hidden layer. decoder_coordinate_input data / zeros / prototype data What the crystal decoder receives as starting atom positions during training. 'data' (published model) uses the true positions; generation always starts from 'zeros' or the prototype. 'zeros'/'prototype' make training and generation consistent (experimental)."},{"location":"reference/config.html#training","title":"training:","text":"setting type default meaning epochs integer 200 Number of passes over the training data. batch_size integer 16 Materials per training step (use 8 if memory is short). learning_rate number 0.001 Adam learning rate. contrastive_weight number 5.0 Strength of the alignment between structure and property latents. temperature number 0.01 Sharpness of the contrastive (InfoNCE) alignment. contrastive_warmup_epochs integer/null None Epochs over which the alignment strength ramps up from 0. Default: 60% of the epochs (the published model used 1200 of 2000). loss_weights section seed integer 0 Random seed for weight initialisation and data shuffling. device string auto 'auto', 'cpu' or 'cuda'. save_every integer 50 Write an intermediate checkpoint every N epochs."},{"location":"reference/config.html#trainingloss_weights","title":"training.loss_weights:","text":"setting type default meaning joint_reconstruction number 1.0 Rebuild the crystal from the joint (structure+property) latent. joint_property number 1.0 Predict the properties from the joint latent. property_reconstruction number 0.5 Rebuild the crystal from the property latent alone - this is what makes inverse design possible. property_property number 0.5 Predict the properties back from the property latent."},{"location":"reference/config.html#generation","title":"generation:","text":"setting type default meaning family string perovskite_abx3 Built-in family name or path to your own family .yaml file. variant string/null None Sub-family, e.g. 'halide' or 'oxide' for perovskites. objectives array required Which properties to steer and how. targets array required One entry per design target, e.g. {dir_gap: 1.5, heat_all: -0.1}. Each runs its own search. per_target integer 4 Candidates to save for each target. population integer 48 Latent vectors optimised in parallel in each round. rounds integer 20 Maximum search rounds per target. steps integer 800 Gradient steps per round. learning_rate number 0.0012 Step size of the latent search. anchor_weight number 12.0 Keeps the search close to the latent that the target properties map to. temperature_start number 1.6 Softness of element choices at the start of a round. temperature_end number 0.9 Softness of element choices at the end of a round. diversity_weight number 0.9 Pushes the parallel searches apart from each other. diversity_tau number 0.25 history_weight number 1.0 Pushes new searches away from latents that already produced a saved candidate. history_tau number 0.25 restart_patience integer 250 Steps without improvement before a search is restarted with noise. restart_noise number 0.35 grad_clip number 1.0 z_clip number 5.0 init_sigma number 0.35 Noise added to the starting latents. init_anchor_mix number 0.75 Share of the property-encoder latent in the starting point. init_rff_mix number 0.6 Share of the target-dependent random-feature direction in the starting point. init_noise_mix number 0.25 Share of random noise in the starting point. rff_frequencies integer 48 decode_temperature number 1.25 Randomness when turning element scores into a choice. decode_topk integer 12 Only the k best-scoring elements of a group can be chosen. decode_tries integer 12 Attempts per latent to find a composition that passes all constraints. anti_repeat_alpha number 0.6 Down-weights elements that were already used in saved candidates. anti_repeat_group string/null None Group the anti-repeat applies to (default: first sampled group). geometry_scale number 1.0 Global strength of the family's search terms. property_first boolean False Ignore the family's search terms during the search (properties only). dedup_formula boolean True Save each composition at most once. min_cosine_sep number 0.985 Skip candidates whose latent is this similar (cosine) to an already saved one. unique_decimals integer 3 exclude_elements array Elements never to use, e.g. [Pb, Cd]. only_elements object Restrict groups to these elements, e.g. {B: [Ti, Zr, Hf]}. overrides object Change parameters of the family's search terms or constraints, e.g. {tolerance_factor: {max: 1.0}}. extra_constraints array Additional rules appended to the family's constraints, e.g. [{name: property_window, property: dir_gap, min: 1.0, max: 3.0}]. disabled_rules array Rules to switch off for this run, by name (or id when two rules share a name), e.g. [charge_neutrality]. The generation report lists them. seed integer 937 Random seed of the search. amp boolean True Use mixed precision on GPUs. output_prefix string/null None File-name prefix of saved CIFs (default: family variant)."},{"location":"reference/config.html#generationobjectives","title":"generation.objectives[]","text":"setting type default meaning property string required Property (column name) this objective refers to. loss l2 / l1 / at_most / at_least l2 How a prediction is compared with the target: l2 = squared distance, l1 = absolute distance, at_most = only values above the target are penalised, at_least = only values below. weight number 10000.0 Importance of this objective during the latent search. select_weight number 1.0 Importance of this objective when ranking finished candidates. select_loss string/null None Comparison used for ranking (defaults to 'loss')."},{"location":"reference/families.html","title":"Family files","text":"

    A family file is YAML with these keys. meidnet families NAME prints a summary and the constraints with their explanations; errors name the offending key or element.

    key required meaning name, title, description name identification; description appears in the Studio prototype.lattice yes cubic, fcc_primitive, bcc_primitive, or explicit a, b, c, alpha, beta, gamma prototype.reference_a typical cubic cell edge (\u00c5), used by the cubic_cell search term prototype.sites[] yes {group, frac} in decoder slot order; fractional coordinates groups.<G>.description sentence shown in reports groups.<G>.elements yes every element the group may hold (all variants) groups.<G>.sample subset offered to the final element choice (default: elements) groups.<G>.oxidation_states {element: [charges]} used for charge balance and neutrality bias sampling_order order in which groups are chosen; the last is restricted to charge-balancing elements lattice.rule bond_sum (a = factor \u00d7 \u03a3 r_ionic of groups, clipped to min..max) or fixed variants.<V>.groups per-variant elements, sample, oxidation_states of any group variants.<V>.params {rule_or_term_name: {param: value}} applied to search terms and constraints require_variant default true: a variant must be chosen when variants exist search_terms[] {name, \u2026params} soft penalties during the latent search logit_transforms[] {name, \u2026params} edits of the decoder's element scores (e.g. neutrality_bias) constraints[] {name, \u2026params} hard rules; min_distance runs before symmetry refinement, the rest after refine_symmetry, symprec run spglib refinement on candidates (default true, 0.05)

    User-side changes without editing the file: generation.variant, exclude_elements, only_elements, overrides ({name: {param: value}} for any search term or constraint).

    The built-in files are the complete reference: perovskite_abx3.yaml, double_perovskite_a2bbx6.yaml.

    "},{"location":"reference/rules.html","title":"Rules and search terms","text":"

    Rules (hard constraints) decide whether a candidate is saved. Search terms (soft penalties) steer the latent search towards decodable, sensible outputs. Both are referenced by name in family files.

    "},{"location":"reference/rules.html#built-in-rules","title":"Built-in rules","text":""},{"location":"reference/rules.html#bond_window-sensible-fromto-bonds","title":"bond_window \u2014 Sensible {from}\u2013{to} bonds","text":"

    Every {from} atom needs a {to} neighbour at {low}\u2013{high} \u00d7 the sum of their ionic radii: bonds may be neither squashed nor stretched.

    "},{"location":"reference/rules.html#charge_neutrality-charge-balance","title":"charge_neutrality \u2014 Charge balance","text":"

    The formal charges must add up to zero for at least one combination of the listed oxidation states (e.g. Cs\u207a Pb\u00b2\u207a I\u207b\u00d73 = 0).

    "},{"location":"reference/rules.html#min_distance-no-overlapping-atoms","title":"min_distance \u2014 No overlapping atoms","text":"

    Two atoms closer than {min} \u00c5 would overlap, which is physically impossible.

    "},{"location":"reference/rules.html#octahedral_factor-octahedral-factor","title":"octahedral_factor \u2014 Octahedral factor","text":"

    \u03bc = r_B / r_X says whether the B cation is large enough to hold six X anions around it ({min} \u2264 \u03bc \u2264 {max}).

    "},{"location":"reference/rules.html#property_window-predicted-property-window","title":"property_window \u2014 Predicted {property} window","text":"

    The model's predicted {property} must lie between {min} and {max}.

    "},{"location":"reference/rules.html#tolerance_factor-goldschmidt-tolerance-factor","title":"tolerance_factor \u2014 Goldschmidt tolerance factor","text":"

    t = (r_A + r_X) / (\u221a2 (r_B + r_X)) measures how well the A cation fits the cage of B\u2013X octahedra. Cubic perovskites form roughly for {min} \u2264 t \u2264 {max}.

    "},{"location":"reference/rules.html#search-terms","title":"Search terms","text":"
    • cubic_cell \u2014 Pulls the decoded cell towards a cube of the family's reference volume.
    • entropy_bonus \u2014 Rewards uncertainty early in a round so the search explores (fades to zero).
    • group_consistency \u2014 Sites of the same group should agree on their element (ramps up during a round).
    • prototype_alignment \u2014 Pulls decoded positions onto the prototype's site positions.
    • short_distance_penalty \u2014 Large penalty if any two decoded atoms come closer than min.
    • site_repulsion \u2014 Short-range exponential repulsion between decoded atoms.
    • site_separation \u2014 Keeps decoded atoms from collapsing onto each other (1/distance).
    • soft_charge \u2014 Expected total charge of the most likely composition should be zero (ramps up during a round).
    • tolerance_penalty \u2014 Soft version of the Goldschmidt tolerance-factor window, using the most likely elements.
    "},{"location":"reference/rules.html#logit-transforms","title":"Logit transforms","text":"
    • neutrality_bias \u2014 Raises the scores of anions whose charge can neutralise the most likely cations.
    "},{"location":"reference/rules.html#your-own-rule","title":"Your own rule","text":"
    from meidnet.constraints import CONSTRAINTS\n\n@CONSTRAINTS.register(\"no_lead\", \"Rejects any composition containing Pb.\")\ndef no_lead(cand):\n    return cand.result(\"no_lead\", \"Pb\" not in cand.elements.values())\n

    Save it as my_rules.py, list it under plugins: in meidnet.yaml, and add - {name: no_lead} to the family's constraints. See Add a rule.

    "},{"location":"research/paper.html","title":"Paper","text":"

    MEIDNet: Multimodal generative AI framework for inverse materials design. Anand Babu, Rog\u00e9rio Almeida Gouv\u00eaa, Pierre Vandergheynst, Gian-Marco Rignanese. npj Computational Materials (2026). doi:10.1038/s41524-026-02153-3 \u00b7 arXiv:2601.22009

    @article{meidnet2026,\n  title   = {MEIDNet: Multimodal generative AI framework for inverse materials design},\n  author  = {Anand Babu and Rog{\\'e}rio Almeida Gouv{\\^e}a and Pierre Vandergheynst and Gian-Marco Rignanese},\n  journal = {npj Computational Materials},\n  year    = {2026},\n  doi     = {10.1038/s41524-026-02153-3}\n}\n
    "},{"location":"research/paper.html#code-and-data-of-the-paper","title":"Code and data of the paper","text":"
    • Code as published: tag v1.0.0-paper (frozen; MEIDNet 2 reproduces it bit for bit \u2014 see Perov-5).
    • Checkpoints: checkpoints/ in the repository (2.8 MB each).
    • Data: Perov-5 in the CDVAE split (meidnet download-data); Castelli et al., Energy Environ. Sci. 5, 5814 (2012); Xie et al., ICLR (2022).
    • Generated structures: examples/perov5/paper_results/.
    "},{"location":"research/paper.html#related","title":"Related","text":"
    • Satorras, Hoogeboom, Welling, E(n) equivariant graph neural networks, ICML 2021 (the encoder).
    • Radford et al., CLIP, ICML 2021 (the alignment objective).
    • Batatia et al., MACE-MP-0 (stability screening).
    "},{"location":"start/colab.html","title":"Run in Google Colab","text":"

    Each notebook is a complete, runnable workflow. Training runs in your Colab session (free GPU is enough): your data never leaves your Google account.

    Notebook What it does Time 01 \u2014 Quickstart published model, pick a target, read the report, explore the whole design space in 3D 5 min, CPU 02 \u2014 Your own data upload table + CIFs \u2192 check \u2192 train \u2192 generate \u2192 download everything 15 min + training 03 \u2014 Add a rule write a custom constraint in a few lines and see its effect before and after 5 min 04 \u2014 MEIDNet Studio in Colab the live Studio inside the notebook, then the same blocks driven from Python 10 min"},{"location":"start/colab.html#block-by-block-like-the-studio","title":"Block by block, like the Studio","text":"

    Every notebook follows the seven blocks of the Studio, in order and in the same colours: Block 1 \u00b7 Data (blue), Block 2 \u00b7 Model (orange), Block 3 \u00b7 Family (green), Block 4 \u00b7 Rules (amber), Block 5 \u00b7 Targets (pink), Block 6 \u00b7 Search (dark green) and Block 7 \u00b7 Candidates (violet). Each block starts with a coloured header saying what it does, has a form for its settings, shows its result as charts (and crystals in 3D where it helps), and ends by printing what flows on to the next block.

    Run the cells from top to bottom (Runtime \u2192 Run all also works). Change a value in a form and re-run from that block on: the later blocks pick up the change.

    The notebooks are generated from scripts/make_notebooks.py, so they stay in step with the command line.

    "},{"location":"start/quickstart.html","title":"5-minute quickstart","text":"In the browser (nothing to install)On your computerIn Colab

    Open the live Studio. It runs the published perovskite model: move rule limits and targets and watch what passes, run a latent search and read each candidate's checklist, export the meidnet.yaml. (Search budgets are capped on the shared server; the local install below has no limits and takes your own data.)

    pip install meidnet          # PyTorch CPU wheels are fine; CUDA is optional\nmeidnet demo                 # halide perovskites, band gap 2.0 eV \u2192 runs/demo/\n

    demo downloads the 2.8 MB published checkpoint, searches for 3 candidates (about a minute on a CPU), checks them against the family's rules and opens generation_report.html.

    meidnet demo --family oxide --band-gap 3.0 --enthalpy -0.2 -n 5\nmeidnet studio               # the live workbench with the same model\n

    Notebook 01 \u2014 quickstart

    "},{"location":"start/quickstart.html#what-you-just-saw","title":"What you just saw","text":"
    1. Targets \u2192 latent. The property encoder maps (band gap, enthalpy) to a point in the shared latent space.
    2. Search. A population of latents near that point is optimised so that the decoder's output matches the targets and the family's soft terms (cubic cell, sensible charges, \u2026).
    3. Decode + rules. Each latent is decoded into one element per site; the composition is placed on the family's prototype and must pass every hard rule (charge balance, tolerance factor, \u2026).
    4. Report. The best new, unique candidates are saved as CIFs with a card that shows each rule's value and a gauge of predicted vs target property.
    "},{"location":"start/quickstart.html#next","title":"Next","text":"
    • What data do I need? then Bring your own dataset.
    • MEIDNet Studio to see the effect of every change live.
    "},{"location":"start/what-data.html","title":"What data do I need?","text":""},{"location":"start/what-data.html#the-checklist","title":"The checklist","text":"You need Format Notes Structures CIF text in a cif column of the table, or one <id>.cif file per material in a folder up to max_sites atoms per cell (default 20); any elements Properties one or more numeric columns any units; each property is standardised unless you say otherwise An id a column whose values match the CIF file names (if you use files) 12, 12.0 and \"12\" are treated as the same id A family the prototype your structures share (e.g. cubic ABX\u2083) needed for generation; training without one is possible (align_to_prototype: false)

    A table like this is enough:

    material_id,band_gap,dielectric,cif\nmat_001,1.42,18.3,\"# generated using pymatgen\ndata_CsPbBr3\n_cell_length_a 5.87 ...\"\nmat_002,2.16,11.7,\"...\"\n

    CSV, Excel (.xlsx) and JSON tables are read; Parquet too once pyarrow is installed (pip install \"meidnet[parquet]\").

    "},{"location":"start/what-data.html#how-much-data","title":"How much data?","text":"

    The published model used 11k structures. A few thousand is typical; a few hundred lets you try the pipeline, but expect rough predictions (the training report tells you how rough).

    "},{"location":"start/what-data.html#what-happens-to-each-row","title":"What happens to each row","text":"

    meidnet check reads every row exactly as training would and writes check_report.html with one line per reason a row was skipped (unreadable CIF, too many atoms, missing value, does not match the prototype) and example ids. Nothing is silently dropped.

    "},{"location":"start/what-data.html#properties-and-ranges","title":"Properties and ranges","text":"

    MEIDNet can only design for property values it has seen. The check report draws each property's distribution; the generation report flags targets outside the training range as extrapolation.

    "},{"location":"start/what-data.html#which-family","title":"Which family?","text":"
    • Cubic ABX\u2083 perovskite \u2192 built-in perovskite_abx3 (variants: oxide, halide, chalcogenide, nitride).
    • Rock-salt-ordered double perovskite A\u2082BB\u2032X\u2086 \u2192 built-in double_perovskite_a2bbx6.
    • Anything else with a fixed arrangement of sites \u2192 write a family file (positions, which elements may occupy which site, their charges, a cell-size rule, rules).
    • Mixed prototypes \u2192 you can still train (align_to_prototype: false) and use the model to predict/screen, but generation needs one prototype.
    "},{"location":"understand/how-it-works.html","title":"How MEIDNet works","text":""},{"location":"understand/how-it-works.html#one-latent-space-for-two-modalities","title":"One latent space for two modalities","text":"
    crystal (CIF) \u2500\u2500\u25ba SE(3)-equivariant encoder \u2500\u2500\u25ba z_c \u2500\u2510                     \u250c\u2500\u25ba crystal decoder   \u2500\u2500\u25ba lattice, species, positions\n                                                      \u251c\u2500\u2500 shared space \u2500\u2500\u2500\u2500\u2500\u2524\nproperties \u2500\u2500\u2500\u2500\u25ba property encoder (MLP) \u2500\u2500\u2500\u2500\u2500\u25ba z_p \u2500\u2518  pulled together     \u2514\u2500\u25ba property decoder  \u2500\u2500\u25ba properties\n                                                        (InfoNCE)\n
    • The crystal encoder embeds each atom's element, passes messages along neighbour bonds using only squared distances (so rotating or translating the cell changes nothing), pools the atoms and adds the lattice.
    • The property encoder maps the vector of (normalised) properties to the same space.
    • Both are projected and normalised to unit length; a contrastive loss makes each structure's latent closest to its own property latent and far from the others in the batch. Its weight ramps up during training so the decoders stabilise first.
    • The decoders are trained from the joint latent and from the property latent alone \u2014 that second term is what makes inverse design possible: a point chosen from properties must decode into a structure.

    The training objective is

    \\[ L = w_1 L_\\text{recon}(z_\\text{joint}) + w_2 L_\\text{prop}(z_\\text{joint}) + w_3 L_\\text{recon}(z_p) + w_4 L_\\text{prop}(z_p) + \\lambda(t)\\, L_\\text{InfoNCE}(z_c, z_p). \\]"},{"location":"understand/how-it-works.html#inverse-design-search-in-that-space","title":"Inverse design = search in that space","text":"

    For each target:

    1. Start a population of latents at the property encoder's output for the target, plus a target-dependent random direction (so different targets start apart) and noise.
    2. Optimise them by gradient descent on: distance of the decoded properties to the target (each objective with its loss and weight); a cosine anchor to the target latent; the family's search terms (atoms apart, cubic cell, positions near the prototype, expected charge zero, same element on equivalent sites, loose tolerance factor); and repulsion between population members and from earlier successes (diversity).
    3. Decode: for each latent, choose one element per site group from the decoder's scores with temperature and top-k sampling; the last group is drawn only from elements whose charge balances the others. Place the elements on the prototype; the cell edge comes from ionic radii (a = 2(r_B + r_X) for perovskites).
    4. Rules: symmetry-refine, then apply every hard constraint. Only compositions that pass all are kept.
    5. Rank by distance to the target (select_weight, select_loss), skip duplicates and near-duplicate latents, save as CIF.
    "},{"location":"understand/how-it-works.html#what-family-means","title":"What \"family\" means","text":"

    A family is data, not code: a prototype (site positions), site groups (allowed elements and oxidation states), a cell-size rule, soft search terms, and hard rules. The published perovskite tables are families/perovskite_abx3.yaml; the same machinery runs double_perovskite_a2bbx6.yaml and any file you write.

    "},{"location":"understand/how-it-works.html#the-design-space","title":"The design space","text":"

    For a prototype family, the set of compositions the decoder can output is finite. meidnet space and the Studio enumerate it and score each composition with the structure encoder. The search's candidates are a subset of this space; comparing the two views (encoder prediction vs search's decoded prediction) is a useful sanity check that the reports encourage.

    "},{"location":"understand/how-it-works.html#reproducibility","title":"Reproducibility","text":"

    MEIDNet 2 reproduces MEIDNet 1 exactly: the same featurisation, forward pass, training step and generation output (tests/test_v1_regression.py compares against the frozen v1 code in tests/legacy_v1/). One fix: v1's results depended on Python's hash seed through set iteration order; v2 fixes element order, so the same seed gives the same CIFs in any process.

    "},{"location":"understand/interpretability.html","title":"Why you can trust (or not) a candidate","text":"

    MEIDNet shows its reasoning rather than a score. Where to look:

    "},{"location":"understand/interpretability.html#before-trusting-the-model","title":"Before trusting the model","text":"
    • Training report \u2192 accuracy table. A property rated weak means its predictions are not much better than guessing the mean; targets on it are unreliable.
    • Training report \u2192 alignment. Low retrieval accuracy means property latents do not land near the right structures; inverse design from them is guesswork.
    • Check report \u2192 distributions. A target in a region with almost no training data is extrapolation.
    "},{"location":"understand/interpretability.html#for-each-candidate","title":"For each candidate","text":"
    • The checklist \u2014 every rule's measured value and allowed window. These are deterministic chemistry (charges, radii, distances), not model outputs.
    • The gauges \u2014 where the prediction sits relative to the target and to the training range.
    • Warnings \u2014 predicted value outside the training range (extrapolation) and latent hit the search limit (the search pushed a latent to the z_clip bound; predictions there are less reliable). The model is trained on latents of length 1 and every search moves them further out (typically to length 3\u20135), so the report says this once for the whole run rather than on every card; each candidate's length is the latent_norm column of candidates.csv.
    • Encoder vs search \u2014 in the Studio, compare the search's decoded prediction with the structure encoder's prediction for the same composition. Large disagreement is a red flag.
    "},{"location":"understand/interpretability.html#for-the-run-as-a-whole","title":"For the run as a whole","text":"
    • The funnel \u2014 which rule removed most attempts. If it is a chemical rule (charge balance, tolerance factor), the family's element lists may be too broad for the target; if it is no charge-balancing element, the oxidation-state tables may be missing an entry.
    • Rejected examples \u2014 what the search wanted to make; often more informative than what it kept.
    • Latent map \u2014 whether the population spread out (diversity) or collapsed.
    "},{"location":"understand/interpretability.html#what-meidnet-never-claims","title":"What MEIDNet never claims","text":"

    A candidate is a composition on an ideal prototype with model-predicted properties. Whether it is stable, synthesisable, or has those properties is for meidnet screen, DFT and the lab to decide.

    "},{"location":"understand/limits.html","title":"Scope and roadmap","text":""},{"location":"understand/limits.html#what-works-today","title":"What works today","text":"Structures any CIF with up to max_sites atoms (default 20); any elements Properties any number of scalar columns Training your data, your properties; the published settings by default Generation prototype families: a fixed arrangement of sites whose occupants and cell size are chosen. Built in: cubic ABX\u2083, A\u2082BB\u2032X\u2086; write your own in YAML Rules charge balance, tolerance and octahedral factors, bond windows, minimum distance, predicted-property windows, element filters, and your own Python rules Explanations reports for data, training and generation; the Studio for live what-if"},{"location":"understand/limits.html#what-does-not-work-yet","title":"What does not work (yet)","text":"
    • New atomic arrangements. The decoder's positions are not used to create geometry; candidates are compositions placed on the family prototype. Generating free geometry needs a decoder trained to produce it (the published decoder receives the true positions during training).
    • Spectra, images, text as modalities. The property modality is a vector of scalars. Binned XRD or DOS could be added as a vector modality with its own encoder; it is designed for but not implemented.
    • Large cells. max_sites can be raised, but memory and time grow with the square of it, and the published model was trained on 5-atom cells.
    • Fine-tuning the published model on new properties. Its property head has two outputs; train a new model.
    "},{"location":"understand/limits.html#roadmap-in-order-of-likely-value","title":"Roadmap (in order of likely value)","text":"
    1. Vector modalities (XRD, DOS) with a shared-latent encoder each.
    2. A training.init_from option to continue training from a checkpoint with the same properties.
    3. A geometry-generating decoder variant (trained from prototype positions instead of true positions \u2014 model.decoder_coordinate_input: prototype is the first step and is available as an experimental setting).
    4. More built-in families (spinel, Heusler, rocksalt/zincblende, MXene M\u2082XT\u2082).

    Contributions of family files and rules are the easiest way to help; see the GitHub repository.

    "},{"location":"use/config.html","title":"The configuration file","text":"

    meidnet.yaml holds everything a run depends on. One file = one reproducible experiment: it is copied into every output folder and stored inside model.pt.

    name: my_oxides                 # outputs \u2192 runs/my_oxides/\nplugins: []                     # Python files with custom rules (optional)\n\ndata:        # where the structures and properties come from        \u2192 meidnet check\nmodel:       # size of the network (defaults are the paper's)\ntraining:    # epochs, batch size, learning rate, alignment schedule \u2192 meidnet train\ngeneration:  # family, objectives, targets, rules overrides, budget  \u2192 meidnet generate\n

    Rules of thumb:

    • Unknown keys are errors. A typo such as epocs: stops the run with a message naming the key.
    • Paths are relative to the YAML file, so a project folder can be moved or shared.
    • Defaults reproduce the paper. Leave a section out and the published settings apply.
    • meidnet schema prints the JSON Schema (editors such as VS Code use it for completion and validation).

    The complete list of settings with their meaning and defaults is in the configuration reference.

    "},{"location":"use/config.html#the-four-sections-in-one-picture","title":"The four sections in one picture","text":"
    data:\n  table: materials.csv            # one row per material\n  properties: [{column: band_gap, unit: eV}, {column: dielectric}]\n  align_to_prototype: true        # atoms re-ordered to the family prototype\n\ntraining:\n  epochs: 200\n  contrastive_weight: 5.0         # how strongly structure and property latents are pulled together\n\ngeneration:\n  family: perovskite_abx3\n  variant: oxide\n  objectives:                     # what to steer, and how to compare prediction with target\n    - {property: band_gap, loss: l2, weight: 10000}\n  targets:                        # one search per entry\n    - {band_gap: 2.0}\n  overrides:                      # change a family rule's parameters without editing the family file\n    tolerance_factor: {min: 0.85, max: 1.0}\n  exclude_elements: [Pb]\n
    "},{"location":"use/explore-3d.html","title":"Explore in 3D","text":"

    The Studio can show any set of structures as a map linked to a 3D viewer: each point on the map is one crystal; click a point and its structure appears next to the map, where you can rotate and zoom it. The viewer is chemiscope, an open-source structure\u2013property explorer.

    Open it from any button marked \ud83e\uddca:

    Button Where Shows \ud83e\uddca Explore all compositions in 3D Family block the design space: every composition of the family on its prototype \ud83e\uddca Map + 3D of all compositions Targets block the same design space, to compare predictions with your targets \ud83e\uddca All candidates in 3D Candidates block the candidates of your last search \ud83e\uddca See these structures in 3D Data block your data after Check my data, or the Perov-5 training data behind the published model \ud83e\uddca show in the 3D explorer the details window of any composition the design space, with that composition selected

    You can also open the page directly in the explorer with a deep link: ?explore=space, ?explore=candidates or ?explore=data.

    "},{"location":"use/explore-3d.html#reading-the-map","title":"Reading the map","text":"
    • Axes. For the design space, the two predicted properties (for the published model: formation enthalpy across, band gap up). For your data, your property columns. For candidates, their predicted properties. Any other property can be put on an axis from the map's settings menu.
    • Colour (design space): yellow = passes every rule, purple = rejected by at least one rule. Symbol: pass / rejected. For candidates the colour is the search score (lower = closer to the targets).
    • Properties you can plot or colour by:
    Name Meaning formula the composition site_A, site_B, \u2026 the element on each site group lattice_a the cubic cell edge estimated from ionic radii (\u00c5) pred_<property> the model's prediction from the structure encoder, with its unit rule_<name> the value each rule measures, e.g. rule_tolerance_factor passes_all, verdict 1 / \"pass\" when every rule passes (with the limits currently set on the page) score, round, target_<property>, still_passes candidates only: search score, round found, the target, and whether it still passes the current rules

    The design space shows up to 2,000 compositions: those passing every rule first, then a fixed random sample of the rest. Your data shows the structures that passed Check my data; the Perov-5 training data shows its first 1,500 structures.

    "},{"location":"use/explore-3d.html#clicking-a-point","title":"Clicking a point","text":"

    The crystal appears on the right: drag to rotate, scroll to zoom. The panel below lists every property of that structure. Bonds and the unit cell are drawn. Design-space structures are the ideal prototype filled with that composition's elements, at the cell size the family's lattice rule estimates.

    "},{"location":"use/explore-3d.html#when-chemiscope-cannot-load","title":"When chemiscope cannot load","text":"

    The explorer is downloaded from a public CDN the first time you open it (about 3 MB). Without internet access, or if the CDN is blocked, the Studio switches to its built-in viewer: a scatter plot of the same two axes (coloured points pass every rule) linked to the Studio's own crystal viewer \u2014 click a point, drag the cell to rotate, double-click to spin. Nothing else changes.

    "},{"location":"use/explore-3d.html#save-the-dataset","title":"Save the dataset","text":"

    \u2b07 dataset JSON saves what you see as a chemiscope dataset. Open it later at chemiscope.org (the site reads the file inside your browser; nothing is uploaded), share it with a colleague, or put it in a paper's supplementary material.

    The same datasets can be produced without the Studio:

    meidnet space meidnet.yaml --chemiscope space.json   # the design space of your configuration\n

    and from Python:

    from meidnet.studio.chemiscope import space_dataset, candidates_dataset, records_dataset\n

    In the Colab notebooks, the show3d(...) helper displays structures inline with chemiscope.show.

    "},{"location":"use/explore-3d.html#credits","title":"Credits","text":"

    chemiscope is a third-party library (BSD-3-Clause licence) by G. Fraux, R. K. Cersonsky and M. Ceriotti. If you use the 3D explorer in published work, please cite: G. Fraux, R. K. Cersonsky, M. Ceriotti, Chemiscope: interactive structure-property explorer for materials and molecules, Journal of Open Source Software 5, 2117 (2020).

    "},{"location":"use/reports.html","title":"Reading the reports","text":"

    Each command writes a self-contained HTML file in runs/<name>/. All three open with a one-sentence verdict (green / amber / red), show the evidence, and end with what to do next.

    "},{"location":"use/reports.html#check_reporthtml","title":"check_report.html","text":"
    • Verdict: how many rows are usable; the most common problem if many were skipped.
    • What MEIDNet read: table, columns, structure source, family and how many structures were re-ordered to the prototype.
    • Rows that were skipped: one row per reason with counts and example ids, and a \"how do I fix these?\" box.
    • Your properties: histogram, min/median/max, and warnings (e.g. \"72 % of band-gap values are 0 \u2014 the model sees few examples of other values\").
    • Your structures: atoms per cell, most common elements.
    "},{"location":"use/reports.html#training_reporthtml","title":"training_report.html","text":"
    • Verdict: are the properties predicted well from structure alone?
    • How accurate: per property, typical error (MAE), natural spread (std), R\u00b2, and a word \u2014 good if the error is under a quarter of the spread, fair under a half, weak otherwise \u2014 with predicted-vs-true plots.
    • Did the two modalities align: retrieval accuracy (a structure's own property vector is its nearest match x % of the time) and the cosine curve. Inverse design depends on this.
    • Training curves and notes (e.g. the alignment ramp longer than training).
    "},{"location":"use/reports.html#generation_reporthtml","title":"generation_report.html","text":"
    • Verdict: how many candidates, and whether any predicted value lies outside the training range.
    • Per target: the funnel (compositions alive after each rule \u2014 the biggest drop is the rule that limits the family most), the candidate cards, and examples of rejected compositions with the rule and the measured value.
    • A candidate card shows: elements per site, cell edge, round found, a gauge per objective (training range as a track, target as a tick, prediction as a dot), the checklist with each rule's value and allowed window (hover for the explanation), and warnings: prediction outside the training range, or a latent far from where training latents live (predictions less reliable).
    • Where the search went: latents before and after optimisation projected to two dimensions.
    "},{"location":"use/reports.html#candidatescsv-and-generationjson","title":"candidates.csv and generation.json","text":"

    The CSV has one row per candidate with targets, predictions, elements, cell edge, every rule's value, score and file name. The JSON holds the full funnel counts, rejected examples and settings \u2014 everything the report shows.

    "},{"location":"use/studio.html","title":"MEIDNet Studio \u2014 the live workbench","text":"
    meidnet studio                 # the published perovskite model\nmeidnet studio meidnet.yaml    # your project\n

    A page opens in your browser. The workflow is drawn as a strip of seven coloured blocks; click a block to open it. Whatever you change, the blocks after it update at once, and a line under the strip says, in words, what the change did to each later block \u2014 the way a LabVIEW diagram shows data flowing after you turn a knob.

    No installation? Use the hosted Studio \u2014 the same page, running on a shared server.

     Data \u2500\u2500\u25ba Model \u2500\u2500\u25ba Family \u2500\u2500\u25ba Rules \u2500\u2500\u25ba Targets \u2500\u2500\u25ba Search \u2500\u2500\u25ba Candidates\n 11,356    MAE       halide     5 rules   Eg 2.0     idle       0 found\n materials 0.17 eV   924 comps  27 pass   \u0394Hf \u22120.1\n
    "},{"location":"use/studio.html#reading-the-workflow-strip","title":"Reading the workflow strip","text":"

    Each block has its own colour, used everywhere on the page (its panel, its charts, its chips in the flow chain):

    Block Colour What it holds Data blue the table of materials the model learns from Model orange the trained network (published or yours) Family green the crystal prototype and the elements allowed on each site Rules amber the hard checks every candidate must pass Targets pink the property values you want Search dark green the latent search of the paper Candidates violet what the search found
    • The dot in the corner of a block tells its state: grey = idle, green = fine, amber = just changed or needs a look (for example a target outside the training range), red = error, pulsing = running.
    • The two lines inside a block are its live status: \"924 comps\", \"27 pass all\", \"MAE 0.17\", \"running\".
    • The label on each arrow says what flows to the next block: \"11,356 materials \u00b7 2 properties\", \"924 compositions\", \"27 pass all rules\", \"3 found\".
    • Moving dots on the arrows appear after every change: they travel from the block you edited to every block it reaches, and those blocks flash. This is the quickest way to see what depends on what.

    Dashed pink lines are always a target; a violet \u2605 is always something the search found; green \u2713 means passes.

    "},{"location":"use/studio.html#the-flow-chain-and-the-flow-log","title":"The flow chain and the flow log","text":"

    Under the strip, the flow chain repeats the last change in words, block by block, for example:

    Rules: Goldschmidt tolerance factor max 1.05 \u2192 1 \u279c Rules: pass all rules 27 \u2192 23 (\u22124) \u279c Targets: best match CsSnBr\u2083 \u2192 CsGeBr\u2083 \u279c Candidates: 1 of 3 found candidates would now be rejected

    Click a step to open that block.

    The Flow log below it keeps the last 60 changes. Click an entry to replay its flow on the strip.

    "},{"location":"use/studio.html#what-is-live-and-how","title":"What is live, and how","text":"

    For a prototype family the set of possible compositions is finite (924 for halide perovskites, 10,340 for halide double perovskites). When you open a family the server builds this design space once: every composition on the prototype, the value of each rule's descriptor (tolerance factor, octahedral factor, \u2026) and the model's predicted properties from the structure encoder. From then on, the page can answer instantly:

    You change You see immediately a rule's limit how many compositions still pass that rule and all rules; the histogram of the descriptor with the window shaded; the funnel an element (click a chip to exclude it) compositions removed, new counts, new best matches a target the compositions predicted closest; the target drawn on the predicted-property histogram and scatter; an extrapolation warning if it lies outside the training range the comparison kind (as close as possible / at most / at least) the ranking anything, after a search which of the found candidates would now be rejected

    Descriptor values are computed by the same Python functions that meidnet generate uses, so what the page shows is what the search enforces.

    "},{"location":"use/studio.html#the-two-directions-of-the-model","title":"The two directions of the model","text":"

    The live tables use structure \u2192 property (encode the prototype structure, decode its properties): fast, and it scores every composition. The Search block runs the paper's property \u2192 structure latent optimisation and shows its progress (target, round, step, loss), the log, and each candidate as it is found, with its checklist. Both views are of the same network; comparing them tells you whether the search found what the encoder considers best, and whether the predictions agree.

    "},{"location":"use/studio.html#the-blocks","title":"The blocks","text":"
    • Data \u2014 how many materials were usable, why rows were skipped, property distributions, the elements present; Bring your own data (below).
    • Model \u2014 properties, units, training ranges, validation errors, loss and alignment curves; Train your own model (below).
    • Family \u2014 prototype sites with a rotatable 3D preview of the cell, element chips per site group with their charges (click to exclude), cell-size rule.
    • Rules \u2014 each rule with its explanation, parameters, pass count and descriptor histogram; add a predicted-property window rule; the funnel.
    • Targets \u2014 one card per objective: target value, comparison kind, weights; the predicted-property scatter of all compositions (coloured = passes, grey = rejected, \u2605 = found by the search). Click any point or table row for its checklist and its crystal in 3D.
    • Search \u2014 budget (candidates, latents per round, rounds, steps), a rough time estimate, Run/Stop, progress, log, link to the full report.
    • Candidates \u2014 one card per candidate: a rotatable 3D cell, elements, gauges of predicted values against the targets, the rule checklist, warnings, CIF links.

    Crystals are drawn by a small built-in viewer: drag to rotate, double-click to spin.

    "},{"location":"use/studio.html#beginner-mode","title":"Beginner mode","text":"

    The Beginner mode switch in the header is on by default. It adds, in every block:

    • an Input \u279c Logic \u279c Output strip with live numbers (\"924 compositions \u279c 5 hard rules, applied in order \u279c 27 pass every rule\") and the name of the Python function that does the work;
    • a short In plain words explanation;
    • a glossary in the side panel (composition, rule, target, latent space, extrapolation).

    Switch it off for a compact page. The choice is remembered by your browser.

    "},{"location":"use/studio.html#behind-the-scenes","title":"Behind the scenes","text":"

    At the bottom of every block, Behind the scenes shows the same settings as the part of meidnet.yaml they come from and as the Python that does the same thing \u2014 with the values currently on the page. Copy either into your own project; nothing on the page is hidden from the command line.

    "},{"location":"use/studio.html#bring-your-own-data","title":"Bring your own data","text":"

    The Data block has an adapter: it maps your column names to what MEIDNet expects. Nothing in your files is changed.

    1. Upload a table \u2014 CSV, Excel (.xlsx) or JSON, one row per material \u2014 with an id column, numeric property columns and the structures, either as a column of CIF text or as a zip of <id>.cif files (select both files together). Parquet works too when pyarrow is installed.
    2. Map the columns. The Studio suggests the id column, the CIF column and the numeric property columns; pick the properties you want, give each a unit (eV, eV/atom, \u2026) and optionally a label, and choose the family (and variant) your structures belong to. Align to prototype re-orders each structure's atoms onto the family's sites, which generation needs; raise the prototype tolerance if many rows are skipped as \"does not match the prototype\".
    3. Check my data. Every row is parsed and checked; you see how many are usable, why the others were skipped (with examples), the distribution of each property and the elements present.
    4. Train in the Model block: choose the number of epochs and press \u25b6 Train. The loss and alignment curves grow while it trains; at the end the validation errors appear and every block switches to your model and your properties \u2014 the design space is re-scored, the targets are your properties. Each property's typical error (MAE) is judged in words against the spread (standard deviation) of that property in your data \u2014 good below a quarter of the spread, fair below half, weak above \u2014 the same rule the training report uses; \"matched correctly\" is how often a validation structure is matched to its own properties.
    5. Switch between your model and the published model at any time in the Model block. Start over removes your upload and model from the session.

    A few epochs give a rough model in a minute or two; 100\u2013200 epochs are typical for real work \u2014 run meidnet train locally (with a GPU if you have one) and open the result with meidnet studio meidnet.yaml.

    On the hosted Studio

    Each browser tab has its own private session: other visitors never see your data, model or candidates, and a session's files are deleted after an hour without activity. The shared machine allows up to 1,500 rows and 8 MB per upload, 30 training epochs, 20 atoms per cell, and two jobs (training or search) at a time across all visitors; search budgets are capped too. Run the Studio on your own computer to lift every limit.

    "},{"location":"use/studio.html#edit-the-configuration-as-text","title":"Edit the configuration as text","text":"

    \u270e Edit as text in the header opens a drawer with the generation section of meidnet.yaml exactly as the blocks have it: family, variant, excluded elements, rule overrides, extra rules, objectives, targets and search budget. Change anything and press Apply \u2014 the blocks update and the flow chain shows what changed in each.

    • Validate only checks without applying. Errors name the exact setting, for example steps: Input should be greater than or equal to 1.
    • On the hosted Studio, values above the shared limits are accepted but capped, and a note says so: rounds 99 \u2192 6 (limit on this shared server).
    • Predicted-property windows added in the Rules block appear as extra_constraints, so the file round-trips: apply it, download it, run it with meidnet generate.

    \u2b07 Download meidnet.yaml (also in the Search block) writes the whole configuration \u2014 rules switched off on the page are listed under disabled_rules, excluded elements become exclude_elements, changed limits become overrides \u2014 so the command line reproduces exactly what the page shows. Settings the blocks do not show (for example search terms set in your own meidnet.yaml) are kept as they are.

    "},{"location":"use/studio.html#explore-in-3d","title":"Explore in 3D","text":"

    Buttons marked \ud83e\uddca open the 3D explorer: a map with one point per structure, linked to a 3D viewer \u2014 the design space of the family, the candidates of a search, or the training data. See Explore in 3D.

    "},{"location":"use/studio.html#deep-links","title":"Deep links","text":"

    Add to the address to open the page at a given place:

    Address ends with Opens ?panel=rules the Rules block (any block: data, model, family, rules, targets, search, candidates) ?explore=space the 3D explorer on the design space (candidates or data work too)

    Both can be combined: ?panel=targets&explore=space.

    "},{"location":"use/studio.html#dark-mode","title":"Dark mode","text":"

    The \ud83c\udf19 / \u2600\ufe0f button in the header switches between the light and the dark theme; your browser remembers the choice.

    "},{"location":"use/studio.html#static-copy-for-a-website","title":"Static copy for a website","text":"
    meidnet studio --export-static docs/studio.html\n

    embeds the state and design spaces into one HTML file. Everything works without a server except uploading, training and running a new search \u2014 useful for a plain web host. The hosted Studio is the full server instead:

    meidnet studio --public --host 0.0.0.0 --port 7860 --run-root /tmp/runs --docs-dir site\n

    --public gives every visitor a private session and caps the budgets so the shared machine stays responsive; --docs-dir also serves a built copy of this documentation at /docs/.

    "},{"location":"use/your-data.html","title":"Bring your own dataset","text":"

    This page takes a table of structures and properties to designed candidates. Every step writes a report that explains what happened; nothing requires Python.

    "},{"location":"use/your-data.html#0-install","title":"0. Install","text":"
    pip install meidnet\n
    "},{"location":"use/your-data.html#1-describe-your-data-meidnet-init","title":"1. Describe your data: meidnet init","text":"
    meidnet init --name my_oxides --table materials.csv \\\n             --id-column material_id --cif-column cif \\\n             --properties band_gap dielectric \\\n             --family perovskite_abx3 --variant oxide\n

    This writes a commented meidnet.yaml. Open it: every line says what it does. If your CIFs are files instead of a column, set cif_column: null and structures_dir: structures/.

    data:\n  table: materials.csv\n  id_column: material_id\n  cif_column: cif\n  properties:\n    - {column: band_gap,   unit: eV}\n    - {column: dielectric, unit: \"\"}\n  val_fraction: 0.1\n  max_sites: 20\n  align_to_prototype: true\n
    "},{"location":"use/your-data.html#2-is-it-usable-meidnet-check","title":"2. Is it usable? meidnet check","text":"
    meidnet check meidnet.yaml\n
    412 of 430 materials usable.\n  skipped     14  does not match the perovskite_abx3 prototype   e.g. m_031, m_118, m_204\n  skipped      4  missing property value                          e.g. m_077, m_300\nreport: runs/my_oxides/check_report.html\n

    The report shows the verdict, the distribution of each property, the elements present and, for each skip reason, how to fix it (for example raise data.prototype_tolerance for distorted cells).

    "},{"location":"use/your-data.html#3-learn-the-latent-space-meidnet-train","title":"3. Learn the latent space: meidnet train","text":"
    meidnet train meidnet.yaml            # 200 epochs by default\nmeidnet train meidnet.yaml --epochs 5 # a quick smoke test first\n

    training_report.html answers three questions in plain words: how accurate is each property prediction (error vs natural spread \u2192 good / fair / weak), did the two modalities align (retrieval accuracy), and what were the training curves. The model is saved as runs/my_oxides/model.pt with the property names, units and normalisation inside; meidnet info runs/my_oxides/model.pt describes it.

    Time

    About 21 s per epoch per 11k structures on a laptop GPU; roughly 10\u00d7 slower on CPU. Use Colab's free GPU (notebook 02) if you have none.

    "},{"location":"use/your-data.html#4-design-meidnet-generate","title":"4. Design: meidnet generate","text":"

    Set the targets in meidnet.yaml:

    generation:\n  family: perovskite_abx3\n  variant: oxide\n  objectives:\n    - {property: band_gap,   loss: l2,       weight: 10000}\n    - {property: dielectric, loss: at_least, weight: 5000}\n  targets:\n    - {band_gap: 2.0, dielectric: 20}\n    - {band_gap: 3.0, dielectric: 20}\n  per_target: 4\n  exclude_elements: [Pb, Cd]\n
    meidnet generate meidnet.yaml\n

    generation_report.html shows, per target, the funnel (how many attempts each rule removed), one card per candidate (elements, cell, each rule's measured value and window, predicted vs target gauges, warnings about extrapolation) and examples of rejected compositions. CIFs and candidates.csv are in runs/my_oxides/generation/.

    "},{"location":"use/your-data.html#5-explore-live-meidnet-studio","title":"5. Explore live: meidnet studio","text":"
    meidnet studio meidnet.yaml\n

    Change a rule's limit, exclude an element or move a target and see immediately how many compositions pass and which are predicted closest; run searches and export the YAML. Studio guide.

    "},{"location":"use/your-data.html#6-confirm","title":"6. Confirm","text":"

    Predicted properties are estimates. meidnet screen runs/my_oxides/generation (MACE universal potential, optional extra) gives a first stability filter; DFT or experiment has the last word.

    "}]} \ No newline at end of file +{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"],"fields":{"title":{"boost":1000.0},"text":{"boost":1.0},"tags":{"boost":1000000.0}}},"docs":[{"location":"index.html","title":"MEIDNet Prism","text":"

    \u2190 MEIDNet Prism home \u00b7 the documentation of the MEIDNet framework and its platform.

    Design crystalline materials from target properties \u2014 with your own data, your own rules, and no code to edit.

    MEIDNet learns one latent space shared by crystal structures and their properties, then searches it for new materials that hit property targets while obeying the chemical and structural rules of a material family. The published cubic-perovskite model (band gap + formation enthalpy) is one application of it; the framework accepts any table of structures and scalar properties and any prototype family you describe.

    Your browser cannot play this video: download it. MEIDNet in one minute: data, model, family, rules, targets, search and candidates. The same tour opens on a first visit to the live Studio (the \u201c\u25b6 How it works\u201d button replays it).
    • Try MEIDNet

      See what it does in 30 seconds: the live Studio needs nothing installed; meidnet demo runs on any laptop.

      5-minute quickstart

    • Use MEIDNet on my data

      A table with an id, properties and CIFs is all you need. Four commands take you from \"is my data usable?\" to candidates with explanations.

      Bring your own dataset

    • Change what it does

      Targets, elements, rules, family \u2014 all in one YAML file, or by moving sliders in MEIDNet Studio and exporting the file.

      Recipes

    • Understand it

      How the two modalities are aligned, how the search works, what the reports mean, and where the limits are.

      How MEIDNet works

    "},{"location":"index.html#the-workflow","title":"The workflow","text":"
       Data \u2500\u2500\u25ba Model \u2500\u2500\u25ba Family \u2500\u2500\u25ba Rules \u2500\u2500\u25ba Targets \u2500\u2500\u25ba Search \u2500\u2500\u25ba Candidates\n   table    shared    prototype  hard      property   latent     CIFs + report\n   + CIFs   latent    + sites    checks    goals      optimisation\n

    Each block is a setting in meidnet.yaml, a node in MEIDNet Studio \u2014 where changing one block immediately shows its effect on the ones after it \u2014 and a section in the HTML report each step writes.

    "},{"location":"index.html#honest-scope","title":"Honest scope","text":"
    • Generation works for prototype families: a fixed arrangement of sites whose occupants and cell size are chosen (ABX\u2083 perovskites, A\u2082BB\u2032X\u2086 double perovskites, and anything you describe the same way). MEIDNet does not invent new atomic arrangements. Details and roadmap.
    • Predicted properties are the model's estimates. The reports say when a prediction is an extrapolation. Confirm candidates with DFT or experiment; meidnet screen is a first filter.
    "},{"location":"index.html#paper","title":"Paper","text":"

    A. Babu, R. Almeida Gouv\u00eaa, P. Vandergheynst, G.-M. Rignanese, MEIDNet: Multimodal generative AI framework for inverse materials design, npj Computational Materials (2026). Citation and benchmarks \u00b7 code as published (v1.0).

    "},{"location":"benchmarks/index.html","title":"MEIDNet Benchmarks","text":"

    Reproducible results for multimodal materials representation and inverse design, one page per dataset. Different datasets are different tasks, so there is no leaderboard across them: compare rows only within a page.

    "},{"location":"benchmarks/index.html#the-evidence-ladder","title":"The evidence ladder","text":"level means \u25cb\u25cb\u25cb\u25cb\u25cb generated the model produced it \u25cf\u25cb\u25cb\u25cb\u25cb ML filtered it passed the family's rules and the model's own checks \u25cf\u25cf\u25cb\u25cb\u25cb MLIP validated relaxed and found stable with a machine-learned potential (meidnet screen, MACE) \u25cf\u25cf\u25cf\u25cb\u25cb DFT validated confirmed by density-functional calculations \u25cf\u25cf\u25cf\u25cf\u25cb experimentally validated synthesised and measured

    Each row also carries a status: Published (quoted from the paper), Community submitted (a pull request), MEIDNet verified (reproduced here from the submitted configuration). How to contribute.

    "},{"location":"benchmarks/index.html#datasets","title":"Datasets","text":"
    • Carbon-24 \u2014 published: structure-representation generalization (not a multimodal benchmark) \u00b7 0 result(s), 0 verified
    • MP-20 \u2014 published: structure-representation generalization (not a multimodal benchmark) \u00b7 0 result(s), 0 verified
    • Perov-5 \u2014 the published multimodal benchmark: alignment, property reconstruction and inverse design \u00b7 3 result(s), 0 verified
    "},{"location":"benchmarks/carbon24.html","title":"Carbon-24","text":"

    About 10,000 carbon structures with up to 24 atoms per cell (CDVAE split). In the MEIDNet paper it is the second structure-representation generalization test; no property modality and no checkpoint here.

    10,153 rows \u00b7 split: CDVAE: 6,091 train / 2,031 val / 2,031 test \u00b7 role in the paper: published: structure-representation generalization (not a multimodal benchmark) \u00b7 dataset source

    Read a row

    Rows are compared only within this dataset and category. The ladder \u25cf\u25cb\u25cb\u25cb\u25cb \u2026 \u25cf\u25cf\u25cf\u25cf\u25cf is the evidence level (generated \u2192 ML filtered \u2192 MLIP validated \u2192 DFT validated \u2192 experimentally validated). Published = taken from the paper; MEIDNet verified = reproduced here from the submitted configuration.

    No results yet. Contribute one.

    "},{"location":"benchmarks/mp20.html","title":"MP-20","text":"

    About 45,000 Materials Project structures with at most 20 atoms per cell (CDVAE split). In the MEIDNet paper it tests how the crystal encoder and decoder generalise beyond perovskites; no property modality was aligned on it and no MP-20 checkpoint is shipped in this repository.

    45,231 rows \u00b7 split: CDVAE: 27,136 train / 9,047 val / 9,046 test \u00b7 role in the paper: published: structure-representation generalization (not a multimodal benchmark) \u00b7 dataset source

    Read a row

    Rows are compared only within this dataset and category. The ladder \u25cf\u25cb\u25cb\u25cb\u25cb \u2026 \u25cf\u25cf\u25cf\u25cf\u25cf is the evidence level (generated \u2192 ML filtered \u2192 MLIP validated \u2192 DFT validated \u2192 experimentally validated). Published = taken from the paper; MEIDNet verified = reproduced here from the submitted configuration.

    No results yet. Contribute one.

    "},{"location":"benchmarks/perov5.html","title":"Perov-5","text":"

    18,928 cubic ABX3 perovskites with relaxed structures, formation enthalpy (heat_all, eV/atom) and direct band gap (dir_gap, eV). The CDVAE split of the Castelli et al. (2012) perovskite set; the dataset behind the published MEIDNet model and the live Studio.

    18,928 rows \u00b7 split: CDVAE: 11,356 train / 3,785 val / 3,787 test \u00b7 properties: heat_all, dir_gap \u00b7 role in the paper: the published multimodal benchmark: alignment, property reconstruction and inverse design \u00b7 dataset source

    Read a row

    Rows are compared only within this dataset and category. The ladder \u25cf\u25cb\u25cb\u25cb\u25cb \u2026 \u25cf\u25cf\u25cf\u25cf\u25cf is the evidence level (generated \u2192 ML filtered \u2192 MLIP validated \u2192 DFT validated \u2192 experimentally validated). Published = taken from the paper; MEIDNet verified = reproduced here from the submitted configuration.

    "},{"location":"benchmarks/perov5.html#representation-quality","title":"Representation quality","text":"result model modalities cosine_matched l2_matched evidence status MEIDNet (paper): alignment of structure and property latents on Perov-5 MEIDNet early fusion + curriculum (production model) structure, property:heat_all, property:dir_gap 0.97 0.24 \u25cf\u25cb\u25cb\u25cb\u25cb generated Published (from the paper)"},{"location":"benchmarks/perov5.html#conditional-design","title":"Conditional design","text":"result model modalities n_generated n_sun sun_rate evidence status MEIDNet (paper): inverse design of perovskites from property targets MEIDNet early fusion + curriculum (production model) structure, property:heat_all, property:dir_gap 140 structures 19 structures 0.136 \u25cf\u25cf\u25cf\u25cf\u25cb DFT validated Published (from the paper)"},{"location":"benchmarks/perov5.html#validation-level","title":"Validation level","text":"result model modalities n_screened evidence status MLIP screening of the shipped paper candidates (MACE-MP-0) MEIDNet early fusion + curriculum (production model) structure, property:heat_all, property:dir_gap 26 structures \u25cf\u25cf\u25cf\u25cb\u25cb MLIP validated Community submitted"},{"location":"benchmarks/results/paper-inverse-design.html","title":"MEIDNet (paper): inverse design of perovskites from property targets","text":"

    Published (from the paper) \u00b7 \u25cf\u25cf\u25cf\u25cf\u25cb DFT validated

    Dataset Perov-5 Modalities structure, property:heat_all, property:dir_gap Model MEIDNet early fusion + curriculum (production model) (MEIDNet 1.0 (paper), perovskite_abx3) Category Conditional design Submitted by Anand Babu (UCLouvain) on 2026-05-29 Configuration examples/perov5/meidnet.yaml Checkpoint dual_autoencoder_clip_earlyfusion_propertyaware_2k.pth"},{"location":"benchmarks/results/paper-inverse-design.html#results","title":"Results","text":"metric value split note n_generated 140 structures candidates generated from property targets in the campaign reported in the paper n_sun 19 structures stable, unique and novel after screening sun_rate 0.136 19 of 140"},{"location":"benchmarks/results/paper-inverse-design.html#validation","title":"Validation","text":"

    Evidence level: \u25cf\u25cf\u25cf\u25cf\u25cb DFT validated.

    Evidence:

    • https://doi.org/10.1038/s41524-026-02153-3
    • examples/perov5/paper_results/

    These numbers are quoted from the paper and have not been re-run from this repository.

    "},{"location":"benchmarks/results/paper-inverse-design.html#notes","title":"Notes","text":"

    Numbers quoted from the paper. The 26 CIFs in examples/perov5/paper_results/ are the candidates shipped with the paper; the MLIP screening row re-runs meidnet screen on them.

    "},{"location":"benchmarks/results/paper-representation.html","title":"MEIDNet (paper): alignment of structure and property latents on Perov-5","text":"

    Published (from the paper) \u00b7 \u25cf\u25cb\u25cb\u25cb\u25cb generated

    Dataset Perov-5 Modalities structure, property:heat_all, property:dir_gap Model MEIDNet early fusion + curriculum (production model) (MEIDNet 1.0 (paper), perovskite_abx3) Category Representation quality Submitted by Anand Babu (UCLouvain) on 2026-05-29 Configuration examples/perov5/meidnet.yaml Checkpoint dual_autoencoder_clip_earlyfusion_propertyaware_2k.pth"},{"location":"benchmarks/results/paper-representation.html#results","title":"Results","text":"metric value split note cosine_matched 0.97 validation mean cosine similarity between the structure and property latents of the same material, as reported in the paper l2_matched 0.24 validation mean L2 distance between the two latents of the same material, as reported in the paper"},{"location":"benchmarks/results/paper-representation.html#validation","title":"Validation","text":"

    Evidence level: \u25cf\u25cb\u25cb\u25cb\u25cb generated.

    Evidence:

    • https://doi.org/10.1038/s41524-026-02153-3
    "},{"location":"benchmarks/results/paper-representation.html#reproduce","title":"Reproduce","text":"

    Configuration examples/perov5/meidnet.yaml, split val:

    python scripts/benchmarks.py reproduce paper-representation\n

    These numbers are quoted from the paper and have not been re-run from this repository.

    "},{"location":"benchmarks/results/paper-representation.html#notes","title":"Notes","text":"

    Numbers quoted from the paper (npj Computational Materials, 2026). reproduce re-evaluates the shipped checkpoint on the CDVAE validation split and adds MAE, R2 and retrieval metrics; cosine_matched is compared with the quoted value.

    "},{"location":"benchmarks/results/paper-results-mace-screen.html","title":"MLIP screening of the shipped paper candidates (MACE-MP-0)","text":"

    Community submitted \u00b7 \u25cf\u25cf\u25cf\u25cb\u25cb MLIP validated

    Dataset Perov-5 Modalities structure, property:heat_all, property:dir_gap Model MEIDNet early fusion + curriculum (production model) (MEIDNet 2.0.0, perovskite_abx3) Category Validation level Submitted by Anand Babu (UCLouvain) on 2026-10-03 Configuration examples/perov5/meidnet.yaml Checkpoint dual_autoencoder_clip_earlyfusion_propertyaware_2k.pth Resources laptop CPU"},{"location":"benchmarks/results/paper-results-mace-screen.html#results","title":"Results","text":"metric value split note n_screened 26 structures the CIFs in examples/perov5/paper_results/"},{"location":"benchmarks/results/paper-results-mace-screen.html#validation","title":"Validation","text":"

    Evidence level: \u25cf\u25cf\u25cf\u25cb\u25cb MLIP validated.

    Evidence:

    • examples/perov5/paper_results/
    "},{"location":"benchmarks/results/paper-results-mace-screen.html#reproduce","title":"Reproduce","text":"

    Configuration examples/perov5/meidnet.yaml:

    meidnet screen examples/perov5/paper_results --train-csv data/perov5/train.csv\n
    "},{"location":"benchmarks/results/paper-results-mace-screen.html#notes","title":"Notes","text":"

    n_stable / n_unique / n_novel / n_sun are filled in by python scripts/benchmarks.py reproduce paper-results-mace-screen (MACE-MP-0 relaxation, about ten minutes on a CPU).

    "},{"location":"community/index.html","title":"Community results","text":"

    Every submitted result, newest first. Contribute yours.

    date result dataset category evidence status 2026-10-03 MLIP screening of the shipped paper candidates (MACE-MP-0) Perov-5 Validation level \u25cf\u25cf\u25cf\u25cb\u25cb MLIP validated Community submitted 2026-05-29 MEIDNet (paper): inverse design of perovskites from property targets Perov-5 Conditional design \u25cf\u25cf\u25cf\u25cf\u25cb DFT validated Published (from the paper) 2026-05-29 MEIDNet (paper): alignment of structure and property latents on Perov-5 Perov-5 Representation quality \u25cf\u25cb\u25cb\u25cb\u25cb generated Published (from the paper)"},{"location":"community/contribute.html","title":"Contribute a benchmark result","text":"

    MEIDNet Benchmarks collect results per dataset, each with the evidence behind it. Anyone who runs MEIDNet on a dataset \u2014 the published Perov-5, or their own \u2014 can submit a result. Submissions go through GitHub, so every number has a public record and a reviewer.

    "},{"location":"community/contribute.html#what-a-submission-is","title":"What a submission is","text":"

    One JSON file, benchmarks/submissions/<dataset>/<id>.json, that follows benchmarks/schema.json. The short version:

    field what to put dataset an id from benchmarks/datasets/ (perov5, \u2026); a new dataset needs its own datasets/<id>.json with source, size and split modalities what the model aligned: structure, property:<column> (one per property), later xrd, \u2026 model MEIDNet version, family and variant, the configuration (meidnet.yaml), checkpoint link and its SHA-256 category representation_quality, conditional_design, efficiency or validation \u2014 one category per file results metric \u2192 {value, unit}; only the metric names allowed for that category (listed in the schema) validation_level the evidence ladder: generated \u2192 ml_filtered \u2192 mlip_validated \u2192 dft_validated \u2192 experimentally_validated evidence public links: CIF archive, stability.csv, DFT inputs/outputs, paper DOI reproduce configuration, seed and the exact command that produces the numbers status always community_submitted for a new file"},{"location":"community/contribute.html#the-five-things-people-get-wrong","title":"The five things people get wrong","text":"
    1. Mixing categories in one file. A representation result (MAE, R\u00b2, retrieval) and a design result (SUN rate) are two files.
    2. A metric name that is not in the list. validate refuses it; the lists exist so that rows are comparable.
    3. No split. State the split you evaluated on (or link a file of ids). A number on an unknown split is not comparable.
    4. Evidence that is not public. A result without an inspectable artefact stays at generated.
    5. Claiming a validation level the evidence does not support. dft_validated needs the DFT outputs, not the intention.
    "},{"location":"community/contribute.html#how-it-works","title":"How it works","text":"
    1. Copy benchmarks/submissions/_template.json, fill it in, run python scripts/benchmarks.py validate.
    2. Open a pull request (the template has a checklist). CI runs validate.
    3. Once merged, the result is rendered on the dataset's benchmark page and gets a permanent page of its own, marked Community submitted.
    4. The maintainer may run python scripts/benchmarks.py reproduce <id>: it re-runs the stated command from the stated configuration and compares the numbers. If they agree within the tolerances, the row becomes MEIDNet verified (a record in benchmarks/verified/, which only the maintainer can add).

    Verified means reproduced from the submitted configuration with this code. It does not mean the material is real: that is what the validation level says.

    If you prefer not to open a pull request, the issue template Benchmark submission asks for the same fields and the maintainer converts it. Submissions are reviewed by the author of MEIDNet; there is no promised response time.

    "},{"location":"examples/double-perovskite.html","title":"Double perovskites A\u2082BB\u2032X\u2086","text":"

    meidnet/families/double_perovskite_a2bbx6.yaml is a second family written with the same ingredients as the perovskite one. It shows what a family file can express:

    • a 10-site prototype in the rhombohedral primitive cell of an fcc lattice (lattice: fcc_primitive: edge a/\u221a2, angles 60\u00b0), A at (\u00bc,\u00bc,\u00bc) and (\u00be,\u00be,\u00be), B at the origin, B\u2032 at (\u00bd,\u00bd,\u00bd), six X;
    • four site groups with their own element lists and oxidation states (B: +1/+2, B\u2032: +3/+4);
    • a cell-size rule a = 2 (r_B + r_B\u2032 + 2 r_X) (one B\u2013X\u2013B\u2032 chain per half cell edge);
    • rules that average radii over several groups: tolerance_factor: {A: A, B: [B1, B2], X: X}.
    meidnet families double_perovskite_a2bbx6 --variant halide\n
    Rock-salt ordered double perovskite A2BB'X6 (Fm-3m)  [variant: halide]\n  formula pattern: A2B1B2X6   (10 atoms per cell)\n  A: 9 candidate elements - Large cation in the cuboctahedral cavity\n  B1: 15 candidate elements - First octahedral cation (usually +1 or +2)\n  B2: 20 candidate elements - Second octahedral cation (usually +3 or +4)\n  X: 4 candidate elements - Anion shared between neighbouring B and B' octahedra\n

    The halide variant has 10,800 compositions; 305 pass every rule (Cs\u2082AgBiI\u2086 among them). Candidates are built in the 10-atom primitive cell; the symmetry refinement step writes them out as the conventional 40-atom Fm-3m cell, which is what the CIF files contain.

    "},{"location":"examples/double-perovskite.html#using-it","title":"Using it","text":"

    A model for this family must be trained on double-perovskite structures aligned to the prototype:

    family: double_perovskite_a2bbx6\ndata:\n  table: double_perovskites.csv\n  properties: [{column: band_gap, unit: eV}]\n  align_to_prototype: true\ngeneration:\n  family: double_perovskite_a2bbx6\n  variant: halide\n  objectives: [{property: band_gap}]\n  targets: [{band_gap: 2.0}]\n

    The published checkpoint

    The Perov-5 model was trained on 5-atom ABX\u2083 cells. The Studio will score double-perovskite compositions with it (the encoder accepts any cell up to 20 atoms), but those predictions are far outside its training distribution \u2014 a demonstration of the mechanics, not a result.

    "},{"location":"examples/perov5.html","title":"Perov-5 \u2014 the paper's experiment","text":"

    The published MEIDNet model: cubic ABX\u2083 perovskites from the Perov-5 dataset (CDVAE split of Castelli et al.), properties formation enthalpy (heat_all, eV/atom) and direct band gap (dir_gap, eV).

    "},{"location":"examples/perov5.html#use-the-published-checkpoint","title":"Use the published checkpoint","text":"
    meidnet demo --family halide --band-gap 2.0            # or: oxide, chalcogenide, nitride\nmeidnet studio                                          # explore it live\n

    The three checkpoints from the paper are in checkpoints/; ..._propertyaware_2k.pth is the recommended one. meidnet info checkpoints/<file> describes any of them. They load into MEIDNet 2 unchanged.

    "},{"location":"examples/perov5.html#retrain-from-scratch","title":"Retrain from scratch","text":"
    meidnet download-data                       # train/val/test CSVs \u2192 data/perov5/ (18,928 rows, 18 MB)\nmeidnet train examples/perov5/meidnet.yaml  # 200 epochs \u2248 1 h on a laptop GPU\nmeidnet generate examples/perov5/meidnet.yaml\n

    examples/perov5/meidnet.yaml reproduces the paper's settings, including two that differ from MEIDNet 2's defaults: normalize: false for both properties and align_to_prototype: false (atoms in file order), and the 1200-epoch alignment ramp.

    "},{"location":"examples/perov5.html#the-papers-generated-structures","title":"The paper's generated structures","text":"

    examples/perov5/paper_results/ holds the 26 CIFs (halide, oxide, chalcogenide) reported in the paper. meidnet screen examples/perov5/paper_results --train-csv data/perov5/train.csv recomputes their MACE stability.

    "},{"location":"examples/perov5.html#regression-against-v1","title":"Regression against v1","text":"

    tests/legacy_v1/ contains the v1.0 code byte for byte. pytest -m slow runs it and MEIDNet 2 on the same targets and asserts identical CIFs, predictions and file names.

    "},{"location":"explore/capabilities.html","title":"Capabilities: what works today","text":"

    Generated from the code, so it cannot promise more than the code does. Supported = in this release; Community-tested = a benchmark result reproduced here names it; Planned = designed for, not implemented (roadmap).

    "},{"location":"explore/capabilities.html#modalities","title":"Modalities","text":"modality status how Crystal structure (CIF, up to max_sites atoms, default 20) Supported an equivariant graph encoder; structures are aligned to the family prototype Scalar properties (any number of numeric columns) Supported one property encoder; every column becomes a target you can set Published properties: direct band gap (dir_gap), formation enthalpy (heat_all) Supported the shipped Perov-5 checkpoint Vector modalities: binned XRD, DOS Planned an encoder per vector modality into the shared latent space Spectra (Raman, UV-Vis) Planned as vector modalities Text (descriptions, synthesis) Planned a text encoder into the shared latent space Images (microscopy) Planned an image encoder into the shared latent space"},{"location":"explore/capabilities.html#fusion","title":"Fusion","text":"

    One scheme: early fusion - the structure latent and the property latent of a material are averaged into the joint latent (meidnet/model.py), after a contrastive alignment whose weight ramps up over training.contrastive_warmup_epochs (the curriculum of the paper). There is no late-fusion option; a selector would be a fiction.

    "},{"location":"explore/capabilities.html#input-formats","title":"Input formats","text":"format note CSV any delimiter pandas reads Excel (.xlsx, .xls) first sheet JSON table records Parquet needs the optional pyarrow CIF text in a column one structure per row CIF files (.cif, or a .zip of them) named .cif"},{"location":"explore/capabilities.html#material-families","title":"Material families","text":"family variants site groups double_perovskite_a2bbx6 - Rock-salt ordered double perovskite A2BB'X6 (Fm-3m) halide, oxide A, B1, B2, X perovskite_abx3 - Cubic ABX3 perovskite (Pm-3m) oxide, halide, chalcogenide, nitride A, B, X

    Any other prototype family can be described in a family file.

    "},{"location":"explore/capabilities.html#rules-hard-constraints","title":"Rules (hard constraints)","text":"
    • bond_window - Sensible {from}\u2013{to} bonds
    • charge_neutrality - Charge balance
    • min_distance - No overlapping atoms
    • octahedral_factor - Octahedral factor
    • property_window - Predicted {property} window
    • tolerance_factor - Goldschmidt tolerance factor
    "},{"location":"explore/capabilities.html#search-terms-soft","title":"Search terms (soft)","text":"
    • cubic_cell - Pulls the decoded cell towards a cube of the family's reference volume.
    • entropy_bonus - Rewards uncertainty early in a round so the search explores (fades to zero).
    • group_consistency - Sites of the same group should agree on their element (ramps up during a round).
    • prototype_alignment - Pulls decoded positions onto the prototype's site positions.
    • short_distance_penalty - Large penalty if any two decoded atoms come closer than min.
    • site_repulsion - Short-range exponential repulsion between decoded atoms.
    • site_separation - Keeps decoded atoms from collapsing onto each other (1/distance).
    • soft_charge - Expected total charge of the most likely composition should be zero (ramps up during a round).
    • tolerance_penalty - Soft version of the Goldschmidt tolerance-factor window, using the most likely elements.
    "},{"location":"explore/capabilities.html#after-generation","title":"After generation","text":"
    • meidnet screen: MACE-MP-0 relaxation and the stable / unique / novel (SUN) counts (optional pip install meidnet[stability]). DFT and experiments are yours: the benchmark evidence ladder records them.
    "},{"location":"explore/datasets.html","title":"Datasets","text":"

    Datasets that work with MEIDNet, and what has been done with each. One card per dataset, never one score across datasets: the tasks, sizes and properties differ, so results are only compared within a dataset (see Benchmarks).

    • Perov-5 \u2014 published multimodal benchmark

      18,928 cubic ABX\u2083 perovskites (CDVAE split: 11,356 train / 3,785 validation / 3,787 test), each with a relaxed structure, formation enthalpy (heat_all, eV/atom) and direct band gap (dir_gap, eV).

      Modalities used in MEIDNet: crystal structure \u00b7 electronic property (band gap) \u00b7 thermodynamic property (formation enthalpy).

      What the paper does with it: multimodal alignment of the structure and property encoders, property reconstruction, and the inverse-design demonstration (candidates generated from property targets, screened with MACE and validated by DFT).

      In this repository: meidnet download-data fetches it; the published checkpoint checkpoints/dual_autoencoder_clip_earlyfusion_propertyaware_2k.pth was trained on it; it is the data behind the live Studio.

      Benchmark results \u00b7 The paper's experiment \u00b7 Dataset source (CDVAE) \u00b7 Castelli et al. 2012, Xie et al. 2022

    • MP-20 \u2014 published: structure-representation generalization

      ~45,000 structures from the Materials Project with at most 20 atoms per cell (CDVAE split).

      Published MEIDNet use: generalization of the crystal encoder / decoder beyond perovskites (structure representation and reconstruction). It is not a multimodal benchmark in the paper: no property modality was aligned on it, and no MP-20 checkpoint is shipped here.

      Benchmark results \u00b7 Dataset source (CDVAE)

    • Carbon-24 \u2014 published: structure-representation generalization

      ~10,000 carbon structures with up to 24 atoms per cell (CDVAE split).

      Published MEIDNet use: the same structure-representation test as MP-20. Not a multimodal benchmark; no Carbon-24 checkpoint is shipped here.

      Benchmark results \u00b7 Dataset source (CDVAE)

    "},{"location":"explore/datasets.html#your-dataset","title":"Your dataset","text":"

    Any table with an id, one or more scalar property columns and a crystal structure per row (CIF text in a column or one .cif file per id) can be used directly: upload it in the Studio or run meidnet init \u2192 check \u2192 train \u2192 generate (bring your own dataset). The structures must belong to one material family (prototype + site groups), which is what the rules and the search need.

    "},{"location":"explore/datasets.html#datasets-we-would-like-to-see-benchmarked","title":"Datasets we would like to see benchmarked","text":"

    Battery conductors, MOFs, 2D materials and spinels are natural next families. If you run MEIDNet on one of them, contribute the result: the dataset gets its own benchmark page, and a verified row once the result has been reproduced here.

    "},{"location":"recipes/add-constraint.html","title":"Add a rule (constraint)","text":"

    A rule is a function that looks at a candidate and returns pass/fail, the measured value, the allowed window and a sentence. The search enforces it; every report and the Studio explain it.

    "},{"location":"recipes/add-constraint.html#1-write-it","title":"1. Write it","text":"

    my_rules.py:

    from meidnet.constraints import CONSTRAINTS\n\n@CONSTRAINTS.register(\"no_lead\", \"Rejects any composition containing Pb.\")\ndef no_lead(cand):\n    return cand.result(\"no_lead\", \"Pb\" not in cand.elements.values())\n\n@CONSTRAINTS.register(\"mass_window\", \"Mean atomic mass must lie between min and max (g/mol).\")\ndef mass_window(cand, min=0.0, max=200.0):\n    from pymatgen.core import Element\n    m = sum(Element(e).atomic_mass for e in cand.structure.species) / len(cand.structure)\n    return cand.result(\"mass_window\", min <= m <= max, float(m), (min, max), f\"mean mass {m:.1f}\")\n

    What a candidate gives you: cand.elements ({group: element}), cand.structure (pymatgen, after symmetry refinement), cand.raw (before), cand.lattice_a, cand.predictions ({property: value}), cand.radius(group).

    "},{"location":"recipes/add-constraint.html#2-register-the-file-and-name-the-rule","title":"2. Register the file and name the rule","text":"
    plugins: [my_rules.py]\ngeneration:\n  family: my_perovskite.yaml      # a copy of the built-in family with the rules appended:\n
    constraints:\n  - {name: charge_neutrality}\n  - ...\n  - {name: no_lead}\n  - {name: mass_window, min: 0, max: 120}\n
    "},{"location":"recipes/add-constraint.html#3-run","title":"3. Run","text":"

    meidnet generate meidnet.yaml. The candidate cards list No lead and mass window with the measured value; the funnel shows how many compositions each removed.

    A complete example lives in examples/custom_rules/ (a no toxic elements rule and a max cell edge rule), and notebook 03 does the same in Colab.

    "},{"location":"recipes/add-constraint.html#soft-terms","title":"Soft terms","text":"

    Rules decide acceptance. To steer the latent search instead (a penalty with a gradient), register a search term with @SEARCH_TERMS.register in meidnet.terms; it receives a SearchContext with the decoder's outputs. See rules and search terms.

    "},{"location":"recipes/add-property.html","title":"Add a property","text":"

    MEIDNet's property modality is a vector of any length. Adding a property is a data change, not a code change.

    1. Add the column to your table.
    2. List it under data.properties (label and unit are for the reports; normalize: true is the default and recommended when properties have different scales):

      data:\n  properties:\n    - {column: band_gap,     label: Band gap,          unit: eV}\n    - {column: formation_e,  label: Formation energy,  unit: eV/atom}\n    - {column: dielectric,   label: Dielectric constant}\n
    3. meidnet check \u2192 meidnet train. The training report rates each property separately.

    4. Use it in generation.objectives and targets.

    Properties with many missing values: rows with a missing value are skipped (the check report counts them). Fill them, or drop the property.

    The published model

    The Perov-5 checkpoint has exactly two properties (formation enthalpy, direct band gap) and cannot steer others; train your own model to add some.

    "},{"location":"recipes/change-family.html","title":"Change the material family","text":"

    A family file tells MEIDNet what kind of crystal to build. The built-in ones are meidnet/families/perovskite_abx3.yaml and double_perovskite_a2bbx6.yaml; meidnet families <name> prints a summary. To use your own, copy one, edit it, and point to it:

    generation:\n  family: my_family.yaml\n  variant: null          # or a variant you define\n
    "},{"location":"recipes/change-family.html#the-parts-of-a-family-file","title":"The parts of a family file","text":"
    name: spinel_ab2x4\ntitle: Normal spinel AB2X4 (Fd-3m, primitive cell)\n\nprototype:                    # WHERE atoms sit (fractional coordinates, in decoder slot order)\n  lattice: fcc_primitive      # cubic | fcc_primitive | bcc_primitive | explicit a,b,c,alpha,beta,gamma\n  reference_a: 8.1\n  sites:\n    - {group: A, frac: [0.125, 0.125, 0.125]}\n    - ...\n\ngroups:                       # WHO may sit there, and with which charges\n  A: {description: tetrahedral cation, elements: [Mg, Zn, Fe, Co, Ni, Mn], oxidation_states: {Mg: [2], ...}}\n  B: {...}\n  X: {...}\n\nsampling_order: [B, X, A]     # the LAST group is restricted to charge-balancing elements\n\nlattice:                      # HOW BIG the cell is\n  rule: bond_sum              # a = factor \u00d7 \u03a3 ionic radii of the listed groups (or rule: fixed)\n  groups: [B, X]\n  factor: 2.0\n  min: 3.0\n  max: 12.0\n\nvariants:                     # named sub-families: element subsets and rule parameters\n  oxide: {groups: {X: {elements: [O]}}, params: {tolerance_factor: {min: 0.8, max: 1.05}}}\n\nsearch_terms:                 # soft penalties during the latent search (see reference/rules)\n  - {name: site_separation, weight: 200}\n  - ...\n\nconstraints:                  # hard rules every saved candidate must pass\n  - {name: charge_neutrality}\n  - {name: min_distance, min: 0.8}\n  - ...\n
    "},{"location":"recipes/change-family.html#alignment-of-training-data","title":"Alignment of training data","text":"

    For generation to mean anything, the model must have been trained on structures whose atoms are in the prototype's site order. data.align_to_prototype: true (default) re-orders every training structure to match the family named in generation.family (or top-level family:); structures that do not fit are reported by meidnet check. Raise data.prototype_tolerance for distorted cells.

    "},{"location":"recipes/change-family.html#checking-a-family-file","title":"Checking a family file","text":"
    meidnet families my_family.yaml --variant oxide\nmeidnet space meidnet.yaml      # every composition with rule values \u2192 CSV\n

    Errors name the problem: unknown element symbols, a prototype group without a definition, an override that matches no rule, a group left empty after your filters.

    "},{"location":"recipes/change-family.html#worked-example","title":"Worked example","text":"

    The double perovskite page walks through a 10-site, non-cubic-primitive family written this way.

    "},{"location":"recipes/change-targets.html","title":"Change the target properties","text":""},{"location":"recipes/change-targets.html#a-different-value","title":"A different value","text":"
    generation:\n  targets:\n    - {band_gap: 1.2}\n    - {band_gap: 1.8}      # each entry is its own search\n
    "},{"location":"recipes/change-targets.html#a-different-kind-of-goal","title":"A different kind of goal","text":"

    loss says how a prediction is compared with the target:

    loss meaning typical use l2 squared distance (default) hit a value l1 absolute distance hit a value, less sensitive to outliers at_most only values above the target are penalised formation enthalpy \u2264 \u22120.1 eV/atom at_least only values below are penalised dielectric constant \u2265 20
    objectives:\n  - {property: band_gap,   loss: l2,       weight: 10000, select_weight: 1.0}\n  - {property: enthalpy,   loss: at_most,  weight: 6000,  select_weight: 0.4}\n

    weight acts during the latent search; select_weight when ranking finished candidates.

    "},{"location":"recipes/change-targets.html#a-different-property","title":"A different property","text":"

    The model knows the properties it was trained on (meidnet info model.pt lists them). To steer a property that is not in the model, add it to the data and retrain.

    "},{"location":"recipes/change-targets.html#in-the-studio","title":"In the Studio","text":"

    Drag the target slider; the predicted-closest table and the scatter update at once, and a warning appears if the target lies outside the training range. Export the YAML when you are happy.

    "},{"location":"recipes/elements.html","title":"Exclude or restrict elements","text":"
    generation:\n  exclude_elements: [Pb, Cd, Hg]           # never used, on any site\n  only_elements:                           # restrict a site group to a shortlist\n    B: [Ti, Zr, Hf, Sn]\n

    Both are applied to the family before the search starts, so the decoder cannot even propose an excluded element. The Studio's element chips do the same thing (click to toggle) and show the counts change.

    To change which elements a family allows at all, or their oxidation states, copy the family file and edit the groups: section \u2014 see Change the material family.

    "},{"location":"recipes/screen.html","title":"Screen stability with MACE","text":"
    pip install \"meidnet[stability]\"            # ase + mace-torch (~500 MB of weights on first use)\nmeidnet screen runs/my_oxides/generation --train-csv materials.csv\n

    For each CIF: relax cell and positions with the MACE-MP-0 universal potential, compute the formation energy per atom against elemental reference phases, and report

    • S stable \u2014 \u0394H_f \u2264 0.10 eV/atom (Materials Project's \"potentially synthesisable\" criterion; --threshold),
    • U unique \u2014 no structural duplicate among the candidates,
    • N novel \u2014 reduced formula absent from the training table.

    Results go to stability.csv in the folder. Structures with |\u0394H_f| > 5 eV/atom are flagged as artefacts and excluded from the rates. The reference phases and GGA-style O/N corrections are those of the paper.

    This is a first filter, not a verdict: MACE formation energies carry ~0.1 eV/atom uncertainty, and a cubic prototype may relax to a lower-symmetry structure.

    "},{"location":"reference/api.html","title":"Python API","text":"

    The command line is a thin layer over these functions; use them from notebooks or your own scripts.

    from meidnet.config import load_config, config_from_dict\nfrom meidnet.pipeline import check, train, generate\n\ncfg = load_config(\"meidnet.yaml\")\ninfo = check(cfg)                 # dict: report (counts, skip reasons), records, family, report_path\ninfo = check(cfg, keep_structures=True)   # records also keep their pymatgen Structure (record.structure)\nmodel_path = train(cfg)           # writes model.pt + training_report.html, returns the path\nresult = generate(cfg)            # GenerationResult: .saved (candidates), .targets (funnel logs)\n
    "},{"location":"reference/api.html#pieces","title":"Pieces","text":"
    from meidnet.checkpoint import load_checkpoint, describe, property_ranges\nlm = load_checkpoint(\"model.pt\")          # LoadedModel: .model, .stats (property names/units/normalisation)\nlm.model.encode_crystal(x)                # structure \u2192 latent (x from meidnet.data.featurize)\nlm.model.encode_properties(p)             # normalised property vector \u2192 latent\n\nfrom meidnet.family import load_family\nfam = load_family(\"perovskite_abx3\", variant=\"halide\", exclude=[\"Pb\"], overrides={\"tolerance_factor\": {\"max\": 1.0}})\n\nfrom meidnet.constraints import build_candidate, evaluate, CONSTRAINTS\ncand = evaluate(build_candidate(fam, {\"A\": \"Cs\", \"B\": \"Sn\", \"X\": \"Br\"}), fam.constraints)\ncand.passed, [r.to_dict() for r in cand.results]\n\nfrom meidnet.generate import Designer\nfrom meidnet.config import GenerationSection\ng = GenerationSection(family=\"perovskite_abx3\", variant=\"halide\",\n                      objectives=[{\"property\": \"dir_gap\"}], targets=[{\"dir_gap\": 2.0}], rounds=3, steps=200)\nres = Designer(lm, fam, g, on_saved=lambda c, t: print(c.formula)).run(\"out/\")\n\nfrom meidnet.designspace import enumerate_space, space_to_csv\nspace = enumerate_space(fam, lm)          # every composition: rule values + predictions\n

    Custom rules and search terms: CONSTRAINTS.register, meidnet.terms.SEARCH_TERMS.register.

    "},{"location":"reference/api.html#extra-rules-in-the-configuration","title":"Extra rules in the configuration","text":"

    generation.extra_constraints adds rules to the family's own for one run, without editing the family file \u2014 this is how the Studio saves a predicted-property window:

    generation:\n  family: perovskite_abx3\n  variant: halide\n  extra_constraints:\n    - {name: property_window, property: dir_gap, min: 1.5, max: 3.0}\n

    meidnet.pipeline.family_for(cfg, need_variant=True) returns the family a run uses, with these rules appended; generation.family / generation.variant take precedence over the top-level family.

    "},{"location":"reference/api.html#3d-datasets-chemiscope","title":"3D datasets (chemiscope)","text":"

    meidnet.studio.chemiscope turns MEIDNet objects into chemiscope datasets \u2014 plain dicts you can save with json.dump and open at chemiscope.org, or show in a notebook with chemiscope.show(...):

    from meidnet.studio.chemiscope import (space_dataset, candidates_dataset, records_dataset,\n                                       published_data_dataset, structure_to_chemiscope)\n\nds = space_dataset(fam, space, lm, max_structures=2000)   # design space: formula, site_*, pred_*, rule_*, passes_all\nds = candidates_dataset(candidate_dicts, fam, run_dir, lm=lm)   # candidates of a run (reads their CIFs when present;\n                                                                # lm adds units and property names)\nds = records_dataset(info[\"records\"], cfg.data.properties, source=\"my data\")   # needs check(cfg, keep_structures=True)\nstructure_to_chemiscope(structure)                         # one pymatgen Structure \u2192 {size, names, x, y, z, cell}\n

    candidate_dicts are saved candidates as dicts \u2014 [c.to_dict() for c in res.saved] after a Designer run, or [c for t in json.load(open(\"generation.json\"))[\"targets\"] for c in t[\"saved\"]] from a finished run. run_dir is the run folder that contains generation/ (cfg.out_dir after meidnet generate); where a CIF file is not found, the structure is rebuilt on the prototype from the candidate's elements.

    "},{"location":"reference/api.html#scripting-the-studio","title":"Scripting the Studio","text":"

    The Studio server is a small class you can drive without a browser \u2014 the 04 Colab notebook does exactly this. Every method takes the same JSON-like dicts the page sends; session keeps users (or experiments) apart.

    import base64\nfrom meidnet.studio.server import Studio\n\nst = Studio(None)                                   # the published model; or Studio(load_config(\"meidnet.yaml\"))\nsid = \"my-session\"\nst.state(sid)                                       # model, ranges, families, limits, session info\n\nb64 = base64.b64encode(open(\"materials.csv\", \"rb\").read()).decode()\nup = st.upload({\"session\": sid, \"files\": [{\"name\": \"materials.csv\", \"b64\": b64}]})\nup[\"suggest\"]                                       # suggested id / CIF / property columns\n\nst.check_data({\"session\": sid, \"id_column\": \"material_id\", \"cif_column\": \"cif\",\n               \"properties\": [{\"column\": \"dir_gap\", \"unit\": \"eV\"}],\n               \"family\": \"perovskite_abx3\", \"variant\": \"halide\"})\nst.start_train({\"session\": sid, \"epochs\": 10, \"batch_size\": 16})\nst.status(sid, \"train\")                             # poll: running, progress (epoch, loss, curve), done, error\nst.select_model(sid, \"published\")                   # or \"own\"\n\nst.family({\"session\": sid, \"family\": \"perovskite_abx3\", \"variant\": \"halide\"})   # design space, scored\nst.start_search({\"session\": sid, \"generation\": {\"per_target\": 2, \"rounds\": 2, \"steps\": 100}})\nst.status(sid, \"search\")                            # candidates as they are found\nst.chemiscope(sid, \"space\", \"perovskite_abx3\", \"halide\")   # or \"candidates\" / \"data\"\nst.validate({\"yaml\": \"generation:\\n  rounds: 2\\n\"})        # {ok, generation, notes} or {ok: False, errors}\nst.export({\"session\": sid, \"generation\": {}})              # the meidnet.yaml text\n
    "},{"location":"reference/cli.html","title":"Command line","text":"
    meidnet init            write a starter meidnet.yaml next to your data\nmeidnet check  CONFIG   is my data usable?            \u2192 check_report.html\nmeidnet train  CONFIG   learn the latent space        \u2192 model.pt, training_report.html\nmeidnet generate CONFIG design candidates             \u2192 CIFs, generation_report.html\nmeidnet studio [CONFIG] interactive workbench in your browser\nmeidnet space  CONFIG   every composition of the family with rule values and predictions \u2192 CSV\nmeidnet demo            quick demo with the published perovskite model\nmeidnet screen DIR      stability screening of CIFs with MACE (optional extra)\nmeidnet info MODEL      describe a checkpoint\nmeidnet families [NAME] list / describe material families\nmeidnet schema          JSON Schema of the config (for editors and the Studio)\nmeidnet download-data   fetch the Perov-5 dataset used in the paper\n

    meidnet <command> --help lists the options. Useful ones:

    init --template perov5 the paper's configuration instead of a blank one train --epochs N override training.epochs generate --quick few rounds and steps \u2014 a smoke test generate --model PATH use another checkpoint studio --export-static FILE.html self-contained Studio for a website studio --port 8765 --no-open choose the port; do not open a browser studio --public --host 0.0.0.0 host the Studio for other people: private session per visitor, capped budgets studio --run-root DIR --docs-dir site where sessions and searches are written; serve a built docs site at /docs/ space --chemiscope FILE.json also write the design space as a 3D dataset for chemiscope.org demo --family oxide --band-gap 3 --enthalpy -0.2 -n 5 demo targets

    If meidnet is not on your PATH, python -m meidnet.cli \u2026 is equivalent.

    "},{"location":"reference/config.html","title":"Configuration reference","text":"

    Every setting of meidnet.yaml, generated from the code (meidnet schema gives the JSON Schema). Unknown keys are rejected with a message naming the key, so typos cannot silently change a run.

    "},{"location":"reference/config.html#top-level","title":"Top level","text":"setting type default meaning name string meidnet_run Name of this project; outputs go to output_dir. description string Free text shown at the top of reports. output_dir string runs/{name} Where checkpoints, CIFs and reports are written. family string/null None Family used to align training structures (defaults to generation.family). model_path string/null None Existing checkpoint to generate from (skip training). Default: /model.pt plugins array Python files that register your own constraints or search terms (see 'Add a constraint'). data object/null None model section training section generation object/null None"},{"location":"reference/config.html#data","title":"data:","text":"setting type default meaning table string required Path to a CSV/Excel/JSON table with one row per material. id_column string material_id Column with a unique identifier for each material. cif_column string/null cif Column that contains the CIF text of each structure. Set to null if you use structures_dir. structures_dir string/null None Folder with one CIF file per material, named .cif (used when there is no cif_column). properties array required Numeric columns that form the property modality (any number, at least one). val_table string/null None Optional separate validation table with the same columns. val_fraction number 0.1 Fraction of rows held out for validation when no val_table is given (0 = no validation). max_sites integer 20 Largest number of atoms per cell. Bigger structures are skipped. neighbor_cutoff number 4.0 Distance (\u00c5) below which two atoms count as neighbours. align_to_prototype boolean True Re-order the atoms of every structure so they match the family prototype's site order (needed for meaningful generation). The published Perov-5 model was trained without it. prototype_tolerance number 0.15 How far (fraction of a cell edge) an atom may sit from its prototype site and still count as that site. Raise it to accept distorted structures; lower it to keep only ideal ones."},{"location":"reference/config.html#dataproperties","title":"data.properties[]","text":"setting type default meaning column string required Name of the column in your table that holds this property. label string/null None Human-readable name used in reports (defaults to the column name). unit string Unit shown in reports, e.g. 'eV' or 'eV/atom'. normalize boolean True Standardise the property to zero mean and unit spread before training. Keep this on when properties have very different scales."},{"location":"reference/config.html#model","title":"model:","text":"setting type default meaning latent_dim integer 128 Size of the shared latent space where structures and properties meet. node_hidden_dim integer 128 Width of the per-atom features inside the graph network. edge_dim integer 64 Width of the per-bond messages inside the graph network. species_embedding_dim integer 64 Size of the learned element embedding. property_hidden_dim integer 128 Width of the property encoder's hidden layer. decoder_coordinate_input data / zeros / prototype data What the crystal decoder receives as starting atom positions during training. 'data' (published model) uses the true positions; generation always starts from 'zeros' or the prototype. 'zeros'/'prototype' make training and generation consistent (experimental)."},{"location":"reference/config.html#training","title":"training:","text":"setting type default meaning epochs integer 200 Number of passes over the training data. batch_size integer 16 Materials per training step (use 8 if memory is short). learning_rate number 0.001 Adam learning rate. contrastive_weight number 5.0 Strength of the alignment between structure and property latents. temperature number 0.01 Sharpness of the contrastive (InfoNCE) alignment. contrastive_warmup_epochs integer/null None Epochs over which the alignment strength ramps up from 0. Default: 60% of the epochs (the published model used 1200 of 2000). loss_weights section seed integer 0 Random seed for weight initialisation and data shuffling. device string auto 'auto', 'cpu' or 'cuda'. save_every integer 50 Write an intermediate checkpoint every N epochs."},{"location":"reference/config.html#trainingloss_weights","title":"training.loss_weights:","text":"setting type default meaning joint_reconstruction number 1.0 Rebuild the crystal from the joint (structure+property) latent. joint_property number 1.0 Predict the properties from the joint latent. property_reconstruction number 0.5 Rebuild the crystal from the property latent alone - this is what makes inverse design possible. property_property number 0.5 Predict the properties back from the property latent."},{"location":"reference/config.html#generation","title":"generation:","text":"setting type default meaning family string perovskite_abx3 Built-in family name or path to your own family .yaml file. variant string/null None Sub-family, e.g. 'halide' or 'oxide' for perovskites. objectives array required Which properties to steer and how. targets array required One entry per design target, e.g. {dir_gap: 1.5, heat_all: -0.1}. Each runs its own search. per_target integer 4 Candidates to save for each target. population integer 48 Latent vectors optimised in parallel in each round. rounds integer 20 Maximum search rounds per target. steps integer 800 Gradient steps per round. learning_rate number 0.0012 Step size of the latent search. anchor_weight number 12.0 Keeps the search close to the latent that the target properties map to. temperature_start number 1.6 Softness of element choices at the start of a round. temperature_end number 0.9 Softness of element choices at the end of a round. diversity_weight number 0.9 Pushes the parallel searches apart from each other. diversity_tau number 0.25 history_weight number 1.0 Pushes new searches away from latents that already produced a saved candidate. history_tau number 0.25 restart_patience integer 250 Steps without improvement before a search is restarted with noise. restart_noise number 0.35 grad_clip number 1.0 z_clip number 5.0 init_sigma number 0.35 Noise added to the starting latents. init_anchor_mix number 0.75 Share of the property-encoder latent in the starting point. init_rff_mix number 0.6 Share of the target-dependent random-feature direction in the starting point. init_noise_mix number 0.25 Share of random noise in the starting point. rff_frequencies integer 48 decode_temperature number 1.25 Randomness when turning element scores into a choice. decode_topk integer 12 Only the k best-scoring elements of a group can be chosen. decode_tries integer 12 Attempts per latent to find a composition that passes all constraints. anti_repeat_alpha number 0.6 Down-weights elements that were already used in saved candidates. anti_repeat_group string/null None Group the anti-repeat applies to (default: first sampled group). geometry_scale number 1.0 Global strength of the family's search terms. property_first boolean False Ignore the family's search terms during the search (properties only). dedup_formula boolean True Save each composition at most once. min_cosine_sep number 0.985 Skip candidates whose latent is this similar (cosine) to an already saved one. unique_decimals integer 3 exclude_elements array Elements never to use, e.g. [Pb, Cd]. only_elements object Restrict groups to these elements, e.g. {B: [Ti, Zr, Hf]}. overrides object Change parameters of the family's search terms or constraints, e.g. {tolerance_factor: {max: 1.0}}. extra_constraints array Additional rules appended to the family's constraints, e.g. [{name: property_window, property: dir_gap, min: 1.0, max: 3.0}]. disabled_rules array Rules to switch off for this run, by name (or id when two rules share a name), e.g. [charge_neutrality]. The generation report lists them. seed integer 937 Random seed of the search. amp boolean True Use mixed precision on GPUs. output_prefix string/null None File-name prefix of saved CIFs (default: family variant)."},{"location":"reference/config.html#generationobjectives","title":"generation.objectives[]","text":"setting type default meaning property string required Property (column name) this objective refers to. loss l2 / l1 / at_most / at_least l2 How a prediction is compared with the target: l2 = squared distance, l1 = absolute distance, at_most = only values above the target are penalised, at_least = only values below. weight number 10000.0 Importance of this objective during the latent search. select_weight number 1.0 Importance of this objective when ranking finished candidates. select_loss string/null None Comparison used for ranking (defaults to 'loss')."},{"location":"reference/families.html","title":"Family files","text":"

    A family file is YAML with these keys. meidnet families NAME prints a summary and the constraints with their explanations; errors name the offending key or element.

    key required meaning name, title, description name identification; description appears in the Studio prototype.lattice yes cubic, fcc_primitive, bcc_primitive, or explicit a, b, c, alpha, beta, gamma prototype.reference_a typical cubic cell edge (\u00c5), used by the cubic_cell search term prototype.sites[] yes {group, frac} in decoder slot order; fractional coordinates groups.<G>.description sentence shown in reports groups.<G>.elements yes every element the group may hold (all variants) groups.<G>.sample subset offered to the final element choice (default: elements) groups.<G>.oxidation_states {element: [charges]} used for charge balance and neutrality bias sampling_order order in which groups are chosen; the last is restricted to charge-balancing elements lattice.rule bond_sum (a = factor \u00d7 \u03a3 r_ionic of groups, clipped to min..max) or fixed variants.<V>.groups per-variant elements, sample, oxidation_states of any group variants.<V>.params {rule_or_term_name: {param: value}} applied to search terms and constraints require_variant default true: a variant must be chosen when variants exist search_terms[] {name, \u2026params} soft penalties during the latent search logit_transforms[] {name, \u2026params} edits of the decoder's element scores (e.g. neutrality_bias) constraints[] {name, \u2026params} hard rules; min_distance runs before symmetry refinement, the rest after refine_symmetry, symprec run spglib refinement on candidates (default true, 0.05)

    User-side changes without editing the file: generation.variant, exclude_elements, only_elements, overrides ({name: {param: value}} for any search term or constraint).

    The built-in files are the complete reference: perovskite_abx3.yaml, double_perovskite_a2bbx6.yaml.

    "},{"location":"reference/rules.html","title":"Rules and search terms","text":"

    Rules (hard constraints) decide whether a candidate is saved. Search terms (soft penalties) steer the latent search towards decodable, sensible outputs. Both are referenced by name in family files.

    "},{"location":"reference/rules.html#built-in-rules","title":"Built-in rules","text":""},{"location":"reference/rules.html#bond_window-sensible-fromto-bonds","title":"bond_window \u2014 Sensible {from}\u2013{to} bonds","text":"

    Every {from} atom needs a {to} neighbour at {low}\u2013{high} \u00d7 the sum of their ionic radii: bonds may be neither squashed nor stretched.

    "},{"location":"reference/rules.html#charge_neutrality-charge-balance","title":"charge_neutrality \u2014 Charge balance","text":"

    The formal charges must add up to zero for at least one combination of the listed oxidation states (e.g. Cs\u207a Pb\u00b2\u207a I\u207b\u00d73 = 0).

    "},{"location":"reference/rules.html#min_distance-no-overlapping-atoms","title":"min_distance \u2014 No overlapping atoms","text":"

    Two atoms closer than {min} \u00c5 would overlap, which is physically impossible.

    "},{"location":"reference/rules.html#octahedral_factor-octahedral-factor","title":"octahedral_factor \u2014 Octahedral factor","text":"

    \u03bc = r_B / r_X says whether the B cation is large enough to hold six X anions around it ({min} \u2264 \u03bc \u2264 {max}).

    "},{"location":"reference/rules.html#property_window-predicted-property-window","title":"property_window \u2014 Predicted {property} window","text":"

    The model's predicted {property} must lie between {min} and {max}.

    "},{"location":"reference/rules.html#tolerance_factor-goldschmidt-tolerance-factor","title":"tolerance_factor \u2014 Goldschmidt tolerance factor","text":"

    t = (r_A + r_X) / (\u221a2 (r_B + r_X)) measures how well the A cation fits the cage of B\u2013X octahedra. Cubic perovskites form roughly for {min} \u2264 t \u2264 {max}.

    "},{"location":"reference/rules.html#search-terms","title":"Search terms","text":"
    • cubic_cell \u2014 Pulls the decoded cell towards a cube of the family's reference volume.
    • entropy_bonus \u2014 Rewards uncertainty early in a round so the search explores (fades to zero).
    • group_consistency \u2014 Sites of the same group should agree on their element (ramps up during a round).
    • prototype_alignment \u2014 Pulls decoded positions onto the prototype's site positions.
    • short_distance_penalty \u2014 Large penalty if any two decoded atoms come closer than min.
    • site_repulsion \u2014 Short-range exponential repulsion between decoded atoms.
    • site_separation \u2014 Keeps decoded atoms from collapsing onto each other (1/distance).
    • soft_charge \u2014 Expected total charge of the most likely composition should be zero (ramps up during a round).
    • tolerance_penalty \u2014 Soft version of the Goldschmidt tolerance-factor window, using the most likely elements.
    "},{"location":"reference/rules.html#logit-transforms","title":"Logit transforms","text":"
    • neutrality_bias \u2014 Raises the scores of anions whose charge can neutralise the most likely cations.
    "},{"location":"reference/rules.html#your-own-rule","title":"Your own rule","text":"
    from meidnet.constraints import CONSTRAINTS\n\n@CONSTRAINTS.register(\"no_lead\", \"Rejects any composition containing Pb.\")\ndef no_lead(cand):\n    return cand.result(\"no_lead\", \"Pb\" not in cand.elements.values())\n

    Save it as my_rules.py, list it under plugins: in meidnet.yaml, and add - {name: no_lead} to the family's constraints. See Add a rule.

    "},{"location":"research/paper.html","title":"Paper","text":"

    MEIDNet: Multimodal generative AI framework for inverse materials design. Anand Babu, Rog\u00e9rio Almeida Gouv\u00eaa, Pierre Vandergheynst, Gian-Marco Rignanese. npj Computational Materials (2026). doi:10.1038/s41524-026-02153-3 \u00b7 arXiv:2601.22009

    @article{meidnet2026,\n  title   = {MEIDNet: Multimodal generative AI framework for inverse materials design},\n  author  = {Anand Babu and Rog{\\'e}rio Almeida Gouv{\\^e}a and Pierre Vandergheynst and Gian-Marco Rignanese},\n  journal = {npj Computational Materials},\n  year    = {2026},\n  doi     = {10.1038/s41524-026-02153-3}\n}\n
    "},{"location":"research/paper.html#code-and-data-of-the-paper","title":"Code and data of the paper","text":"
    • Code as published: tag v1.0.0-paper (frozen; MEIDNet 2 reproduces it bit for bit \u2014 see Perov-5).
    • Checkpoints: checkpoints/ in the repository (2.8 MB each).
    • Data: Perov-5 in the CDVAE split (meidnet download-data); Castelli et al., Energy Environ. Sci. 5, 5814 (2012); Xie et al., ICLR (2022).
    • Generated structures: examples/perov5/paper_results/.
    "},{"location":"research/paper.html#related","title":"Related","text":"
    • Satorras, Hoogeboom, Welling, E(n) equivariant graph neural networks, ICML 2021 (the encoder).
    • Radford et al., CLIP, ICML 2021 (the alignment objective).
    • Batatia et al., MACE-MP-0 (stability screening).
    "},{"location":"start/colab.html","title":"Run in Google Colab","text":"

    Each notebook is a complete, runnable workflow. Training runs in your Colab session (free GPU is enough): your data never leaves your Google account.

    Notebook What it does Time 01 \u2014 Quickstart published model, pick a target, read the report, explore the whole design space in 3D 5 min, CPU 02 \u2014 Your own data upload table + CIFs \u2192 check \u2192 train \u2192 generate \u2192 download everything 15 min + training 03 \u2014 Add a rule write a custom constraint in a few lines and see its effect before and after 5 min 04 \u2014 MEIDNet Studio in Colab the live Studio inside the notebook, then the same blocks driven from Python 10 min"},{"location":"start/colab.html#block-by-block-like-the-studio","title":"Block by block, like the Studio","text":"

    Every notebook follows the seven blocks of the Studio, in order and in the same colours: Block 1 \u00b7 Data (blue), Block 2 \u00b7 Model (orange), Block 3 \u00b7 Family (green), Block 4 \u00b7 Rules (amber), Block 5 \u00b7 Targets (pink), Block 6 \u00b7 Search (dark green) and Block 7 \u00b7 Candidates (violet). Each block starts with a coloured header saying what it does, has a form for its settings, shows its result as charts (and crystals in 3D where it helps), and ends by printing what flows on to the next block.

    Run the cells from top to bottom (Runtime \u2192 Run all also works). Change a value in a form and re-run from that block on: the later blocks pick up the change.

    The notebooks are generated from scripts/make_notebooks.py, so they stay in step with the command line.

    "},{"location":"start/quickstart.html","title":"5-minute quickstart","text":"In the browser (nothing to install)On your computerIn Colab

    Open the live Studio. It runs the published perovskite model: move rule limits and targets and watch what passes, run a latent search and read each candidate's checklist, export the meidnet.yaml. (Search budgets are capped on the shared server; the local install below has no limits and takes your own data.)

    pip install meidnet          # PyTorch CPU wheels are fine; CUDA is optional\nmeidnet demo                 # halide perovskites, band gap 2.0 eV \u2192 runs/demo/\n

    demo downloads the 2.8 MB published checkpoint, searches for 3 candidates (about a minute on a CPU), checks them against the family's rules and opens generation_report.html.

    meidnet demo --family oxide --band-gap 3.0 --enthalpy -0.2 -n 5\nmeidnet studio               # the live workbench with the same model\n

    Notebook 01 \u2014 quickstart

    "},{"location":"start/quickstart.html#what-you-just-saw","title":"What you just saw","text":"
    1. Targets \u2192 latent. The property encoder maps (band gap, enthalpy) to a point in the shared latent space.
    2. Search. A population of latents near that point is optimised so that the decoder's output matches the targets and the family's soft terms (cubic cell, sensible charges, \u2026).
    3. Decode + rules. Each latent is decoded into one element per site; the composition is placed on the family's prototype and must pass every hard rule (charge balance, tolerance factor, \u2026).
    4. Report. The best new, unique candidates are saved as CIFs with a card that shows each rule's value and a gauge of predicted vs target property.
    "},{"location":"start/quickstart.html#next","title":"Next","text":"
    • What data do I need? then Bring your own dataset.
    • MEIDNet Studio to see the effect of every change live.
    "},{"location":"start/what-data.html","title":"What data do I need?","text":""},{"location":"start/what-data.html#the-checklist","title":"The checklist","text":"You need Format Notes Structures CIF text in a cif column of the table, or one <id>.cif file per material in a folder up to max_sites atoms per cell (default 20); any elements Properties one or more numeric columns any units; each property is standardised unless you say otherwise An id a column whose values match the CIF file names (if you use files) 12, 12.0 and \"12\" are treated as the same id A family the prototype your structures share (e.g. cubic ABX\u2083) needed for generation; training without one is possible (align_to_prototype: false)

    A table like this is enough:

    material_id,band_gap,dielectric,cif\nmat_001,1.42,18.3,\"# generated using pymatgen\ndata_CsPbBr3\n_cell_length_a 5.87 ...\"\nmat_002,2.16,11.7,\"...\"\n

    CSV, Excel (.xlsx) and JSON tables are read; Parquet too once pyarrow is installed (pip install \"meidnet[parquet]\").

    "},{"location":"start/what-data.html#how-much-data","title":"How much data?","text":"

    The published model used 11k structures. A few thousand is typical; a few hundred lets you try the pipeline, but expect rough predictions (the training report tells you how rough).

    "},{"location":"start/what-data.html#what-happens-to-each-row","title":"What happens to each row","text":"

    meidnet check reads every row exactly as training would and writes check_report.html with one line per reason a row was skipped (unreadable CIF, too many atoms, missing value, does not match the prototype) and example ids. Nothing is silently dropped.

    "},{"location":"start/what-data.html#properties-and-ranges","title":"Properties and ranges","text":"

    MEIDNet can only design for property values it has seen. The check report draws each property's distribution; the generation report flags targets outside the training range as extrapolation.

    "},{"location":"start/what-data.html#which-family","title":"Which family?","text":"
    • Cubic ABX\u2083 perovskite \u2192 built-in perovskite_abx3 (variants: oxide, halide, chalcogenide, nitride).
    • Rock-salt-ordered double perovskite A\u2082BB\u2032X\u2086 \u2192 built-in double_perovskite_a2bbx6.
    • Anything else with a fixed arrangement of sites \u2192 write a family file (positions, which elements may occupy which site, their charges, a cell-size rule, rules).
    • Mixed prototypes \u2192 you can still train (align_to_prototype: false) and use the model to predict/screen, but generation needs one prototype.
    "},{"location":"understand/how-it-works.html","title":"How MEIDNet works","text":""},{"location":"understand/how-it-works.html#one-latent-space-for-two-modalities","title":"One latent space for two modalities","text":"
    crystal (CIF) \u2500\u2500\u25ba SE(3)-equivariant encoder \u2500\u2500\u25ba z_c \u2500\u2510                     \u250c\u2500\u25ba crystal decoder   \u2500\u2500\u25ba lattice, species, positions\n                                                      \u251c\u2500\u2500 shared space \u2500\u2500\u2500\u2500\u2500\u2524\nproperties \u2500\u2500\u2500\u2500\u25ba property encoder (MLP) \u2500\u2500\u2500\u2500\u2500\u25ba z_p \u2500\u2518  pulled together     \u2514\u2500\u25ba property decoder  \u2500\u2500\u25ba properties\n                                                        (InfoNCE)\n
    • The crystal encoder embeds each atom's element, passes messages along neighbour bonds using only squared distances (so rotating or translating the cell changes nothing), pools the atoms and adds the lattice.
    • The property encoder maps the vector of (normalised) properties to the same space.
    • Both are projected and normalised to unit length; a contrastive loss makes each structure's latent closest to its own property latent and far from the others in the batch. Its weight ramps up during training so the decoders stabilise first.
    • The decoders are trained from the joint latent and from the property latent alone \u2014 that second term is what makes inverse design possible: a point chosen from properties must decode into a structure.

    The training objective is

    \\[ L = w_1 L_\\text{recon}(z_\\text{joint}) + w_2 L_\\text{prop}(z_\\text{joint}) + w_3 L_\\text{recon}(z_p) + w_4 L_\\text{prop}(z_p) + \\lambda(t)\\, L_\\text{InfoNCE}(z_c, z_p). \\]"},{"location":"understand/how-it-works.html#inverse-design-search-in-that-space","title":"Inverse design = search in that space","text":"

    For each target:

    1. Start a population of latents at the property encoder's output for the target, plus a target-dependent random direction (so different targets start apart) and noise.
    2. Optimise them by gradient descent on: distance of the decoded properties to the target (each objective with its loss and weight); a cosine anchor to the target latent; the family's search terms (atoms apart, cubic cell, positions near the prototype, expected charge zero, same element on equivalent sites, loose tolerance factor); and repulsion between population members and from earlier successes (diversity).
    3. Decode: for each latent, choose one element per site group from the decoder's scores with temperature and top-k sampling; the last group is drawn only from elements whose charge balances the others. Place the elements on the prototype; the cell edge comes from ionic radii (a = 2(r_B + r_X) for perovskites).
    4. Rules: symmetry-refine, then apply every hard constraint. Only compositions that pass all are kept.
    5. Rank by distance to the target (select_weight, select_loss), skip duplicates and near-duplicate latents, save as CIF.
    "},{"location":"understand/how-it-works.html#what-family-means","title":"What \"family\" means","text":"

    A family is data, not code: a prototype (site positions), site groups (allowed elements and oxidation states), a cell-size rule, soft search terms, and hard rules. The published perovskite tables are families/perovskite_abx3.yaml; the same machinery runs double_perovskite_a2bbx6.yaml and any file you write.

    "},{"location":"understand/how-it-works.html#the-design-space","title":"The design space","text":"

    For a prototype family, the set of compositions the decoder can output is finite. meidnet space and the Studio enumerate it and score each composition with the structure encoder. The search's candidates are a subset of this space; comparing the two views (encoder prediction vs search's decoded prediction) is a useful sanity check that the reports encourage.

    "},{"location":"understand/how-it-works.html#reproducibility","title":"Reproducibility","text":"

    MEIDNet 2 reproduces MEIDNet 1 exactly: the same featurisation, forward pass, training step and generation output (tests/test_v1_regression.py compares against the frozen v1 code in tests/legacy_v1/). One fix: v1's results depended on Python's hash seed through set iteration order; v2 fixes element order, so the same seed gives the same CIFs in any process.

    "},{"location":"understand/interpretability.html","title":"Why you can trust (or not) a candidate","text":"

    MEIDNet shows its reasoning rather than a score. Where to look:

    "},{"location":"understand/interpretability.html#before-trusting-the-model","title":"Before trusting the model","text":"
    • Training report \u2192 accuracy table. A property rated weak means its predictions are not much better than guessing the mean; targets on it are unreliable.
    • Training report \u2192 alignment. Low retrieval accuracy means property latents do not land near the right structures; inverse design from them is guesswork.
    • Check report \u2192 distributions. A target in a region with almost no training data is extrapolation.
    "},{"location":"understand/interpretability.html#for-each-candidate","title":"For each candidate","text":"
    • The checklist \u2014 every rule's measured value and allowed window. These are deterministic chemistry (charges, radii, distances), not model outputs.
    • The gauges \u2014 where the prediction sits relative to the target and to the training range.
    • Warnings \u2014 predicted value outside the training range (extrapolation) and latent hit the search limit (the search pushed a latent to the z_clip bound; predictions there are less reliable). The model is trained on latents of length 1 and every search moves them further out (typically to length 3\u20135), so the report says this once for the whole run rather than on every card; each candidate's length is the latent_norm column of candidates.csv.
    • Encoder vs search \u2014 in the Studio, compare the search's decoded prediction with the structure encoder's prediction for the same composition. Large disagreement is a red flag.
    "},{"location":"understand/interpretability.html#for-the-run-as-a-whole","title":"For the run as a whole","text":"
    • The funnel \u2014 which rule removed most attempts. If it is a chemical rule (charge balance, tolerance factor), the family's element lists may be too broad for the target; if it is no charge-balancing element, the oxidation-state tables may be missing an entry.
    • Rejected examples \u2014 what the search wanted to make; often more informative than what it kept.
    • Latent map \u2014 whether the population spread out (diversity) or collapsed.
    "},{"location":"understand/interpretability.html#what-meidnet-never-claims","title":"What MEIDNet never claims","text":"

    A candidate is a composition on an ideal prototype with model-predicted properties. Whether it is stable, synthesisable, or has those properties is for meidnet screen, DFT and the lab to decide.

    "},{"location":"understand/limits.html","title":"Scope and roadmap","text":""},{"location":"understand/limits.html#what-works-today","title":"What works today","text":"Structures any CIF with up to max_sites atoms (default 20); any elements Properties any number of scalar columns Training your data, your properties; the published settings by default Generation prototype families: a fixed arrangement of sites whose occupants and cell size are chosen. Built in: cubic ABX\u2083, A\u2082BB\u2032X\u2086; write your own in YAML Rules charge balance, tolerance and octahedral factors, bond windows, minimum distance, predicted-property windows, element filters, and your own Python rules Explanations reports for data, training and generation; the Studio for live what-if"},{"location":"understand/limits.html#what-does-not-work-yet","title":"What does not work (yet)","text":"
    • New atomic arrangements. The decoder's positions are not used to create geometry; candidates are compositions placed on the family prototype. Generating free geometry needs a decoder trained to produce it (the published decoder receives the true positions during training).
    • Spectra, images, text as modalities. The property modality is a vector of scalars. Binned XRD or DOS could be added as a vector modality with its own encoder; it is designed for but not implemented.
    • Large cells. max_sites can be raised, but memory and time grow with the square of it, and the published model was trained on 5-atom cells.
    • Fine-tuning the published model on new properties. Its property head has two outputs; train a new model.
    "},{"location":"understand/limits.html#roadmap-in-order-of-likely-value","title":"Roadmap (in order of likely value)","text":"
    1. Vector modalities (XRD, DOS) with a shared-latent encoder each.
    2. A training.init_from option to continue training from a checkpoint with the same properties.
    3. A geometry-generating decoder variant (trained from prototype positions instead of true positions \u2014 model.decoder_coordinate_input: prototype is the first step and is available as an experimental setting).
    4. More built-in families (spinel, Heusler, rocksalt/zincblende, MXene M\u2082XT\u2082).

    Contributions of family files and rules are the easiest way to help; see the GitHub repository.

    "},{"location":"use/config.html","title":"The configuration file","text":"

    meidnet.yaml holds everything a run depends on. One file = one reproducible experiment: it is copied into every output folder and stored inside model.pt.

    name: my_oxides                 # outputs \u2192 runs/my_oxides/\nplugins: []                     # Python files with custom rules (optional)\n\ndata:        # where the structures and properties come from        \u2192 meidnet check\nmodel:       # size of the network (defaults are the paper's)\ntraining:    # epochs, batch size, learning rate, alignment schedule \u2192 meidnet train\ngeneration:  # family, objectives, targets, rules overrides, budget  \u2192 meidnet generate\n

    Rules of thumb:

    • Unknown keys are errors. A typo such as epocs: stops the run with a message naming the key.
    • Paths are relative to the YAML file, so a project folder can be moved or shared.
    • Defaults reproduce the paper. Leave a section out and the published settings apply.
    • meidnet schema prints the JSON Schema (editors such as VS Code use it for completion and validation).

    The complete list of settings with their meaning and defaults is in the configuration reference.

    "},{"location":"use/config.html#the-four-sections-in-one-picture","title":"The four sections in one picture","text":"
    data:\n  table: materials.csv            # one row per material\n  properties: [{column: band_gap, unit: eV}, {column: dielectric}]\n  align_to_prototype: true        # atoms re-ordered to the family prototype\n\ntraining:\n  epochs: 200\n  contrastive_weight: 5.0         # how strongly structure and property latents are pulled together\n\ngeneration:\n  family: perovskite_abx3\n  variant: oxide\n  objectives:                     # what to steer, and how to compare prediction with target\n    - {property: band_gap, loss: l2, weight: 10000}\n  targets:                        # one search per entry\n    - {band_gap: 2.0}\n  overrides:                      # change a family rule's parameters without editing the family file\n    tolerance_factor: {min: 0.85, max: 1.0}\n  exclude_elements: [Pb]\n
    "},{"location":"use/explore-3d.html","title":"Explore in 3D","text":"

    The Studio can show any set of structures as a map linked to a 3D viewer: each point on the map is one crystal; click a point and its structure appears next to the map, where you can rotate and zoom it. The viewer is chemiscope, an open-source structure\u2013property explorer.

    Open it from any button marked \ud83e\uddca:

    Button Where Shows \ud83e\uddca Explore all compositions in 3D Family block the design space: every composition of the family on its prototype \ud83e\uddca Map + 3D of all compositions Targets block the same design space, to compare predictions with your targets \ud83e\uddca All candidates in 3D Candidates block the candidates of your last search \ud83e\uddca See these structures in 3D Data block your data after Check my data, or the Perov-5 training data behind the published model \ud83e\uddca show in the 3D explorer the details window of any composition the design space, with that composition selected

    You can also open the page directly in the explorer with a deep link: ?explore=space, ?explore=candidates or ?explore=data.

    "},{"location":"use/explore-3d.html#reading-the-map","title":"Reading the map","text":"
    • Axes. For the design space, the two predicted properties (for the published model: formation enthalpy across, band gap up). For your data, your property columns. For candidates, their predicted properties. Any other property can be put on an axis from the map's settings menu.
    • Colour (design space): yellow = passes every rule, purple = rejected by at least one rule. Symbol: pass / rejected. For candidates the colour is the search score (lower = closer to the targets).
    • Properties you can plot or colour by:
    Name Meaning formula the composition site_A, site_B, \u2026 the element on each site group lattice_a the cubic cell edge estimated from ionic radii (\u00c5) pred_<property> the model's prediction from the structure encoder, with its unit rule_<name> the value each rule measures, e.g. rule_tolerance_factor passes_all, verdict 1 / \"pass\" when every rule passes (with the limits currently set on the page) score, round, target_<property>, still_passes candidates only: search score, round found, the target, and whether it still passes the current rules

    The design space shows up to 2,000 compositions: those passing every rule first, then a fixed random sample of the rest. Your data shows the structures that passed Check my data; the Perov-5 training data shows its first 1,500 structures.

    "},{"location":"use/explore-3d.html#clicking-a-point","title":"Clicking a point","text":"

    The crystal appears on the right: drag to rotate, scroll to zoom. The panel below lists every property of that structure. Bonds and the unit cell are drawn. Design-space structures are the ideal prototype filled with that composition's elements, at the cell size the family's lattice rule estimates.

    "},{"location":"use/explore-3d.html#when-chemiscope-cannot-load","title":"When chemiscope cannot load","text":"

    The explorer is downloaded from a public CDN the first time you open it (about 3 MB). Without internet access, or if the CDN is blocked, the Studio switches to its built-in viewer: a scatter plot of the same two axes (coloured points pass every rule) linked to the Studio's own crystal viewer \u2014 click a point, drag the cell to rotate, double-click to spin. Nothing else changes.

    "},{"location":"use/explore-3d.html#save-the-dataset","title":"Save the dataset","text":"

    \u2b07 dataset JSON saves what you see as a chemiscope dataset. Open it later at chemiscope.org (the site reads the file inside your browser; nothing is uploaded), share it with a colleague, or put it in a paper's supplementary material.

    The same datasets can be produced without the Studio:

    meidnet space meidnet.yaml --chemiscope space.json   # the design space of your configuration\n

    and from Python:

    from meidnet.studio.chemiscope import space_dataset, candidates_dataset, records_dataset\n

    In the Colab notebooks, the show3d(...) helper displays structures inline with chemiscope.show.

    "},{"location":"use/explore-3d.html#credits","title":"Credits","text":"

    chemiscope is a third-party library (BSD-3-Clause licence) by G. Fraux, R. K. Cersonsky and M. Ceriotti. If you use the 3D explorer in published work, please cite: G. Fraux, R. K. Cersonsky, M. Ceriotti, Chemiscope: interactive structure-property explorer for materials and molecules, Journal of Open Source Software 5, 2117 (2020).

    "},{"location":"use/reports.html","title":"Reading the reports","text":"

    Each command writes a self-contained HTML file in runs/<name>/. All three open with a one-sentence verdict (green / amber / red), show the evidence, and end with what to do next.

    "},{"location":"use/reports.html#check_reporthtml","title":"check_report.html","text":"
    • Verdict: how many rows are usable; the most common problem if many were skipped.
    • What MEIDNet read: table, columns, structure source, family and how many structures were re-ordered to the prototype.
    • Rows that were skipped: one row per reason with counts and example ids, and a \"how do I fix these?\" box.
    • Your properties: histogram, min/median/max, and warnings (e.g. \"72 % of band-gap values are 0 \u2014 the model sees few examples of other values\").
    • Your structures: atoms per cell, most common elements.
    "},{"location":"use/reports.html#training_reporthtml","title":"training_report.html","text":"
    • Verdict: are the properties predicted well from structure alone?
    • How accurate: per property, typical error (MAE), natural spread (std), R\u00b2, and a word \u2014 good if the error is under a quarter of the spread, fair under a half, weak otherwise \u2014 with predicted-vs-true plots.
    • Did the two modalities align: retrieval accuracy (a structure's own property vector is its nearest match x % of the time) and the cosine curve. Inverse design depends on this.
    • Training curves and notes (e.g. the alignment ramp longer than training).
    "},{"location":"use/reports.html#generation_reporthtml","title":"generation_report.html","text":"
    • Verdict: how many candidates, and whether any predicted value lies outside the training range.
    • Per target: the funnel (compositions alive after each rule \u2014 the biggest drop is the rule that limits the family most), the candidate cards, and examples of rejected compositions with the rule and the measured value.
    • A candidate card shows: elements per site, cell edge, round found, a gauge per objective (training range as a track, target as a tick, prediction as a dot), the checklist with each rule's value and allowed window (hover for the explanation), and warnings: prediction outside the training range, or a latent far from where training latents live (predictions less reliable).
    • Where the search went: latents before and after optimisation projected to two dimensions.
    "},{"location":"use/reports.html#candidatescsv-and-generationjson","title":"candidates.csv and generation.json","text":"

    The CSV has one row per candidate with targets, predictions, elements, cell edge, every rule's value, score and file name. The JSON holds the full funnel counts, rejected examples and settings \u2014 everything the report shows.

    "},{"location":"use/studio.html","title":"MEIDNet Studio \u2014 the live workbench","text":"
    meidnet studio                 # the published perovskite model\nmeidnet studio meidnet.yaml    # your project\n

    A page opens in your browser. The workflow is drawn as a strip of seven coloured blocks; click a block to open it. Whatever you change, the blocks after it update at once, and a line under the strip says, in words, what the change did to each later block \u2014 the way a LabVIEW diagram shows data flowing after you turn a knob.

    No installation? Use the hosted Studio \u2014 the same page, running on a shared server.

     Data \u2500\u2500\u25ba Model \u2500\u2500\u25ba Family \u2500\u2500\u25ba Rules \u2500\u2500\u25ba Targets \u2500\u2500\u25ba Search \u2500\u2500\u25ba Candidates\n 11,356    MAE       halide     5 rules   Eg 2.0     idle       0 found\n materials 0.17 eV   924 comps  27 pass   \u0394Hf \u22120.1\n
    "},{"location":"use/studio.html#reading-the-workflow-strip","title":"Reading the workflow strip","text":"

    Each block has its own colour, used everywhere on the page (its panel, its charts, its chips in the flow chain):

    Block Colour What it holds Data blue the table of materials the model learns from Model orange the trained network (published or yours) Family green the crystal prototype and the elements allowed on each site Rules amber the hard checks every candidate must pass Targets pink the property values you want Search dark green the latent search of the paper Candidates violet what the search found
    • The dot in the corner of a block tells its state: grey = idle, green = fine, amber = just changed or needs a look (for example a target outside the training range), red = error, pulsing = running.
    • The two lines inside a block are its live status: \"924 comps\", \"27 pass all\", \"MAE 0.17\", \"running\".
    • The label on each arrow says what flows to the next block: \"11,356 materials \u00b7 2 properties\", \"924 compositions\", \"27 pass all rules\", \"3 found\".
    • Moving dots on the arrows appear after every change: they travel from the block you edited to every block it reaches, and those blocks flash. This is the quickest way to see what depends on what.

    Dashed pink lines are always a target; a violet \u2605 is always something the search found; green \u2713 means passes.

    "},{"location":"use/studio.html#the-flow-chain-and-the-flow-log","title":"The flow chain and the flow log","text":"

    Under the strip, the flow chain repeats the last change in words, block by block, for example:

    Rules: Goldschmidt tolerance factor max 1.05 \u2192 1 \u279c Rules: pass all rules 27 \u2192 23 (\u22124) \u279c Targets: best match CsSnBr\u2083 \u2192 CsGeBr\u2083 \u279c Candidates: 1 of 3 found candidates would now be rejected

    Click a step to open that block.

    The Flow log below it keeps the last 60 changes. Click an entry to replay its flow on the strip.

    "},{"location":"use/studio.html#what-is-live-and-how","title":"What is live, and how","text":"

    For a prototype family the set of possible compositions is finite (924 for halide perovskites, 10,340 for halide double perovskites). When you open a family the server builds this design space once: every composition on the prototype, the value of each rule's descriptor (tolerance factor, octahedral factor, \u2026) and the model's predicted properties from the structure encoder. From then on, the page can answer instantly:

    You change You see immediately a rule's limit how many compositions still pass that rule and all rules; the histogram of the descriptor with the window shaded; the funnel an element (click a chip to exclude it) compositions removed, new counts, new best matches a target the compositions predicted closest; the target drawn on the predicted-property histogram and scatter; an extrapolation warning if it lies outside the training range the comparison kind (as close as possible / at most / at least) the ranking anything, after a search which of the found candidates would now be rejected

    Descriptor values are computed by the same Python functions that meidnet generate uses, so what the page shows is what the search enforces.

    "},{"location":"use/studio.html#the-two-directions-of-the-model","title":"The two directions of the model","text":"

    The live tables use structure \u2192 property (encode the prototype structure, decode its properties): fast, and it scores every composition. The Search block runs the paper's property \u2192 structure latent optimisation and shows its progress (target, round, step, loss), the log, and each candidate as it is found, with its checklist. Both views are of the same network; comparing them tells you whether the search found what the encoder considers best, and whether the predictions agree.

    "},{"location":"use/studio.html#the-blocks","title":"The blocks","text":"
    • Data \u2014 how many materials were usable, why rows were skipped, property distributions, the elements present; Bring your own data (below).
    • Model \u2014 properties, units, training ranges, validation errors, loss and alignment curves; Train your own model (below).
    • Family \u2014 prototype sites with a rotatable 3D preview of the cell, element chips per site group with their charges (click to exclude), cell-size rule.
    • Rules \u2014 each rule with its explanation, parameters, pass count and descriptor histogram; add a predicted-property window rule; the funnel.
    • Targets \u2014 one card per objective: target value, comparison kind, weights; the predicted-property scatter of all compositions (coloured = passes, grey = rejected, \u2605 = found by the search). Click any point or table row for its checklist and its crystal in 3D.
    • Search \u2014 budget (candidates, latents per round, rounds, steps), a rough time estimate, Run/Stop, progress, log, link to the full report.
    • Candidates \u2014 one card per candidate: a rotatable 3D cell, elements, gauges of predicted values against the targets, the rule checklist, warnings, CIF links.

    Crystals are drawn by a small built-in viewer: drag to rotate, double-click to spin.

    "},{"location":"use/studio.html#beginner-view-and-researcher-mode","title":"Beginner view and Researcher mode","text":"

    The page opens in the beginner view: every block has an Input \u279c Logic \u279c Output strip with live numbers (\"924 compositions \u279c 5 hard rules, applied in order \u279c 27 pass every rule\") and the name of the Python function that does the work, a short In plain words explanation, and a glossary in the side panel (composition, rule, target, latent space, extrapolation).

    The Researcher mode switch in the header trades these for the technical view: the \u270e Edit as text YAML drawer, and a Behind the scenes panel in every block with the meidnet.yaml and the Python of that block. The choice is remembered by your browser.

    "},{"location":"use/studio.html#behind-the-scenes","title":"Behind the scenes","text":"

    At the bottom of every block, Behind the scenes shows the same settings as the part of meidnet.yaml they come from and as the Python that does the same thing \u2014 with the values currently on the page. Copy either into your own project; nothing on the page is hidden from the command line.

    "},{"location":"use/studio.html#bring-your-own-data","title":"Bring your own data","text":"

    The Data block has an adapter: it maps your column names to what MEIDNet expects. Nothing in your files is changed.

    1. Upload a table \u2014 CSV, Excel (.xlsx) or JSON, one row per material \u2014 with an id column, numeric property columns and the structures, either as a column of CIF text or as a zip of <id>.cif files (select both files together). Parquet works too when pyarrow is installed.
    2. Map the columns. The Studio suggests the id column, the CIF column and the numeric property columns; pick the properties you want, give each a unit (eV, eV/atom, \u2026) and optionally a label, and choose the family (and variant) your structures belong to. Align to prototype re-orders each structure's atoms onto the family's sites, which generation needs; raise the prototype tolerance if many rows are skipped as \"does not match the prototype\".
    3. Check my data. Every row is parsed and checked; you see how many are usable, why the others were skipped (with examples), the distribution of each property and the elements present.
    4. Train in the Model block: choose the number of epochs and press \u25b6 Train. The loss and alignment curves grow while it trains; at the end the validation errors appear and every block switches to your model and your properties \u2014 the design space is re-scored, the targets are your properties. Each property's typical error (MAE) is judged in words against the spread (standard deviation) of that property in your data \u2014 good below a quarter of the spread, fair below half, weak above \u2014 the same rule the training report uses; \"matched correctly\" is how often a validation structure is matched to its own properties.
    5. Switch between your model and the published model at any time in the Model block. Start over removes your upload and model from the session.

    A few epochs give a rough model in a minute or two; 100\u2013200 epochs are typical for real work \u2014 run meidnet train locally (with a GPU if you have one) and open the result with meidnet studio meidnet.yaml.

    On the hosted Studio

    Each browser tab has its own private session: other visitors never see your data, model or candidates, and a session's files are deleted after an hour without activity. The shared machine allows up to 1,500 rows and 8 MB per upload, 30 training epochs, 20 atoms per cell, and two jobs (training or search) at a time across all visitors; search budgets are capped too. Run the Studio on your own computer to lift every limit.

    "},{"location":"use/studio.html#edit-the-configuration-as-text","title":"Edit the configuration as text","text":"

    \u270e Edit as text in the header opens a drawer with the generation section of meidnet.yaml exactly as the blocks have it: family, variant, excluded elements, rule overrides, extra rules, objectives, targets and search budget. Change anything and press Apply \u2014 the blocks update and the flow chain shows what changed in each.

    • Validate only checks without applying. Errors name the exact setting, for example steps: Input should be greater than or equal to 1.
    • On the hosted Studio, values above the shared limits are accepted but capped, and a note says so: rounds 99 \u2192 6 (limit on this shared server).
    • Predicted-property windows added in the Rules block appear as extra_constraints, so the file round-trips: apply it, download it, run it with meidnet generate.

    \u2b07 Download meidnet.yaml (also in the Search block) writes the whole configuration \u2014 rules switched off on the page are listed under disabled_rules, excluded elements become exclude_elements, changed limits become overrides \u2014 so the command line reproduces exactly what the page shows. Settings the blocks do not show (for example search terms set in your own meidnet.yaml) are kept as they are.

    "},{"location":"use/studio.html#explore-in-3d","title":"Explore in 3D","text":"

    Buttons marked \ud83e\uddca open the 3D explorer: a map with one point per structure, linked to a 3D viewer \u2014 the design space of the family, the candidates of a search, or the training data. See Explore in 3D.

    "},{"location":"use/studio.html#deep-links","title":"Deep links","text":"

    Add to the address to open the page at a given place:

    Address ends with Opens ?panel=rules the Rules block (any block: data, model, family, rules, targets, search, candidates) ?explore=space the 3D explorer on the design space (candidates or data work too)

    Both can be combined: ?panel=targets&explore=space.

    "},{"location":"use/studio.html#dark-mode","title":"Dark mode","text":"

    The \ud83c\udf19 / \u2600\ufe0f button in the header switches between the light and the dark theme; your browser remembers the choice.

    "},{"location":"use/studio.html#static-copy-for-a-website","title":"Static copy for a website","text":"
    meidnet studio --export-static docs/studio.html\n

    embeds the state and design spaces into one HTML file. Everything works without a server except uploading, training and running a new search \u2014 useful for a plain web host. The hosted Studio is the full server instead:

    meidnet studio --public --host 0.0.0.0 --port 7860 --run-root /tmp/runs --docs-dir site\n

    --public gives every visitor a private session and caps the budgets so the shared machine stays responsive; --docs-dir also serves a built copy of this documentation at /docs/.

    "},{"location":"use/your-data.html","title":"Bring your own dataset","text":"

    This page takes a table of structures and properties to designed candidates. Every step writes a report that explains what happened; nothing requires Python.

    "},{"location":"use/your-data.html#0-install","title":"0. Install","text":"
    pip install meidnet\n
    "},{"location":"use/your-data.html#1-describe-your-data-meidnet-init","title":"1. Describe your data: meidnet init","text":"
    meidnet init --name my_oxides --table materials.csv \\\n             --id-column material_id --cif-column cif \\\n             --properties band_gap dielectric \\\n             --family perovskite_abx3 --variant oxide\n

    This writes a commented meidnet.yaml. Open it: every line says what it does. If your CIFs are files instead of a column, set cif_column: null and structures_dir: structures/.

    data:\n  table: materials.csv\n  id_column: material_id\n  cif_column: cif\n  properties:\n    - {column: band_gap,   unit: eV}\n    - {column: dielectric, unit: \"\"}\n  val_fraction: 0.1\n  max_sites: 20\n  align_to_prototype: true\n
    "},{"location":"use/your-data.html#2-is-it-usable-meidnet-check","title":"2. Is it usable? meidnet check","text":"
    meidnet check meidnet.yaml\n
    412 of 430 materials usable.\n  skipped     14  does not match the perovskite_abx3 prototype   e.g. m_031, m_118, m_204\n  skipped      4  missing property value                          e.g. m_077, m_300\nreport: runs/my_oxides/check_report.html\n

    The report shows the verdict, the distribution of each property, the elements present and, for each skip reason, how to fix it (for example raise data.prototype_tolerance for distorted cells).

    "},{"location":"use/your-data.html#3-learn-the-latent-space-meidnet-train","title":"3. Learn the latent space: meidnet train","text":"
    meidnet train meidnet.yaml            # 200 epochs by default\nmeidnet train meidnet.yaml --epochs 5 # a quick smoke test first\n

    training_report.html answers three questions in plain words: how accurate is each property prediction (error vs natural spread \u2192 good / fair / weak), did the two modalities align (retrieval accuracy), and what were the training curves. The model is saved as runs/my_oxides/model.pt with the property names, units and normalisation inside; meidnet info runs/my_oxides/model.pt describes it.

    Time

    About 21 s per epoch per 11k structures on a laptop GPU; roughly 10\u00d7 slower on CPU. Use Colab's free GPU (notebook 02) if you have none.

    "},{"location":"use/your-data.html#4-design-meidnet-generate","title":"4. Design: meidnet generate","text":"

    Set the targets in meidnet.yaml:

    generation:\n  family: perovskite_abx3\n  variant: oxide\n  objectives:\n    - {property: band_gap,   loss: l2,       weight: 10000}\n    - {property: dielectric, loss: at_least, weight: 5000}\n  targets:\n    - {band_gap: 2.0, dielectric: 20}\n    - {band_gap: 3.0, dielectric: 20}\n  per_target: 4\n  exclude_elements: [Pb, Cd]\n
    meidnet generate meidnet.yaml\n

    generation_report.html shows, per target, the funnel (how many attempts each rule removed), one card per candidate (elements, cell, each rule's measured value and window, predicted vs target gauges, warnings about extrapolation) and examples of rejected compositions. CIFs and candidates.csv are in runs/my_oxides/generation/.

    "},{"location":"use/your-data.html#5-explore-live-meidnet-studio","title":"5. Explore live: meidnet studio","text":"
    meidnet studio meidnet.yaml\n

    Change a rule's limit, exclude an element or move a target and see immediately how many compositions pass and which are predicted closest; run searches and export the YAML. Studio guide.

    "},{"location":"use/your-data.html#6-confirm","title":"6. Confirm","text":"

    Predicted properties are estimates. meidnet screen runs/my_oxides/generation (MACE universal potential, optional extra) gives a first stability filter; DFT or experiment has the last word.

    "}]} \ No newline at end of file diff --git a/site/sitemap.xml b/site/sitemap.xml index 0f84d1e59bd6da4e9b3067029122835e83199fbf..b5387bc991a6c78e30806c4fdf74fe59eabdbd21 100644 --- a/site/sitemap.xml +++ b/site/sitemap.xml @@ -4,6 +4,42 @@ https://babu09-meidnet.hf.space/docs/index.html 2026-10-03 + + https://babu09-meidnet.hf.space/docs/benchmarks/index.html + 2026-10-03 + + + https://babu09-meidnet.hf.space/docs/benchmarks/carbon24.html + 2026-10-03 + + + https://babu09-meidnet.hf.space/docs/benchmarks/mp20.html + 2026-10-03 + + + https://babu09-meidnet.hf.space/docs/benchmarks/perov5.html + 2026-10-03 + + + https://babu09-meidnet.hf.space/docs/benchmarks/results/paper-inverse-design.html + 2026-10-03 + + + https://babu09-meidnet.hf.space/docs/benchmarks/results/paper-representation.html + 2026-10-03 + + + https://babu09-meidnet.hf.space/docs/benchmarks/results/paper-results-mace-screen.html + 2026-10-03 + + + https://babu09-meidnet.hf.space/docs/community/index.html + 2026-10-03 + + + https://babu09-meidnet.hf.space/docs/community/contribute.html + 2026-10-03 + https://babu09-meidnet.hf.space/docs/examples/double-perovskite.html 2026-10-03 @@ -12,6 +48,14 @@ https://babu09-meidnet.hf.space/docs/examples/perov5.html 2026-10-03 + + https://babu09-meidnet.hf.space/docs/explore/capabilities.html + 2026-10-03 + + + https://babu09-meidnet.hf.space/docs/explore/datasets.html + 2026-10-03 + https://babu09-meidnet.hf.space/docs/recipes/add-constraint.html 2026-10-03 diff --git a/site/sitemap.xml.gz b/site/sitemap.xml.gz index 9f9e7bd73a4aac88d7d0606eea259fabbf929573..80d9f4c47be139d8ba5db77b21de74c653c7cef6 100644 Binary files a/site/sitemap.xml.gz and b/site/sitemap.xml.gz differ diff --git a/site/start/colab.html b/site/start/colab.html index 2ad2bf5a5d4c2ec2c40ae95f36c09d437658f23a..74ba835520665e928123c99edd31317f658180a0 100644 --- a/site/start/colab.html +++ b/site/start/colab.html @@ -22,12 +22,12 @@ - + - Run in Colab - MEIDNet + Run in Colab - MEIDNet Prism @@ -53,6 +53,8 @@ + + @@ -92,9 +94,9 @@
    diff --git a/site/use/studio.html b/site/use/studio.html index 0b2e545b6a804308d0ca00f1850654cf0a11be5c..0e058eaf52463cf3334d6189e0f6f3b09467ea8a 100644 --- a/site/use/studio.html +++ b/site/use/studio.html @@ -13,7 +13,7 @@ - + @@ -22,12 +22,12 @@ - + - MEIDNet Studio (live workbench) - MEIDNet + MEIDNet Studio (live workbench) - MEIDNet Prism @@ -53,6 +53,8 @@ + + @@ -92,9 +94,9 @@
    - - + + - - - - + + + +
  90. + + + + + + + + Add a property + + + + + + + + +
  91. + + + + + + + + + + +
  92. + + + + + + + + Exclude or restrict elements + + + + + + + + +
  93. + + + + + + + + + + +
  94. + + + + + + + + Change the material family + + + + + + + + +
  95. + + + + + + + + + + +
  96. + + + + + + + + Add a rule (constraint) + + + + + + + + +
  97. + + + + + + + + + + +
  98. + + + + + + + + Screen stability with MACE + + + + + + + + +
  99. + + + + + + + + + + +
  100. + + + + + + + + How MEIDNet works + + + + + + + + +
  101. + + + + + + + + + + +
  102. + + + + + + + + Why you can trust (or not) a candidate + + + + + + + + +
  103. + + + + + + + + + + +
  104. + + + + + + + + Scope and roadmap + + + + + + + + +
  105. + + + + + + + + + + +
  106. + + + + + + + + Perov-5 (the paper) + + + + + + + + +
  107. + + + + + + + + + + +
  108. + + + + + + + + Double perovskites + + + + + + + + +
  109. + + + + + + + + + + + + + @@ -1676,6 +1903,58 @@ + + + + + + + + + + +
  110. + + + + + + + + + + +
  111. + + @@ -1779,10 +2066,10 @@
  112. - + - Beginner mode + Beginner view and Researcher mode @@ -2027,15 +2314,14 @@ the encoder considers best, and whether the predictions agree.

    targets, the rule checklist, warnings, CIF links.
  113. Crystals are drawn by a small built-in viewer: drag to rotate, double-click to spin.

    -

    Beginner mode

    -

    The Beginner mode switch in the header is on by default. It adds, in every block:

    -
      -
    • an Input โžœ Logic โžœ Output strip with live numbers ("924 compositions โžœ 5 hard rules, applied in order โžœ - 27 pass every rule") and the name of the Python function that does the work;
    • -
    • a short In plain words explanation;
    • -
    • a glossary in the side panel (composition, rule, target, latent space, extrapolation).
    • -
    -

    Switch it off for a compact page. The choice is remembered by your browser.

    +

    Beginner view and Researcher mode

    +

    The page opens in the beginner view: every block has an Input โžœ Logic โžœ Output strip with live numbers +("924 compositions โžœ 5 hard rules, applied in order โžœ 27 pass every rule") and the name of the Python function that +does the work, a short In plain words explanation, and a glossary in the side panel (composition, rule, +target, latent space, extrapolation).

    +

    The Researcher mode switch in the header trades these for the technical view: the โœŽ Edit as text YAML +drawer, and a Behind the scenes panel in every block with the meidnet.yaml and the Python of that block. +The choice is remembered by your browser.

    Behind the scenes

    At the bottom of every block, Behind the scenes shows the same settings as the part of meidnet.yaml they come from and as the Python that does the same thing โ€” with the values currently on the page. Copy either into diff --git a/site/use/your-data.html b/site/use/your-data.html index 42d0a44b25491e9f9b39f79121117313230bf671..c1e069909e27ad88e6d71a823f287abb741357fb 100644 --- a/site/use/your-data.html +++ b/site/use/your-data.html @@ -22,12 +22,12 @@ - + - Bring your own dataset - MEIDNet + Bring your own dataset - MEIDNet Prism @@ -53,6 +53,8 @@ + + @@ -92,9 +94,9 @@