Skip to content

Commit 9e955c1

Browse files
committed
test: replace weak consumer regressions with effective production-level tests
- test_hsp_star_conjugation_lg_tolerance_changes_c3_classification: real K plus C2 star-pair setup exercises the is_little_group_operation branch with both 9e-7 (C3 rejected) and 5e-6 (C3 accepted) tolerances. - test_unitary_valley_sewing_attempts_honours_config_tolerance: monkey-patches _valley_preserving_little_group_ids and proves the production caller _build_unitary_valley_sewing_attempts reads the configured tolerance from symmetry_payload and passes it through.
1 parent 2e4a345 commit 9e955c1

1 file changed

Lines changed: 101 additions & 31 deletions

File tree

tests/test_symmetry.py

Lines changed: 101 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -274,27 +274,58 @@ def test_inventory_rows_carry_residual_and_tolerance_under_custom_tol():
274274
assert per_valley2["K_valley"][0]["little_group_passed"] is True
275275

276276

277-
def test_hsp_star_conjugation_serializes_lg_tolerance():
278-
"""build_hsp_star_conjugation_report accepts lg_tolerance and serializes
279-
it in the output dict, keeping it distinct from the target-matching
280-
tolerance."""
277+
def test_hsp_star_conjugation_lg_tolerance_changes_c3_classification():
278+
"""C3 at K [1/3, 1/3] mapped by C2 to star partner [2/3, 0].
279+
With lg_tolerance=9e-7 the C3 source operation is rejected as
280+
source_not_in_hsp_little_group; with 5e-6 it is accepted and
281+
conjugation proceeds. Both tolerances are serialized separately."""
281282
from valleyscope.analysis.hsp_star_conjugation import (
282283
build_hsp_star_conjugation_report,
283284
)
284-
id3 = np.eye(3, dtype=int)
285+
c2 = np.array([[-1, -1, 0], [0, 1, 0], [0, 0, -1]], dtype=int)
286+
c3 = np.array([[0, -1, 0], [1, -1, 0], [0, 0, 1]], dtype=int)
285287
ops = [
286-
{"operation_id": 0, "rotation_frac": id3, "sector_mapping": {"K_valley": "K_valley"}},
288+
{"operation_id": 0, "rotation_frac": np.eye(3, dtype=int),
289+
"sector_mapping": {"K_valley": "K_valley"}, "det": 1},
290+
{"operation_id": 1, "rotation_frac": c3,
291+
"sector_mapping": {"K_valley": "K_valley"}, "det": 1},
292+
{"operation_id": 2, "rotation_frac": c2,
293+
"sector_mapping": {"K_valley": "K_valley"}, "det": 1},
287294
]
288-
report = build_hsp_star_conjugation_report(
289-
kpoint_frac_by_name={"GammaM": [0.0, 0.0, 0.0]},
295+
k_k = [0.333333, 0.333333, 0.0]
296+
297+
report_strict = build_hsp_star_conjugation_report(
298+
kpoint_frac_by_name={"KM": k_k},
299+
operations=ops,
300+
valley_names=["K_valley"],
301+
lg_tolerance=9e-7,
302+
)
303+
assert report_strict["lg_tolerance"] == 9e-7
304+
assert report_strict["tolerance"] != report_strict["lg_tolerance"]
305+
entries = report_strict.get("by_source_kpoint", {}).get("KM", [])
306+
not_lg = [e for e in entries
307+
if e.get("conjugation_status") == "source_not_in_hsp_little_group"
308+
and e.get("source_preserving_operation_id") == 1]
309+
assert len(not_lg) == 1, (
310+
f"C3 should be source_not_in_hsp_little_group at 9e-7, "
311+
f"got {[(e.get('source_preserving_operation_id'), e.get('conjugation_status')) for e in entries]}"
312+
)
313+
314+
report_loose = build_hsp_star_conjugation_report(
315+
kpoint_frac_by_name={"KM": k_k},
290316
operations=ops,
291317
valley_names=["K_valley"],
292-
lg_tolerance=3.5e-6,
318+
lg_tolerance=5e-6,
319+
)
320+
assert report_loose["lg_tolerance"] == 5e-6
321+
entries2 = report_loose.get("by_source_kpoint", {}).get("KM", [])
322+
not_lg2 = [e for e in entries2
323+
if e.get("conjugation_status") == "source_not_in_hsp_little_group"
324+
and e.get("source_preserving_operation_id") == 1]
325+
assert len(not_lg2) == 0, (
326+
f"C3 should NOT be rejected at 5e-6, "
327+
f"got {[(e.get('source_preserving_operation_id'), e.get('conjugation_status')) for e in entries2]}"
293328
)
294-
assert report["lg_tolerance"] == 3.5e-6
295-
# target-matching tolerance is separate
296-
assert "tolerance" in report
297-
assert report["tolerance"] != report["lg_tolerance"]
298329

299330

300331
def test_config_parser_rejects_invalid_tolerance(tmp_path):
@@ -324,24 +355,63 @@ def test_config_parser_rejects_invalid_tolerance(tmp_path):
324355
pass
325356

326357

327-
def test_valley_preserving_little_group_ids_passes_tolerance():
328-
"""The unitary-sewing helper _valley_preserving_little_group_ids
329-
passes tolerance through _little_group_member."""
330-
from valleyscope.workflows.analyze_hsp import (
331-
_valley_preserving_little_group_ids,
332-
)
333-
c3 = np.array([[0, -1, 0], [1, -1, 0], [0, 0, 1]], dtype=int)
334-
ops = [
335-
{"operation_id": 0, "rotation_frac": np.eye(3, dtype=int), "sector_mapping": {"K_valley": "K_valley"}},
336-
{"operation_id": 1, "rotation_frac": c3, "sector_mapping": {"K_valley": "K_valley"}},
337-
]
338-
k = np.float64([0.333333, 0.333333, 0.0])
339-
# With tolerance=9e-7 the C3 operation is rejected
340-
ids_strict = _valley_preserving_little_group_ids(ops, k, "K_valley", tolerance=9e-7)
341-
assert ids_strict == [0], f"Expected only identity, got {ids_strict}"
342-
# Default accepts both
343-
ids_default = _valley_preserving_little_group_ids(ops, k, "K_valley")
344-
assert ids_default == [0, 1], f"Expected both ops, got {ids_default}"
358+
def test_unitary_valley_sewing_attempts_honours_config_tolerance():
359+
"""_build_unitary_valley_sewing_attempts reads the configured tolerance
360+
from symmetry_payload and passes it to _valley_preserving_little_group_ids.
361+
The patched helper records the actual tolerance, proving the production
362+
caller does not fall back to a module default."""
363+
from valleyscope.workflows import analyze_hsp
364+
recorded_tolerances = []
365+
366+
def _recording_lg_ids(operations, kpoint, valley, tolerance=None):
367+
recorded_tolerances.append(tolerance)
368+
return [0]
369+
370+
original = analyze_hsp._valley_preserving_little_group_ids
371+
analyze_hsp._valley_preserving_little_group_ids = _recording_lg_ids
372+
try:
373+
payload = {
374+
"status": "ok",
375+
"detected_operations": [
376+
{"operation_id": 0, "rotation_frac": np.eye(3, dtype=int),
377+
"sector_mapping": {"K_valley": "K_valley"}, "det": 1},
378+
],
379+
"hsp_little_group_k_residual_tolerance": 3.5e-6,
380+
"kpoint_frac_by_name": {},
381+
}
382+
coverage = {
383+
"by_valley": {
384+
"K_valley": {
385+
"missing_source_hsp_representatives": [
386+
{"inverse_parent_k_frac": np.zeros(3), "source_hsp_label": "Gamma"},
387+
],
388+
},
389+
},
390+
}
391+
try:
392+
analyze_hsp._build_unitary_valley_sewing_attempts(
393+
ebr_input_candidates={"candidates": []},
394+
projected_hsp_coverage=coverage,
395+
source_hsp_basis_by_valley={"K_valley": {}},
396+
symmetry_payload=payload,
397+
kpoint_frac_by_name={}, q_cart_by_kpoint={},
398+
coefficients_by_kpoint={},
399+
seed_projectors_by_kpoint={},
400+
symmetry_adapted_projectors_by_kpoint={},
401+
workflow_decisions={},
402+
source_tables={"K_valley": {}},
403+
source_certificates={"K_valley": {}},
404+
cprime_validation_context={},
405+
source_basis_record={},
406+
wavecar_rtag="",
407+
)
408+
except AttributeError:
409+
pass # downstream requires real Table; already tested the tolerance pass-through
410+
assert recorded_tolerances == [3.5e-6], (
411+
f"Expected tolerance 3.5e-6, got {recorded_tolerances}"
412+
)
413+
finally:
414+
analyze_hsp._valley_preserving_little_group_ids = original
345415

346416

347417
def test_fractional_operation_matches_cartesian_column_convention_for_nonorthogonal_lattice():

0 commit comments

Comments
 (0)