LCOV - code coverage report
Current view: top level - tests - test_db.py (source / functions) Hit Total Coverage
Test: coverage.info Lines: 103 103 100.0 %
Date: 2025-04-29 15:59:54 Functions: 6 12 50.0 %

          Line data    Source code
       1             : # SPDX-FileCopyrightText: 2025 Pairinteraction Developers
       2             : # SPDX-License-Identifier: LGPL-3.0-or-later
       3             : 
       4             : """Test receiving matrix elements from the databases."""
       5             : 
       6           1 : from collections.abc import Generator
       7           1 : from pathlib import Path
       8             : 
       9           1 : import duckdb
      10           1 : import numpy as np
      11           1 : import pytest
      12           1 : from packaging.version import Version
      13           1 : from sympy.physics.wigner import wigner_3j
      14             : 
      15           1 : import pairinteraction.real as pi
      16           1 : from tests.constants import GAUSS_IN_ATOMIC_UNITS, HARTREE_IN_GHZ, SPECIES_TO_NUCLEAR_SPIN, SUPPORTED_SPECIES
      17             : 
      18           1 : expected_operator = np.array([[3.58588117, 1.66420213], [1.66420213, 4.16645123]])
      19             : 
      20             : 
      21           1 : def fetch_id(n: int, l: float, f: float, s: float, connection: duckdb.DuckDBPyConnection, table: str) -> int:
      22           1 :     return connection.execute(
      23             :         f"SELECT id FROM '{table}' WHERE n = {n} AND f = {f} ORDER BY (exp_s - {s})^2+(exp_l - {l})^2 LIMIT 1"
      24             :     ).fetchone()[0]
      25             : 
      26             : 
      27           1 : def fetch_wigner_element(
      28             :     f_initial: float,
      29             :     f_final: float,
      30             :     m_initial: float,
      31             :     m_final: float,
      32             :     kappa: int,
      33             :     q: int,
      34             :     connection: duckdb.DuckDBPyConnection,
      35             :     table: str,
      36             : ) -> float:
      37           1 :     result = connection.execute(
      38             :         f"SELECT val FROM '{table}' WHERE f_initial = {f_initial} AND f_final = {f_final} AND "
      39             :         f"m_initial = {m_initial} AND m_final = {m_final} AND kappa = {kappa} AND q = {q}"
      40             :     ).fetchone()
      41           1 :     return result[0] if result else 0
      42             : 
      43             : 
      44           1 : def fetch_reduced_matrix_element(
      45             :     id_initial: int, id_final: int, connection: duckdb.DuckDBPyConnection, table: str
      46             : ) -> float:
      47           1 :     result = connection.execute(
      48             :         f"SELECT val FROM '{table}' WHERE id_initial = {id_initial} AND id_final = {id_final}"
      49             :     ).fetchone()
      50           1 :     return result[0] if result else 0
      51             : 
      52             : 
      53           1 : @pytest.fixture(scope="module")
      54           1 : def connection() -> Generator[duckdb.DuckDBPyConnection]:
      55           1 :     with duckdb.connect(":memory:") as connection:
      56           1 :         yield connection
      57             : 
      58             : 
      59           1 : @pytest.mark.parametrize("swap_states", [False, True])
      60           1 : def test_database(connection: duckdb.DuckDBPyConnection, swap_states: bool) -> None:
      61             :     """Test receiving matrix elements from the databases."""
      62           1 :     database = pi.Database.get_global_database()
      63           1 :     bfield_in_gauss = 1500
      64             : 
      65             :     # Define initial and final quantum states
      66           1 :     n_initial, n_final = 54, 54
      67           1 :     l_initial, l_final = 1, 1
      68           1 :     f_initial, f_final = 1, 0
      69           1 :     m_initial, m_final = 0, 0
      70           1 :     s_initial, s_final = 0.6, 1.0
      71             : 
      72             :     # Swap states if required by the test parameter
      73           1 :     if swap_states:
      74           1 :         n_initial, n_final = n_final, n_initial
      75           1 :         l_initial, l_final = l_final, l_initial
      76           1 :         f_initial, f_final = f_final, f_initial
      77           1 :         m_initial, m_final = m_final, m_initial
      78           1 :         s_initial, s_final = s_final, s_initial
      79             : 
      80             :     # Get the Zeeman interaction operator from the database using pairinteraction
      81           1 :     ket_initial = pi.KetAtom("Yb174_mqdt", n=n_initial, l=l_initial, f=f_initial, m=m_initial, s=s_initial)
      82           1 :     ket_final = pi.KetAtom("Yb174_mqdt", n=n_final, l=l_final, f=f_final, m=m_final, s=s_final)
      83           1 :     basis = pi.BasisAtom("Yb174_mqdt", additional_kets=[ket_initial, ket_final])
      84           1 :     operator = (
      85             :         pi.SystemAtom(basis)
      86             :         .set_magnetic_field([0, 0, bfield_in_gauss], unit="G")
      87             :         .set_diamagnetism_enabled(True)
      88             :         .get_hamiltonian(unit="GHz")
      89             :     )
      90           1 :     operator -= np.diag(np.sort([ket_initial.get_energy(unit="GHz"), ket_final.get_energy(unit="GHz")]))
      91           1 :     assert np.allclose(operator, expected_operator)
      92             : 
      93             :     # Get the latest parquet files from the database directory
      94           1 :     parquet_files = {}
      95           1 :     parquet_versions = {}
      96           1 :     for path in list(Path(database.database_dir).rglob("*.parquet")):
      97           1 :         species, version_str = path.parent.name.rsplit("_v", 1)
      98           1 :         table = path.stem
      99           1 :         name = f"{species}_{table}"
     100           1 :         version = Version(version_str)
     101           1 :         if name not in parquet_files or version > parquet_versions[name]:
     102           1 :             parquet_files[name] = path
     103           1 :             parquet_versions[name] = version
     104           1 :     assert "misc_wigner" in parquet_files
     105           1 :     assert "Yb174_mqdt_states" in parquet_files
     106           1 :     assert "Yb174_mqdt_matrix_elements_mu" in parquet_files
     107           1 :     assert "Yb174_mqdt_matrix_elements_q" in parquet_files
     108           1 :     assert "Yb174_mqdt_matrix_elements_q0" in parquet_files
     109             : 
     110             :     # Obtain the ids of the initial and final states
     111           1 :     id_initial = fetch_id(n_initial, l_initial, f_initial, s_initial, connection, parquet_files["Yb174_mqdt_states"])
     112           1 :     assert id_initial == 362 if swap_states else 363
     113             : 
     114           1 :     id_final = fetch_id(n_final, l_final, f_final, s_final, connection, parquet_files["Yb174_mqdt_states"])
     115           1 :     assert id_final == 363 if swap_states else 362
     116             : 
     117             :     # Obtain a matrix element of the magnetic dipole operator (for the chosen kets, it is non-zero iff initial != final)
     118           1 :     kappa, q = 1, 0
     119           1 :     wigner_element = fetch_wigner_element(
     120             :         f_initial, f_final, m_initial, m_final, kappa, q, connection, parquet_files["misc_wigner"]
     121             :     )
     122           1 :     assert np.isclose(
     123             :         wigner_element,
     124             :         float((-1) ** (f_final - m_final) * wigner_3j(f_final, kappa, f_initial, -m_final, q, m_initial)),
     125             :     )
     126             : 
     127           1 :     mu = fetch_reduced_matrix_element(id_initial, id_final, connection, parquet_files["Yb174_mqdt_matrix_elements_mu"])
     128           1 :     matrix_element = -wigner_element * mu * bfield_in_gauss * GAUSS_IN_ATOMIC_UNITS * HARTREE_IN_GHZ
     129           1 :     assert np.isclose(matrix_element, operator[0, 1])
     130             : 
     131             :     # Obtain a matrix element of the diamagnetic operator (for the chosen kets, it is non-zero iff initial == final)
     132           1 :     n_final = n_initial
     133           1 :     l_final = l_initial
     134           1 :     f_final = f_initial
     135           1 :     m_final = m_initial
     136           1 :     s_final = s_initial
     137           1 :     id_final = id_initial
     138             : 
     139           1 :     kappa, q = 0, 0
     140           1 :     wigner_element = fetch_wigner_element(
     141             :         f_initial, f_final, m_initial, m_final, kappa, q, connection, parquet_files["misc_wigner"]
     142             :     )
     143           1 :     assert np.isclose(
     144             :         wigner_element,
     145             :         float((-1) ** (f_final - m_final) * wigner_3j(f_final, kappa, f_initial, -m_final, q, m_initial)),
     146             :     )
     147             : 
     148           1 :     q0 = fetch_reduced_matrix_element(id_initial, id_final, connection, parquet_files["Yb174_mqdt_matrix_elements_q0"])
     149           1 :     matrix_element = 1 / 12 * wigner_element * q0 * (bfield_in_gauss * GAUSS_IN_ATOMIC_UNITS) ** 2 * HARTREE_IN_GHZ
     150             : 
     151           1 :     kappa, q = 2, 0
     152           1 :     wigner_element = fetch_wigner_element(
     153             :         f_initial, f_final, m_initial, m_final, kappa, q, connection, parquet_files["misc_wigner"]
     154             :     )
     155           1 :     assert np.isclose(
     156             :         wigner_element,
     157             :         float((-1) ** (f_final - m_final) * wigner_3j(f_final, kappa, f_initial, -m_final, q, m_initial)),
     158             :     )
     159             : 
     160           1 :     q = fetch_reduced_matrix_element(id_initial, id_final, connection, parquet_files["Yb174_mqdt_matrix_elements_q"])
     161           1 :     matrix_element -= 1 / 12 * wigner_element * q * (bfield_in_gauss * GAUSS_IN_ATOMIC_UNITS) ** 2 * HARTREE_IN_GHZ
     162           1 :     assert np.isclose(matrix_element, operator[0, 0] if swap_states else operator[1, 1])
     163             : 
     164             : 
     165           1 : @pytest.mark.parametrize("species", SUPPORTED_SPECIES)
     166           1 : def test_obtaining_kets(species: str) -> None:
     167             :     """Test obtaining kets from the database."""
     168           1 :     is_mqdt = species.endswith("_mqdt")
     169           1 :     is_single_valence_electron = species in ["Rb"]
     170           1 :     is_triplet = species in ["Sr88_triplet"]
     171             : 
     172           1 :     quantum_number_i = SPECIES_TO_NUCLEAR_SPIN[species] if is_mqdt else 0
     173           1 :     quantum_number_s = 0.5 if is_single_valence_electron else (1 if is_triplet else 0)
     174           1 :     quantum_number_f = quantum_number_i + quantum_number_s
     175           1 :     quantum_number_m = quantum_number_i + quantum_number_s
     176             : 
     177             :     # Obtain a ket from the database
     178           1 :     ket = pi.KetAtom(species, n=60, l=0, f=quantum_number_f, m=quantum_number_m, s=quantum_number_s)
     179             : 
     180           1 :     print(f"|{ket}>")
     181             : 
     182             :     # Check the result
     183           1 :     assert ket.species == species
     184           1 :     assert ket.n == 60 if not is_mqdt else abs(ket.n - 60) < 1
     185           1 :     assert ket.l == 0 if not is_mqdt else abs(ket.l - 0) < 1
     186           1 :     assert ket.f == quantum_number_f
     187           1 :     assert ket.m == quantum_number_m
     188           1 :     assert ket.s == quantum_number_s if not is_mqdt else abs(ket.s - quantum_number_s) < 1
     189           1 :     assert {
     190             :         "Rb": "Rb:60,S_1/2,1/2",
     191             :         "Sr88_singlet": "Sr88_singlet:60,S_0,0",
     192             :         "Sr88_triplet": "Sr88_triplet:60,S_1,1",
     193             :         "Sr88_mqdt": "Sr88:S=0.0,nu=56.7,L=0.0,J=0,0",
     194             :         "Sr87_mqdt": "Sr87:S=0.5,nu=56.6,L=0.0,F=9/2,9/2",
     195             :         "Yb174_mqdt": "Yb174:S=0.0,nu=55.7,L=0.0,J=0,0",
     196             :         "Yb171_mqdt": "Yb171:S=0.3,nu=55.7,L=0.0,F=1/2,1/2",
     197             :         "Yb173_mqdt": "Yb173:S=0.4,nu=56.4,L=0.0,F=5/2,5/2",
     198             :     }[species] == repr(ket)

Generated by: LCOV version 1.16