Download tests/test_induced.py from aibergman/minducer: direct link, hf CLI and curl.
- Browser
- Download file 12.4 kB
-
https://huggingface.co/spaces/aibergman/minducer/resolve/main/tests/test_induced.py
- Command line
-
hf download hf://spaces/aibergman/minducer/tests/test_induced.py
-
curl -L -o test_induced.py https://huggingface.co/spaces/aibergman/minducer/resolve/main/tests/test_induced.py
12.4 kB
| from __future__ import annotations | |
| import numpy as np | |
| from induced_exchange import ( | |
| ExchangeBond, | |
| InducedMomentResponse, | |
| MagneticCrystal, | |
| MagneticSite, | |
| SublatticeClassification, | |
| instantaneous_induced_moments, | |
| reciprocal_lattice, | |
| regular_q_mesh, | |
| ) | |
| from induced_exchange.downfolding import InducedExchangeDownfolding, inverse_fourier_dressed_jij | |
| from induced_exchange.io_uppasd import load_uppasd | |
| def site(index: int, moment: float, position=(0.0, 0.0, 0.0)) -> MagneticSite: | |
| return MagneticSite(index, index, position, moment, (0.0, 0.0, 1.0)) | |
| def crystal(sites, bonds) -> MagneticCrystal: | |
| return MagneticCrystal(np.eye(3), list(sites), list(bonds)) | |
| def test_one_robust_one_induced_infers_x_and_evaluates_induced_moment(): | |
| model = crystal([site(1, 3.0), site(2, 1.5)], [ExchangeBond(2, 1, (0, 0, 0), 2.0)]) | |
| response = InducedMomentResponse(model, [1], [2], mode="j_weighted") | |
| inference = response.infer_x() | |
| assert np.isclose(inference.x[2], 0.5) | |
| assert np.isclose(inference.source_fields[2], 2.0) | |
| # The response API uses dimensionless robust orientation amplitudes and | |
| # returns normalized induced polarization p=m/|m0|. | |
| assert np.allclose(response.response_real_space([4.0]).induced_moments, [4.0]) | |
| def test_historical_equal_neighbours_parallel_and_antiparallel_cancel(): | |
| model = crystal([site(1, 2.0), site(2, 2.0), site(3, 2.0)], []) | |
| response = InducedMomentResponse( | |
| model, | |
| {"robust": [1, 2]}, | |
| {"induced": [3]}, | |
| mode="historical", | |
| x={"induced": 1.0}, | |
| neighbourhood=[1, 2], | |
| ) | |
| assert np.allclose(response.response_real_space([1.0, 1.0]).induced_moments, [2.0]) | |
| assert np.allclose(response.response_real_space([1.0, -1.0]).induced_moments, [0.0]) | |
| assert response.classification.induced_sublattices == {"induced": (3,)} | |
| def test_unequal_j_weights_are_used_in_reference_inference_and_response(): | |
| model = crystal( | |
| [site(1, 1.0), site(2, 1.0), site(3, 3.0)], | |
| [ExchangeBond(3, 1, (0, 0, 0), 2.0), ExchangeBond(3, 2, (0, 0, 0), 1.0)], | |
| ) | |
| response = InducedMomentResponse(model, [1, 2], [3]) | |
| assert np.isclose(response.infer_x().x[3], 1.0 / 3.0) | |
| assert np.allclose(response.response_real_space([1.0, 2.0]).induced_moments, [4.0 / 3.0]) | |
| def test_finite_induced_induced_coupling_matches_direct_matrix_solution(): | |
| model = crystal( | |
| [site(1, 1.0), site(2, 0.0), site(3, 0.0)], | |
| [ | |
| ExchangeBond(2, 1, (0, 0, 0), 2.0), | |
| ExchangeBond(3, 1, (0, 0, 0), 1.0), | |
| ExchangeBond(2, 3, (0, 0, 0), 0.5), | |
| ExchangeBond(3, 2, (0, 0, 0), 0.25), | |
| ], | |
| ) | |
| response = InducedMomentResponse(model, [1], [2, 3], x={2: 0.5, 3: 0.25}) | |
| expected = np.linalg.solve( | |
| np.eye(2) - np.diag([0.5, 0.25]) @ np.array([[0.0, 0.5], [0.25, 0.0]]), | |
| np.diag([0.5, 0.25]) @ np.array([2.0, 1.0]), | |
| ) | |
| result = response.response_real_space([1.0]) | |
| assert np.allclose(result.induced_moments, expected) | |
| assert np.all(result.condition_numbers < 10.0) | |
| def test_q_space_solution_matches_real_space_at_gamma_and_exposes_diagnostics(): | |
| model = crystal( | |
| [site(1, 1.0), site(2, 1.0), site(3, 1.0)], | |
| [ExchangeBond(3, 1, (0, 0, 0), 2.0), ExchangeBond(3, 2, (0, 0, 0), 1.0)], | |
| ) | |
| response = InducedMomentResponse(model, [1, 2], [3], x={3: 0.5}) | |
| real = response.response_real_space([2.0, 1.0]) | |
| reciprocal = response.response_q([[0.0, 0.0, 0.0]], [[2.0, 1.0]]) | |
| assert np.allclose(reciprocal.induced_moments[0], real.induced_moments) | |
| assert np.allclose(reciprocal.m_ind_q_over_m_ind_0[0], [1.0]) | |
| assert np.isclose(reciprocal.condition_numbers[0], 1.0) | |
| def test_fractional_q_response_matches_explicitly_converted_cartesian_q(): | |
| model = crystal( | |
| [site(1, 1.0), site(2, 1.0)], | |
| [ | |
| ExchangeBond(2, 1, (1.0, 0.0, 0.0), 2.0), | |
| ExchangeBond(2, 1, (-1.0, 0.0, 0.0), 2.0), | |
| ExchangeBond(1, 2, (1.0, 0.0, 0.0), 2.0), | |
| ExchangeBond(1, 2, (-1.0, 0.0, 0.0), 2.0), | |
| ], | |
| ) | |
| response = InducedMomentResponse(model, [1], [2], x=0.25) | |
| q_fractional = np.asarray([[0.25, 0.0, 0.0], [0.125, 0.25, 0.0]]) | |
| q_cartesian = reciprocal_lattice(model.cell).fractional_to_cartesian(q_fractional) | |
| fractional = response.response_q(q_fractional, [[1.0], [0.5]], coordinates="fractional") | |
| cartesian = response.response_q(q_cartesian, [[1.0], [0.5]], coordinates="cartesian") | |
| assert np.allclose(fractional.q_cartesian, q_cartesian) | |
| assert np.allclose(fractional.induced_moments, cartesian.induced_moments) | |
| assert np.allclose(fractional.source_fields, cartesian.source_fields) | |
| def test_pair_complete_j_weighted_downfolding_has_adjoint_cross_blocks_and_hermitian_dressing(): | |
| model = crystal( | |
| [site(1, 2.0), site(2, 0.8)], | |
| [ | |
| ExchangeBond(1, 1, (1.0, 0.0, 0.0), 8.0), | |
| ExchangeBond(1, 1, (-1.0, 0.0, 0.0), 8.0), | |
| ExchangeBond(2, 2, (1.0, 0.0, 0.0), 0.8), | |
| ExchangeBond(2, 2, (-1.0, 0.0, 0.0), 0.8), | |
| ExchangeBond(2, 1, (0.5, 0.0, 0.0), 1.2), | |
| ExchangeBond(2, 1, (-0.5, 0.0, 0.0), 1.2), | |
| ExchangeBond(1, 2, (0.5, 0.0, 0.0), 1.2), | |
| ExchangeBond(1, 2, (-0.5, 0.0, 0.0), 1.2), | |
| ], | |
| ) | |
| q = [[0.25, 0.0, 0.0]] | |
| result = InducedExchangeDownfolding( | |
| InducedMomentResponse(model, [1], [2], mode="j_weighted", x=0.5) | |
| ).evaluate(q) | |
| assert np.allclose(result.k_rm, np.swapaxes(result.k_mr.conj(), 1, 2)) | |
| assert result.dressed_hermiticity.is_hermitian | |
| assert not any("K_Mm differs" in warning for warning in result.warnings) | |
| def test_induced_toy_nonzero_q_has_no_false_block_adjoint_warning(): | |
| loaded = load_uppasd("examples/induced_toy/inpsd.dat", energy_unit="meV") | |
| response = InducedMomentResponse(loaded.model, [1], [2], mode="j_weighted", x=0.5) | |
| result = InducedExchangeDownfolding(response).evaluate([[0.25, 0.0, 0.0]]) | |
| assert result.kernel_hermiticity.is_hermitian | |
| assert result.dressed_hermiticity.is_hermitian | |
| assert not any("K_Mm differs from K_mM" in warning for warning in result.warnings) | |
| def test_convenience_wrapper_is_algebraic_and_does_not_propagate(): | |
| model = crystal([site(1, 1.0), site(2, 2.0)], [ExchangeBond(2, 1, (0, 0, 0), 3.0)]) | |
| values = instantaneous_induced_moments(model, [2.0], [1], [2], x={2: 0.5}) | |
| assert np.allclose(values, [3.0]) | |
| def test_classification_is_explicit_and_never_based_on_moment_size(): | |
| classification = SublatticeClassification.from_inputs({"Fe": [1]}, {"Pt": [2]}) | |
| assert classification.robust_sites == (1,) | |
| assert classification.induced_sites == (2,) | |
| def test_near_singular_induced_response_is_flagged_without_regularization(): | |
| model = crystal( | |
| [site(1, 1.0), site(2, 1.0)], | |
| [ExchangeBond(2, 1, (0, 0, 0), 1.0), ExchangeBond(2, 2, (0, 0, 0), 2.0)], | |
| ) | |
| response = InducedMomentResponse(model, [1], [2], x={2: 0.5}) | |
| result = response.response_real_space([1.0]) | |
| assert result.singular[0] | |
| assert any("not regularized" in warning for warning in result.warnings) | |
| assert np.isnan(result.induced_moments).all() | |
| def test_variational_one_induced_site_matches_analytic_dressed_exchange_and_energy(): | |
| model = crystal( | |
| [site(1, 1.0), site(2, 1.0)], | |
| [ | |
| ExchangeBond(1, 1, (0, 0, 0), 1.5), | |
| ExchangeBond(2, 1, (0, 0, 0), 2.0), | |
| ExchangeBond(1, 2, (0, 0, 0), 2.0), | |
| ], | |
| ) | |
| response = InducedMomentResponse(model, [1], [2], x={2: 0.5}) | |
| downfolding = InducedExchangeDownfolding(response) | |
| result = downfolding.evaluate([[0.0, 0.0, 0.0]]) | |
| # J_eff = J_MM + K_Mm X K_mM = 1.5 + 2 * 0.5 * 2. | |
| assert np.allclose(result.raw_robust[0], [[1.5]]) | |
| assert np.allclose(result.delta_induced[0], [[2.0]]) | |
| assert np.allclose(result.dressed[0], [[3.5]]) | |
| check = downfolding.energy_equivalence(result, [1.7]) | |
| assert check.equivalent | |
| assert np.allclose(check.stationary_induced, [[[1.7]]]) | |
| def test_downfolding_includes_induced_induced_propagation(): | |
| model = crystal( | |
| [site(1, 1.0), site(2, 1.0), site(3, 1.0)], | |
| [ | |
| ExchangeBond(2, 1, (0, 0, 0), 2.0), | |
| ExchangeBond(1, 2, (0, 0, 0), 2.0), | |
| ExchangeBond(3, 1, (0, 0, 0), 1.0), | |
| ExchangeBond(1, 3, (0, 0, 0), 1.0), | |
| ExchangeBond(2, 3, (0, 0, 0), 0.5), | |
| ExchangeBond(3, 2, (0, 0, 0), 0.5), | |
| ], | |
| ) | |
| response = InducedMomentResponse(model, [1], [2, 3], x={2: 0.5, 3: 0.25}) | |
| result = InducedExchangeDownfolding(response).evaluate([[0.0, 0.0, 0.0]]) | |
| expected_operator = np.linalg.solve( | |
| np.eye(2) - np.diag([0.5, 0.25]) @ np.array([[0.0, 0.5], [0.5, 0.0]]), | |
| np.diag([0.5, 0.25]), | |
| ) | |
| expected_delta = np.array([[2.0, 1.0]]) @ expected_operator @ np.array([[2.0], [1.0]]) | |
| assert np.allclose(result.response_operator[0], expected_operator) | |
| assert np.allclose(result.delta_induced[0], expected_delta) | |
| def test_downfolding_marks_singular_induced_block_without_regularization(): | |
| model = crystal( | |
| [site(1, 1.0), site(2, 1.0)], | |
| [ | |
| ExchangeBond(2, 2, (0, 0, 0), 2.0), | |
| ExchangeBond(2, 1, (0, 0, 0), 1.0), | |
| ExchangeBond(1, 2, (0, 0, 0), 1.0), | |
| ], | |
| ) | |
| result = InducedExchangeDownfolding( | |
| InducedMomentResponse(model, [1], [2], x=0.5) | |
| ).evaluate([[0.0, 0.0, 0.0]]) | |
| assert result.singular[0] | |
| assert np.isnan(result.dressed[0]).all() | |
| assert any("not regularized" in warning for warning in result.warnings) | |
| def test_downfolding_has_no_dressing_without_induced_sites_or_when_x_is_zero(): | |
| model = crystal( | |
| [site(1, 1.0), site(2, 1.0)], | |
| [ExchangeBond(1, 1, (0, 0, 0), 2.0), ExchangeBond(2, 1, (0, 0, 0), 4.0)], | |
| ) | |
| no_induced = InducedExchangeDownfolding( | |
| InducedMomentResponse(model, [1], [], x=0.0) | |
| ).evaluate([[0.0, 0.0, 0.0]]) | |
| zero_x = InducedExchangeDownfolding( | |
| InducedMomentResponse(model, [1], [2], x=0.0) | |
| ).evaluate([[0.0, 0.0, 0.0]]) | |
| assert np.allclose(no_induced.dressed, no_induced.raw_robust) | |
| assert np.allclose(zero_x.dressed, zero_x.raw_robust) | |
| assert np.allclose(zero_x.delta_induced, 0.0) | |
| def test_ordering_diagnostic_and_inverse_transform_are_explicit_about_sampling(): | |
| model = crystal( | |
| [site(1, 1.0), site(2, 1.0)], | |
| [ | |
| ExchangeBond(1, 1, (1, 0, 0), 1.0), | |
| ExchangeBond(1, 1, (-1, 0, 0), 1.0), | |
| ExchangeBond(2, 1, (0, 0, 0), 1.0), | |
| ExchangeBond(1, 2, (0, 0, 0), 1.0), | |
| ], | |
| ) | |
| response = InducedMomentResponse(model, [1], [2], x=0.0) | |
| downfolding = InducedExchangeDownfolding(response) | |
| result = downfolding.evaluate([[0.0, 0.0, 0.0], [0.5, 0.0, 0.0]]) | |
| comparison = downfolding.ordering_comparison(result) | |
| assert comparison.changed is False | |
| assert comparison.diagnostic.startswith("No —") | |
| real_space = inverse_fourier_dressed_jij(result, displacements=[[1.0, 0.0, 0.0]]) | |
| assert real_space.values.shape == (1, 1, 1) | |
| assert real_space.raw_values is not None | |
| assert real_space.delta_values is not None | |
| assert real_space.shell_diagnostics[0]["dressed_max_abs"] == 2.0 | |
| assert any("finite-q reconstructions" in warning for warning in real_space.warnings) | |
| def test_inverse_default_support_is_robust_block_and_flags_small_mesh_aliasing(): | |
| model = crystal( | |
| [site(1, 1.0), site(2, 1.0, position=(0.5, 0.0, 0.0))], | |
| [ | |
| ExchangeBond(1, 1, (2, 0, 0), 0.1), | |
| ExchangeBond(1, 1, (-2, 0, 0), 0.1), | |
| ExchangeBond(1, 2, (0.5, 0, 0), 3.0), | |
| ExchangeBond(2, 1, (0.5, 0, 0), 3.0), | |
| ], | |
| ) | |
| response = InducedMomentResponse(model, [1], [2], x=0.0) | |
| result = InducedExchangeDownfolding(response).evaluate( | |
| regular_q_mesh(model, (4, 4, 4), coordinates="fractional"), | |
| coordinates="fractional", | |
| ) | |
| assert set(result.source_displacements) == {(2.0, 0.0, 0.0), (-2.0, 0.0, 0.0)} | |
| real_space = inverse_fourier_dressed_jij(result) | |
| assert any("q mesh is too small" in warning for warning in real_space.warnings) | |
| assert not any(np.allclose(displacement, (0.5, 0.0, 0.0)) for displacement in real_space.displacements) | |