feature/SOF-7917 Feat: Defect formation energy#346
Conversation
|
Check out this pull request on See visual diffs & provide feedback on Jupyter Notebooks. Powered by ReviewNB |
📝 WalkthroughWalkthroughAdds interfacial-energy and defect-formation-energy notebooks, updates surface-energy workflow ordering and bulk-energy validation, refactors shared bulk-material resolution, and links the new workflows from project documentation. ChangesMaterials Designer energy workflows
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant Notebook
participant Mat3raAPI
participant Standata
participant ComputeCluster
Notebook->>Mat3raAPI: authenticate and load interface
Notebook->>Standata: load Interfacial Energy workflow
Notebook->>Mat3raAPI: resolve bulk materials with Total Energy
Notebook->>Mat3raAPI: create multi-material job
Mat3raAPI->>ComputeCluster: submit workflow
ComputeCluster-->>Mat3raAPI: return completed properties
Mat3raAPI-->>Notebook: retrieve interfacial_energy result
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (1)
other/materials_designer/workflows/defect_formation_energy.ipynb (1)
376-389: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueConsider using a shared helper for material resolution.
If the newly added
analysis.pyutilities (mentioned in the PR stack) include a generic helper for resolving materials with a specific property by hash and name, consider using it here to replaceresolve_pristine_with_total_energyand reduce code duplication.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@other/materials_designer/workflows/defect_formation_energy.ipynb` around lines 376 - 389, Replace the local resolve_pristine_with_total_energy implementation with the shared analysis.py helper that resolves materials by hash and name while requiring a specific property. Pass the existing material, name, and Total Energy lookup requirements through that helper, preserving hash-first resolution, name fallback, duplicate avoidance, and the existing (Material, property) return contract.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@other/materials_designer/workflows/interfacial_energy.ipynb`:
- Around line 120-123: Validate N_INTERFACES before applying the workflow,
ensuring it is explicitly provided and limited to the supported interface counts
for the structure. Update the apply_workflow_parameters path or its caller so
invalid or missing set-n-interfaces values fail clearly instead of leaving γ
normalization unset.
- Around line 245-254: Update the interface lookup in the `interface is None`
branch and the bulk hash resolver to include an `ACCOUNT_ID` filter in their
platform material queries. Preserve the existing name-regex and hash-matching
behavior while ensuring both lookups only return materials belonging to the
current account.
In `@other/materials_designer/workflows/surface_energy.ipynb`:
- Around line 292-297: Add the missing find_total_energy_for_material function
to mat3ra.notebooks_utils.core.entity.property.api, ensuring it is exported and
supports the call with client and bulk_material.id used by surface_energy.ipynb.
Reuse the existing total-energy property lookup behavior and return the expected
tuple so the bulk-energy lookup proceeds without ImportError.
In `@src/py/mat3ra/notebooks_utils/core/entity/material/analysis.py`:
- Around line 162-165: Update find_owned_material to include owner._id
constrained to owner_id in the bulk_query passed to api_client.materials.list,
then return only the matching owned material. Remove the fallback to matches[0]
so the function returns None when no material belongs to owner_id.
---
Nitpick comments:
In `@other/materials_designer/workflows/defect_formation_energy.ipynb`:
- Around line 376-389: Replace the local resolve_pristine_with_total_energy
implementation with the shared analysis.py helper that resolves materials by
hash and name while requiring a specific property. Pass the existing material,
name, and Total Energy lookup requirements through that helper, preserving
hash-first resolution, name fallback, duplicate avoidance, and the existing
(Material, property) return contract.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: a6512c90-5ff5-4af4-b6f9-58dda3d52e8c
📒 Files selected for processing (6)
README.mdother/materials_designer/workflows/Introduction.ipynbother/materials_designer/workflows/defect_formation_energy.ipynbother/materials_designer/workflows/interfacial_energy.ipynbother/materials_designer/workflows/surface_energy.ipynbsrc/py/mat3ra/notebooks_utils/core/entity/material/analysis.py
| "# Number of interfaces in the supercell: 1 if the slab has vacuum (one physical\n", | ||
| "# interface), 2 for a periodic stack with no vacuum. Substrate and film\n", | ||
| "# multiplicities are derived automatically from the structures.\n", | ||
| "N_INTERFACES = 1" |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
python3 - <<'PY'
import json, os, re, sys, textwrap
from pathlib import Path
root = Path('.')
# Locate relevant files
matches = []
for p in root.rglob('*'):
if p.is_file() and p.suffix in {'.py', '.ipynb', '.md', '.txt', '.yaml', '.yml'}:
try:
data = p.read_text(encoding='utf-8', errors='ignore')
except Exception:
continue
if 'apply_workflow_parameters' in data or 'set-n-interfaces' in data or 'N_INTERFACES' in data:
matches.append(str(p))
print("MATCHES:")
for m in sorted(matches):
print(m)
# Print targeted excerpts from notebook(s)/source if found
for m in sorted(matches):
p = Path(m)
print(f"\n=== {m} ===")
if p.suffix == '.ipynb':
nb = json.loads(p.read_text(encoding='utf-8'))
for idx, cell in enumerate(nb.get('cells', [])):
src = ''.join(cell.get('source', []))
if any(k in src for k in ['apply_workflow_parameters', 'set-n-interfaces', 'N_INTERFACES']):
print(f"\n--- cell {idx} ({cell.get('cell_type')}) ---")
print(src[:4000])
else:
text = p.read_text(encoding='utf-8', errors='ignore')
for needle in ['apply_workflow_parameters', 'set-n-interfaces', 'N_INTERFACES']:
if needle in text:
print(f"\n--- around {needle} ---")
for mobj in re.finditer(re.escape(needle), text):
start = max(0, mobj.start()-500)
end = min(len(text), mobj.end()+1500)
print(text[start:end])
break
PYRepository: mat3ra/api-examples
Length of output: 3776
🏁 Script executed:
python3 - <<'PY'
import json
from pathlib import Path
paths = []
for p in Path('.').rglob('*'):
if p.is_file() and p.suffix in {'.ipynb', '.py', '.json', '.yaml', '.yml', '.md', '.txt'}:
try:
text = p.read_text(encoding='utf-8', errors='ignore')
except Exception:
continue
if 'set-n-interfaces' in text or 'n-interfaces' in text:
paths.append(p)
for p in sorted(paths):
print(f"\n=== {p} ===")
try:
if p.suffix == '.ipynb':
nb = json.loads(p.read_text(encoding='utf-8'))
for i, cell in enumerate(nb.get('cells', [])):
src = ''.join(cell.get('source', []))
if 'set-n-interfaces' in src or 'n-interfaces' in src:
print(f"--- cell {i} ({cell.get('cell_type')}) ---")
print(src[:3000])
else:
text = p.read_text(encoding='utf-8', errors='ignore')
for needle in ['set-n-interfaces', 'n-interfaces']:
if needle in text:
idx = text.index(needle)
print(text[max(0, idx-400):idx+1200])
break
except Exception as e:
print("ERR", e)
PYRepository: mat3ra/api-examples
Length of output: 1475
Validate n_interfaces before applying the workflow. apply_workflow_parameters accepts arbitrary values and silently leaves γ normalization unset when set-n-interfaces is missing, so a run can produce a plausible but misnormalized result.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@other/materials_designer/workflows/interfacial_energy.ipynb` around lines 120
- 123, Validate N_INTERFACES before applying the workflow, ensuring it is
explicitly provided and limited to the supported interface counts for the
structure. Update the apply_workflow_parameters path or its caller so invalid or
missing set-n-interfaces values fail clearly instead of leaving γ normalization
unset.
| "if interface is None:\n", | ||
| " interface_matches = client.materials.list({\n", | ||
| " \"name\": {\"$regex\": re.escape(INTERFACE_NAME), \"$options\": \"i\"},\n", | ||
| " })\n", | ||
| " if not interface_matches:\n", | ||
| " raise ValueError(\n", | ||
| " f\"No interface containing '{INTERFACE_NAME}' was found in '{FOLDER}' or on the platform.\"\n", | ||
| " )\n", | ||
| " interface = Material.create(interface_matches[0])\n", | ||
| " print(f\"♻️ Loaded interface from platform: {interface_matches[0]['_id']}\")\n", |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
python3 - <<'PY'
import json
from pathlib import Path
path = Path('other/materials_designer/workflows/interfacial_energy.ipynb')
nb = json.loads(path.read_text())
for i, cell in enumerate(nb.get('cells', [])):
src = ''.join(cell.get('source', []))
if 'interface_matches = client.materials.list({' in src or 'resolve_bulk_material' in src or 'ACCOUNT_ID' in src or 'N_INTERFACES' in src:
print(f"\n--- cell {i} type={cell.get('cell_type')} ---")
lines = src.splitlines()
for j, line in enumerate(lines, 1):
if any(k in line for k in ['interface_matches = client.materials.list({', 'resolve_bulk_material', 'ACCOUNT_ID', 'N_INTERFACES', 'client.materials.list(']):
start = max(1, j-8)
end = min(len(lines), j+20)
for n in range(start, end+1):
print(f"{n:4d}: {lines[n-1]}")
print()
PYRepository: mat3ra/api-examples
Length of output: 10537
🏁 Script executed:
python3 - <<'PY'
import json
from pathlib import Path
path = Path('other/materials_designer/workflows/interfacial_energy.ipynb')
nb = json.loads(path.read_text())
for i, cell in enumerate(nb.get('cells', [])):
src = ''.join(cell.get('source', []))
if any(key in src for key in ['interface_matches = client.materials.list({', 'resolve_bulk_material', 'ACCOUNT_ID', 'N_INTERFACES']):
print(f"\n--- cell {i} type={cell.get('cell_type')} ---")
lines = src.splitlines()
for j, line in enumerate(lines, 1):
if any(k in line for k in ['interface_matches = client.materials.list({', 'resolve_bulk_material', 'ACCOUNT_ID', 'N_INTERFACES', 'client.materials.list(']):
start = max(1, j-8)
end = min(len(lines), j+20)
for n in range(start, end+1):
print(f"{n:4d}: {lines[n-1]}")
print()
PYRepository: mat3ra/api-examples
Length of output: 10537
🏁 Script executed:
python3 - <<'PY'
import json
from pathlib import Path
path = Path('other/materials_designer/workflows/interfacial_energy.ipynb')
nb = json.loads(path.read_text())
targets = ['interface_matches = client.materials.list({', 'resolve_bulk_material', 'ACCOUNT_ID', 'N_INTERFACES']
for i, cell in enumerate(nb.get('cells', [])):
src = ''.join(cell.get('source', []))
if any(t in src for t in targets):
print(f"\n--- cell {i} type={cell.get('cell_type')} ---")
for j, line in enumerate(src.splitlines(), 1):
if any(t in line for t in targets + ['client.materials.list(']):
start = max(1, j-8)
end = min(len(src.splitlines()), j+20)
for n in range(start, end+1):
print(f"{n:4d}: {src.splitlines()[n-1]}")
print()
PYRepository: mat3ra/api-examples
Length of output: 10537
Scope material lookups to ACCOUNT_ID. The interface regex lookup and the bulk hash resolver both search across all accounts, so this notebook can attach the wrong interface or bulk material when another account has the same name or hash.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@other/materials_designer/workflows/interfacial_energy.ipynb` around lines 245
- 254, Update the interface lookup in the `interface is None` branch and the
bulk hash resolver to include an `ACCOUNT_ID` filter in their platform material
queries. Preserve the existing name-regex and hash-matching behavior while
ensuring both lookups only return materials belonging to the current account.
| "from mat3ra.notebooks_utils.core.entity.material.analysis import get_bulk_material\n", | ||
| "from mat3ra.notebooks_utils.core.entity.property.api import find_total_energy_for_material\n", | ||
| "\n", | ||
| "saved_slab_response = get_or_create_material(client, slab, ACCOUNT_ID)\n", | ||
| "bulk_query, bulk_material_response, bulk_material = get_bulk_material(client, slab, ACCOUNT_ID)\n", | ||
| "\n", | ||
| "print(f\"✅ Saved slab material: {saved_slab_response['_id']}\")\n", | ||
| "saved_slab = Material.create(saved_slab_response)" | ||
| "_, bulk_total_energy_property = find_total_energy_for_material(client, bulk_material.id)\n", |
There was a problem hiding this comment.
🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
ast-grep outline src/py/mat3ra/notebooks_utils/core/entity/property/api.py --items all
rg -nP '^\s*def\s+find_total_energy_for_material\b|import\s+find_total_energy_for_material\b' .Repository: mat3ra/api-examples
Length of output: 1253
Add the missing find_total_energy_for_material export.
other/materials_designer/workflows/surface_energy.ipynb imports find_total_energy_for_material, but mat3ra/notebooks_utils/core/entity/property/api.py does not define it, so this cell will fail with ImportError before the bulk-energy lookup runs.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@other/materials_designer/workflows/surface_energy.ipynb` around lines 292 -
297, Add the missing find_total_energy_for_material function to
mat3ra.notebooks_utils.core.entity.property.api, ensuring it is exported and
supports the call with client and bulk_material.id used by surface_energy.ipynb.
Reuse the existing total-energy property lookup behavior and return the expected
tuple so the bulk-energy lookup proceeds without ImportError.
| def find_owned_material(api_client: Any, bulk_query: dict, owner_id: str) -> Optional[Any]: | ||
| matches = api_client.materials.list(bulk_query) | ||
| owned = next((item for item in matches if item.get("owner", {}).get("_id") == owner_id), None) | ||
| return owned or (matches[0] if matches else None) |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Do not fall back to another account’s material.
When no match belongs to owner_id, Line 165 returns the first global match. The notebook can consequently validate another account’s Total Energy while submitting under the selected account. Include owner._id in the API query and return None when no owned match exists.
Proposed fix
def find_owned_material(api_client: Any, bulk_query: dict, owner_id: str) -> Optional[Any]:
- matches = api_client.materials.list(bulk_query)
- owned = next((item for item in matches if item.get("owner", {}).get("_id") == owner_id), None)
- return owned or (matches[0] if matches else None)
+ matches = api_client.materials.list({**bulk_query, "owner._id": owner_id})
+ return matches[0] if matches else None📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| def find_owned_material(api_client: Any, bulk_query: dict, owner_id: str) -> Optional[Any]: | |
| matches = api_client.materials.list(bulk_query) | |
| owned = next((item for item in matches if item.get("owner", {}).get("_id") == owner_id), None) | |
| return owned or (matches[0] if matches else None) | |
| def find_owned_material(api_client: Any, bulk_query: dict, owner_id: str) -> Optional[Any]: | |
| matches = api_client.materials.list({**bulk_query, "owner._id": owner_id}) | |
| return matches[0] if matches else None |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/py/mat3ra/notebooks_utils/core/entity/material/analysis.py` around lines
162 - 165, Update find_owned_material to include owner._id constrained to
owner_id in the bulk_query passed to api_client.materials.list, then return only
the matching owned material. Remove the fallback to matches[0] so the function
returns None when no material belongs to owner_id.
Summary by CodeRabbit