use ndarray::{Array1, Array2, Array4, Axis, Slice};
use num_complex::Complex64;
use refeff_core::{
BandEnergySearchMesh, BandEnergySearchMeshInput, BandPhaseSearchInterpolation,
BandPhaseSearchInterpolationInput, Real, band_energy_search_mesh,
band_phase_search_interpolation,
};
use crate::BandInput;
use crate::error::{IoError, Result};
use super::common::{checked_l_count, invalid_phase_bin};
use super::types::PhaseBinData;
use super::validate::validate_phase_bin;
#[derive(Debug, Clone, PartialEq)]
pub struct PhaseBinBandData {
pub signed_angular_offset: usize,
pub energies_hartree: Array1<Complex64>,
pub reference_energies_hartree: Array2<Complex64>,
pub spin_phase_shifts: Array4<Complex64>,
pub potential_lmax: Vec<usize>,
pub potential_labels: Vec<String>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct PhaseBinBandSearchSetup {
pub phase_handoff: PhaseBinBandData,
pub energy_mesh: BandEnergySearchMesh,
pub phase_interpolation: BandPhaseSearchInterpolation,
}
impl PhaseBinData {
pub fn to_band_data(&self) -> Result<PhaseBinBandData> {
phase_bin_band_data(self)
}
pub fn to_band_search_setup(&self, band: &BandInput) -> Result<PhaseBinBandSearchSetup> {
band_search_setup_from_handoffs(band, self)
}
}
pub fn phase_bin_band_data(data: &PhaseBinData) -> Result<PhaseBinBandData> {
validate_phase_bin(data)?;
let signed_angular_offset = data
.potentials
.iter()
.map(|potential| potential.lmax)
.max()
.ok_or_else(|| invalid_phase_bin("lmax", "phase.bin contains no potentials"))?;
let signed_l_count = checked_l_count(signed_angular_offset)?;
let mut spin_phase_shifts = Array4::zeros((
data.energy_count,
signed_l_count,
data.spin_count,
data.potential_count(),
));
for (potential_index, potential) in data.potentials.iter().enumerate() {
let potential_l_count = checked_l_count(potential.lmax)?;
let destination_start = signed_angular_offset - potential.lmax;
for energy in 0..data.energy_count {
for l_slot in 0..potential_l_count {
let destination_l_slot = destination_start + l_slot;
for spin in 0..data.spin_count {
spin_phase_shifts[(energy, destination_l_slot, spin, potential_index)] =
potential.phase_shifts[(energy, l_slot, spin)];
}
}
}
}
Ok(PhaseBinBandData {
signed_angular_offset,
energies_hartree: data.energy_grid.clone(),
reference_energies_hartree: data.reference_energy.clone(),
spin_phase_shifts,
potential_lmax: data
.potentials
.iter()
.map(|potential| potential.lmax)
.collect(),
potential_labels: data
.potentials
.iter()
.map(|potential| potential.label.clone())
.collect(),
})
}
pub fn band_search_setup_from_handoffs(
band: &BandInput,
phase: &PhaseBinData,
) -> Result<PhaseBinBandSearchSetup> {
let phase_handoff = phase_bin_band_data(phase)?;
let potential_lmax = phase_handoff.potential_lmax.clone();
band_search_setup_from_phase_handoff(band, phase, phase_handoff, &potential_lmax)
}
pub fn band_search_setup_from_handoffs_with_lmaxph(
band: &BandInput,
phase: &PhaseBinData,
lmaxph: &[usize],
) -> Result<PhaseBinBandSearchSetup> {
let phase_handoff = phase_bin_band_data(phase)?;
band_search_setup_from_phase_handoff(band, phase, phase_handoff, lmaxph)
}
fn band_search_setup_from_phase_handoff(
band: &BandInput,
phase: &PhaseBinData,
phase_handoff: PhaseBinBandData,
potential_lmax: &[usize],
) -> Result<PhaseBinBandSearchSetup> {
validate_active_phase_prefix(phase)?;
let active_len = phase.main_energy_count;
let source_energies_hartree: Array1<Real> = Array1::from_iter(
phase_handoff
.energies_hartree
.iter()
.take(active_len)
.map(|energy| energy.re),
);
let active_references = phase_handoff
.reference_energies_hartree
.slice_axis(Axis(0), Slice::from(..active_len));
let active_phase_shifts = phase_handoff
.spin_phase_shifts
.slice_axis(Axis(0), Slice::from(..active_len));
let energy_mesh = band_energy_search_mesh(BandEnergySearchMeshInput {
requested_min_ev: band.energy_mesh.emin,
requested_max_ev: band.energy_mesh.emax,
requested_step_ev: band.energy_mesh.estep,
phase_energies_hartree: phase_handoff.energies_hartree.view(),
phase_active_len: active_len,
fermi_level_hartree: phase.scalars.fermi_level,
})
.map_err(band_setup_error)?;
let phase_interpolation = band_phase_search_interpolation(BandPhaseSearchInterpolationInput {
search_energies_hartree: energy_mesh.energies_hartree.view(),
source_energies_hartree: source_energies_hartree.view(),
source_reference_energies_hartree: active_references,
source_phase_shifts: active_phase_shifts,
potential_lmax,
interpolation_order: 3,
})
.map_err(band_setup_error)?;
Ok(PhaseBinBandSearchSetup {
phase_handoff,
energy_mesh,
phase_interpolation,
})
}
fn validate_active_phase_prefix(phase: &PhaseBinData) -> Result<()> {
if phase.main_energy_count == 0 || phase.main_energy_count > phase.energy_count {
return Err(invalid_phase_bin(
"ne1",
format!(
"active BAND phase prefix must be in 1..={}, got {}",
phase.energy_count, phase.main_energy_count
),
));
}
Ok(())
}
fn band_setup_error(source: refeff_core::BandError) -> IoError {
IoError::BandSetup { source }
}