know-do-graph 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. agents/__init__.py +0 -0
  2. agents/extraction_agent/__init__.py +0 -0
  3. agents/extraction_agent/agent.py +170 -0
  4. agents/graph_agent/__init__.py +5 -0
  5. agents/graph_agent/agent.py +373 -0
  6. agents/graph_agent/tools.py +2106 -0
  7. agents/maintenance_agent/__init__.py +0 -0
  8. agents/maintenance_agent/agent.py +283 -0
  9. agents/orchestrator/__init__.py +0 -0
  10. agents/orchestrator/agent.py +217 -0
  11. agents/review_agent/__init__.py +0 -0
  12. agents/review_agent/agent.py +188 -0
  13. agents/review_agent/tools.py +472 -0
  14. api/__init__.py +0 -0
  15. api/main.py +136 -0
  16. api/routes/__init__.py +0 -0
  17. api/routes/agent.py +81 -0
  18. api/routes/entries.py +411 -0
  19. api/routes/graph.py +132 -0
  20. api/routes/mem.py +179 -0
  21. api/routes/remote.py +815 -0
  22. api/routes/remote_sync.py +230 -0
  23. api/routes/retrieve.py +88 -0
  24. core/__init__.py +0 -0
  25. core/app_state.py +9 -0
  26. core/events.py +84 -0
  27. core/extraction/__init__.py +0 -0
  28. core/extraction/wikilink_parser.py +48 -0
  29. core/graph/__init__.py +0 -0
  30. core/graph/graph.py +204 -0
  31. core/memory/__init__.py +0 -0
  32. core/memory/memgraph.py +458 -0
  33. core/resources/starter.db +0 -0
  34. core/retrieval/__init__.py +0 -0
  35. core/retrieval/embedder.py +122 -0
  36. core/retrieval/fusion.py +52 -0
  37. core/retrieval/progressive.py +399 -0
  38. core/retrieval/retrieval.py +346 -0
  39. core/retrieval/vector_store.py +91 -0
  40. core/schemas/__init__.py +0 -0
  41. core/schemas/edge.py +46 -0
  42. core/schemas/entry.py +388 -0
  43. core/storage/__init__.py +0 -0
  44. core/storage/database.py +104 -0
  45. core/storage/models.py +66 -0
  46. core/storage/repository.py +243 -0
  47. core/sync/__init__.py +20 -0
  48. core/sync/autolink.py +301 -0
  49. core/sync/db_merge.py +297 -0
  50. core/sync/db_watcher.py +84 -0
  51. core/sync/remote_sync.py +345 -0
  52. examples/__init__.py +0 -0
  53. examples/example_entries.py +206 -0
  54. examples/pymatgen_interface_examples.py +811 -0
  55. frontend/dist/assets/index-BLfo7ZZu.css +1 -0
  56. frontend/dist/assets/index-G-mYbZ9R.js +83 -0
  57. frontend/dist/assets/index-G-mYbZ9R.js.map +1 -0
  58. frontend/dist/index.html +92 -0
  59. know_do_graph-0.1.0.dist-info/METADATA +765 -0
  60. know_do_graph-0.1.0.dist-info/RECORD +63 -0
  61. know_do_graph-0.1.0.dist-info/WHEEL +4 -0
  62. know_do_graph-0.1.0.dist-info/entry_points.txt +2 -0
  63. main.py +944 -0
@@ -0,0 +1,811 @@
1
+ """Seed the graph with generalised pymatgen interface-building knowledge and runnable scripts.
2
+
3
+ Populates:
4
+ - Dependency entries (pymatgen, ASE, mp-api, ...)
5
+ - Generalised capability / procedure entries (lattice matching, slab generation, ...)
6
+ - Runnable script entries (downloadable via GET /entries/{id}/download)
7
+
8
+ Note: Specific material instances (Si, Ge, GaN, ...) and specific material-pair
9
+ interface nodes are intentionally excluded — those are examples / instantiations,
10
+ not generalised knowledge worth storing in the graph.
11
+
12
+ Usage
13
+ -----
14
+ python examples/pymatgen_interface_examples.py
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import sys
20
+ from pathlib import Path
21
+
22
+ sys.path.insert(0, str(Path(__file__).parent.parent))
23
+
24
+ from core import app_state
25
+ from core.schemas.edge import Edge, EdgeRelation
26
+ from core.schemas.entry import Entry, EntryMetadata, EntryType, RefinementStatus, VerificationStatus
27
+ from core.storage.database import SessionLocal, init_db
28
+ from core.storage.repository import EdgeRepository, EntryRepository
29
+
30
+ # ---------------------------------------------------------------------------
31
+ # Helper
32
+ # ---------------------------------------------------------------------------
33
+
34
+ def _existing_titles(repo: EntryRepository) -> set[str]:
35
+ return {e.title for e in repo.get_all()}
36
+
37
+
38
+ def _create(repo: EntryRepository, data: dict) -> Entry | None:
39
+ entry = Entry(**data)
40
+ return repo.create(entry)
41
+
42
+
43
+ # ---------------------------------------------------------------------------
44
+ # 1. Dependency entries
45
+ # ---------------------------------------------------------------------------
46
+
47
+ DEPS: list[dict] = [
48
+ {
49
+ "title": "pymatgen",
50
+ "entry_type": EntryType.dependency,
51
+ "tags": ["python", "materials-science", "library", "dft"],
52
+ "aliases": ["Python Materials Genomics"],
53
+ "content": """\
54
+ # pymatgen — Python Materials Genomics
55
+
56
+ Core library for materials analysis, structure manipulation, and interface construction.
57
+
58
+ ## Install
59
+ ```bash
60
+ pip install pymatgen
61
+ ```
62
+
63
+ ## Key modules for interfaces
64
+ - `pymatgen.core.surface` — `SlabGenerator`, `ReconstructionGenerator`
65
+ - `pymatgen.analysis.interfaces` — `SubstrateAnalyzer`, `InterfaceBuilder`, `CohenValence`
66
+ - `pymatgen.core.structure` — `Structure`, `Lattice`
67
+ - `pymatgen.io.vasp` — POSCAR / CONTCAR read-write
68
+ - `pymatgen.io.ase` — ASE ↔ pymatgen bridge
69
+
70
+ ## External
71
+ - https://pymatgen.org/
72
+ - https://github.com/materialsproject/pymatgen
73
+ """,
74
+ "metadata": EntryMetadata(
75
+ source_provenance="https://pymatgen.org/",
76
+ refinement_status=RefinementStatus.validated,
77
+ verification_status=VerificationStatus.community_tested,
78
+ ),
79
+ },
80
+ {
81
+ "title": "ASE",
82
+ "entry_type": EntryType.dependency,
83
+ "tags": ["python", "atomistic", "library"],
84
+ "aliases": ["Atomic Simulation Environment"],
85
+ "content": """\
86
+ # ASE — Atomic Simulation Environment
87
+
88
+ Python library for atomistic simulations.
89
+ Interoperates with [[pymatgen]] via `pymatgen.io.ase`.
90
+
91
+ ## Install
92
+ ```bash
93
+ pip install ase
94
+ ```
95
+
96
+ ## External
97
+ - https://wiki.fysik.dtu.dk/ase/
98
+ """,
99
+ "metadata": EntryMetadata(
100
+ source_provenance="https://wiki.fysik.dtu.dk/ase/",
101
+ refinement_status=RefinementStatus.validated,
102
+ ),
103
+ },
104
+ {
105
+ "title": "mp-api",
106
+ "entry_type": EntryType.dependency,
107
+ "tags": ["python", "materials-project", "api", "library"],
108
+ "aliases": ["Materials Project API", "MPRester"],
109
+ "content": """\
110
+ # mp-api — Materials Project REST API Client
111
+
112
+ Download bulk structures, band structures, and properties from the Materials Project database.
113
+
114
+ ## Install
115
+ ```bash
116
+ pip install mp-api
117
+ ```
118
+
119
+ ## Usage
120
+ ```python
121
+ from mp_api.client import MPRester
122
+ with MPRester("YOUR_MP_API_KEY") as mpr:
123
+ structure = mpr.get_structure_by_material_id("mp-149") # Si
124
+ ```
125
+
126
+ ## External
127
+ - https://api.materialsproject.org/
128
+ - https://next-gen.materialsproject.org/
129
+ """,
130
+ "metadata": EntryMetadata(
131
+ source_provenance="https://api.materialsproject.org/",
132
+ refinement_status=RefinementStatus.validated,
133
+ ),
134
+ },
135
+ ]
136
+
137
+ # ---------------------------------------------------------------------------
138
+ # 2. Procedure entries
139
+ # ---------------------------------------------------------------------------
140
+
141
+ # NOTE: MATERIALS and INTERFACES lists removed. Specific material instances
142
+ # (Si, Ge, GaN, etc.) and specific material-pair interface nodes are
143
+ # instantiations, not generalised knowledge.
144
+
145
+ MATERIALS: list[dict] = [] # intentionally empty
146
+ INTERFACES: list[dict] = [] # intentionally empty
147
+
148
+ PROCEDURES: list[dict] = [
149
+ {
150
+ "title": "Build Material Interface via Slab Stacking",
151
+ "entry_type": EntryType.data,
152
+ "tags": ["interface-construction", "slab-stacking", "pymatgen", "heterostructure"],
153
+ "content": """\
154
+ ## Build Material Interface via Slab Stacking
155
+
156
+ General workflow for constructing a heterointerface between two materials using
157
+ the slab-stacking method with [[pymatgen]].
158
+
159
+ ### Prerequisites
160
+ - [[pymatgen]]
161
+ - Bulk structures for substrate and film (from [[mp-api]] or local CIF files)
162
+
163
+ ### Steps
164
+ 1. Download bulk structures using [[Download Bulk Structure from Materials Project]].
165
+ 2. Run [[Substrate Analysis Script]] to find compatible orientations and quantify lattice mismatch.
166
+ 3. Generate oriented slabs for both materials with [[Slab Generation Script]].
167
+ 4. Build the interface supercell with [[Interface Builder Script]].
168
+ 5. Run ionic relaxation (e.g. [[ASE Relaxation]] with a DFT or ML calculator).
169
+ 6. Compute interface energy with [[Interface Energy Calculation Script]].
170
+ 7. Optionally compute band alignment with [[Band Alignment Calculation Script]].
171
+
172
+ ### Key considerations
173
+ - Lattice mismatch determines strain state; use [[Lattice Matching via ZSL Algorithm]] to
174
+ find the minimum-strain supercell.
175
+ - Polar surfaces (e.g. wurtzite nitrides) require termination treatment to
176
+ avoid a diverging electrostatic potential (Tasker Type-III rule).
177
+ - Vacuum layer thickness: ≥ 15 Å to suppress image interactions.
178
+
179
+ ### Expected outputs
180
+ - Interface POSCAR / CIF file
181
+ - Interface energy in J/m²
182
+ - Band offsets (from DFT or model calculations)
183
+ """,
184
+ "metadata": EntryMetadata(refinement_status=RefinementStatus.linked),
185
+ },
186
+ {
187
+ "title": "Download Bulk Structure from Materials Project",
188
+ "entry_type": EntryType.procedure,
189
+ "tags": ["materials-project", "pymatgen", "data-retrieval", "mp-api"],
190
+ "content": """\
191
+ ## Download Bulk Structure from Materials Project
192
+
193
+ Retrieve a relaxed bulk structure using [[mp-api]].
194
+
195
+ ### Prerequisites
196
+ - [[mp-api]] (`pip install mp-api`)
197
+ - A valid Materials Project API key (set as `MP_API_KEY` environment variable)
198
+
199
+ ### Steps
200
+ 1. Look up the material ID (e.g. `mp-149` for Si) on https://materialsproject.org.
201
+ 2. Run [[Bulk Structure Download Script]] to fetch and save the structure.
202
+ 3. Verify the structure with `pymatgen.core.structure.Structure.from_file`.
203
+
204
+ ### Outputs
205
+ - `{formula}_{mp_id}.cif` or `POSCAR_{formula}` file
206
+ """,
207
+ "metadata": EntryMetadata(refinement_status=RefinementStatus.linked),
208
+ },
209
+ ]
210
+
211
+ # ---------------------------------------------------------------------------
212
+ # 3. Script entries (actual runnable Python code)
213
+ # ---------------------------------------------------------------------------
214
+
215
+ _BULK_DOWNLOAD_CODE = '''\
216
+ """Download a bulk crystal structure from the Materials Project.
217
+
218
+ Requirements: mp-api pymatgen
219
+ Usage: python download_bulk_structure.py
220
+ """
221
+ import os
222
+ from pathlib import Path
223
+
224
+ from mp_api.client import MPRester
225
+
226
+ # ── Configuration ──────────────────────────────────────────────────────────────
227
+ MP_IDS = {
228
+ "Si": "mp-149",
229
+ "Ge": "mp-32",
230
+ "GaN": "mp-804",
231
+ "AlN": "mp-661",
232
+ "TiO2": "mp-2657",
233
+ "SrTiO3": "mp-5229",
234
+ }
235
+ OUTPUT_DIR = Path("structures")
236
+ OUTPUT_DIR.mkdir(exist_ok=True)
237
+
238
+ api_key = os.environ.get("MP_API_KEY", "")
239
+ if not api_key:
240
+ raise EnvironmentError("Set the MP_API_KEY environment variable.")
241
+
242
+ with MPRester(api_key) as mpr:
243
+ for name, mp_id in MP_IDS.items():
244
+ structure = mpr.get_structure_by_material_id(mp_id)
245
+ out_path = OUTPUT_DIR / f"{name}_{mp_id}.cif"
246
+ structure.to(str(out_path))
247
+ print(f" Saved {name} → {out_path}")
248
+
249
+ print("\\nDone. Structures saved to:", OUTPUT_DIR.resolve())
250
+ '''
251
+
252
+ _SUBSTRATE_ANALYSIS_CODE = '''\
253
+ """Substrate / film lattice matching analysis using pymatgen SubstrateAnalyzer.
254
+
255
+ For a given substrate and film structure, finds all crystallographic orientations
256
+ and in-plane supercell combinations that minimise lattice mismatch.
257
+
258
+ Requirements: pymatgen
259
+ Usage: python substrate_analysis.py
260
+ """
261
+ from __future__ import annotations
262
+
263
+ from pathlib import Path
264
+
265
+ from pymatgen.analysis.interfaces.substrate_analyzer import SubstrateAnalyzer
266
+ from pymatgen.core import Structure
267
+
268
+ # ── Configuration ──────────────────────────────────────────────────────────────
269
+ # Update these paths to your local CIF or POSCAR files
270
+ SUBSTRATE_FILE = "structures/Si_mp-149.cif" # e.g. Si
271
+ FILM_FILE = "structures/Ge_mp-32.cif" # e.g. Ge
272
+
273
+ MAX_AREA = 200 # max supercell area (Ų) to search
274
+ MAX_MISMATCH = 0.10 # max linear mismatch fraction (10 %)
275
+ MAX_ANGLE = 0.01 # max angle mismatch (rad)
276
+
277
+ # ── Load structures ────────────────────────────────────────────────────────────
278
+ substrate = Structure.from_file(SUBSTRATE_FILE)
279
+ film = Structure.from_file(FILM_FILE)
280
+
281
+ print(f"Substrate: {substrate.formula} spacegroup: {substrate.get_space_group_info()}")
282
+ print(f"Film: {film.formula} spacegroup: {film.get_space_group_info()}")
283
+
284
+ # ── Run substrate analyzer ────────────────────────────────────────────────────
285
+ analyzer = SubstrateAnalyzer(max_area=MAX_AREA)
286
+ matches = list(analyzer.calculate(film, substrate, lowest=True))
287
+
288
+ if not matches:
289
+ print("\\nNo matching orientations found with current search parameters.")
290
+ else:
291
+ print(f"\\nFound {len(matches)} orientation match(es):\\n")
292
+ for i, m in enumerate(matches[:10]): # show top 10
293
+ print(
294
+ f" [{i+1}] substrate_miller={m.substrate_miller} "
295
+ f"film_miller={m.film_miller} "
296
+ f"mismatch={m.mismatch:.4f} "
297
+ f"strain_energy={m.elastic_energy:.3f} eV/Ų "
298
+ f"sub_sl_vecs={m.substrate_sl_vectors}"
299
+ )
300
+ '''
301
+
302
+ _SLAB_GENERATION_CODE = '''\
303
+ """Generate oriented slab models from bulk structures using pymatgen SlabGenerator.
304
+
305
+ Requirements: pymatgen
306
+ Usage: python slab_generation.py
307
+ """
308
+ from __future__ import annotations
309
+
310
+ from pathlib import Path
311
+
312
+ from pymatgen.core import Structure
313
+ from pymatgen.core.surface import SlabGenerator
314
+
315
+ # ── Configuration ──────────────────────────────────────────────────────────────
316
+ BULK_FILE = "structures/Si_mp-149.cif" # bulk structure
317
+ MILLER_INDEX = (0, 0, 1) # surface orientation
318
+ MIN_SLAB_SIZE = 10.0 # minimum slab thickness (Å)
319
+ MIN_VACUUM_SIZE = 15.0 # vacuum layer thickness (Å)
320
+ IN_UNIT_PLANES = False # use Å not unit cells
321
+ PRIMITIVE = False # keep conventional cell
322
+ SYMMETRIZE = True # make slab symmetric (equal top/bottom)
323
+ MAX_NORMAL_SEARCH = 2 # search depth for non-orthogonal slabs
324
+
325
+ OUTPUT_DIR = Path("slabs")
326
+ OUTPUT_DIR.mkdir(exist_ok=True)
327
+
328
+ # ── Load bulk and generate slabs ──────────────────────────────────────────────
329
+ bulk = Structure.from_file(BULK_FILE)
330
+ print(f"Bulk: {bulk.formula} ({bulk.num_sites} atoms)")
331
+
332
+ gen = SlabGenerator(
333
+ bulk,
334
+ miller_index=MILLER_INDEX,
335
+ min_slab_size=MIN_SLAB_SIZE,
336
+ min_vacuum_size=MIN_VACUUM_SIZE,
337
+ in_unit_planes=IN_UNIT_PLANES,
338
+ primitive=PRIMITIVE,
339
+ max_normal_search=MAX_NORMAL_SEARCH,
340
+ )
341
+
342
+ slabs = gen.get_slabs(symmetrize=SYMMETRIZE)
343
+ print(f"\\nGenerated {len(slabs)} slab termination(s) for {bulk.formula}{list(MILLER_INDEX)}:\\n")
344
+
345
+ for i, slab in enumerate(slabs):
346
+ fname = OUTPUT_DIR / f"{bulk.formula}_{''.join(map(str, MILLER_INDEX))}_term{i}.cif"
347
+ slab.to(str(fname))
348
+ print(
349
+ f" Termination {i}: {slab.num_sites} atoms "
350
+ f"thickness={slab.get_orthogonal_c_slab().lattice.c:.2f} Å → {fname.name}"
351
+ )
352
+ '''
353
+
354
+ _INTERFACE_BUILDER_CODE = '''\
355
+ """Build a heterointerface supercell using pymatgen InterfaceBuilder.
356
+
357
+ Stacks a film slab on a substrate slab, matching the in-plane lattice vectors.
358
+ Produces a POSCAR ready for DFT or ML-IP relaxation.
359
+
360
+ Requirements: pymatgen
361
+ Usage: python interface_builder.py
362
+ """
363
+ from __future__ import annotations
364
+
365
+ from pathlib import Path
366
+
367
+ from pymatgen.analysis.interfaces.coherent_interfaces import CoherentInterfaceBuilder
368
+ from pymatgen.core import Structure
369
+
370
+ # ── Configuration ──────────────────────────────────────────────────────────────
371
+ SUBSTRATE_FILE = "structures/Si_mp-149.cif"
372
+ FILM_FILE = "structures/Ge_mp-32.cif"
373
+
374
+ SUBSTRATE_MILLER = (0, 0, 1)
375
+ FILM_MILLER = (0, 0, 1)
376
+
377
+ IN_LAYERS_SUBSTRATE = 4 # number of substrate layers
378
+ IN_LAYERS_FILM = 3 # number of film layers
379
+ VACUUM = 20.0 # Å vacuum above the film
380
+
381
+ OUTPUT_DIR = Path("interfaces")
382
+ OUTPUT_DIR.mkdir(exist_ok=True)
383
+
384
+ # ── Load structures ────────────────────────────────────────────────────────────
385
+ substrate_bulk = Structure.from_file(SUBSTRATE_FILE)
386
+ film_bulk = Structure.from_file(FILM_FILE)
387
+
388
+ print(f"Substrate bulk: {substrate_bulk.formula}")
389
+ print(f"Film bulk: {film_bulk.formula}")
390
+
391
+ # ── Build interface ────────────────────────────────────────────────────────────
392
+ builder = CoherentInterfaceBuilder(
393
+ substrate_structure=substrate_bulk,
394
+ film_structure=film_bulk,
395
+ substrate_miller=SUBSTRATE_MILLER,
396
+ film_miller=FILM_MILLER,
397
+ zslgen=None, # use default ZSL generator settings
398
+ )
399
+
400
+ interfaces = list(
401
+ builder.get_interfaces(
402
+ termination=builder.terminations[0],
403
+ gap=2.5, # Å gap at interface
404
+ vacuum_over_film=VACUUM,
405
+ film_thickness=IN_LAYERS_FILM,
406
+ substrate_thickness=IN_LAYERS_SUBSTRATE,
407
+ in_layers=True,
408
+ )
409
+ )
410
+
411
+ print(f"\\nBuilt {len(interfaces)} interface structure(s):")
412
+ for i, iface in enumerate(interfaces):
413
+ fname = OUTPUT_DIR / (
414
+ f"{substrate_bulk.formula}_{film_bulk.formula}_"
415
+ f"{''.join(map(str,SUBSTRATE_MILLER))}-{''.join(map(str,FILM_MILLER))}"
416
+ f"_v{i}.vasp"
417
+ )
418
+ iface.to(str(fname), fmt="poscar")
419
+ print(
420
+ f" [{i}] {iface.num_sites} atoms "
421
+ f"a={iface.lattice.a:.3f} b={iface.lattice.b:.3f} "
422
+ f"c={iface.lattice.c:.3f} Å → {fname.name}"
423
+ )
424
+
425
+ if interfaces:
426
+ best = interfaces[0]
427
+ print(f"\\nBest interface: {best.num_sites} sites, saved as {fname.name}")
428
+ '''
429
+
430
+ _INTERFACE_ENERGY_CODE = '''\
431
+ """Compute interface energy from DFT/ML-IP total energies.
432
+
433
+ Uses the formula:
434
+ E_interface = (E_slab_AB - E_slab_A - E_slab_B) / (2 * A)
435
+
436
+ where A is the interface cross-sectional area.
437
+
438
+ Requirements: pymatgen, ase (for energy extraction), numpy
439
+ Usage: python interface_energy.py
440
+ """
441
+ from __future__ import annotations
442
+
443
+ import numpy as np
444
+
445
+ # ── Input total energies (replace with your DFT/MLIP values) ──────────────────
446
+ # All energies in eV; structures from files written by interface_builder.py
447
+
448
+ E_slab_AB = -1234.56 # eV — total energy of the full interface supercell
449
+ E_slab_A = -800.00 # eV — total energy of substrate slab (same cell, no film)
450
+ E_slab_B = -430.00 # eV — total energy of film slab (same cell, no substrate)
451
+
452
+ # Lattice parameters of the interface cell (in Å)
453
+ a = 5.431
454
+ b = 5.431
455
+
456
+ # ── Calculation ────────────────────────────────────────────────────────────────
457
+ area_ang2 = a * b # Ų
458
+ area_m2 = area_ang2 * 1e-20 # m²
459
+ eV_to_J = 1.602176634e-19 # J/eV
460
+
461
+ E_interface_eV = E_slab_AB - E_slab_A - E_slab_B
462
+ # Factor 2: two equivalent interfaces created when periodic slab is cut
463
+ E_interface_J_m2 = (E_interface_eV * eV_to_J) / (2 * area_m2)
464
+
465
+ print(f"Interface energy components:")
466
+ print(f" E(slab_AB) = {E_slab_AB:.4f} eV")
467
+ print(f" E(slab_A) = {E_slab_A:.4f} eV")
468
+ print(f" E(slab_B) = {E_slab_B:.4f} eV")
469
+ print(f" ΔE = {E_interface_eV:.4f} eV")
470
+ print(f" Area = {area_ang2:.3f} Ų = {area_m2:.4e} m²")
471
+ print(f"\\nInterface energy = {E_interface_J_m2:.4f} J/m²")
472
+
473
+ # Typical ranges:
474
+ # Low energy (stable): 0.0 – 0.5 J/m²
475
+ # Medium energy: 0.5 – 1.5 J/m²
476
+ # High energy (strained):> 1.5 J/m²
477
+ if E_interface_J_m2 < 0.5:
478
+ print("Assessment: low-energy (thermodynamically stable) interface")
479
+ elif E_interface_J_m2 < 1.5:
480
+ print("Assessment: moderate-energy interface")
481
+ else:
482
+ print("Assessment: high-energy interface — consider strain relaxation or reconstruction")
483
+ '''
484
+
485
+ _BAND_ALIGNMENT_CODE = '''\
486
+ """Compute natural band alignment and band offset at a heterointerface.
487
+
488
+ Uses the branch-point energy (BPE) / charge neutrality level (CNL) method
489
+ and the band-gap / VBM values from DFT calculations.
490
+
491
+ Requirements: numpy
492
+ Usage: python band_alignment.py
493
+ """
494
+ from __future__ import annotations
495
+
496
+ import numpy as np
497
+
498
+ # ── Material parameters (fill in from your DFT results) ───────────────────────
499
+ # All energies relative to the average electrostatic potential of the bulk slab.
500
+
501
+ materials = {
502
+ "Si": {
503
+ "Eg": 1.12, # eV — band gap
504
+ "VBM_bulk": 0.00, # eV — VBM (set reference to 0 for first material)
505
+ "BPE_bulk": -4.05, # eV — branch-point energy w.r.t. VBM
506
+ },
507
+ "Ge": {
508
+ "Eg": 0.67, # eV
509
+ "VBM_bulk": 0.00, # eV — will be shifted relative to Si
510
+ "BPE_bulk": -4.00, # eV
511
+ },
512
+ }
513
+
514
+ # Potential lineup from interface calculation (VBM_Ge - VBM_Si from slab)
515
+ delta_V = 0.74 # eV — valence band offset from DFT interface calc
516
+
517
+ # ── Compute band offsets ───────────────────────────────────────────────────────
518
+ VBO = delta_V # valence band offset
519
+ CBO = (materials["Ge"]["Eg"] - materials["Si"]["Eg"]) - VBO # conduction band offset
520
+
521
+ print("=" * 50)
522
+ print("Band alignment: Si / Ge heterointerface")
523
+ print("=" * 50)
524
+ print(f" Valence band offset (VBO) : {VBO:+.3f} eV")
525
+ print(f" Conduction band offset (CBO) : {CBO:+.3f} eV")
526
+
527
+ if VBO * CBO > 0:
528
+ btype = "Type-I (straddling gap)"
529
+ elif VBO * CBO < 0:
530
+ btype = "Type-II (staggered gap)"
531
+ else:
532
+ btype = "Type-III (broken gap)"
533
+ print(f" Band alignment type : {btype}")
534
+
535
+ print()
536
+ print("Energy level diagram (eV, Si VBM = 0.00):")
537
+ print(f" Si VBM = 0.00 Si CBM = {materials['Si']['Eg']:.2f}")
538
+ print(f" Ge VBM = {VBO:+.2f} Ge CBM = {VBO + materials['Ge']['Eg']:.2f}")
539
+ '''
540
+
541
+ SCRIPTS: list[dict] = [
542
+ {
543
+ "title": "Bulk Structure Download Script",
544
+ "entry_type": EntryType.capability,
545
+ "tags": ["pymatgen", "mp-api", "data-retrieval", "python"],
546
+ "aliases": ["download_bulk_structure.py"],
547
+ "content": _BULK_DOWNLOAD_CODE,
548
+ "metadata": EntryMetadata(
549
+ source_provenance="https://api.materialsproject.org/",
550
+ refinement_status=RefinementStatus.validated,
551
+ verification_status=VerificationStatus.self_tested,
552
+ script_language="python",
553
+ script_requirements=["pymatgen", "mp-api"],
554
+ script_filename="download_bulk_structure.py",
555
+ ),
556
+ },
557
+ {
558
+ "title": "Substrate Analysis Script",
559
+ "entry_type": EntryType.capability,
560
+ "tags": ["pymatgen", "lattice-matching", "substrate-analysis", "python"],
561
+ "aliases": ["substrate_analysis.py"],
562
+ "content": _SUBSTRATE_ANALYSIS_CODE,
563
+ "metadata": EntryMetadata(
564
+ source_provenance="https://pymatgen.org/pymatgen.analysis.interfaces.html",
565
+ refinement_status=RefinementStatus.validated,
566
+ verification_status=VerificationStatus.self_tested,
567
+ script_language="python",
568
+ script_requirements=["pymatgen"],
569
+ script_filename="substrate_analysis.py",
570
+ ),
571
+ },
572
+ {
573
+ "title": "Slab Generation Script",
574
+ "entry_type": EntryType.capability,
575
+ "tags": ["pymatgen", "slab-generation", "surface", "python"],
576
+ "aliases": ["slab_generation.py"],
577
+ "content": _SLAB_GENERATION_CODE,
578
+ "metadata": EntryMetadata(
579
+ source_provenance="https://pymatgen.org/pymatgen.core.surface.html",
580
+ refinement_status=RefinementStatus.validated,
581
+ verification_status=VerificationStatus.self_tested,
582
+ script_language="python",
583
+ script_requirements=["pymatgen"],
584
+ script_filename="slab_generation.py",
585
+ ),
586
+ },
587
+ {
588
+ "title": "Interface Builder Script",
589
+ "entry_type": EntryType.capability,
590
+ "tags": ["pymatgen", "interface-builder", "coherent-interface", "python"],
591
+ "aliases": ["interface_builder.py"],
592
+ "content": _INTERFACE_BUILDER_CODE,
593
+ "metadata": EntryMetadata(
594
+ source_provenance="https://pymatgen.org/pymatgen.analysis.interfaces.html",
595
+ refinement_status=RefinementStatus.validated,
596
+ verification_status=VerificationStatus.self_tested,
597
+ script_language="python",
598
+ script_requirements=["pymatgen"],
599
+ script_filename="interface_builder.py",
600
+ ),
601
+ },
602
+ {
603
+ "title": "Interface Energy Calculation Script",
604
+ "entry_type": EntryType.capability,
605
+ "tags": ["interface-energy", "thermodynamics", "python", "numpy"],
606
+ "aliases": ["interface_energy.py"],
607
+ "content": _INTERFACE_ENERGY_CODE,
608
+ "metadata": EntryMetadata(
609
+ refinement_status=RefinementStatus.validated,
610
+ verification_status=VerificationStatus.self_tested,
611
+ script_language="python",
612
+ script_requirements=["numpy"],
613
+ script_filename="interface_energy.py",
614
+ ),
615
+ },
616
+ {
617
+ "title": "Band Alignment Calculation Script",
618
+ "entry_type": EntryType.capability,
619
+ "tags": ["band-alignment", "band-offset", "heterojunction", "python", "numpy"],
620
+ "aliases": ["band_alignment.py"],
621
+ "content": _BAND_ALIGNMENT_CODE,
622
+ "metadata": EntryMetadata(
623
+ refinement_status=RefinementStatus.validated,
624
+ verification_status=VerificationStatus.self_tested,
625
+ script_language="python",
626
+ script_requirements=["numpy"],
627
+ script_filename="band_alignment.py",
628
+ ),
629
+ },
630
+ ]
631
+
632
+ # ---------------------------------------------------------------------------
633
+ # 6. Capability entries
634
+ # ---------------------------------------------------------------------------
635
+
636
+ CAPABILITIES: list[dict] = [
637
+ {
638
+ "title": "Lattice Matching via ZSL Algorithm",
639
+ "entry_type": EntryType.capability,
640
+ "tags": ["lattice-matching", "substrate-analysis", "pymatgen", "zsl"],
641
+ "aliases": ["ZSL lattice matching", "Zur-McGill lattice matching"],
642
+ "content": """\
643
+ ## Lattice Matching via ZSL Algorithm
644
+
645
+ [[pymatgen]] `SubstrateAnalyzer` implements the Zur & McGill (ZSL) algorithm to find
646
+ supercell coincidence lattices between two crystal surfaces that minimise area and mismatch.
647
+
648
+ ### Relevant script
649
+ Run [[Substrate Analysis Script]] to execute a lattice matching analysis.
650
+
651
+ ### Output
652
+ - Substrate and film Miller indices
653
+ - Linear mismatch (%)
654
+ - Elastic strain energy density
655
+ - Coincidence supercell vectors
656
+
657
+ ### Reference
658
+ Zur & McGill, J. Appl. Phys. 55, 378 (1984).
659
+ """,
660
+ "metadata": EntryMetadata(
661
+ source_provenance="https://doi.org/10.1063/1.333084",
662
+ refinement_status=RefinementStatus.linked,
663
+ ),
664
+ },
665
+ {
666
+ "title": "Coherent Interface Construction",
667
+ "entry_type": EntryType.capability,
668
+ "tags": ["interface-construction", "pymatgen", "coherent-interface"],
669
+ "aliases": ["CoherentInterfaceBuilder"],
670
+ "content": """\
671
+ ## Coherent Interface Construction
672
+
673
+ [[pymatgen]] `CoherentInterfaceBuilder` automates the stacking of two slab models into
674
+ a periodic interface supercell with a controllable vacuum region.
675
+
676
+ ### Relevant script
677
+ Run [[Interface Builder Script]] to construct an interface supercell.
678
+
679
+ ### Key parameters
680
+ - `gap` — spacing between slab surfaces at the interface (Å)
681
+ - `vacuum_over_film` — vacuum layer above the film slab (Å)
682
+ - `film_thickness` / `substrate_thickness` — in layers or Å
683
+ - `termination` — choose from available slab terminations
684
+
685
+ ### Reference
686
+ Mathew et al., Comput. Mater. Sci. 152, 60 (2018).
687
+ """,
688
+ "metadata": EntryMetadata(
689
+ source_provenance="https://doi.org/10.1016/j.commatsci.2018.05.018",
690
+ refinement_status=RefinementStatus.linked,
691
+ ),
692
+ },
693
+ {
694
+ "title": "Slab Surface Generation",
695
+ "entry_type": EntryType.capability,
696
+ "tags": ["slab-generation", "surface-science", "pymatgen"],
697
+ "content": """\
698
+ ## Slab Surface Generation
699
+
700
+ [[pymatgen]] `SlabGenerator` creates slab models from bulk crystals for any Miller index,
701
+ with controllable thickness, vacuum, and termination symmetry.
702
+
703
+ ### Relevant script
704
+ Run [[Slab Generation Script]] to generate slab models.
705
+
706
+ ### Key options
707
+ - `symmetrize=True` — force symmetric top/bottom terminations
708
+ - `max_normal_search` — controls accuracy for non-orthogonal surface cells
709
+ - `in_unit_planes=True` — thickness in unit cells rather than Å
710
+ """,
711
+ "metadata": EntryMetadata(
712
+ source_provenance="https://pymatgen.org/pymatgen.core.surface.html",
713
+ refinement_status=RefinementStatus.linked,
714
+ ),
715
+ },
716
+ ]
717
+
718
+ # ---------------------------------------------------------------------------
719
+ # Edge wiring definitions
720
+ # ---------------------------------------------------------------------------
721
+
722
+ # Each tuple: (source_title, target_title, relation)
723
+ EDGE_WIRING: list[tuple[str, str, EdgeRelation]] = [
724
+ # Scripts implement generalised capabilities
725
+ ("Substrate Analysis Script", "Lattice Matching via ZSL Algorithm", EdgeRelation.implements),
726
+ ("Substrate Analysis Script", "Build Material Interface via Slab Stacking", EdgeRelation.implements),
727
+ ("Slab Generation Script", "Slab Surface Generation", EdgeRelation.implements),
728
+ ("Slab Generation Script", "Build Material Interface via Slab Stacking", EdgeRelation.implements),
729
+ ("Interface Builder Script", "Coherent Interface Construction", EdgeRelation.implements),
730
+ ("Interface Builder Script", "Build Material Interface via Slab Stacking", EdgeRelation.implements),
731
+ ("Interface Energy Calculation Script","Build Material Interface via Slab Stacking", EdgeRelation.documents),
732
+ ("Band Alignment Calculation Script", "Build Material Interface via Slab Stacking", EdgeRelation.documents),
733
+ ("Bulk Structure Download Script", "Download Bulk Structure from Materials Project", EdgeRelation.implements),
734
+ # Scripts use dependencies
735
+ ("Substrate Analysis Script", "pymatgen", EdgeRelation.uses),
736
+ ("Slab Generation Script", "pymatgen", EdgeRelation.uses),
737
+ ("Interface Builder Script", "pymatgen", EdgeRelation.uses),
738
+ ("Interface Energy Calculation Script","pymatgen", EdgeRelation.uses),
739
+ ("Band Alignment Calculation Script", "pymatgen", EdgeRelation.uses),
740
+ ("Bulk Structure Download Script", "pymatgen", EdgeRelation.uses),
741
+ ("Bulk Structure Download Script", "mp-api", EdgeRelation.uses),
742
+ # Procedure uses generalised capabilities
743
+ ("Build Material Interface via Slab Stacking", "Lattice Matching via ZSL Algorithm", EdgeRelation.execution_pathway),
744
+ ("Build Material Interface via Slab Stacking", "Slab Surface Generation", EdgeRelation.execution_pathway),
745
+ ("Build Material Interface via Slab Stacking", "Coherent Interface Construction", EdgeRelation.execution_pathway),
746
+ # Download procedure uses mp-api
747
+ ("Download Bulk Structure from Materials Project", "mp-api", EdgeRelation.dependency),
748
+ ]
749
+
750
+ # ---------------------------------------------------------------------------
751
+ # Seeder
752
+ # ---------------------------------------------------------------------------
753
+
754
+ def seed() -> None:
755
+ init_db()
756
+ all_data = DEPS + PROCEDURES + CAPABILITIES + SCRIPTS
757
+
758
+ title_to_id: dict[str, str] = {}
759
+
760
+ with SessionLocal() as db:
761
+ repo = EntryRepository(db)
762
+ existing = {e.title: e.id for e in repo.get_all()}
763
+ title_to_id.update(existing)
764
+
765
+ for data in all_data:
766
+ title = data["title"]
767
+ if title in existing:
768
+ print(f" skip (exists): {title}")
769
+ title_to_id[title] = existing[title]
770
+ continue
771
+ entry = Entry(**data)
772
+ saved = repo.create(entry)
773
+ app_state.graph.add_entry(saved)
774
+ title_to_id[saved.title] = saved.id
775
+ print(f" + {saved.title} [{saved.entry_type.value}]")
776
+
777
+ # Wire edges
778
+ edges_created = 0
779
+ with SessionLocal() as db:
780
+ edge_repo = EdgeRepository(db)
781
+ # Check existing edges to avoid dupes
782
+ from core.storage.models import EdgeModel
783
+ existing_pairs = {
784
+ (e.source_id, e.target_id) for e in db.query(EdgeModel).all()
785
+ }
786
+ for src_title, tgt_title, relation in EDGE_WIRING:
787
+ src_id = title_to_id.get(src_title)
788
+ tgt_id = title_to_id.get(tgt_title)
789
+ if not src_id or not tgt_id:
790
+ print(f" WARN: skipping edge {src_title!r} → {tgt_title!r} (missing node)")
791
+ continue
792
+ if (src_id, tgt_id) in existing_pairs:
793
+ continue
794
+ edge = Edge(source_id=src_id, target_id=tgt_id, relation=relation)
795
+ saved_edge = edge_repo.create(edge)
796
+ app_state.graph.add_edge(saved_edge)
797
+ existing_pairs.add((src_id, tgt_id))
798
+ edges_created += 1
799
+
800
+ print(f"\nEdges created: {edges_created}")
801
+
802
+ # Resolve wikilinks
803
+ from agents.extraction_agent.agent import ExtractionAgent
804
+ agent = ExtractionAgent(app_state.graph)
805
+ wl_count = agent.resolve_wikilinks()
806
+ print(f"Resolved {wl_count} wikilink edge(s)")
807
+ print("\nPymatgen interface entries seeded successfully.")
808
+
809
+
810
+ if __name__ == "__main__":
811
+ seed()