magnus.hamiltonians.hamiltonians_pseudodirac

hamiltonians_pseudodirac.py

Compute pseudo-Dirac neutrino Hamiltonians for an arbitrary pairing pattern.

A Dirac neutrino may in fact be two Majorana states separated by a tiny mass-squared splitting. Each mass eigenstate that carries such a partner becomes two eigenstates, \((|\nu_j\rangle \pm |s_j\rangle)/\sqrt{2}\), with masses \(m_j^2\) and \(m_j^2 + \delta m_j^2\); each eigenstate without a partner stays single. The pairing is chosen per mass state, so a three-flavor spectrum with partners on two of its three states is a five-dimensional problem, and is supported.

The physical content is a separation of scales. The pair splittings are far smaller than the standard ones, so the standard phases average away over an astrophysical baseline while each pair stays mutually coherent – which is exactly the regime the coherent-block averaging form is for, and the regime in which the naive sum over eigenstates is wrong. See magnus.avgprob.coherence_blocks() and magnus.avgprob.coherence_report().

No partial averaging is provided here, deliberately. This package’s contract is that a fully coherent pair is handled by the block form, a fully decohered pair by the ordinary sum, and anything in between is refused rather than approximated. These routines build the Hamiltonian; the existing engines propagate it.

Routine listings

  • PseudoDiracSplittingWarning - Raised when a splitting is not small

  • pseudo_dirac_mixing_matrix - Returns the extended mixing matrix

  • pseudo_dirac_mass_squared - Returns the extended mass-squared values

  • hamiltonian_pseudo_dirac_vacuum_energy_independent - Returns H_vac (no

    1/E)

  • hamiltonian_pseudo_dirac_vacuum - Returns H_vac

  • hamiltonian_pseudo_dirac_matter - Returns H_matter

Exceptions

PseudoDiracSplittingWarning

A pseudo-Dirac splitting is not small against the standard ones.

Functions

pseudo_dirac_mixing_matrix(→ numpy.ndarray)

Returns the extended mixing matrix for a pseudo-Dirac spectrum.

pseudo_dirac_mass_squared(→ numpy.ndarray)

Returns the extended mass-squared values for a pseudo-Dirac spectrum.

hamiltonian_pseudo_dirac_vacuum_energy_independent(...)

Returns the pseudo-Dirac vacuum Hamiltonian, without the 1/E factor.

hamiltonian_pseudo_dirac_vacuum(→ numpy.ndarray)

Returns the pseudo-Dirac Hamiltonian for oscillations in vacuum.

hamiltonian_pseudo_dirac_matter(→ numpy.ndarray)

Returns the matter part of the pseudo-Dirac Hamiltonian.

Module Contents

exception magnus.hamiltonians.hamiltonians_pseudodirac.PseudoDiracSplittingWarning[source]

Bases: UserWarning

A pseudo-Dirac splitting is not small against the standard ones.

The physics of the pseudo-Dirac case rests on a separation of scales: the pair splittings must be far below the standard mass-squared differences, so that the standard phases decohere while the pairs stay coherent. A splitting comparable with \(\Delta m^2_{21}\) describes a different system – effectively a sterile state with an ordinary splitting – for which the four- and five-flavor routines are the appropriate tools.

The calculation still proceeds; nothing is clamped.

Added in version 1.0.5.

magnus.hamiltonians.hamiltonians_pseudodirac.pseudo_dirac_mixing_matrix(mixing_matrix: Sequence | numpy.ndarray, pairs: Mapping[int, float] | None = None) → numpy.ndarray[source]

Returns the extended mixing matrix for a pseudo-Dirac spectrum.

Each paired mass eigenstate \(j\) is replaced by the two combinations \((|\nu_j\rangle \pm |s_j\rangle)/\sqrt{2}\), which contribute two columns; each unpaired state contributes one. Rows are ordered as the n_active active flavors first, then one sterile partner per paired mass state, in ascending order of that state’s index.

The result is unitary whenever the input is, for any pairing pattern.

Added in version 1.0.5.

Parameters:
  • mixing_matrix (list or np.ndarray) – The n_active x n_active mixing matrix of the active sector, e.g. the output of magnus.hamiltonians.hamiltonians3nu.pmns_mixing_matrix().

  • pairs (dict, optional) – Mapping from mass-state index to its pseudo-Dirac splitting \(\delta m^2_j\), in eV^2. States absent from the mapping are unpaired. An empty mapping (the default) returns the input unchanged, so the Dirac case is recovered exactly.

Returns:

The n x n complex mixing matrix, with n = n_active + len(pairs).

Return type:

np.ndarray

Raises:

ValueError – If mixing_matrix is not square, or a pairing index is out of range, or a splitting is not positive and finite.

See also

pseudo_dirac_mass_squared

the matching mass-squared values.

Examples

Three active flavors with partners on the first and third mass states is a five-dimensional problem, and the extended matrix is still unitary.

import numpy as np
import magnus.hamiltonians as hamiltonians

U = hamiltonians.pmns_mixing_matrix(0.5558, 0.6856, 0.1499, 3.7001)
W = hamiltonians.pseudo_dirac_mixing_matrix(U, {0: 1.0e-18, 2: 4.0e-18})

print(W.shape, bool(np.allclose(W @ W.conj().T, np.eye(len(W)))))
(5, 5) True
magnus.hamiltonians.hamiltonians_pseudodirac.pseudo_dirac_mass_squared(mass_squared: Sequence[float] | numpy.ndarray, pairs: Mapping[int, float] | None = None) → numpy.ndarray[source]

Returns the extended mass-squared values for a pseudo-Dirac spectrum.

A paired state \(j\) contributes \(m_j^2\) and \(m_j^2 + \delta m_j^2\), in that order; an unpaired state contributes \(m_j^2\) alone. The ordering matches pseudo_dirac_mixing_matrix() column for column.

Added in version 1.0.5.

Parameters:
  • mass_squared (list or np.ndarray) – The n_active mass-squared values of the active sector, in eV^2. Only differences matter, so these are usually [0, Dm21, Dm31].

  • pairs (dict, optional) – Mapping from mass-state index to its splitting, as in pseudo_dirac_mixing_matrix().

Returns:

The n mass-squared values, n = n_active + len(pairs).

Return type:

np.ndarray

Warns:

PseudoDiracSplittingWarning – If a splitting is not small against the standard splittings. The pseudo-Dirac regime rests on that separation of scales; a comparable splitting is a sterile state with an ordinary mass, better described by the four- or five-flavor routines.

Examples

import magnus.hamiltonians as hamiltonians

hamiltonians.pseudo_dirac_mass_squared([0.0, 7.5e-5, 2.511e-3],
                                       {0: 1.0e-18, 2: 4.0e-18})
array([0.000e+00, 1.000e-18, 7.500e-05, 2.511e-03, 2.511e-03])
magnus.hamiltonians.hamiltonians_pseudodirac.hamiltonian_pseudo_dirac_vacuum_energy_independent(mixing_matrix: Sequence | numpy.ndarray, mass_squared: Sequence[float] | numpy.ndarray, pairs: Mapping[int, float] | None = None, nubar: bool | None = False) → numpy.ndarray[source]

Returns the pseudo-Dirac vacuum Hamiltonian, without the 1/E factor.

\(H_{\rm vac} E = \tfrac{1}{2} W M^2 W^\dagger\), with \(W\) the extended mixing matrix and \(M^2\) the extended mass-squared values. Because the energy factors out, this is what the energy-batched engine reuses across a scan; see hamiltonian_pseudo_dirac_vacuum().

Added in version 1.0.5.

Parameters:
  • mixing_matrix (list or np.ndarray) – The active-sector mixing matrix.

  • mass_squared (list or np.ndarray) – The active-sector mass-squared values, in eV^2.

  • pairs (dict, optional) – The pairing specification; see pseudo_dirac_mixing_matrix(). An empty mapping reproduces the ordinary Dirac Hamiltonian exactly.

  • nubar (bool, optional) – If True, compute the antineutrino Hamiltonian, which conjugates the mixing matrix. Default: False.

Returns:

The n x n complex Hamiltonian, multiplied by the energy.

Return type:

np.ndarray

Examples

With no pairs this is the ordinary three-flavor vacuum Hamiltonian, to machine precision.

import numpy as np
import magnus.hamiltonians as hamiltonians

U = hamiltonians.pmns_mixing_matrix(0.5558, 0.6856, 0.1499, 3.7001)
H = hamiltonians.hamiltonian_pseudo_dirac_vacuum_energy_independent(
        U, [0.0, 7.5e-5, 2.511e-3], {})
H3 = hamiltonians.hamiltonian_3nu_vacuum_energy_independent(
        0.5558, 0.6856, 0.1499, 3.7001, 7.5e-5, 2.511e-3)

print(float(np.max(np.abs(H - H3))))
1.0842023485134613e-19
magnus.hamiltonians.hamiltonians_pseudodirac.hamiltonian_pseudo_dirac_vacuum(energy: float, mixing_matrix: Sequence | numpy.ndarray, mass_squared: Sequence[float] | numpy.ndarray, pairs: Mapping[int, float] | None = None, nubar: bool | None = False) → numpy.ndarray[source]

Returns the pseudo-Dirac Hamiltonian for oscillations in vacuum.

\(H_{\rm vac} = W M^2 W^\dagger / (2E)\).

Added in version 1.0.5.

Parameters:
  • energy (float) – Neutrino energy, in eV.

  • mixing_matrix (list or np.ndarray) – The active-sector mixing matrix.

  • mass_squared (list or np.ndarray) – The active-sector mass-squared values, in eV^2.

  • pairs (dict, optional) – The pairing specification; see pseudo_dirac_mixing_matrix().

  • nubar (bool, optional) – If True, compute the antineutrino Hamiltonian. Default: False.

Returns:

The n x n complex Hamiltonian, in eV.

Return type:

np.ndarray

Examples

import magnus.globaldefs as gd
import magnus.hamiltonians as hamiltonians

U = hamiltonians.pmns_mixing_matrix(0.5558, 0.6856, 0.1499, 3.7001)
H = hamiltonians.hamiltonian_pseudo_dirac_vacuum(
        1.0*gd.UNIT_GEV, U, [0.0, 7.5e-5, 2.511e-3], {1: 1.0e-18})

print(H.shape)
(4, 4)
magnus.hamiltonians.hamiltonians_pseudodirac.hamiltonian_pseudo_dirac_matter(VCC: float | numpy.ndarray, n_active: int, pairs: Mapping[int, float] | None = None, ratio_number_neutrons_to_protons: int | float | None = 1.0) → numpy.ndarray[source]

Returns the matter part of the pseudo-Dirac Hamiltonian.

\(H_{\rm matter} = V_{\rm CC}\,P\), with \(P\) the flavor structure returned by magnus.matter.matter_potential_projector(). The sterile partners feel neither charged nor neutral current, so once the actives’ common neutral-current term is removed they are left carrying \(-V_{\rm NC} = (r/2)\,V_{\rm CC}\), exactly as the sterile states of a 3+N spectrum do. The projector is taken from matter rather than written out again here: writing that structure a second time by hand is what once gave the sterile states of the NSI route no matter at all.

Add this to hamiltonian_pseudo_dirac_vacuum() to obtain the full Hamiltonian. The split is deliberate and preserves separability – the vacuum part carries all of the energy dependence and the matter part all of the position dependence – so the energy-batched engine applies unchanged.

Added in version 1.0.5.

Parameters:
  • VCC (float or np.ndarray) – The charged-current matter potential, in eV, already carrying its sign for neutrinos or antineutrinos as magnus.matter.vcc_func_from_rho_func() returns it; do not negate it again. An array of potentials, one per position, returns a stack of Hamiltonians with the position axis leading.

  • n_active (int) – The number of active flavors. Must be 3: the shared projector places the charged-current entry on the first flavor and treats states beyond the third as sterile, so any other value would silently mislabel a sterile partner as an active flavor.

  • pairs (dict, optional) – The pairing specification; only its length is used here, since the matter term does not depend on the splittings.

  • ratio_number_neutrons_to_protons (int or float, optional) – \(r = n_n/n_p\) of the medium. Default: 1.0 (isoscalar). The same limitation the Earth wrappers carry applies here: this is one scalar for the whole trajectory, while a layered profile has one per layer.

Returns:

The n x n matter Hamiltonian, or a stack of them.

Return type:

np.ndarray

Raises:

ValueError – If n_active is not 3.

Examples

Three active flavors and two partners: the charged-current entry on \(\nu_e\), nothing on the other actives, and \(r/2\) on each sterile.

import numpy as np
import magnus.hamiltonians as hamiltonians

H = hamiltonians.hamiltonian_pseudo_dirac_matter(
        1.0, 3, {0: 1.0e-18, 2: 4.0e-18},
        ratio_number_neutrons_to_protons=1.2)

print(np.diag(H).real)
[1.  0.  0.  0.6 0.6]