#[cfg(test)]
mod lipschitz_tests {
use super::super::adaptation_bus::{
norm2_diff, AdaptContext, DriftRateAdapter, MetaAdapter, NoOpAdapter, PlasticityAdapter,
ThetaDelta,
};
use super::super::auto_builder::ConfigDiagnostics;
use proptest::prelude::*;
use rand::SeedableRng;
fn empirical_lipschitz<A: MetaAdapter>(
adapter: &mut A,
eps: f64,
n_pairs: usize,
rng: &mut impl rand::Rng,
) -> f64 {
let base_ctx = AdaptContext {
theta: [0.5, 0.5],
diagnostics: ConfigDiagnostics::default(),
prediction: 0.0,
target: 0.0,
sample_idx: 1000,
};
let mut max_ratio: f64 = 0.0;
for _ in 0..n_pairs {
let theta_a: [f64; 2] = [rng.gen_range(0.0..=1.0), rng.gen_range(0.0..=1.0)];
let theta_b: [f64; 2] = [
(theta_a[0] + rng.gen_range(-eps..=eps)).clamp(0.0, 1.0),
(theta_a[1] + rng.gen_range(-eps..=eps)).clamp(0.0, 1.0),
];
let ctx_a = AdaptContext {
theta: theta_a,
..base_ctx.clone()
};
let ctx_b = AdaptContext {
theta: theta_b,
..base_ctx.clone()
};
let delta_a = adapter.apply(&ctx_a).delta;
let delta_b = adapter.apply(&ctx_b).delta;
let output_dist = norm2_diff(&delta_a, &delta_b);
let input_dist = norm2_diff(&theta_a, &theta_b);
if input_dist > 1e-15 {
max_ratio = max_ratio.max(output_dist / input_dist);
}
}
max_ratio
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(500))]
#[test]
fn noop_adapter_non_expansive(
seed in 0u64..u64::MAX,
) {
let mut rng = rand::rngs::SmallRng::seed_from_u64(seed);
let mut adapter = NoOpAdapter;
let declared_l = adapter.lipschitz_bound(); let actual_l = empirical_lipschitz(&mut adapter, 1e-3, 200, &mut rng);
prop_assert!(
actual_l <= declared_l + 1e-10,
"NoOpAdapter: empirical L={} exceeds declared L={}", actual_l, declared_l
);
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(1000))]
#[test]
fn drift_rate_adapter_non_expansive(
delta_max in 0.001f64..0.05,
seed in 0u64..u64::MAX,
) {
let mut rng = rand::rngs::SmallRng::seed_from_u64(seed);
let mut adapter = DriftRateAdapter::new(delta_max);
let actual_l = empirical_lipschitz(&mut adapter, 1e-3, 500, &mut rng);
prop_assert!(
actual_l <= 1.0 + 1e-10,
"DriftRateAdapter: empirical L={} > 1.0 (expansive)", actual_l
);
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(1000))]
#[test]
fn plasticity_adapter_non_expansive(
delta_max in 0.001f64..0.1,
seed in 0u64..u64::MAX,
) {
let mut rng = rand::rngs::SmallRng::seed_from_u64(seed);
let mut adapter = PlasticityAdapter::new(delta_max);
let actual_l = empirical_lipschitz(&mut adapter, 1e-3, 500, &mut rng);
prop_assert!(
actual_l <= 1.0 + 1e-10,
"PlasticityAdapter: empirical L={} > 1.0 (expansive)", actual_l
);
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(1000))]
#[test]
fn contraction_adapter_at_declared_bound(
rho in 0.1f64..0.4,
seed in 0u64..u64::MAX,
) {
let mut rng = rand::rngs::SmallRng::seed_from_u64(seed);
struct ContractionAdapter { rho: f64 }
impl MetaAdapter for ContractionAdapter {
fn lipschitz_bound(&self) -> f64 { self.rho.max(1.0 - self.rho) }
fn update_period(&self) -> u64 { 30 }
fn apply(&mut self, ctx: &AdaptContext) -> ThetaDelta {
let target = [0.5_f64, 0.5_f64];
ThetaDelta { delta: [
self.rho * (target[0] - ctx.theta[0]),
self.rho * (target[1] - ctx.theta[1]),
] }
}
fn progress(&self) -> f64 { 0.0 }
}
let mut adapter = ContractionAdapter { rho };
let declared_l = adapter.lipschitz_bound();
let actual_l = empirical_lipschitz(&mut adapter, 1e-4, 200, &mut rng);
prop_assert!(
actual_l <= declared_l * 1.05,
"ContractionAdapter(rho={:.3}): empirical L={:.6} > declared L={:.4} * 1.05",
rho, actual_l, declared_l
);
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(500))]
#[test]
fn drift_rate_adapter_non_expansive_after_flush(
delta_max in 0.001f64..0.05,
seed in 0u64..u64::MAX,
) {
let mut rng = rand::rngs::SmallRng::seed_from_u64(seed);
let mut adapter = DriftRateAdapter::new(delta_max);
let base_ctx = AdaptContext {
theta: [0.5, 0.5],
diagnostics: ConfigDiagnostics::default(),
prediction: 0.5,
target: 0.3,
sample_idx: 100,
};
for _ in 0..10 {
adapter.apply(&base_ctx);
}
adapter.flush_state();
let actual_l = empirical_lipschitz(&mut adapter, 1e-3, 200, &mut rng);
prop_assert!(
actual_l <= 1.0 + 1e-10,
"DriftRateAdapter post-flush: empirical L={} > 1.0", actual_l
);
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(500))]
#[test]
fn plasticity_adapter_non_expansive_after_flush(
delta_max in 0.001f64..0.1,
seed in 0u64..u64::MAX,
) {
let mut rng = rand::rngs::SmallRng::seed_from_u64(seed);
let mut adapter = PlasticityAdapter::new(delta_max);
let base_ctx = AdaptContext {
theta: [0.5, 0.5],
diagnostics: ConfigDiagnostics::default(),
prediction: 0.5,
target: 0.3,
sample_idx: 100,
};
for _ in 0..10 {
adapter.apply(&base_ctx);
}
adapter.flush_state();
let actual_l = empirical_lipschitz(&mut adapter, 1e-3, 200, &mut rng);
prop_assert!(
actual_l <= 1.0 + 1e-10,
"PlasticityAdapter post-flush: empirical L={} > 1.0", actual_l
);
}
}
}