File size: 4,727 Bytes
9e289e7
 
 
 
 
 
 
 
3ab1010
9e289e7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3ab1010
 
 
 
 
 
 
 
 
9e289e7
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
from __future__ import annotations

import numpy as np

from induced_exchange import (
    ExchangeBond,
    ExchangeDataset,
    ExternalInducedResponse,
    InducedMomentResponse,
    MagneticCrystal,
    MagneticSite,
    UnitMetadata,
    compare_exchange_datasets,
    compare_induced_response,
    validate_dataset_compatibility,
)


def site(index: int, moment: float = 1.0, position=(0.0, 0.0, 0.0)) -> MagneticSite:
    return MagneticSite(index, index, position, moment, (0.0, 0.0, 1.0))


def bond(i: int, j: int, displacement, jij: float) -> ExchangeBond:
    return ExchangeBond(i, j, tuple(displacement), jij)


def crystal(bonds, *, moments=(1.0,), cell=None, energy="unspecified") -> MagneticCrystal:
    if cell is None:
        cell = np.eye(3)
    sites = [site(index + 1, moment) for index, moment in enumerate(moments)]
    return MagneticCrystal(np.asarray(cell, dtype=float), sites, list(bonds), UnitMetadata(energy=energy))


def test_identical_datasets_have_equal_raw_robust_dressed_and_export_tables(tmp_path):
    model = crystal(
        [bond(1, 1, (1, 0, 0), 1.0), bond(1, 1, (-1, 0, 0), 1.0), bond(2, 1, (0, 0, 0), 2.0), bond(1, 2, (0, 0, 0), 2.0)],
        moments=(1.0, 1.0),
    )
    result = compare_exchange_datasets(
        ExchangeDataset(model, label="LKAG", robust_sites=(1,), induced_sites=(2,), x=0.5),
        ExchangeDataset(model, label="frozen magnon", robust_sites=(1,), induced_sites=(2,), x=0.5),
        [[0.0, 0.0, 0.0], [0.25, 0.0, 0.0]],
        include_magnons=False,
    )
    assert result.compatibility.compatible
    assert np.allclose(result.raw_a, result.raw_b)
    assert np.allclose(result.robust_a, result.robust_b)
    assert np.allclose(result.dressed_a, result.dressed_b)
    written = result.export(tmp_path)
    assert (tmp_path / "imx06_raw_jq_eigenvalues.csv").exists()
    assert written["summary"].exists()


def test_structural_compatibility_rejects_different_basis_but_allows_exchange_difference():
    a = crystal([], moments=(1.0,))
    b = crystal([], moments=(1.0,), cell=[[1.0, 0.0, 0.0], [0.0, 2.0, 0.0], [0.0, 0.0, 1.0]])
    report = validate_dataset_compatibility(a, b)
    assert not report.compatible
    assert any("cells" in error for error in report.errors)


def test_sign_reversed_dataset_changes_raw_ordering_diagnostic():
    a = crystal([bond(1, 1, (1, 0, 0), 1.0), bond(1, 1, (-1, 0, 0), 1.0)])
    b = crystal([bond(1, 1, (1, 0, 0), -1.0), bond(1, 1, (-1, 0, 0), -1.0)])
    result = compare_exchange_datasets(a, b, [[0.0, 0.0, 0.0], [0.5, 0.0, 0.0]], include_magnons=False, stiffness_q_max=None)
    assert result.dataset_a.raw_ordering.kind == "FM at Gamma"
    assert result.dataset_b.raw_ordering.kind == "AF-like/non-Gamma"
    assert any("Dataset B raw ordering" in item for item in result.diagnostics)


def test_response_comparison_matching_and_mismatch_are_quantified():
    model = crystal([bond(2, 1, (0, 0, 0), 2.0), bond(1, 2, (0, 0, 0), 2.0)], moments=(1.0, 1.0))
    response_a = InducedMomentResponse(model, [1], [2], x=0.5)
    response_b = InducedMomentResponse(model, [1], [2], x=0.5)
    q = [[0.0, 0.0, 0.0], [0.25, 0.0, 0.0]]
    matching = compare_induced_response(response_a, response_b, q, [[1.0], [1.0]], external=ExternalInducedResponse([1.0, 1.0], q=np.asarray(q)))
    assert matching.metrics_a is not None and matching.metrics_a.rmse == 0.0
    mismatching = compare_induced_response(response_a, response_b, q, [[1.0], [1.0]], external=ExternalInducedResponse([1.0, 2.0], q=np.asarray(q)))
    assert mismatching.metrics_a is not None and mismatching.metrics_a.strongly_disagrees
    assert any("do not reproduce" in warning for warning in mismatching.warnings)


def test_response_comparison_converts_normalized_model_response_to_physical_moments():
    model = crystal([bond(2, 1, (0, 0, 0), 2.0), bond(1, 2, (0, 0, 0), 2.0)], moments=(3.0, 1.5))
    response = InducedMomentResponse(model, [1], [2], x=0.5)
    external = ExternalInducedResponse([1.5, 1.5], q=np.asarray([[0.0, 0.0, 0.0], [0.25, 0.0, 0.0]]))
    result = compare_induced_response(response, response, external.q, [[1.0], [1.0]], external=external)
    assert np.allclose(result.model_a, [[1.0], [1.0]])
    assert result.metrics_a is not None and result.metrics_a.rmse == 0.0


def test_external_response_file_accepts_both_supported_layouts(tmp_path):
    q_file = tmp_path / "q_response.dat"
    q_file.write_text("0 0 0 1\n0.5 0 0 0.2\n", encoding="utf-8")
    path_file = tmp_path / "path_response.dat"
    path_file.write_text("0 1\n1 0.2\n", encoding="utf-8")
    assert ExternalInducedResponse.from_file(q_file).q.shape == (2, 3)
    assert ExternalInducedResponse.from_file(path_file).path_coordinate.tolist() == [0.0, 1.0]