Skip to content

feature/SOF-7917 Feat: Defect formation energy#346

Open
VsevolodX wants to merge 3 commits into
feature/SOF-7954from
feature/SOF-7917
Open

feature/SOF-7917 Feat: Defect formation energy#346
VsevolodX wants to merge 3 commits into
feature/SOF-7954from
feature/SOF-7917

Conversation

@VsevolodX

@VsevolodX VsevolodX commented Jul 16, 2026

Copy link
Copy Markdown
Member
  • update: defect energy NB

Summary by CodeRabbit

  • New Features
    • Added notebook workflows for calculating interfacial energy in heterostructures and neutral defect formation energy.

@review-notebook-app

Copy link
Copy Markdown

Check out this pull request on  ReviewNB

See visual diffs & provide feedback on Jupyter Notebooks.


Powered by ReviewNB

@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds 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.

Changes

Materials Designer energy workflows

Layer / File(s) Summary
Shared bulk resolution utilities
src/py/mat3ra/notebooks_utils/core/entity/material/analysis.py
Bulk crystals are derived from slab or interface metadata, converted into queries, and matched to owned materials.
Interfacial energy workflow
other/materials_designer/workflows/interfacial_energy.ipynb
Adds interface and bulk resolution, workflow parameterization, multi-material job submission, monitoring, and interfacial-energy result retrieval.
Defect formation energy workflow
other/materials_designer/workflows/defect_formation_energy.ipynb
Adds a neutral defect formation energy workflow using defective and pristine materials, elemental references, Quantum ESPRESSO, and asynchronous job monitoring.
Surface energy workflow update
other/materials_designer/workflows/surface_energy.ipynb
Loads the workflow earlier, requires a finished bulk Total Energy property, and reorganizes configuration, submission, and result sections.
Workflow documentation
README.md, other/materials_designer/workflows/Introduction.ipynb
Adds links for interfacial-energy and defect-formation-energy notebooks and renames the interface-energy entry.

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
Loading

Possibly related PRs

Suggested reviewers: timurbazhirov

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly references the new defect formation energy work, which is a real part of the changeset.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/SOF-7917

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (1)
other/materials_designer/workflows/defect_formation_energy.ipynb (1)

376-389: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider using a shared helper for material resolution.

If the newly added analysis.py utilities (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 replace resolve_pristine_with_total_energy and 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

📥 Commits

Reviewing files that changed from the base of the PR and between 6a6518d and 3dd4662.

📒 Files selected for processing (6)
  • README.md
  • other/materials_designer/workflows/Introduction.ipynb
  • other/materials_designer/workflows/defect_formation_energy.ipynb
  • other/materials_designer/workflows/interfacial_energy.ipynb
  • other/materials_designer/workflows/surface_energy.ipynb
  • src/py/mat3ra/notebooks_utils/core/entity/material/analysis.py

Comment on lines +120 to +123
"# 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"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 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
PY

Repository: 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)
PY

Repository: 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.

Comment on lines +245 to +254
"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",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ 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()
PY

Repository: 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()
PY

Repository: 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()
PY

Repository: 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.

Comment on lines +292 to +297
"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",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 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.

Comment on lines +162 to +165
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)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ 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.

Suggested change
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.

@VsevolodX VsevolodX changed the title feature/SOF 7917 feature/SOF-7917 Feat: Defect formation energy Jul 16, 2026
@VsevolodX
VsevolodX changed the base branch from main to feature/SOF-7954 July 16, 2026 01:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants