use crate::dtype::Dtype;
use crate::encode::build_pipeline_config;
use tensogram_encodings::ByteOrder;
use super::types::*;
pub(crate) fn validate_fidelity(
objects: &mut [ObjectContext<'_>],
issues: &mut Vec<ValidationIssue>,
) {
for (i, obj) in objects.iter_mut().enumerate() {
let desc = match obj.descriptor.as_ref() {
Some(d) => d,
None => continue, };
let decoded: &[u8] = match &obj.decode_state {
DecodeState::Decoded(bytes) => bytes,
DecodeState::DecodeFailed => continue, DecodeState::NotDecoded => {
if desc.encoding == "none" && desc.filter == "none" && desc.compression == "none" {
obj.payload
} else {
let shape_product = desc
.shape
.iter()
.try_fold(1u64, |acc, &x| acc.checked_mul(x));
let num_elements = match shape_product.and_then(|p| usize::try_from(p).ok()) {
Some(n) => n,
None => {
issues.push(err(
IssueCode::DecodeObjectFailed,
ValidationLevel::Fidelity,
Some(i),
Some(obj.frame_offset),
"cannot compute element count from shape".to_string(),
));
continue;
}
};
match build_pipeline_config(desc, num_elements, desc.dtype) {
Ok(config) => {
match tensogram_encodings::pipeline::decode_pipeline(
obj.payload,
&config,
false,
) {
Ok(decoded_bytes) => {
obj.decode_state = DecodeState::Decoded(decoded_bytes);
let DecodeState::Decoded(b) = &obj.decode_state else {
continue;
};
b
}
Err(e) => {
obj.decode_state = DecodeState::DecodeFailed;
issues.push(err(
IssueCode::DecodeObjectFailed,
ValidationLevel::Fidelity,
Some(i),
Some(obj.frame_offset),
format!("full decode failed: {e}"),
));
continue;
}
}
}
Err(e) => {
obj.decode_state = DecodeState::DecodeFailed;
issues.push(err(
IssueCode::DecodeObjectFailed,
ValidationLevel::Fidelity,
Some(i),
Some(obj.frame_offset),
format!("cannot build pipeline config: {e}"),
));
continue;
}
}
}
}
};
let shape_product = desc
.shape
.iter()
.try_fold(1u64, |acc, &x| acc.checked_mul(x));
let expected_size = match shape_product {
Some(product) => {
if desc.dtype == Dtype::Bitmask {
usize::try_from(product.div_ceil(8)).ok()
} else {
usize::try_from(product)
.ok()
.and_then(|p| p.checked_mul(desc.dtype.byte_width()))
}
}
None => {
issues.push(err(
IssueCode::DecodedSizeMismatch,
ValidationLevel::Fidelity,
Some(i),
Some(obj.frame_offset),
"shape product overflows, cannot verify decoded size".to_string(),
));
continue;
}
};
let expected_size = match expected_size {
Some(s) => s,
None => {
issues.push(err(
IssueCode::DecodedSizeMismatch,
ValidationLevel::Fidelity,
Some(i),
Some(obj.frame_offset),
"expected decoded size overflows usize".to_string(),
));
continue;
}
};
if decoded.len() != expected_size {
issues.push(err(
IssueCode::DecodedSizeMismatch,
ValidationLevel::Fidelity,
Some(i),
Some(obj.frame_offset),
format!(
"decoded size {} != expected {} (shape {:?}, dtype {})",
decoded.len(),
expected_size,
desc.shape,
desc.dtype
),
));
continue;
}
let byte_order = desc.byte_order;
match desc.dtype {
Dtype::Float32 => scan_f32(decoded, byte_order, i, obj.frame_offset, issues),
Dtype::Float64 => scan_f64(decoded, byte_order, i, obj.frame_offset, issues),
Dtype::Float16 => scan_f16(decoded, byte_order, i, obj.frame_offset, issues),
Dtype::Bfloat16 => scan_bf16(decoded, byte_order, i, obj.frame_offset, issues),
Dtype::Complex64 => scan_complex64(decoded, byte_order, i, obj.frame_offset, issues),
Dtype::Complex128 => scan_complex128(decoded, byte_order, i, obj.frame_offset, issues),
_ => {} }
}
}
fn scan_f32(
data: &[u8],
byte_order: ByteOrder,
obj_idx: usize,
frame_offset: usize,
issues: &mut Vec<ValidationIssue>,
) {
let chunks = data.chunks_exact(4);
if !chunks.remainder().is_empty() {
issues.push(err(
IssueCode::DecodedSizeMismatch,
ValidationLevel::Fidelity,
Some(obj_idx),
Some(frame_offset),
format!(
"decoded size {} is not a multiple of 4 (float32)",
data.len()
),
));
return;
}
for (idx, chunk) in chunks.enumerate() {
let &[b0, b1, b2, b3] = chunk else {
continue;
};
let bytes = [b0, b1, b2, b3];
let val = match byte_order {
ByteOrder::Big => f32::from_be_bytes(bytes),
ByteOrder::Little => f32::from_le_bytes(bytes),
};
if val.is_nan() {
issues.push(err(
IssueCode::NanDetected,
ValidationLevel::Fidelity,
Some(obj_idx),
Some(frame_offset),
format!("NaN at element {idx}"),
));
return; }
if val.is_infinite() {
issues.push(err(
IssueCode::InfDetected,
ValidationLevel::Fidelity,
Some(obj_idx),
Some(frame_offset),
format!("Inf at element {idx}"),
));
return;
}
}
}
fn scan_f64(
data: &[u8],
byte_order: ByteOrder,
obj_idx: usize,
frame_offset: usize,
issues: &mut Vec<ValidationIssue>,
) {
let chunks = data.chunks_exact(8);
if !chunks.remainder().is_empty() {
issues.push(err(
IssueCode::DecodedSizeMismatch,
ValidationLevel::Fidelity,
Some(obj_idx),
Some(frame_offset),
format!(
"decoded size {} is not a multiple of 8 (float64)",
data.len()
),
));
return;
}
for (idx, chunk) in chunks.enumerate() {
let &[b0, b1, b2, b3, b4, b5, b6, b7] = chunk else {
continue;
};
let bytes = [b0, b1, b2, b3, b4, b5, b6, b7];
let val = match byte_order {
ByteOrder::Big => f64::from_be_bytes(bytes),
ByteOrder::Little => f64::from_le_bytes(bytes),
};
if val.is_nan() {
issues.push(err(
IssueCode::NanDetected,
ValidationLevel::Fidelity,
Some(obj_idx),
Some(frame_offset),
format!("NaN at element {idx}"),
));
return;
}
if val.is_infinite() {
issues.push(err(
IssueCode::InfDetected,
ValidationLevel::Fidelity,
Some(obj_idx),
Some(frame_offset),
format!("Inf at element {idx}"),
));
return;
}
}
}
fn scan_f16(
data: &[u8],
byte_order: ByteOrder,
obj_idx: usize,
frame_offset: usize,
issues: &mut Vec<ValidationIssue>,
) {
let chunks = data.chunks_exact(2);
if !chunks.remainder().is_empty() {
issues.push(err(
IssueCode::DecodedSizeMismatch,
ValidationLevel::Fidelity,
Some(obj_idx),
Some(frame_offset),
format!(
"decoded size {} is not a multiple of 2 (float16)",
data.len()
),
));
return;
}
for (idx, chunk) in chunks.enumerate() {
let &[b0, b1] = chunk else {
continue;
};
let bytes = [b0, b1];
let bits = match byte_order {
ByteOrder::Big => u16::from_be_bytes(bytes),
ByteOrder::Little => u16::from_le_bytes(bytes),
};
let exp = (bits >> 10) & 0x1F;
let mantissa = bits & 0x03FF;
if exp == 0x1F {
if mantissa != 0 {
issues.push(err(
IssueCode::NanDetected,
ValidationLevel::Fidelity,
Some(obj_idx),
Some(frame_offset),
format!("NaN at element {idx}"),
));
return;
} else {
issues.push(err(
IssueCode::InfDetected,
ValidationLevel::Fidelity,
Some(obj_idx),
Some(frame_offset),
format!("Inf at element {idx}"),
));
return;
}
}
}
}
fn scan_bf16(
data: &[u8],
byte_order: ByteOrder,
obj_idx: usize,
frame_offset: usize,
issues: &mut Vec<ValidationIssue>,
) {
let chunks = data.chunks_exact(2);
if !chunks.remainder().is_empty() {
issues.push(err(
IssueCode::DecodedSizeMismatch,
ValidationLevel::Fidelity,
Some(obj_idx),
Some(frame_offset),
format!(
"decoded size {} is not a multiple of 2 (bfloat16)",
data.len()
),
));
return;
}
for (idx, chunk) in chunks.enumerate() {
let &[b0, b1] = chunk else {
continue;
};
let bytes = [b0, b1];
let bits = match byte_order {
ByteOrder::Big => u16::from_be_bytes(bytes),
ByteOrder::Little => u16::from_le_bytes(bytes),
};
let exp = (bits >> 7) & 0xFF;
let mantissa = bits & 0x7F;
if exp == 0xFF {
if mantissa != 0 {
issues.push(err(
IssueCode::NanDetected,
ValidationLevel::Fidelity,
Some(obj_idx),
Some(frame_offset),
format!("NaN at element {idx}"),
));
return;
} else {
issues.push(err(
IssueCode::InfDetected,
ValidationLevel::Fidelity,
Some(obj_idx),
Some(frame_offset),
format!("Inf at element {idx}"),
));
return;
}
}
}
}
fn scan_complex64(
data: &[u8],
byte_order: ByteOrder,
obj_idx: usize,
frame_offset: usize,
issues: &mut Vec<ValidationIssue>,
) {
let chunks = data.chunks_exact(8);
if !chunks.remainder().is_empty() {
issues.push(err(
IssueCode::DecodedSizeMismatch,
ValidationLevel::Fidelity,
Some(obj_idx),
Some(frame_offset),
format!(
"decoded size {} is not a multiple of 8 (complex64)",
data.len()
),
));
return;
}
for (idx, chunk) in chunks.enumerate() {
let &[r0, r1, r2, r3, i0, i1, i2, i3] = chunk else {
continue;
};
let real_bytes = [r0, r1, r2, r3];
let imag_bytes = [i0, i1, i2, i3];
let (real, imag) = match byte_order {
ByteOrder::Big => (
f32::from_be_bytes(real_bytes),
f32::from_be_bytes(imag_bytes),
),
ByteOrder::Little => (
f32::from_le_bytes(real_bytes),
f32::from_le_bytes(imag_bytes),
),
};
for (val, component) in [(real, "real"), (imag, "imaginary")] {
if val.is_nan() {
issues.push(err(
IssueCode::NanDetected,
ValidationLevel::Fidelity,
Some(obj_idx),
Some(frame_offset),
format!("NaN at element {idx} ({component} component)"),
));
return;
}
if val.is_infinite() {
issues.push(err(
IssueCode::InfDetected,
ValidationLevel::Fidelity,
Some(obj_idx),
Some(frame_offset),
format!("Inf at element {idx} ({component} component)"),
));
return;
}
}
}
}
fn scan_complex128(
data: &[u8],
byte_order: ByteOrder,
obj_idx: usize,
frame_offset: usize,
issues: &mut Vec<ValidationIssue>,
) {
let chunks = data.chunks_exact(16);
if !chunks.remainder().is_empty() {
issues.push(err(
IssueCode::DecodedSizeMismatch,
ValidationLevel::Fidelity,
Some(obj_idx),
Some(frame_offset),
format!(
"decoded size {} is not a multiple of 16 (complex128)",
data.len()
),
));
return;
}
for (idx, chunk) in chunks.enumerate() {
let &[
r0,
r1,
r2,
r3,
r4,
r5,
r6,
r7,
i0,
i1,
i2,
i3,
i4,
i5,
i6,
i7,
] = chunk
else {
continue;
};
let real_bytes = [r0, r1, r2, r3, r4, r5, r6, r7];
let imag_bytes = [i0, i1, i2, i3, i4, i5, i6, i7];
let (real, imag) = match byte_order {
ByteOrder::Big => (
f64::from_be_bytes(real_bytes),
f64::from_be_bytes(imag_bytes),
),
ByteOrder::Little => (
f64::from_le_bytes(real_bytes),
f64::from_le_bytes(imag_bytes),
),
};
for (val, component) in [(real, "real"), (imag, "imaginary")] {
if val.is_nan() {
issues.push(err(
IssueCode::NanDetected,
ValidationLevel::Fidelity,
Some(obj_idx),
Some(frame_offset),
format!("NaN at element {idx} ({component} component)"),
));
return;
}
if val.is_infinite() {
issues.push(err(
IssueCode::InfDetected,
ValidationLevel::Fidelity,
Some(obj_idx),
Some(frame_offset),
format!("Inf at element {idx} ({component} component)"),
));
return;
}
}
}
}