LCOV - code coverage report
Current view: top level - tests - test_perturbative.py (source / functions) Hit Total Coverage
Test: coverage.info Lines: 115 116 99.1 %
Date: 2025-12-08 07:47:12 Functions: 9 18 50.0 %

          Line data    Source code
       1             : # SPDX-FileCopyrightText: 2025 PairInteraction Developers
       2             : # SPDX-License-Identifier: LGPL-3.0-or-later
       3             : 
       4           1 : from __future__ import annotations
       5             : 
       6           1 : import logging
       7           1 : from typing import TYPE_CHECKING
       8             : 
       9           1 : import numpy as np
      10           1 : import pytest
      11           1 : from pairinteraction.perturbative.perturbation_theory import calculate_perturbative_hamiltonian
      12           1 : from scipy import sparse
      13             : 
      14           1 : from .utils import no_log_propagation
      15             : 
      16             : if TYPE_CHECKING:
      17             :     from pairinteraction import SystemPair
      18             :     from scipy.sparse import csr_matrix
      19             : 
      20             :     from .utils import PairinteractionModule
      21             : 
      22             : 
      23           1 : def _check_sparse_matrices_equal(matrix_a: csr_matrix, matrix_b: csr_matrix) -> bool:
      24             :     """Check for equality of sparse matrices efficiently.
      25             : 
      26             :     This functions compares two sparse matrices in compressed sparse row format on their equality.
      27             : 
      28             :     Args:
      29             :         matrix_a: A sparse matrix in csr format.
      30             :         matrix_b: A sparse matrix in csr format.
      31             : 
      32             :     Returns:
      33             :         bool: True if matrices are equal, False if not.
      34             : 
      35             :     """
      36           1 :     matrix_a.sort_indices()
      37           1 :     matrix_b.sort_indices()
      38           1 :     if not (
      39             :         matrix_a.format == "csr"
      40             :         and matrix_b.format == "csr"
      41             :         and len(matrix_a.indices) == len(matrix_b.indices)
      42             :         and len(matrix_a.indptr) == len(matrix_b.indptr)
      43             :         and len(matrix_a.data) == len(matrix_b.data)
      44             :     ):
      45           0 :         return False
      46             : 
      47           1 :     return bool(
      48             :         np.all(matrix_a.indices == matrix_b.indices)
      49             :         and np.all(matrix_a.indptr == matrix_b.indptr)
      50             :         and np.allclose(matrix_a.data, matrix_b.data, rtol=0, atol=1e-14)
      51             :     )
      52             : 
      53             : 
      54           1 : @pytest.fixture
      55           1 : def system_pair_sample(pi_module: PairinteractionModule) -> SystemPair:
      56           1 :     basis = pi_module.BasisAtom(
      57             :         species="Rb",
      58             :         n=(59, 63),
      59             :         l=(0, 1),
      60             :         m=(-1.5, 1.5),
      61             :     )
      62           1 :     system = pi_module.SystemAtom(basis=basis)
      63           1 :     system.set_diamagnetism_enabled(False)
      64           1 :     system.set_magnetic_field([0, 0, 1e-3], "gauss")
      65           1 :     if not system.is_diagonal:
      66           1 :         pi_module.diagonalize([system], diagonalizer="eigen", sort_by_energy=False)
      67           1 :     basis_pair = pi_module.BasisPair([system, system])
      68           1 :     system_pair = pi_module.SystemPair(basis_pair)
      69           1 :     theta = 0
      70           1 :     r = 12
      71           1 :     system_pair.set_distance_vector(r * np.array([np.sin(theta), 0, np.cos(theta)]), "micrometer")
      72           1 :     system_pair.set_interaction_order(3)
      73           1 :     return system_pair
      74             : 
      75             : 
      76           1 : def test_perturbative_calculation1(caplog: pytest.LogCaptureFixture) -> None:
      77             :     """Test of mathematic functionality."""
      78           1 :     hamiltonian = sparse.csr_matrix(
      79             :         [[0, 1, 1, 0, 2], [1, 1, 0, 1, 3], [1, 0, 10, 1, 0], [0, 1, 1, 11, 1], [2, 3, 0, 1, 12]]
      80             :     )
      81           1 :     model_space_indices = [0, 1]
      82             : 
      83             :     # Order 0
      84           1 :     h_eff_dict, eig_perturb = calculate_perturbative_hamiltonian(hamiltonian, model_space_indices, perturbation_order=0)
      85           1 :     h_eff = sum(h for h in h_eff_dict.values())
      86           1 :     assert np.any(h_eff == np.array([[0, 0], [0, 1]]))
      87           1 :     assert _check_sparse_matrices_equal(eig_perturb, sparse.csr_matrix(sparse.eye(2, 5, k=0)))
      88             : 
      89             :     # Order 1
      90           1 :     h_eff_dict, eig_perturb = calculate_perturbative_hamiltonian(hamiltonian, model_space_indices, perturbation_order=1)
      91           1 :     h_eff = sum(h for h in h_eff_dict.values())
      92           1 :     assert np.any(h_eff == np.array([[0, 1], [1, 1]]))
      93           1 :     assert _check_sparse_matrices_equal(eig_perturb, sparse.csr_matrix([[1, 0, 0, 0, 0], [0, 1, 0, 0, 0]]))
      94             : 
      95             :     # Order 2
      96           1 :     h_eff_dict, eig_perturb = calculate_perturbative_hamiltonian(hamiltonian, model_space_indices, perturbation_order=2)
      97           1 :     h_eff = sum(h for h in h_eff_dict.values())
      98           1 :     a_00 = 0 + 1 * 1 / (0 - 10) + 2 * 2 / (0 - 12)
      99           1 :     a_01 = 1 + 2 * 3 / (0 - 12)
     100           1 :     a_10 = 1 + 3 * 2 / (1 - 12)
     101           1 :     a_11 = 1 + 1 * 1 / (1 - 11) + 3 * 3 / (1 - 12)
     102           1 :     hamiltonian_new = np.array([[a_00, (a_01 + a_10) / 2], [(a_01 + a_10) / 2, a_11]])
     103           1 :     assert np.any(h_eff == hamiltonian_new)
     104             : 
     105           1 :     v0 = sparse.eye(1, 5, k=0) + 1 / (0 - 10) * sparse.eye(1, 5, k=2) + 2 / (0 - 12) * sparse.eye(1, 5, k=4)
     106           1 :     v1 = sparse.eye(1, 5, k=1) + 1 / (1 - 11) * sparse.eye(1, 5, k=3) + 3 / (1 - 12) * sparse.eye(1, 5, k=4)
     107           1 :     assert _check_sparse_matrices_equal(eig_perturb, sparse.csr_matrix(sparse.vstack([v0, v1])))
     108             : 
     109             :     # Order 3
     110           1 :     with caplog.at_level(logging.ERROR):
     111           1 :         h_eff_dict, eig_perturb = calculate_perturbative_hamiltonian(
     112             :             hamiltonian, model_space_indices, perturbation_order=3
     113             :         )
     114           1 :     h_eff = sum(h for h in h_eff_dict.values())
     115           1 :     a_00 -= 2 * 3 * 1 / ((0 - 12) * (1 - 12))
     116           1 :     a_01 += (
     117             :         1 * 1 * 1 / ((1 - 10) * (1 - 11))
     118             :         + 2 * 1 * 1 / ((1 - 11) * (1 - 12))
     119             :         - 1 * 1 * 1 / ((0 - 10) * (1 - 10))
     120             :         - 2 * 2 * 1 / ((1 - 12) * (0 - 12))
     121             :     )
     122           1 :     a_10 += (
     123             :         1 * 1 * 1 / ((0 - 10) * (0 - 11))
     124             :         + 2 * 1 * 1 / ((0 - 11) * (0 - 12))
     125             :         - 1 * 1 * 1 / ((0 - 11) * (1 - 11))
     126             :         - 3 * 3 * 1 / ((0 - 12) * (1 - 12))
     127             :     )
     128           1 :     a_11 += 1 * 1 * 3 / ((1 - 11) * (1 - 12)) + 3 * 1 * 1 / ((1 - 11) * (1 - 12)) - 3 * 2 * 1 / ((1 - 12) * (0 - 12))
     129           1 :     hamiltonian_new = np.array([[a_00, (a_01 + a_10) / 2], [(a_01 + a_10) / 2, a_11]])
     130           1 :     assert np.any(h_eff == hamiltonian_new)
     131             : 
     132           1 :     v0 = v0 + (
     133             :         -0.5 * (1 * 1 / (0 - 10) ** 2 + 2 * 2 / (0 - 12) ** 2) * sparse.eye(1, 5, k=0)
     134             :         + (1 * 1 / ((0 - 10) * (0 - 11)) + 2 * 1 / ((0 - 11) * (0 - 12))) * sparse.eye(1, 5, k=3)
     135             :         + 1 * 1 / ((0 - 11) * (0 - 1)) * sparse.eye(1, 5, k=3)
     136             :         + 3 * 1 / ((0 - 1) * (0 - 12)) * sparse.eye(1, 5, k=4)
     137             :         + 3 * 2 / ((0 - 1) * (0 - 12)) * sparse.eye(1, 5, k=1)
     138             :     )
     139           1 :     v1 = v1 + (
     140             :         -0.5 * (1 * 1 / (1 - 11) ** 2 + 3 * 3 / (1 - 12) ** 2) * sparse.eye(1, 5, k=1)
     141             :         + 1 * 1 / ((1 - 10) * (1 - 11)) * sparse.eye(1, 5, k=2)
     142             :         + 1 * 3 / ((1 - 11) * (1 - 12)) * sparse.eye(1, 5, k=3)
     143             :         + 1 * 1 / ((1 - 11) * (1 - 12)) * sparse.eye(1, 5, k=4)
     144             :         + 1 * 1 / ((1 - 0) * (1 - 10)) * sparse.eye(1, 5, k=2)
     145             :         + 2 * 1 / ((1 - 0) * (1 - 12)) * sparse.eye(1, 5, k=4)
     146             :         + 3 * 2 / ((1 - 0) * (1 - 12)) * sparse.eye(1, 5, k=0)
     147             :     )
     148           1 :     assert _check_sparse_matrices_equal(eig_perturb, sparse.csr_matrix(sparse.vstack([v0, v1])))
     149             : 
     150             : 
     151           1 : def test_c3_with_sample_system(pi_module: PairinteractionModule, system_pair_sample: SystemPair) -> None:
     152             :     """Test whether the C3 coefficient with a given system is calculated correctly."""
     153           1 :     ket1 = pi_module.KetAtom("Rb", n=61, l=0, j=0.5, m=0.5)
     154           1 :     ket2 = pi_module.KetAtom("Rb", n=61, l=1, j=1.5, m=0.5)
     155           1 :     c3_obj = pi_module.C3(ket1, ket2)
     156           1 :     c3_obj._distance_vector = None  # avoid warning due when setting system pair
     157           1 :     c3_obj.system_pair = system_pair_sample
     158             : 
     159           1 :     c3 = c3_obj.get(unit="planck_constant * gigahertz * micrometer^3")
     160           1 :     assert np.isclose(-0.5 * c3, 3.1515)
     161             : 
     162             : 
     163           1 : def test_c3(pi_module: PairinteractionModule) -> None:
     164             :     """Test whether the C3 coefficient with automatically constructed system is calculated correctly."""
     165           1 :     ket1 = pi_module.KetAtom("Rb", n=61, l=0, j=0.5, m=0.5)
     166           1 :     ket2 = pi_module.KetAtom("Rb", n=61, l=1, j=1.5, m=0.5)
     167           1 :     c3_obj = pi_module.C3(ket1, ket2)
     168             : 
     169           1 :     c3_obj.set_electric_field([0, 0, 0], "volt/cm")
     170           1 :     c3_obj.set_magnetic_field([0, 0, 10], "gauss")
     171             : 
     172           1 :     c3 = c3_obj.get(unit="planck_constant * gigahertz * micrometer^3")
     173           1 :     assert np.isclose(-0.5 * c3, 3.2188)
     174             : 
     175             : 
     176           1 : def test_c6_with_sample_system(pi_module: PairinteractionModule, system_pair_sample: SystemPair) -> None:
     177             :     """Test whether the C6 coefficient with a given system is calculated correctly."""
     178           1 :     ket = pi_module.KetAtom(species="Rb", n=61, l=0, j=0.5, m=0.5)
     179           1 :     c6_obj = pi_module.C6(ket, ket)
     180           1 :     c6_obj._distance_vector = None  # avoid warning due when setting system pair
     181           1 :     c6_obj.system_pair = system_pair_sample
     182             : 
     183           1 :     c6 = c6_obj.get(unit="planck_constant * gigahertz * micrometer^6")
     184           1 :     assert np.isclose(c6, 167.880)
     185             : 
     186             : 
     187           1 : def test_c6(pi_module: PairinteractionModule) -> None:
     188             :     """Test whether the C6 coefficient with automatically constructed system is calculated correctly."""
     189           1 :     ket = pi_module.KetAtom(species="Rb", n=61, l=0, j=0.5, m=0.5)
     190           1 :     c6_obj = pi_module.C6(ket, ket)
     191             : 
     192           1 :     c6_obj.set_electric_field([0, 0, 0], "volt/cm")
     193           1 :     c6_obj.set_magnetic_field([0, 0, 10], "gauss")
     194             : 
     195           1 :     c6 = c6_obj.get(unit="planck_constant * gigahertz * micrometer^6")
     196           1 :     assert np.isclose(c6, 169.149)
     197             : 
     198             : 
     199           1 : def test_exact_resonance_detection(
     200             :     pi_module: PairinteractionModule, system_pair_sample: SystemPair, capsys: pytest.CaptureFixture[str]
     201             : ) -> None:
     202             :     """Test whether resonance with infinite admixture is correctly detected."""
     203           1 :     ket1 = pi_module.KetAtom("Rb", n=61, l=0, j=0.5, m=0.5)
     204           1 :     ket2 = pi_module.KetAtom("Rb", n=61, l=1, j=1.5, m=0.5)
     205           1 :     eff_system = pi_module.EffectiveSystemPair([(ket1, ket2)])
     206           1 :     eff_system.system_pair = system_pair_sample
     207             : 
     208             :     # workaround to test for errors, without showing them in the std output
     209           1 :     with no_log_propagation("pairinteraction"), np.errstate(invalid="ignore"):
     210           1 :         eff_system.get_effective_hamiltonian()
     211           1 :     captured = capsys.readouterr()
     212           1 :     assert "Detected 'inf' entries" in captured.err
     213           1 :     assert "|~Rb:61,P_3/2,1/2; Rb:61,S_1/2,1/2⟩ has infinite admixture" in captured.err
     214             : 
     215             : 
     216           1 : def test_near_resonance_detection(pi_module: PairinteractionModule, capsys: pytest.CaptureFixture[str]) -> None:
     217             :     """Test whether a near resonance is correctly detected."""
     218           1 :     ket1 = pi_module.KetAtom("Rb", n=60, l=0, j=0.5, m=0.5)
     219           1 :     ket2 = pi_module.KetAtom("Rb", n=61, l=0, j=0.5, m=0.5)
     220           1 :     eff_system = pi_module.EffectiveSystemPair([(ket1, ket2), (ket2, ket1)])
     221           1 :     eff_system.set_magnetic_field([0, 0, 245], "gauss")
     222           1 :     eff_system.set_minimum_number_of_ket_pairs(100)
     223           1 :     eff_system.set_distance(10, 35.1, "micrometer")
     224           1 :     eff_system.get_effective_hamiltonian()
     225             : 
     226             :     # workaround to test for errors, without showing them in the std output
     227           1 :     with no_log_propagation("pairinteraction"):
     228           1 :         eff_system.check_for_resonances(0.99)
     229           1 :     captured = capsys.readouterr()
     230           1 :     assert "The most perturbing states are" in captured.err
     231           1 :     assert "Rb:60,P_3/2,1/2; Rb:60,P_3/2,3/2" in captured.err

Generated by: LCOV version 1.16