Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
4e53261
Update: assert a slab resolves back to its own bulk [SOF-8034]
VsevolodX Aug 25, 2026
81cae34
Chore: consume the made WIP wheel until made#297 releases [SOF-8034]
VsevolodX Aug 25, 2026
bbd46e1
Chore: install the made WIP wheel in JupyterLite too [SOF-8034]
VsevolodX Aug 25, 2026
9a1aea7
Fix: resolve a bulk material with the owner in the query [SOF-8034]
VsevolodX Aug 25, 2026
aa51b59
Fix: query set membership instead of filtering the response [SOF-8034]
VsevolodX Aug 25, 2026
01827b7
Fix: keep the local isEntitySet guard, and pin the new query shape [S…
VsevolodX Aug 25, 2026
c0032d2
Fix: keep the owner guard in the bulk resolver [SOF-8034]
VsevolodX Aug 25, 2026
bb17342
Fix: list mat3ra-code and mat3ra-esse with the made GH wheel [SOF-8034]
VsevolodX Aug 25, 2026
e4f6415
update: cleanup + remove commetns
VsevolodX Aug 25, 2026
ca1688c
update: cleanup + remove commetns
VsevolodX Aug 25, 2026
1ace7c4
Fix: default the bulk total-energy source to my_account [SOF-8034]
VsevolodX Aug 25, 2026
bffde8a
Chore: consume the standata WIP wheel [SOF-8034]
VsevolodX Aug 25, 2026
4e22c70
Chore: trim the source docstring [SOF-8034]
VsevolodX Aug 25, 2026
45bb2fe
Fix: use a literal + in the standata wheel URL, not %2B [SOF-8034]
VsevolodX Aug 25, 2026
71ae545
Fix: serve the standata wheel same-origin for JupyterLite [SOF-8034]
VsevolodX Aug 25, 2026
0b38e78
Fix: list standata's deps alongside its wheel [SOF-8034]
VsevolodX Aug 25, 2026
8cc6ca4
Fix: resolve a bulk crystal by _id, then hash, then scaledHash [SOF-8…
VsevolodX Aug 25, 2026
c7b86ba
Chore: use the standata wheel with all three workflows scoped [SOF-8034]
VsevolodX Aug 25, 2026
df833c0
Fix: get_for_job already unwraps data [SOF-8034]
VsevolodX Aug 26, 2026
b66f6a3
Chore: drop the temporary wheel and package pins [SOF-8034]
VsevolodX Aug 26, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -618,7 +618,7 @@
"visualize_properties(interfacial_energy_data, title=\"Interfacial Energy\")\n",
"\n",
"print(f\"Interface (material 0): {saved_interface.name} ({saved_interface.id}), \"\n",
" f\"total energy: {interface_te_data[0]['data']['value']} eV\")\n",
" f\"total energy: {interface_te_data[0]['value']} eV\")\n",
"print(f\"Substrate bulk (material 1): {substrate_bulk.name} ({substrate_bulk.id}), \"\n",
" f\"total energy: {substrate_te_property['data']['value']} eV\")\n",
"print(f\"Film bulk (material 2): {film_bulk.name} ({film_bulk.id}), \"\n",
Expand Down
2 changes: 1 addition & 1 deletion other/materials_designer/workflows/surface_energy.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -526,7 +526,7 @@
"visualize_properties(surface_energy_data, title=\"Surface Energy\")\n",
"\n",
"print(f\"Slab: {saved_slab.name} ({saved_slab.id}), \"\n",
" f\"total energy: {slab_total_energy_data[0]['data']['value']} eV\")\n",
" f\"total energy: {slab_total_energy_data[0]['value']} eV\")\n",
"print(f\"Bulk: {bulk_material.name} ({bulk_material.id}), \"\n",
" f\"total energy: {bulk_total_energy_property['data']['value']} eV\")"
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ def get_slab_bulk_crystal(slab_material: Material) -> dict:

def resolve_bulk_query_from_crystal(bulk_crystal: dict) -> dict:
"""Builds a materials.list query that resolves a bulk crystal to a platform material."""
for key in ("scaledHash", "hash", "_id"):
for key in ("_id", "hash", "scaledHash"):
if bulk_crystal.get(key) is not None:
return {key: bulk_crystal[key]}
try:
Expand Down
8 changes: 5 additions & 3 deletions src/py/mat3ra/notebooks_utils/core/entity/material/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,8 @@ def get_bulk_material_by_crystal(api_client: APIClient, bulk_crystal: Material,


def _require_material_for_owner(api_client: APIClient, query: dict, owner_id: str) -> Material:
matches = api_client.materials.list(query)
material_response = next((item for item in matches if item.get("owner", {}).get("_id") == owner_id), None)
matches = api_client.materials.list({**query, "owner._id": owner_id})
material_response = next(iter(matches), None)
if material_response is None:
raise ValueError(
"The bulk material resolved from metadata is not present on the platform for this account. "
Expand Down Expand Up @@ -142,7 +142,9 @@ def list_materials_in_set(api_client: APIClient, owner_id: str, material_set: Di
that already have one do not re-query for it.
"""
material_set_id = material_set["_id"]
matches = api_client.materials.list({"owner._id": owner_id, "inSet._id": material_set_id})
matches = api_client.materials.list(
{"owner._id": owner_id, "inSet._id": material_set_id, "isEntitySet": {"$ne": True}}
)
members = [material for material in matches if not material.get("isEntitySet")]
return sorted(members, key=lambda material: _index_in_set(material, material_set_id))

Expand Down
5 changes: 3 additions & 2 deletions src/py/mat3ra/notebooks_utils/core/entity/property/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ def update_property_holder_value(client: APIClient, property_holder_id: str, val
return client.properties.update(property_holder_id, {"$set": {"data.value": value}})


def find_total_energy_for_material(client: APIClient, material_id: str, source: str = "public") -> Optional[dict]:
def find_total_energy_for_material(client: APIClient, material_id: str, source: str = "my_account") -> Optional[dict]:
"""
Find the best-precision total_energy property for a material. Mirrors the
platform's "Resolve Total Energies for Elemental Materials" subworkflow,
Expand All @@ -84,7 +84,8 @@ def find_total_energy_for_material(client: APIClient, material_id: str, source:
Args:
client (APIClient): API client instance.
material_id (str): Material _id to look up the total_energy property for.
source (str): Source of the total energy property: `public`, `my_account` or `curators`.
source (str): Source of the total energy property: `my_account` (default), `curators` or
`public`.

Returns:
The best-precision total_energy property, or None if none exists.
Expand Down
30 changes: 25 additions & 5 deletions tests/py/unit/core/entity/test_material_analysis.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
"""Unit tests for bulk-crystal query resolution."""

import pytest
from mat3ra.notebooks_utils.core.entity.material.analysis import resolve_bulk_query_from_crystal
from mat3ra.made.material import Material
from mat3ra.made.tools.analyze.lattice_planes import CrystalLatticePlanesMaterialAnalyzer
from mat3ra.made.tools.helpers import create_slab
from mat3ra.notebooks_utils.core.entity.material.analysis import get_slab_bulk_crystal, resolve_bulk_query_from_crystal
from mat3ra.standata.materials import Materials

SILICON = Materials.get_by_name_first_match("Silicon")
Expand All @@ -12,17 +15,34 @@
[
(
{"scaledHash": "scaled-hash-value", "hash": "hash-value", "_id": "material-id"},
{"scaledHash": "scaled-hash-value"},
{"_id": "material-id"},
),
({"hash": "hash-value", "_id": "material-id"}, {"hash": "hash-value"}),
({"_id": "material-id"}, {"_id": "material-id"}),
({"scaledHash": "scaled-hash-value", "hash": "hash-value"}, {"hash": "hash-value"}),
({"scaledHash": "scaled-hash-value"}, {"scaledHash": "scaled-hash-value"}),
],
)
def test_resolve_bulk_query_prefers_scaled_hash_then_hash_then_id(extra_keys, expected):
def test_resolve_bulk_query_prefers_id_then_hash_then_scaled_hash(extra_keys, expected):
assert resolve_bulk_query_from_crystal({**SILICON, **extra_keys}) == expected


def test_resolve_bulk_query_computes_hash_when_none_present():
query = resolve_bulk_query_from_crystal(SILICON)
assert set(query) == {"hash"}
assert query["hash"]


def test_slab_bulk_crystal_is_the_material_the_slab_was_built_from():
"""
A slab built from a primitive bulk must resolve back to that primitive bulk, not to the
conventional cell -- otherwise the Total Energy job run on the input is unreachable and
surface energy combines a slab SCF with a bulk reference from a different cell.
"""
primitive = Material.create(Materials.get_by_name_first_match("Nickel"))
conventional = CrystalLatticePlanesMaterialAnalyzer(
material=primitive, miller_indices=(0, 0, 1)
).material_with_conventional_lattice
assert conventional.hash != primitive.hash

slab = create_slab(crystal=primitive, miller_indices=(0, 0, 1), number_of_layers=3)

assert get_slab_bulk_crystal(slab)["hash"] == primitive.hash
5 changes: 4 additions & 1 deletion tests/py/unit/core/entity/test_material_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,9 @@ def test_list_materials_in_set_does_not_re_resolve_the_set():
materials = list_materials_in_set(client, OWNER_ID, ENTITY_SET)

assert [material["_id"] for material in materials] == EXPECTED_ORDERED_IDS
client.materials.list.assert_called_once_with({"owner._id": OWNER_ID, "inSet._id": MATERIAL_SET_ID})
client.materials.list.assert_called_once_with(
{"owner._id": OWNER_ID, "inSet._id": MATERIAL_SET_ID, "isEntitySet": {"$ne": True}}
)


@pytest.mark.parametrize(
Expand All @@ -119,6 +121,7 @@ def test_list_materials_by_set_orders_by_inset_index(members, expected_ids):
assert client.materials.list.call_args_list[1].args[0] == {
"owner._id": OWNER_ID,
"inSet._id": MATERIAL_SET_ID,
"isEntitySet": {"$ne": True},
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ def _client():
return client


def test_find_total_energy_for_material_defaults_to_public_scope():
def test_find_total_energy_for_material_defaults_to_my_account_scope():
client = _client()

result = find_total_energy_for_material(client, MATERIAL_ID)
Expand All @@ -32,6 +32,7 @@ def test_find_total_energy_for_material_defaults_to_public_scope():
query={
"exabyteId": EXABYTE_ID,
"slug": "total_energy",
"owner._id": OWNER_ACCOUNT_ID,
},
projection={"sort": {"precision.value": -1}, "limit": 1},
)
Expand Down
Loading