mirror of
https://github.com/ruvnet/RuVector.git
synced 2026-05-25 15:03:46 +00:00
fix: resolve P0 safety issues in ruvector-dither, thermorust, and exo-ai
- Replace debug_assert with assert for bits bounds in quantize functions - Guard ChannelDither against 0 channels and invalid bits - Handle non-finite beta/rate in Langevin/Poisson noise (return 0) - Remove unused itertools dependency from thermorust - Fix partial_cmp().unwrap() NaN panics across 7 exo-ai files - Fix SystemTime unwrap() in transfer_crdt (use unwrap_or_default) - Fix domain ID mismatch (exo_retrieval → exo-retrieval) in orchestrator - Update tests to match corrected domain IDs Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
parent
658c762994
commit
b4d2b7343f
14 changed files with 32 additions and 21 deletions
|
|
@ -32,6 +32,8 @@ impl ChannelDither {
|
|||
/// If the slice is not a multiple of `n_channels`, the remainder is
|
||||
/// processed using channel 0.
|
||||
pub fn quantize_batch(&mut self, activations: &mut [f32]) {
|
||||
assert!(!self.channels.is_empty(), "ChannelDither must have >= 1 channel");
|
||||
assert!(self.bits >= 2 && self.bits <= 31, "bits must be in [2, 31]");
|
||||
let nc = self.channels.len();
|
||||
let qmax = ((1u32 << (self.bits - 1)) - 1) as f32;
|
||||
let lsb = 1.0 / qmax;
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ use crate::DitherSource;
|
|||
/// ```
|
||||
#[inline]
|
||||
pub fn quantize_dithered(x: f32, bits: u32, eps: f32, source: &mut impl DitherSource) -> f32 {
|
||||
debug_assert!(bits >= 1 && bits <= 31, "bits must be in [1, 31]");
|
||||
assert!(bits >= 2 && bits <= 31, "bits must be in [2, 31]");
|
||||
let qmax = ((1u32 << (bits - 1)) - 1) as f32;
|
||||
let lsb = 1.0 / qmax;
|
||||
let dither = source.next(eps * lsb);
|
||||
|
|
@ -50,6 +50,7 @@ pub fn quantize_slice_dithered(
|
|||
eps: f32,
|
||||
source: &mut impl DitherSource,
|
||||
) {
|
||||
assert!(bits >= 2 && bits <= 31, "bits must be in [2, 31]");
|
||||
let qmax = ((1u32 << (bits - 1)) - 1) as f32;
|
||||
let lsb = 1.0 / qmax;
|
||||
for x in xs.iter_mut() {
|
||||
|
|
@ -64,7 +65,7 @@ pub fn quantize_slice_dithered(
|
|||
/// Useful when you need the integer representation rather than a re-scaled float.
|
||||
#[inline]
|
||||
pub fn quantize_to_code(x: f32, bits: u32, eps: f32, source: &mut impl DitherSource) -> i32 {
|
||||
debug_assert!(bits >= 1 && bits <= 31);
|
||||
assert!(bits >= 2 && bits <= 31, "bits must be in [2, 31]");
|
||||
let qmax = ((1u32 << (bits - 1)) - 1) as f32;
|
||||
let lsb = 1.0 / qmax;
|
||||
let dither = source.next(eps * lsb);
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ readme = "README.md"
|
|||
[dependencies]
|
||||
rand = { version = "0.8", features = ["small_rng"] }
|
||||
rand_distr = "0.4"
|
||||
itertools = "0.12"
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = { version = "0.5", features = ["html_reports"] }
|
||||
|
|
|
|||
|
|
@ -9,16 +9,23 @@ use rand_distr::{Distribution, Normal, Poisson};
|
|||
/// the noise amplitude must be √(2kT) = √(2/β) in dimensionless units.
|
||||
#[inline]
|
||||
pub fn langevin_noise(beta: f32, rng: &mut impl Rng) -> f32 {
|
||||
if beta <= 0.0 || !beta.is_finite() {
|
||||
return 0.0;
|
||||
}
|
||||
let sigma = (2.0 / beta).sqrt();
|
||||
Normal::new(0.0_f32, sigma)
|
||||
.expect("sigma must be finite and positive")
|
||||
.unwrap_or_else(|_| Normal::new(0.0_f32, 1e-6).unwrap())
|
||||
.sample(rng)
|
||||
}
|
||||
|
||||
/// Draw `n` independent Langevin noise samples.
|
||||
pub fn langevin_noise_vec(beta: f32, n: usize, rng: &mut impl Rng) -> Vec<f32> {
|
||||
if beta <= 0.0 || !beta.is_finite() {
|
||||
return vec![0.0; n];
|
||||
}
|
||||
let sigma = (2.0 / beta).sqrt();
|
||||
let dist = Normal::new(0.0_f32, sigma).expect("sigma must be finite");
|
||||
let dist = Normal::new(0.0_f32, sigma)
|
||||
.unwrap_or_else(|_| Normal::new(0.0_f32, 1e-6).unwrap());
|
||||
(0..n).map(|_| dist.sample(rng)).collect()
|
||||
}
|
||||
|
||||
|
|
@ -27,7 +34,10 @@ pub fn langevin_noise_vec(beta: f32, n: usize, rng: &mut impl Rng) -> Vec<f32> {
|
|||
/// Returns the kick to add to a single activation (0.0 if no spike this step).
|
||||
#[inline]
|
||||
pub fn poisson_spike(rate: f64, kick: f32, rng: &mut impl Rng) -> f32 {
|
||||
let dist = Poisson::new(rate).expect("rate must be > 0");
|
||||
if rate <= 0.0 || !rate.is_finite() {
|
||||
return 0.0;
|
||||
}
|
||||
let dist = Poisson::new(rate).unwrap_or_else(|_| Poisson::new(1e-6).unwrap());
|
||||
let count = dist.sample(rng) as u64;
|
||||
if count > 0 {
|
||||
// Random sign
|
||||
|
|
|
|||
|
|
@ -138,7 +138,6 @@ fn cold_system_stays_near_ground_state() {
|
|||
|
||||
#[test]
|
||||
fn langevin_lowers_energy_on_average() {
|
||||
use thermorust::energy::SoftSpin;
|
||||
use thermorust::motifs::SoftSpinMotif;
|
||||
let n = 8;
|
||||
let mut motif = SoftSpinMotif::random(n, 1.0, 0.5, 13);
|
||||
|
|
|
|||
|
|
@ -62,8 +62,8 @@ pub struct ExoTransferOrchestrator {
|
|||
impl ExoTransferOrchestrator {
|
||||
/// Create a new orchestrator.
|
||||
pub fn new(_node_id: impl Into<String>) -> Self {
|
||||
let src_id = DomainId("exo_retrieval".to_string());
|
||||
let dst_id = DomainId("exo_graph".to_string());
|
||||
let src_id = DomainId("exo-retrieval".to_string());
|
||||
let dst_id = DomainId("exo-graph".to_string());
|
||||
|
||||
let mut engine = DomainExpansionEngine::new();
|
||||
engine.register_domain(Box::new(ExoRetrievalDomain::new()));
|
||||
|
|
|
|||
|
|
@ -60,8 +60,8 @@ fn test_full_transfer_pipeline_multi_cycle() {
|
|||
|
||||
// - CRDT should know both domain IDs.
|
||||
let prior = orch.best_prior().expect("CRDT must hold a prior");
|
||||
assert_eq!(prior.src_domain, "exo_retrieval");
|
||||
assert_eq!(prior.dst_domain, "exo_graph");
|
||||
assert_eq!(prior.src_domain, "exo-retrieval");
|
||||
assert_eq!(prior.dst_domain, "exo-graph");
|
||||
assert!(prior.improvement >= 0.0 && prior.improvement <= 1.0);
|
||||
assert!(prior.confidence >= 0.0 && prior.confidence <= 1.0);
|
||||
assert!(prior.cycle >= 1);
|
||||
|
|
@ -111,7 +111,7 @@ fn test_crdt_prior_consistency() {
|
|||
}
|
||||
|
||||
let prior = orch.best_prior().expect("prior must exist after 3 cycles");
|
||||
assert_eq!(prior.src_domain, "exo_retrieval");
|
||||
assert_eq!(prior.dst_domain, "exo_graph");
|
||||
assert_eq!(prior.src_domain, "exo-retrieval");
|
||||
assert_eq!(prior.dst_domain, "exo-graph");
|
||||
assert!(prior.cycle >= 1 && prior.cycle <= 3);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -260,7 +260,7 @@ impl SubstrateBackend for NeuromorphicBackend {
|
|||
SearchResult { id, score, embedding: vec![] }
|
||||
})
|
||||
.collect();
|
||||
results.sort_unstable_by(|a, b| b.score.partial_cmp(&a.score).unwrap());
|
||||
results.sort_unstable_by(|a, b| b.score.partial_cmp(&a.score).unwrap_or(std::cmp::Ordering::Equal));
|
||||
results.truncate(k);
|
||||
let _elapsed = t0.elapsed();
|
||||
results
|
||||
|
|
|
|||
|
|
@ -112,7 +112,7 @@ impl InterferenceState {
|
|||
amplitude_im: im,
|
||||
})
|
||||
.collect();
|
||||
measurements.sort_unstable_by(|a, b| b.probability.partial_cmp(&a.probability).unwrap());
|
||||
measurements.sort_unstable_by(|a, b| b.probability.partial_cmp(&a.probability).unwrap_or(std::cmp::Ordering::Equal));
|
||||
measurements.truncate(k);
|
||||
measurements
|
||||
}
|
||||
|
|
@ -180,7 +180,7 @@ impl SubstrateBackend for QuantumStubBackend {
|
|||
SearchResult { id: *id, score: score.max(0.0), embedding: pattern.clone() }
|
||||
})
|
||||
.collect();
|
||||
results.sort_unstable_by(|a, b| b.score.partial_cmp(&a.score).unwrap());
|
||||
results.sort_unstable_by(|a, b| b.score.partial_cmp(&a.score).unwrap_or(std::cmp::Ordering::Equal));
|
||||
results.truncate(k);
|
||||
let _elapsed = t0.elapsed();
|
||||
results
|
||||
|
|
|
|||
|
|
@ -219,7 +219,7 @@ impl GenomicPatternStore {
|
|||
results.sort_unstable_by(|a, b| {
|
||||
b.weighted_score
|
||||
.partial_cmp(&a.weighted_score)
|
||||
.unwrap()
|
||||
.unwrap_or(std::cmp::Ordering::Equal)
|
||||
});
|
||||
results.truncate(k);
|
||||
results
|
||||
|
|
|
|||
|
|
@ -149,7 +149,7 @@ impl ReasoningBank {
|
|||
(t, sim)
|
||||
})
|
||||
.collect();
|
||||
scored.sort_unstable_by(|a, b| b.1.partial_cmp(&a.1).unwrap());
|
||||
scored.sort_unstable_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
|
||||
scored.truncate(k);
|
||||
scored.into_iter().map(|(t, _)| t).collect()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -135,7 +135,7 @@ fn current_millis() -> u64 {
|
|||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.unwrap_or_default()
|
||||
.as_millis() as u64
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -214,7 +214,7 @@ impl SparseRipsComplex {
|
|||
// Sort edges by weight (filtration order)
|
||||
let mut sorted_edges: Vec<&SimplexEdge> = edges.iter().collect();
|
||||
sorted_edges
|
||||
.sort_unstable_by(|a, b| a.weight.partial_cmp(&b.weight).unwrap());
|
||||
.sort_unstable_by(|a, b| a.weight.partial_cmp(&b.weight).unwrap_or(std::cmp::Ordering::Equal));
|
||||
|
||||
for edge in sorted_edges {
|
||||
let pu = find(&mut parent, edge.u as usize);
|
||||
|
|
|
|||
|
|
@ -135,7 +135,7 @@ impl QuantumDecayPool {
|
|||
fn evict_weakest(&mut self) {
|
||||
if let Some(idx) = self.patterns.iter()
|
||||
.enumerate()
|
||||
.min_by(|a, b| a.1.decoherence_score().partial_cmp(&b.1.decoherence_score()).unwrap())
|
||||
.min_by(|a, b| a.1.decoherence_score().partial_cmp(&b.1.decoherence_score()).unwrap_or(std::cmp::Ordering::Equal))
|
||||
.map(|(i, _)| i)
|
||||
{
|
||||
self.patterns.remove(idx);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue