crate::ix!();
#[derive(Debug, Clone, Builder, Getters)]
#[builder(pattern = "owned", setter(into, strip_option), default)]
#[getset(get = "pub")]
pub struct WarriorProfileAdaptiveSampler {
target_fill: f64,
ess_target: f64,
temp_bounds: (f64, f64),
}
impl Default for WarriorProfileAdaptiveSampler {
fn default() -> Self {
Self { target_fill: 0.65, ess_target: 3.0, temp_bounds: (0.05, 4.0) }
}
}
impl WarriorProfileAdaptiveSampler {
#[inline(always)]
fn dot(a: &[f64], b: &[f64]) -> f64 {
debug_assert_eq!(a.len(), b.len());
a.iter().zip(b).map(|(x, y)| x * y).sum()
}
#[inline(always)]
fn max_sim<V>(space_vec: &[f64]) -> f64
where
V: CapabilityVariant + Clone + 'static,
{
V::all_variants()
.iter()
.map(|v| Self::dot(space_vec, &v.intrinsic_ratings().to_vec()))
.fold(f64::MIN, f64::max)
}
pub fn sample_profile<R>(
&self,
space: &WarriorCapabilityIntrinsicDimensionRatings,
rng: &mut R,
) -> WarriorCapabilityProfile
where
R: rand::Rng + ?Sized,
{
let space_vec = space.to_vec();
let max_sims = [
Self::max_sim::<WarriorBraveryCapability>(&space_vec),
Self::max_sim::<WarriorLeadershipCapability>(&space_vec),
Self::max_sim::<WarriorStrategicCapability>(&space_vec),
Self::max_sim::<WarriorProtectiveCapability>(&space_vec),
Self::max_sim::<WarriorPhysicalCapability>(&space_vec),
Self::max_sim::<WarriorResilienceCapability>(&space_vec),
Self::max_sim::<WarriorResponsivenessCapability>(&space_vec),
Self::max_sim::<WarriorExplorationCapability>(&space_vec),
Self::max_sim::<WarriorThreatAdaptationCapability>(&space_vec),
Self::max_sim::<WarriorConflictMitigationCapability>(&space_vec),
Self::max_sim::<WarriorApproachCapability>(&space_vec),
Self::max_sim::<WarriorEmbodimentCapability>(&space_vec),
Self::max_sim::<WarriorMetaCapability>(&space_vec),
Self::max_sim::<WarriorOffensiveMagicCapability>(&space_vec),
Self::max_sim::<WarriorDefensiveMagicCapability>(&space_vec),
Self::max_sim::<WarriorIllusionaryMagicCapability>(&space_vec),
Self::max_sim::<WarriorCyberDefenseCapability>(&space_vec),
Self::max_sim::<WarriorTechAdaptationCapability>(&space_vec),
];
trace!(?max_sims, "per‑category maximum similarities");
let mut sorted = max_sims;
sorted.sort_by(|a, b| a.partial_cmp(b).unwrap());
let q = 1.0 - self.target_fill;
let cut_idx = ((q * sorted.len() as f64).floor() as usize).min(sorted.len() - 1);
let min_sim = sorted[cut_idx];
trace!(
target_fill = self.target_fill,
quantile = 1.0 - q,
cut_idx,
min_similarity = min_sim,
"adaptive threshold chosen",
);
WarriorCapabilityProfileBuilder::default()
.bravery (self.sample_set::<WarriorBraveryCapability, R>(space, rng, min_sim))
.leadership (self.sample_set::<WarriorLeadershipCapability, R>(space, rng, min_sim))
.strategy (self.sample_set::<WarriorStrategicCapability, R>(space, rng, min_sim))
.protection (self.sample_set::<WarriorProtectiveCapability, R>(space, rng, min_sim))
.physical (self.sample_set::<WarriorPhysicalCapability, R>(space, rng, min_sim))
.resilience (self.sample_set::<WarriorResilienceCapability, R>(space, rng, min_sim))
.responsiveness (self.sample_set::<WarriorResponsivenessCapability, R>(space, rng, min_sim))
.exploration (self.sample_set::<WarriorExplorationCapability, R>(space, rng, min_sim))
.threat_adaptation(self.sample_set::<WarriorThreatAdaptationCapability, R>(space, rng, min_sim))
.conflict_mitigation(self.sample_set::<WarriorConflictMitigationCapability,R>(space, rng, min_sim))
.approach (self.sample_set::<WarriorApproachCapability, R>(space, rng, min_sim))
.embodiment (self.sample_set::<WarriorEmbodimentCapability, R>(space, rng, min_sim))
.meta (self.sample_set::<WarriorMetaCapability, R>(space, rng, min_sim))
.offensive_magic (self.sample_set::<WarriorOffensiveMagicCapability, R>(space, rng, min_sim))
.defensive_magic (self.sample_set::<WarriorDefensiveMagicCapability, R>(space, rng, min_sim))
.illusionary_magic(self.sample_set::<WarriorIllusionaryMagicCapability, R>(space, rng, min_sim))
.cyber_defense (self.sample_set::<WarriorCyberDefenseCapability, R>(space, rng, min_sim))
.tech_adaptation (self.sample_set::<WarriorTechAdaptationCapability, R>(space, rng, min_sim))
.build()
.unwrap()
}
#[instrument(level = "trace", skip(self, space, rng))]
fn sample_set<V, R>(
&self,
space: &WarriorCapabilityIntrinsicDimensionRatings,
rng: &mut R,
min_sim: f64,
) -> HashSet<V>
where
V: CapabilityVariant + Clone + Eq + std::hash::Hash + std::fmt::Debug + 'static,
R: rand::Rng + ?Sized,
{
let variants = V::all_variants();
let space_vec = space.to_vec();
let mut kept: Vec<(usize, f64)> = variants
.iter()
.enumerate()
.filter_map(|(idx, v)| {
let sim = Self::dot(&space_vec, &v.intrinsic_ratings().to_vec());
(sim > min_sim).then(|| (idx, sim - min_sim))
})
.collect();
if kept.is_empty() {
trace!("category left empty (no variant > min_sim)");
return HashSet::new();
}
let shifted: Vec<f64> = kept.iter().map(|&(_, s)| s).collect();
let tau = self.find_temperature(&shifted);
let weights: Vec<f64> = shifted.iter().map(|&s| (s / tau).exp()).collect();
let sum_w: f64 = weights.iter().sum();
let probs: Vec<f64> = weights.iter().map(|w| w / sum_w).collect();
let ess = 1.0 / probs.iter().map(|p| p * p).sum::<f64>();
trace!(
category = std::any::type_name::<V>(),
min_sim,
tau,
ess,
n_kept = kept.len(),
kept_max = shifted.iter().cloned().fold(f64::MIN, f64::max),
"gated variants and tuned temperature",
);
let dist = WeightedIndex::new(&weights).expect("positive weights");
let choice = variants[kept[dist.sample(rng)].0].clone();
debug!(
category = std::any::type_name::<V>(),
?choice,
?probs,
"selected capability variant",
);
HashSet::from([choice])
}
fn find_temperature(&self, shifted: &[f64]) -> f64 {
let (mut lo, mut hi) = self.temp_bounds;
for _ in 0..15 {
let mid = 0.5 * (lo + hi);
let ws: Vec<f64> = shifted.iter().map(|&s| (s / mid).exp()).collect();
let sum: f64 = ws.iter().sum();
let probs: Vec<f64> = ws.iter().map(|w| w / sum).collect();
let ess = 1.0 / probs.iter().map(|p| p * p).sum::<f64>();
if ess < self.ess_target { hi = mid; } else { lo = mid; }
}
0.5 * (lo + hi)
}
}