LCOV - code coverage report
Current view: top level - tests - test_green_tensor.py (source / functions) Hit Total Coverage
Test: coverage.info Lines: 98 98 100.0 %
Date: 2026-06-16 12:53:10 Functions: 9 9 100.0 %

          Line data    Source code
       1             : # SPDX-FileCopyrightText: 2024 PairInteraction Developers
       2             : # SPDX-License-Identifier: LGPL-3.0-or-later
       3             : 
       4           1 : from __future__ import annotations
       5             : 
       6           1 : from typing import TYPE_CHECKING
       7             : 
       8           1 : import numpy as np
       9           1 : import pytest
      10           1 : from pairinteraction import ureg
      11           1 : from pairinteraction.green_tensor import GreenTensorFreeSpace
      12           1 : from pairinteraction.green_tensor.green_tensor_cavity import GreenTensorCavity
      13           1 : from pairinteraction.green_tensor.green_tensor_surface import GreenTensorSurface
      14             : 
      15             : if TYPE_CHECKING:
      16             :     from pairinteraction.green_tensor.green_tensor_base import GreenTensorBase
      17             :     from pairinteraction.units import NDArray
      18             : 
      19             :     from .utils import PairinteractionModule
      20             : 
      21           1 : DISTANCE_VECTOR_MUM_LIST = [[1, 0, 0], [0, 1, 0], [0, 0, 1], [1, 0, 3.5]]
      22             : 
      23             : 
      24           1 : @pytest.mark.parametrize("distance_vector_mum", DISTANCE_VECTOR_MUM_LIST)
      25           1 : def test_static_green_tensor(distance_vector_mum: list[float]) -> None:
      26           1 :     gt_reference = reference_green_tensor_vacuum(distance_vector_mum)
      27             : 
      28             :     gt: GreenTensorBase
      29           1 :     gt = GreenTensorFreeSpace([0, 0, 0], distance_vector_mum, unit="micrometer", static_limit=True)
      30           1 :     gt_dipole_dipole = gt.get(1, 1, transition_energy=0, scaled=True)
      31           1 :     np.testing.assert_allclose(gt_dipole_dipole.m, gt_reference)
      32             : 
      33           1 :     gt = GreenTensorSurface(
      34             :         [0, 0, 0],
      35             :         distance_vector_mum,
      36             :         point_on_plane=[0, 0, 1000],
      37             :         surface_normal=[0, 0, 1],
      38             :         unit="micrometer",
      39             :         static_limit=True,
      40             :     )
      41           1 :     gt_dipole_dipole = gt.get(1, 1, transition_energy=0, scaled=True)
      42           1 :     np.testing.assert_allclose(gt_dipole_dipole.m, gt_reference, rtol=1e-6, atol=1e-16)
      43             : 
      44           1 :     gt = GreenTensorCavity(
      45             :         [0, 0, 0],
      46             :         distance_vector_mum,
      47             :         point_on_plane1=[0, 0, -1000],
      48             :         point_on_plane2=[0, 0, 1000],
      49             :         surface_normal=[0, 0, 1],
      50             :         unit="micrometer",
      51             :         static_limit=True,
      52             :     )
      53           1 :     gt_dipole_dipole = gt.get(1, 1, transition_energy=0, scaled=True)
      54           1 :     np.testing.assert_allclose(gt_dipole_dipole.m, gt_reference, rtol=1e-6, atol=1e-16)
      55             : 
      56             : 
      57           1 : @pytest.mark.parametrize("distance_vector_mum", DISTANCE_VECTOR_MUM_LIST)
      58           1 : def test_static_green_tensor_pair_potential(
      59             :     pi_module: PairinteractionModule, use_real: bool, distance_vector_mum: list[float]
      60             : ) -> None:
      61           1 :     if use_real and distance_vector_mum[1] != 0:
      62           1 :         return  # run tests with y-component only for complex Green tensors
      63             : 
      64           1 :     ket = pi_module.KetAtom("Rb", n=60, l=0, m=0.5)
      65           1 :     basis = pi_module.BasisAtom("Rb", n=(ket.n - 2, ket.n + 2), l=(0, 2))
      66           1 :     system = pi_module.SystemAtom(basis)
      67           1 :     system.set_magnetic_field([0, 0, 1], "gauss")
      68           1 :     system.set_electric_field([0, 0, 1], "V/cm")
      69           1 :     system.diagonalize()
      70             : 
      71           1 :     pair_energy_ghz = 2 * system.get_corresponding_energy(ket, unit="GHz")
      72           1 :     energy_range_ghz = (pair_energy_ghz - 3, pair_energy_ghz + 3)
      73           1 :     basis_pair = pi_module.BasisPair([system, system], energy=energy_range_ghz, energy_unit="GHz")
      74             : 
      75           1 :     system_pair_vacuum = pi_module.SystemPair(basis_pair)
      76           1 :     system_pair_vacuum.set_distance_vector(distance_vector_mum, unit="micrometer")
      77           1 :     system_pair_vacuum.set_interaction_order(3)
      78             : 
      79           1 :     gt = GreenTensorFreeSpace([0, 0, 0], distance_vector_mum, unit="micrometer", interaction_order=3, static_limit=True)
      80           1 :     gt.set_relative_permittivity(1.0)
      81           1 :     system_pair_free_space = pi_module.SystemPair(basis_pair).set_green_tensor(gt)
      82             : 
      83           1 :     pi_module.diagonalize([system_pair_free_space, system_pair_vacuum], sort_by_energy=True)
      84           1 :     np.testing.assert_allclose(
      85             :         system_pair_vacuum.get_eigenenergies("GHz") - pair_energy_ghz,
      86             :         system_pair_free_space.get_eigenenergies("GHz") - pair_energy_ghz,
      87             :     )
      88             : 
      89             : 
      90           1 : @pytest.mark.parametrize("distance_vector_mum", DISTANCE_VECTOR_MUM_LIST)
      91           1 : def test_vacuum_green_tensor(
      92             :     pi_module: PairinteractionModule, use_real: bool, distance_vector_mum: list[float]
      93             : ) -> None:
      94           1 :     if use_real and distance_vector_mum[1] != 0:
      95           1 :         return  # run tests with y-component only for complex Green tensors
      96             : 
      97           1 :     ket1 = pi_module.KetAtom("Rb", n=60, l=0, j=0.5, m=0.5)
      98           1 :     ket2 = pi_module.KetAtom("Rb", n=60, l=1, j=0.5, m=0.5)
      99             : 
     100           1 :     basis = pi_module.BasisAtom("Rb", n=(0, 0), additional_kets=[ket1, ket2])
     101           1 :     system = pi_module.SystemAtom(basis)
     102           1 :     pair_energy = ket1.get_energy("GHz") + ket2.get_energy("GHz")
     103           1 :     basis_pair = pi_module.BasisPair((system, system), energy=(pair_energy - 0.1, pair_energy + 0.1), energy_unit="GHz")
     104             : 
     105           1 :     dd = ket1.get_matrix_element(ket2, "electric_dipole", q=0)
     106           1 :     gt_reference = reference_green_tensor_vacuum(distance_vector_mum)
     107           1 :     reference = dd * gt_reference[2, 2] * dd
     108             : 
     109             :     # test internal vacuum green tensor
     110           1 :     system_pair = pi_module.SystemPair(basis_pair)
     111           1 :     system_pair.set_distance_vector(distance_vector_mum, "micrometer")
     112           1 :     hamiltonian = system_pair.get_hamiltonian("hartree").toarray()
     113           1 :     np.testing.assert_allclose(hamiltonian[1, 0], reference)
     114             : 
     115             :     # test custom free space vacuum green tensor
     116           1 :     system_pair = pi_module.SystemPair(basis_pair)
     117           1 :     gt = GreenTensorFreeSpace([0, 0, 0], distance_vector_mum, unit="micrometer", static_limit=True)
     118           1 :     system_pair.set_green_tensor(gt)
     119           1 :     hamiltonian = system_pair.get_hamiltonian("hartree").toarray()
     120           1 :     np.testing.assert_allclose(hamiltonian[1, 0], reference)
     121             : 
     122             : 
     123           1 : def reference_green_tensor_vacuum(distance_vector_mum: list[float]) -> NDArray:
     124           1 :     distance_mum = np.linalg.norm(distance_vector_mum)
     125           1 :     distance_au = ureg.Quantity(distance_mum, "micrometer").to_base_units().m
     126           1 :     gt: NDArray = (
     127             :         np.eye(3) - 3 * np.outer(distance_vector_mum, distance_vector_mum) / distance_mum**2
     128             :     ) / distance_au**3
     129           1 :     return gt
     130             : 
     131             : 
     132           1 : def rotation_matrix_from_axis_angle(axis: list[float], angle: float) -> NDArray:
     133           1 :     axis_array = np.array(axis, dtype=float)
     134           1 :     axis_array /= np.linalg.norm(axis_array)
     135           1 :     cross_matrix = np.array(
     136             :         [
     137             :             [0, -axis_array[2], axis_array[1]],
     138             :             [axis_array[2], 0, -axis_array[0]],
     139             :             [-axis_array[1], axis_array[0], 0],
     140             :         ]
     141             :     )
     142           1 :     return np.eye(3) + np.sin(angle) * cross_matrix + (1 - np.cos(angle)) * cross_matrix @ cross_matrix  # type: ignore [no-any-return]
     143             : 
     144             : 
     145           1 : def test_surface_green_tensor_rotation_invariance() -> None:
     146           1 :     rotation = rotation_matrix_from_axis_angle([1, 2, 0.5], np.deg2rad(37))
     147           1 :     pos1 = np.array([0.4, -0.2, 0.8])
     148           1 :     pos2 = np.array([1.1, 0.3, 1.4])
     149           1 :     point_on_plane = np.array([0.0, 0.0, 0.0])
     150             : 
     151           1 :     gt_reference = GreenTensorSurface(
     152             :         pos1,
     153             :         pos2,
     154             :         point_on_plane=point_on_plane,
     155             :         surface_normal=[0, 0, 1],
     156             :         unit="micrometer",
     157             :         static_limit=True,
     158             :     ).get(1, 1, transition_energy=0, scaled=True)
     159           1 :     gt_rotated = GreenTensorSurface(
     160             :         rotation.T @ pos1,
     161             :         rotation.T @ pos2,
     162             :         point_on_plane=rotation.T @ point_on_plane,
     163             :         surface_normal=rotation.T @ np.array([0, 0, 1]),
     164             :         unit="micrometer",
     165             :         static_limit=True,
     166             :     ).get(1, 1, transition_energy=0, scaled=True)
     167             : 
     168           1 :     np.testing.assert_allclose(
     169             :         gt_rotated.m,
     170             :         rotation.T @ gt_reference.m @ rotation,
     171             :         rtol=1e-10,
     172             :         atol=1e-12,
     173             :     )
     174             : 
     175             : 
     176           1 : def test_cavity_green_tensor_rotation_invariance() -> None:
     177           1 :     rotation = rotation_matrix_from_axis_angle([0.3, 1.0, -0.2], np.deg2rad(51))
     178           1 :     pos1 = np.array([0.2, -0.1, -0.2])
     179           1 :     pos2 = np.array([0.8, 0.5, 0.4])
     180           1 :     point_on_plane1 = np.array([0.0, 0.0, -1.0])
     181           1 :     point_on_plane2 = np.array([0.0, 0.0, 1.5])
     182             : 
     183           1 :     gt_reference = GreenTensorCavity(
     184             :         pos1,
     185             :         pos2,
     186             :         point_on_plane1=point_on_plane1,
     187             :         point_on_plane2=point_on_plane2,
     188             :         surface_normal=[0, 0, 1],
     189             :         unit="micrometer",
     190             :         static_limit=True,
     191             :     ).get(1, 1, transition_energy=0, scaled=True)
     192           1 :     gt_rotated = GreenTensorCavity(
     193             :         rotation.T @ pos1,
     194             :         rotation.T @ pos2,
     195             :         point_on_plane1=rotation.T @ point_on_plane1,
     196             :         point_on_plane2=rotation.T @ point_on_plane2,
     197             :         surface_normal=rotation.T @ np.array([0, 0, 1]),
     198             :         unit="micrometer",
     199             :         static_limit=True,
     200             :     ).get(1, 1, transition_energy=0, scaled=True)
     201             : 
     202           1 :     np.testing.assert_allclose(
     203             :         gt_rotated.m,
     204             :         rotation.T @ gt_reference.m @ rotation,
     205             :         rtol=1e-10,
     206             :         atol=1e-12,
     207             :     )
     208             : 
     209             : 
     210           1 : def test_surface_green_tensor_rejects_zero_normal() -> None:
     211           1 :     with pytest.raises(ValueError, match="cannot be zero"):
     212           1 :         GreenTensorSurface(
     213             :             [0, 0, 0],
     214             :             [0, 0, 1],
     215             :             point_on_plane=[0, 0, 0],
     216             :             surface_normal=[0, 0, 0],
     217             :             unit="micrometer",
     218             :             static_limit=True,
     219             :         )
     220             : 
     221             : 
     222           1 : def test_cavity_green_tensor_rejects_coincident_planes() -> None:
     223           1 :     gt = GreenTensorCavity(
     224             :         [0, 0, 0],
     225             :         [0, 0, 1],
     226             :         point_on_plane1=[0, 0, 0],
     227             :         point_on_plane2=[1, 0, 0],
     228             :         surface_normal=[0, 0, 1],
     229             :         unit="micrometer",
     230             :         static_limit=True,
     231             :     )
     232             : 
     233           1 :     with pytest.raises(ValueError, match="must be distinct"):
     234           1 :         gt.get(1, 1, transition_energy=0, scaled=True)

Generated by: LCOV version 1.16