use std::{
cmp::Ordering,
error::Error,
fmt::{Debug, Display},
fs::File,
io::{BufWriter, Write},
ops::{AddAssign, RangeInclusive},
path::Path,
};
use chrono::{SecondsFormat, Utc};
use colorous::Gradient;
use mapproj::CanonicalProjection;
use num::{Integer, PrimInt};
use num_traits::{AsPrimitive, Float, FloatConst, FromPrimitive, ToBytes};
#[cfg(not(target_arch = "wasm32"))]
use super::img::to_mom_png_file;
use super::{
skymap::{SkyMap, SkyMapValue},
HHash,
};
use crate::nested::map::mom::impls::zvec::MomVecImpl;
use crate::nested::{
map::{
fits::{
error::FitsError,
write::{
write_final_padding, write_keyword_record, write_primary_hdu, write_str_keyword_record,
write_str_mandatory_keyword_record, write_uint_mandatory_keyword_record,
},
},
img::{to_mom_png, ColorMapFunctionType, PosConversion, Val},
},
n_hash,
};
pub mod impls;
pub trait ZUniqHashT:
'static + HHash + PrimInt + Send + Sync + Debug + AddAssign + Display + Clone + ToBytes
{
const N_RESERVED_BITS: u8 = 2;
const DIM: u8 = 2;
const N_D0_CELLS: u8 = 12;
const N_D0_BITS: u8 = 4;
const N_BYTES: u8 = size_of::<Self>() as u8;
const N_BITS: u8 = Self::N_BYTES << 3;
const MAX_DEPTH: u8 = (Self::N_BITS - (Self::N_RESERVED_BITS + Self::N_D0_BITS)) / Self::DIM;
const LAST_LAYER_MASK: Self; const FITS_DATATYPE: &'static str;
fn two() -> Self;
fn three() -> Self;
fn mult_by_dim<T: PrimInt>(v: T) -> T {
v.unsigned_shl(1)
}
fn div_by_dim<T: PrimInt>(v: T) -> T {
v.unsigned_shr(1)
}
fn shift(delta_depth: u8) -> u8 {
Self::mult_by_dim(delta_depth)
}
fn delta_with_depth_max(depth: u8) -> u8 {
Self::MAX_DEPTH - depth
}
fn shift_from_depth_max(depth: u8) -> u8 {
Self::shift(Self::delta_with_depth_max(depth))
}
fn from_zuniq(zuniq: Self) -> (u8, Self) {
let n_trailing_zero = zuniq.trailing_zeros() as u8;
let delta_depth = Self::div_by_dim(n_trailing_zero);
let depth = Self::MAX_DEPTH - delta_depth;
let hash = zuniq >> (n_trailing_zero + 1) as usize;
(depth, hash)
}
fn depth_from_zuniq(zuniq: Self) -> u8 {
let n_trailing_zero = zuniq.trailing_zeros() as u8;
let delta_depth = Self::div_by_dim(n_trailing_zero);
Self::MAX_DEPTH - delta_depth
}
fn to_zuniq(depth: u8, hash: Self) -> Self {
let zuniq = (hash << 1) | Self::one();
zuniq.unsigned_shl(Self::shift_from_depth_max(depth) as u32)
}
fn are_overlapping(zuniq_l: Self, zuniq_r: Self) -> bool {
let (depth_l, hash_l) = Self::from_zuniq(zuniq_l);
let (depth_r, hash_r) = Self::from_zuniq(zuniq_r);
Self::are_overlapping_cells(depth_l, hash_l, depth_r, hash_r)
}
fn are_overlapping_cells(depth_l: u8, hash_l: Self, depth_r: u8, hash_r: Self) -> bool {
match depth_l.cmp(&depth_r) {
Ordering::Equal => hash_l == hash_r,
Ordering::Less => hash_l == hash_r.unsigned_shr(((depth_r - depth_l) << 1) as u32),
Ordering::Greater => hash_r == hash_l.unsigned_shr(((depth_l - depth_r) << 1) as u32),
}
}
}
impl ZUniqHashT for u32 {
const LAST_LAYER_MASK: Self = 3;
const FITS_DATATYPE: &'static str = "u32";
fn two() -> Self {
2
}
fn three() -> Self {
Self::LAST_LAYER_MASK
}
}
impl ZUniqHashT for u64 {
const LAST_LAYER_MASK: Self = 3;
const FITS_DATATYPE: &'static str = "u64";
fn two() -> Self {
2
}
fn three() -> Self {
Self::LAST_LAYER_MASK
}
}
pub enum LhsRhsBoth<V> {
Left(V),
Right(V),
Both(V, V),
}
#[allow(clippy::len_without_is_empty, clippy::type_complexity)]
pub trait Mom<'a>: Sized {
const Z_SIZE: usize = size_of::<Self::ZUniqHType>();
const V_SIZE: usize = size_of::<Self::ValueType>();
const ZV_SIZE: usize = Self::Z_SIZE + Self::V_SIZE;
type ZUniqHType: ZUniqHashT;
type ValueType: SkyMapValue + 'a;
type OverlappedEntries: Iterator<Item = (Self::ZUniqHType, &'a Self::ValueType)>;
type OverlappedEntriesCopy: Iterator<Item = (Self::ZUniqHType, Self::ValueType)>;
type ZuniqIt: Iterator<Item = Self::ZUniqHType>;
type ValuesIt: Iterator<Item = &'a Self::ValueType>;
type ValuesCopyIt: Iterator<Item = Self::ValueType>;
type EntriesIt: Iterator<Item = (Self::ZUniqHType, &'a Self::ValueType)>;
type EntriesCopyIt: Iterator<Item = (Self::ZUniqHType, Self::ValueType)>;
type OwnedEntriesIt: Iterator<Item = (Self::ZUniqHType, Self::ValueType)>;
fn depth_max(&self) -> u8;
fn len(&self) -> usize;
fn get_cell_containing(
&'a self,
zuniq_at_depth_max: Self::ZUniqHType,
) -> Result<Option<(Self::ZUniqHType, &'a Self::ValueType)>, String> {
self
.check_zuniq_depth_is_depth_max(zuniq_at_depth_max)
.map(|_| self.get_cell_containing_unsafe(zuniq_at_depth_max))
}
fn get_copy_of_cell_containing(
&'a self,
zuniq_at_depth_max: Self::ZUniqHType,
) -> Result<Option<(Self::ZUniqHType, Self::ValueType)>, String> {
self
.check_zuniq_depth_is_depth_max(zuniq_at_depth_max)
.map(|_| self.get_copy_of_cell_containing_unsafe(zuniq_at_depth_max))
}
fn get_cell_containing_unsafe(
&'a self,
zuniq_at_depth_max: Self::ZUniqHType,
) -> Option<(Self::ZUniqHType, &'a Self::ValueType)>;
fn get_copy_of_cell_containing_unsafe(
&'a self,
zuniq_at_depth_max: Self::ZUniqHType,
) -> Option<(Self::ZUniqHType, Self::ValueType)>;
fn get_overlapped_cells(&'a self, zuniq: Self::ZUniqHType) -> Self::OverlappedEntries;
fn get_copy_of_overlapped_cells(&'a self, zuniq: Self::ZUniqHType)
-> Self::OverlappedEntriesCopy;
fn zuniqs(&'a self) -> Self::ZuniqIt;
fn values(&'a self) -> Self::ValuesIt;
fn values_copy(&'a self) -> Self::ValuesCopyIt;
fn entries(&'a self) -> Self::EntriesIt;
fn entries_copy(&'a self) -> Self::EntriesCopyIt;
fn owned_entries(self) -> Self::OwnedEntriesIt;
fn check_zuniq_depth_is_depth_max(
&self,
zuniq_at_depth_max: Self::ZUniqHType,
) -> Result<(), String> {
let depth = Self::ZUniqHType::depth_from_zuniq(zuniq_at_depth_max);
if depth == self.depth_max() {
Ok(())
} else {
Err(format!(
"Wrong depth for zuniq {}. Expected: {}. Actual: {}",
zuniq_at_depth_max,
self.depth_max(),
depth
))
}
}
fn check_is_mom(&'a self) -> Result<(), String> {
let mut it = self.zuniqs();
if let Some(mut l) = it.next() {
let (mut depth_l, mut hash_l) = Self::ZUniqHType::from_zuniq(l);
for r in it {
if depth_l < self.depth_max() {
return Err(format!("Element has a larger depth than MOM maximum depth. Elem: {}; Depth: {}; Mom max depth: {}", l, depth_l, self.depth_max()));
}
let (depth_r, hash_r) = Self::ZUniqHType::from_zuniq(r);
if l >= r {
return Err(format!("The MOM is not ordered: {} >= {}", l, r));
} else if Self::ZUniqHType::are_overlapping_cells(depth_l, hash_l, depth_r, hash_r) {
return Err(format!("Overlapping elements in the MOM: {} and {}. I.e. depth: {}; hash: {} and depth: {}, hash: {}.", l, r, depth_l, hash_l, depth_r, hash_r));
}
l = r;
depth_l = depth_r;
hash_l = hash_r;
}
}
Ok(())
}
fn from_hpx_sorted_entries<I, M>(depth: u8, sorted_entries_it: I, merger: M) -> Self
where
I: Iterator<Item = (Self::ZUniqHType, Self::ValueType)>,
M: Fn(
u8,
Self::ZUniqHType,
[Self::ValueType; 4],
) -> Result<Self::ValueType, [Self::ValueType; 4]>;
fn from_hpx_sorted_entries_fallible<E, I, M>(
depth: u8,
sorted_entries_it: I,
merger: M,
) -> Result<Self, E>
where
E: Error,
I: Iterator<Item = Result<(Self::ZUniqHType, Self::ValueType), E>>,
M: Fn(
u8,
Self::ZUniqHType,
[Self::ValueType; 4],
) -> Result<Self::ValueType, [Self::ValueType; 4]>;
fn from_skymap_ref<'s, S, M>(skymap: &'s S, merger: M) -> Self
where
S: SkyMap<'s, HashType = Self::ZUniqHType, ValueType = Self::ValueType>,
M: Fn(u8, Self::ZUniqHType, [&Self::ValueType; 4]) -> Option<Self::ValueType>,
Self::ValueType: 's;
fn from_skymap<'s, S, M>(skymap: S, merger: M) -> Self
where
S: SkyMap<'s, HashType = Self::ZUniqHType, ValueType = Self::ValueType>,
M: Fn(
u8,
Self::ZUniqHType,
[Self::ValueType; 4],
) -> Result<Self::ValueType, [Self::ValueType; 4]>,
Self::ValueType: 's;
fn merge<'s, L, R, S, O, M>(lhs: L, rhs: R, split: S, op: O, merge: M) -> Self
where
L: Mom<'s, ZUniqHType = Self::ZUniqHType, ValueType = Self::ValueType>,
R: Mom<'s, ZUniqHType = Self::ZUniqHType, ValueType = Self::ValueType>,
S: Fn(u8, Self::ZUniqHType, Self::ValueType) -> [Self::ValueType; 4],
O: Fn(LhsRhsBoth<Self::ValueType>) -> Option<Self::ValueType>,
M: Fn(
u8,
Self::ZUniqHType,
[Self::ValueType; 4],
) -> Result<Self::ValueType, [Self::ValueType; 4]>;
}
pub enum CountMom {
U32(MomVecImpl<u32, u32>),
U64(MomVecImpl<u64, u32>),
}
impl From<MomVecImpl<u32, u32>> for CountMom {
fn from(value: MomVecImpl<u32, u32>) -> Self {
Self::U32(value)
}
}
impl From<MomVecImpl<u64, u32>> for CountMom {
fn from(value: MomVecImpl<u64, u32>) -> Self {
Self::U64(value)
}
}
impl CountMom {
#[cfg(not(target_arch = "wasm32"))]
pub fn to_fits_file<P: AsRef<Path>, T: AsRef<str>>(
&self,
path: P,
value_name: T,
) -> Result<(), FitsError> {
match self {
Self::U32(mom) => mom.to_fits_file(path, value_name),
Self::U64(mom) => mom.to_fits_file(path, value_name),
}
}
pub fn to_fits<T: AsRef<str>, W: Write>(
&self,
writer: W,
value_name: T,
) -> Result<(), FitsError> {
match self {
Self::U32(mom) => mom.to_fits(writer, value_name),
Self::U64(mom) => mom.to_fits(writer, value_name),
}
}
}
pub enum DensMom {
U32(MomVecImpl<u32, f64>),
U64(MomVecImpl<u64, f64>),
}
impl From<MomVecImpl<u32, f64>> for DensMom {
fn from(value: MomVecImpl<u32, f64>) -> Self {
Self::U32(value)
}
}
impl From<MomVecImpl<u64, f64>> for DensMom {
fn from(value: MomVecImpl<u64, f64>) -> Self {
Self::U64(value)
}
}
impl From<CountMom> for DensMom {
fn from(value: CountMom) -> Self {
match value {
CountMom::U32(cmom) => MomVecImpl::<u32, f64>::from_counts_to_densities(&cmom).into(),
CountMom::U64(cmom) => MomVecImpl::<u64, f64>::from_counts_to_densities(&cmom).into(),
}
}
}
impl DensMom {
pub fn unwrap_u32(self) -> MomVecImpl<u32, f64> {
match self {
Self::U32(cmom) => cmom,
Self::U64(_) => panic!("Wrong DensMom type. Expected: u32. Actual: u64."),
}
}
pub fn unwrap_u64(self) -> MomVecImpl<u64, f64> {
match self {
Self::U32(_) => panic!("Wrong DensMom type. Expected: u32. Actual: u64."),
Self::U64(cmom) => cmom,
}
}
#[cfg(not(target_arch = "wasm32"))]
pub fn to_fits_file<P: AsRef<Path>, T: AsRef<str>>(
&self,
path: P,
value_name: T,
) -> Result<(), FitsError> {
match self {
Self::U32(mom) => mom.to_fits_file(path, value_name),
Self::U64(mom) => mom.to_fits_file(path, value_name),
}
}
pub fn to_fits<T: AsRef<str>, W: Write>(
&self,
writer: W,
value_name: T,
) -> Result<(), FitsError> {
match self {
Self::U32(mom) => mom.to_fits(writer, value_name),
Self::U64(mom) => mom.to_fits(writer, value_name),
}
}
}
pub trait ViewableMom<'a>: Mom<'a>
where
<Self as Mom<'a>>::ValueType: SkyMapValue + Val + 'a,
{
#[cfg(not(target_arch = "wasm32"))]
fn to_mom_png_file<P: CanonicalProjection, W: AsRef<Path>>(
&'a self,
img_size: (u16, u16),
proj: Option<P>,
proj_center: Option<(f64, f64)>,
proj_bounds: Option<(RangeInclusive<f64>, RangeInclusive<f64>)>,
pos_convert: Option<PosConversion>,
color_map: Option<Gradient>,
color_map_func_type: Option<ColorMapFunctionType>,
path: W,
view: bool,
) -> Result<(), Box<dyn Error>> {
to_mom_png_file(
self,
img_size,
proj,
proj_center,
proj_bounds,
pos_convert,
color_map,
color_map_func_type,
path,
view,
)
}
fn to_mom_png<P: CanonicalProjection, W: Write>(
&'a self,
img_size: (u16, u16),
proj: Option<P>,
proj_center: Option<(f64, f64)>,
proj_bounds: Option<(RangeInclusive<f64>, RangeInclusive<f64>)>,
pos_convert: Option<PosConversion>,
color_map: Option<Gradient>,
color_map_func_type: Option<ColorMapFunctionType>,
writer: W,
) -> Result<(), Box<dyn Error>> {
to_mom_png(
self,
img_size,
proj,
proj_center,
proj_bounds,
pos_convert,
color_map,
color_map_func_type,
writer,
)
}
}
impl<'a, V: SkyMapValue + Val + 'a, M: Mom<'a, ValueType = V>> ViewableMom<'a> for M {}
pub trait WritableMom<'a>: Mom<'a>
where
<Self as Mom<'a>>::ValueType: SkyMapValue + ToBytes + 'a,
{
fn write_all_entries<W: Write>(&'a self, mut writer: W) -> Result<usize, FitsError> {
for (z, v) in self.entries_copy() {
writer
.write_all(z.to_le_bytes().as_ref())
.and_then(|()| writer.write_all(v.to_le_bytes().as_ref()))
.map_err(FitsError::Io)?;
}
Ok(self.len() * Self::ZV_SIZE)
}
fn write_all_bintable_entries<W: Write>(&'a self, mut writer: W) -> Result<usize, FitsError> {
for (z, v) in self.entries_copy() {
writer
.write_all(z.to_be_bytes().as_ref())
.and_then(|()| writer.write_all(v.to_be_bytes().as_ref()))
.map_err(FitsError::Io)?;
}
Ok(self.len() * Self::ZV_SIZE)
}
#[cfg(not(target_arch = "wasm32"))]
fn to_fits_file<P: AsRef<Path>, T: AsRef<str>>(
&'a self,
path: P,
value_name: T,
) -> Result<(), FitsError> {
File::create(path)
.map_err(FitsError::Io)
.and_then(|file| self.to_fits(BufWriter::new(file), value_name))
}
fn to_fits<T: AsRef<str>, W: Write>(
&'a self,
mut writer: W,
value_name: T,
) -> Result<(), FitsError> {
let mut header_block = [b' '; 2880];
let mut it = header_block.chunks_mut(80);
it.next().unwrap()[0..30].copy_from_slice(b"SIMPLE = T"); it.next().unwrap()[0..30].copy_from_slice(b"BITPIX = 8"); it.next().unwrap()[0..30].copy_from_slice(b"NAXIS = 2"); write_uint_mandatory_keyword_record(it.next().unwrap(), b"NAXIS1 ", Self::ZV_SIZE as u64); write_uint_mandatory_keyword_record(it.next().unwrap(), b"NAXIS2 ", self.len() as u64); it.next().unwrap()[0..30].copy_from_slice(b"EXTEND = F"); it.next().unwrap()[0..35].copy_from_slice(b"PRODTYPE= 'HEALPIX MULTI ORDER MAP'"); write_uint_mandatory_keyword_record(it.next().unwrap(), b"HPXORDER", self.depth_max() as u64);
write_str_keyword_record(
it.next().unwrap(),
b"ZUNIQ_DT",
Self::ZUniqHType::FITS_DATATYPE,
);
write_str_keyword_record(
it.next().unwrap(),
b"VALUE_DT",
Self::ValueType::FITS_DATATYPE,
);
write_str_keyword_record(it.next().unwrap(), b"VAL_NAME", value_name.as_ref());
it.next().unwrap()[0..20].copy_from_slice(b"DTENDIAN= 'LITTLE '");
write_keyword_record(
it.next().unwrap(),
b"DATE ",
format!(
"'{}'",
Utc::now().to_rfc3339_opts(SecondsFormat::Secs, true)
)
.as_str(),
);
write_keyword_record(
it.next().unwrap(),
b"CREATOR ",
format!(
"'Rust crate {} {}'",
env!("CARGO_PKG_NAME"),
env!("CARGO_PKG_VERSION")
)
.as_str(),
);
it.next().unwrap()[0..3].copy_from_slice(b"END");
writer.write_all(&header_block[..]).map_err(FitsError::Io)?;
self
.write_all_entries(&mut writer)
.and_then(|n_bytes_written| write_final_padding(writer, n_bytes_written))
}
#[cfg(not(target_arch = "wasm32"))]
fn to_fits_bintable_file<P: AsRef<Path>, T: AsRef<str>>(
&'a self,
path: P,
value_name: T,
) -> Result<(), FitsError> {
File::create(path)
.map_err(FitsError::Io)
.and_then(|file| self.to_fits_bintable(BufWriter::new(file), value_name))
}
fn to_fits_bintable<T: AsRef<str>, W: Write>(
&'a self,
mut writer: W,
value_colname: T,
) -> Result<(), FitsError> {
self
.write_bintable_fits_header(&mut writer, value_colname)
.and_then(|()| self.write_all_bintable_entries(&mut writer))
.and_then(|n_data_bytes| write_final_padding(&mut writer, n_data_bytes))
}
fn write_bintable_fits_header<T: AsRef<str>, W: Write>(
&'a self,
mut writer: W,
value_colname: T,
) -> Result<(), FitsError> {
write_primary_hdu(&mut writer)?;
let mut header_block = [b' '; 2880];
let mut it = header_block.chunks_mut(80);
it.next().unwrap()[0..20].copy_from_slice(b"XTENSION= 'BINTABLE'");
it.next().unwrap()[0..30].copy_from_slice(b"BITPIX = 8");
it.next().unwrap()[0..30].copy_from_slice(b"NAXIS = 2");
write_uint_mandatory_keyword_record(it.next().unwrap(), b"NAXIS1 ", Self::ZV_SIZE as u64);
write_uint_mandatory_keyword_record(it.next().unwrap(), b"NAXIS2 ", self.len() as u64);
it.next().unwrap()[0..30].copy_from_slice(b"PCOUNT = 0");
it.next().unwrap()[0..30].copy_from_slice(b"GCOUNT = 1");
it.next().unwrap()[0..30].copy_from_slice(b"TFIELDS = 2");
it.next().unwrap()[0..20].copy_from_slice(b"TTYPE1 = 'ZUNIQ '");
write_str_mandatory_keyword_record(
it.next().unwrap(),
b"TFORM1 ",
Self::ZUniqHType::FITS_TFORM,
);
it.next().unwrap()[0..20].copy_from_slice(b"TTYPE2 = 'VALUE '");
write_str_mandatory_keyword_record(
it.next().unwrap(),
b"TFORM2 ",
Self::ValueType::FITS_TFORM,
);
it.next().unwrap()[0..23].copy_from_slice(b"PRODTYPE= 'HEALPIX MOM'");
it.next().unwrap()[0..20].copy_from_slice(b"COORDSYS= 'CEL '");
it.next().unwrap()[0..20].copy_from_slice(b"EXTNAME = 'xtension'");
write_uint_mandatory_keyword_record(it.next().unwrap(), b"MAXORDER", self.depth_max() as u64);
write_str_mandatory_keyword_record(it.next().unwrap(), b"VALNAME ", value_colname.as_ref());
write_keyword_record(
it.next().unwrap(),
b"CREATOR ",
format!(
"'Rust crate {} {}'",
env!("CARGO_PKG_NAME"),
env!("CARGO_PKG_VERSION")
)
.as_str(),
);
it.next().unwrap()[0..3].copy_from_slice(b"END");
writer.write_all(&header_block[..]).map_err(FitsError::Io)
}
}
impl<'a, V: SkyMapValue + ToBytes + 'a, M: Mom<'a, ValueType = V>> WritableMom<'a> for M {}
pub fn new_chi2_density_merger<
Z: ZUniqHashT,
V: SkyMapValue + Float + FloatConst + FromPrimitive,
>(
chi2_of_3dof_threshold: f64,
depth_threshold: Option<u8>,
) -> impl Fn(u8, Z, [V; 4]) -> Result<V, [V; 4]> {
let merger_ref = new_chi2_density_ref_merger_with_depth_threshold(
chi2_of_3dof_threshold,
depth_threshold.unwrap_or(0),
);
move |depth: u8, hash: Z, values: [V; 4]| -> Result<V, [V; 4]> {
merger_ref(
depth,
hash,
[&values[0], &values[1], &values[2], &values[3]],
)
.ok_or(values)
}
}
pub fn new_chi2_density_ref_merger_no_depth_threshold<
Z: ZUniqHashT,
V: SkyMapValue + Float + FloatConst + FromPrimitive,
>(
chi2_of_3dof_threshold: f64,
) -> impl Fn(u8, Z, [&V; 4]) -> Option<V> {
let threshold = V::from_f64(chi2_of_3dof_threshold).unwrap();
let four = V::from_f64(4.0).unwrap();
move |depth: u8, _hash: Z, [n0, n1, n2, n3]: [&V; 4]| -> Option<V> {
let one_over_s = V::from_u64(n_hash(depth + 1).unsigned_shr(2)).unwrap() / V::PI();
let mu0 = *n0;
let mu1 = *n1;
let mu2 = *n2;
let mu3 = *n3;
let sum = mu0 + mu1 + mu2 + mu3;
let weighted_var_inv = V::one() / mu0.max(one_over_s)
+ V::one() / mu1.max(one_over_s)
+ V::one() / mu2.max(one_over_s)
+ V::one() / mu3.max(one_over_s);
let weighted_mean = four / weighted_var_inv;
let chi2_of_3dof = (sum - four * weighted_mean) / one_over_s;
if chi2_of_3dof < threshold {
Some(sum / four)
} else {
None
}
}
}
pub fn new_chi2_density_ref_merger_with_depth_threshold<
Z: ZUniqHashT,
V: SkyMapValue + Float + FloatConst + FromPrimitive,
>(
chi2_of_3dof_threshold: f64,
depth_threshold: u8,
) -> impl Fn(u8, Z, [&V; 4]) -> Option<V> {
let merger = new_chi2_density_ref_merger_no_depth_threshold(chi2_of_3dof_threshold);
move |depth: u8, hash: Z, n: [&V; 4]| -> Option<V> {
if depth >= depth_threshold {
merger(depth, hash, n)
} else {
None
}
}
}
pub fn new_chi2_count_merger_no_depth_threshold<
Z: ZUniqHashT,
V: SkyMapValue + Integer + AsPrimitive<f64>,
>(
chi2_of_3dof_threshold: f64,
) -> impl Fn(u8, Z, [V; 4]) -> Result<V, [V; 4]> {
let merger_ref = new_chi2_count_ref_merger_no_depth_threshold(chi2_of_3dof_threshold);
move |depth: u8, hash: Z, values: [V; 4]| -> Result<V, [V; 4]> {
merger_ref(
depth,
hash,
[&values[0], &values[1], &values[2], &values[3]],
)
.ok_or(values)
}
}
pub fn new_chi2_count_merger_with_depth_threshold<
Z: ZUniqHashT,
V: SkyMapValue + Integer + AsPrimitive<f64>,
>(
chi2_of_3dof_threshold: f64,
depth_threshold: u8,
) -> impl Fn(u8, Z, [V; 4]) -> Result<V, [V; 4]> {
let merger_ref =
new_chi2_count_ref_merger_with_depth_threshold(chi2_of_3dof_threshold, depth_threshold);
move |depth: u8, hash: Z, values: [V; 4]| -> Result<V, [V; 4]> {
merger_ref(
depth,
hash,
[&values[0], &values[1], &values[2], &values[3]],
)
.ok_or(values)
}
}
pub fn new_chi2_count_ref_merger_no_depth_threshold<
Z: ZUniqHashT,
V: SkyMapValue + Integer + AsPrimitive<f64>,
>(
chi2_of_3dof_threshold: f64,
) -> impl Fn(u8, Z, [&V; 4]) -> Option<V> {
move |_depth: u8, _hash: Z, [n0, n1, n2, n3]: [&V; 4]| -> Option<V> {
let mu0 = (*n0).as_();
let mu1 = (*n1).as_();
let mu2 = (*n2).as_();
let mu3 = (*n3).as_();
let sum = mu0 + mu1 + mu2 + mu3;
let weighted_var_inv = 1.0 / mu0 + 1.0 / mu1 + 1.0 / mu2 + 1.0 / mu3;
let weighted_mean = 4.0 / weighted_var_inv;
let chi2_of_3dof = sum - 4.0 * weighted_mean;
if chi2_of_3dof < chi2_of_3dof_threshold {
Some(*n0 + *n1 + *n2 + *n3)
} else {
None
}
}
}
pub fn new_chi2_count_ref_merger_with_depth_threshold<
Z: ZUniqHashT,
V: SkyMapValue + Integer + AsPrimitive<f64>,
>(
chi2_of_3dof_threshold: f64,
depth_threshold: u8,
) -> impl Fn(u8, Z, [&V; 4]) -> Option<V> {
let merger = new_chi2_count_ref_merger_no_depth_threshold(chi2_of_3dof_threshold);
move |depth: u8, hash: Z, n: [&V; 4]| -> Option<V> {
if depth >= depth_threshold {
merger(depth, hash, n)
} else {
None
}
}
}
#[cfg(test)]
mod tests {
use crate::nested::map::img::to_mom_png_file;
use crate::nested::map::mom::{new_chi2_count_ref_merger_no_depth_threshold, LhsRhsBoth};
use mapproj::pseudocyl::mol::Mol;
use super::{
super::{
img::{ColorMapFunctionType, PosConversion},
skymap::SkyMapEnum,
},
impls::zvec::MomVecImpl,
Mom,
};
#[test]
#[cfg(not(target_arch = "wasm32"))]
fn test_mom_diff_spec() {
let path = "test/resources/skymap/skymap.2mass.depth6.fits";
let skymap_2mass = SkyMapEnum::from_fits_file(path).unwrap();
let path = "test/resources/skymap/skymap.gaiadr3.depth6.fits";
let skymap_gaia = SkyMapEnum::from_fits_file(path).unwrap();
match (skymap_2mass, skymap_gaia) {
(SkyMapEnum::ImplicitU64I32(iskymap_2mass), SkyMapEnum::ImplicitU64I32(iskymap_gaia_dr3)) => {
let chi2_merger = new_chi2_count_ref_merger_no_depth_threshold(16.266);
let mom_2mass = MomVecImpl::from_skymap_ref(&iskymap_2mass, chi2_merger);
let chi2_merger = new_chi2_count_ref_merger_no_depth_threshold(16.266);
let mom_gaia = MomVecImpl::from_skymap_ref(&iskymap_gaia_dr3, chi2_merger);
let n_tot_2mass = mom_2mass.values().sum::<i32>() as f64;
let n_tot_gaia = mom_gaia.values().sum::<i32>() as f64;
let mom_2mass = MomVecImpl::from_map(mom_2mass, |_, n| n as f64 / n_tot_2mass);
let mom_gaia = MomVecImpl::from_map(mom_gaia, |_, n| n as f64 / n_tot_gaia);
to_mom_png_file::<'_, _, Mol, _>(
&mom_2mass,
(1600, 800),
None,
None,
None,
Some(PosConversion::EqMap2GalImg),
None,
Some(ColorMapFunctionType::LinearLog), "test/resources/skymap/mom_2mass.png",
false,
)
.unwrap();
to_mom_png_file::<'_, _, Mol, _>(
&mom_gaia,
(1600, 800),
None,
None,
None,
Some(PosConversion::EqMap2GalImg),
None,
Some(ColorMapFunctionType::LinearLog), "test/resources/skymap/mom_gaia.png",
false,
)
.unwrap();
let split = |_depth: u8, _hash: u64, pdf: f64| {
let pdf_div_4 = pdf / 4.0;
[pdf_div_4, pdf_div_4, pdf_div_4, pdf_div_4]
};
let op = |lrb: LhsRhsBoth<f64>| match lrb {
LhsRhsBoth::Left(l) => Some(l),
LhsRhsBoth::Right(r) => Some(r),
LhsRhsBoth::Both(l, r) => Some(l - r),
};
let merge = |_depth: u8, _hash: u64, [v1, v2, v3, v4]: [f64; 4]| {
let sum = v1 + v2 + v3 + v4;
if sum.abs() < 1e-4 {
Ok(sum)
} else {
Err([v1, v2, v3, v4])
}
};
let mom = MomVecImpl::merge(mom_2mass, mom_gaia, split, op, merge);
to_mom_png_file::<'_, _, Mol, _>(
&mom,
(1600, 800),
None,
None,
None,
Some(PosConversion::EqMap2GalImg),
None,
Some(ColorMapFunctionType::Linear), "test/resources/skymap/mreged_mom.png",
false,
)
.unwrap();
}
_ => panic!(),
}
}
}