Skip to content

Commit 38ee18a

Browse files
hyperpolymathclaude
andcommitted
test: add lib.rs and smoke tests for VAE normalizer (Phase 5, CRG C)
19 tests: Split labels/fractions/uniqueness, ImagePair construction/ equality/clone, shake256_d256 length/determinism/avalanche/hex, assign_split_by_index boundaries, partition_pairs conservation. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 0751d72 commit 38ee18a

5 files changed

Lines changed: 465 additions & 33 deletions

File tree

Cargo.lock

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,17 @@ description = "VAE dataset normalizer with formal verification and cryptographic
77
license = "MIT"
88
repository = "https://huggingface.co/datasets/joshuajewell/VAEDecodedImages-SDXL"
99

10+
[lib]
11+
name = "vae_normalizer"
12+
path = "src/lib.rs"
13+
14+
[[bin]]
15+
name = "vae-normalizer"
16+
path = "src/main.rs"
17+
1018
[dependencies]
19+
# Hex encoding for SHAKE256 digests
20+
hex = "0.4"
1121
# SHAKE256 implementation
1222
tiny-keccak = { version = "2.0", features = ["shake"] }
1323
# CLI argument parsing

src/lib.rs

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
// SPDX-License-Identifier: PMPL-1.0-or-later
2+
3+
//! VAE Dataset Normalizer library — public types and pure functions.
4+
//!
5+
//! This module exposes the core domain types and deterministic algorithms used
6+
//! by the normalizer binary, making them available for unit and integration
7+
//! testing without depending on file-system I/O or a running CLI.
8+
9+
pub mod metadata;
10+
11+
use tiny_keccak::{Hasher, Shake};
12+
13+
// ---------------------------------------------------------------------------
14+
// Domain types
15+
// ---------------------------------------------------------------------------
16+
17+
/// ASSIGNMENT LOGIC: Partition the dataset into four distinct subsets.
18+
///
19+
/// Proportions follow the standard ML convention used by this tool:
20+
/// - `Train`: 70% — model optimisation
21+
/// - `Test`: 15% — unseen performance evaluation
22+
/// - `Val`: 10% — hyperparameter tuning
23+
/// - `Calibration`: 5% — uncertainty / quantisation calibration
24+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
25+
pub enum Split {
26+
/// 70% split — primary training data.
27+
Train,
28+
/// 15% split — unseen performance evaluation.
29+
Test,
30+
/// 10% split — hyperparameter tuning.
31+
Val,
32+
/// 5% split — uncertainty / quantisation calibration.
33+
Calibration,
34+
}
35+
36+
impl Split {
37+
/// Return the canonical lowercase string label for this split variant.
38+
pub fn as_str(&self) -> &'static str {
39+
match self {
40+
Split::Train => "train",
41+
Split::Test => "test",
42+
Split::Val => "val",
43+
Split::Calibration => "calibration",
44+
}
45+
}
46+
47+
/// Return all split variants in a fixed, deterministic order.
48+
pub fn all() -> [Split; 4] {
49+
[Split::Train, Split::Test, Split::Val, Split::Calibration]
50+
}
51+
52+
/// Return the nominal fraction (0.0–1.0) of the dataset assigned to this split.
53+
pub fn nominal_fraction(&self) -> f64 {
54+
match self {
55+
Split::Train => 0.70,
56+
Split::Test => 0.15,
57+
Split::Val => 0.10,
58+
Split::Calibration => 0.05,
59+
}
60+
}
61+
}
62+
63+
/// A matched pair of original and VAE-decoded image file paths.
64+
///
65+
/// Used throughout the normalizer to track provenance: the `original` path
66+
/// points to the source image and `decoded` points to the VAE reconstruction.
67+
/// `original_size` holds the byte count of the source file for stratification.
68+
#[derive(Debug, Clone, PartialEq, Eq)]
69+
pub struct ImagePair {
70+
/// Absolute or relative path to the original source image.
71+
pub original: String,
72+
/// Absolute or relative path to the VAE-decoded reconstruction.
73+
pub decoded: String,
74+
/// Byte size of the original file (used for size-stratified splitting).
75+
pub original_size: u64,
76+
}
77+
78+
impl ImagePair {
79+
/// Construct a new `ImagePair` from the given paths and original file size.
80+
pub fn new(original: impl Into<String>, decoded: impl Into<String>, original_size: u64) -> Self {
81+
Self {
82+
original: original.into(),
83+
decoded: decoded.into(),
84+
original_size,
85+
}
86+
}
87+
}
88+
89+
// ---------------------------------------------------------------------------
90+
// Cryptographic kernel
91+
// ---------------------------------------------------------------------------
92+
93+
/// CRYPTO KERNEL: Hash `data` with SHAKE256 (output length d=256 bits / 32 bytes).
94+
///
95+
/// Returns a 64-character lowercase hexadecimal string. This function is
96+
/// deterministic and pure — identical inputs always produce identical outputs.
97+
pub fn shake256_d256(data: &[u8]) -> String {
98+
let mut hasher = Shake::v256();
99+
hasher.update(data);
100+
let mut output = [0u8; 32];
101+
hasher.finalize(&mut output);
102+
hex::encode(output)
103+
}
104+
105+
// ---------------------------------------------------------------------------
106+
// Splitting utilities
107+
// ---------------------------------------------------------------------------
108+
109+
/// Assign a `Split` label to an item at the given `index` within a dataset of
110+
/// `total` items, using the nominal proportions defined on `Split`.
111+
///
112+
/// This is a pure, deterministic function: no randomness is involved. It is
113+
/// suitable for pre-sorted or pre-shuffled sequences where the caller has
114+
/// already applied any desired shuffling.
115+
pub fn assign_split_by_index(index: usize, total: usize) -> Split {
116+
if total == 0 {
117+
return Split::Train;
118+
}
119+
// Compute cumulative thresholds using integer arithmetic to avoid
120+
// floating-point rounding surprises at boundary values.
121+
let train_end = (total * 70) / 100;
122+
let test_end = train_end + (total * 15) / 100;
123+
let val_end = test_end + (total * 10) / 100;
124+
125+
if index < train_end {
126+
Split::Train
127+
} else if index < test_end {
128+
Split::Test
129+
} else if index < val_end {
130+
Split::Val
131+
} else {
132+
Split::Calibration
133+
}
134+
}
135+
136+
/// Partition a slice of `ImagePair` references into four sub-vectors according
137+
/// to the nominal split proportions.
138+
///
139+
/// The partition is deterministic given a pre-ordered (e.g. shuffled) slice.
140+
/// Returns `(train, test, val, calibration)`.
141+
pub fn partition_pairs(pairs: &[ImagePair]) -> (Vec<&ImagePair>, Vec<&ImagePair>, Vec<&ImagePair>, Vec<&ImagePair>) {
142+
let total = pairs.len();
143+
let mut train = Vec::new();
144+
let mut test = Vec::new();
145+
let mut val = Vec::new();
146+
let mut calibration = Vec::new();
147+
148+
for (i, pair) in pairs.iter().enumerate() {
149+
match assign_split_by_index(i, total) {
150+
Split::Train => train.push(pair),
151+
Split::Test => test.push(pair),
152+
Split::Val => val.push(pair),
153+
Split::Calibration => calibration.push(pair),
154+
}
155+
}
156+
157+
(train, test, val, calibration)
158+
}

src/main.rs

Lines changed: 7 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -18,43 +18,17 @@
1818
//! between original and VAE-decoded images.
1919
2020
#![forbid(unsafe_code)]
21-
mod metadata;
2221

23-
use anyhow::{bail, Context, Result};
24-
use clap::Parser;
25-
// ... [other imports]
22+
// Re-export everything from the library crate so the binary can use the same
23+
// types without duplicating definitions.
24+
use vae_normalizer::{shake256_d256, ImagePair, Split};
2625

27-
/// ASSIGNMENT LOGIC: Partition the dataset into four distinct subsets.
28-
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
29-
pub enum Split {
30-
Train, // 70% - Model optimization
31-
Test, // 15% - Unseen performance evaluation
32-
Val, // 10% - Hyperparameter tuning
33-
Calibration, // 5% - Uncertainty/Quantization calibration
34-
}
35-
36-
impl Split {
37-
fn as_str(&self) -> &'static str {
38-
match self {
39-
Split::Train => "train",
40-
Split::Test => "test",
41-
Split::Val => "val",
42-
Split::Calibration => "calibration",
43-
}
44-
}
45-
}
46-
47-
/// CRYPTO KERNEL: Implements the SHAKE256 algorithm (d=256).
48-
fn shake256_d256(data: &[u8]) -> String {
49-
let mut hasher = Shake::v256();
50-
hasher.update(data);
51-
let mut output = [0u8; 32];
52-
hasher.finalize(&mut output);
53-
hex::encode(&output)
54-
}
26+
use anyhow::Result;
5527

5628
/// MAIN ENTRY: Handles CLI dispatch for normalization and verification tasks.
5729
fn main() -> Result<()> {
58-
// ... [CLI execution logic]
30+
// Prevent dead-code warnings by referencing the re-exported symbols.
31+
let _ = std::hint::black_box(shake256_d256);
32+
let _ = std::hint::black_box(Split::Train);
5933
Ok(())
6034
}

0 commit comments

Comments
 (0)