crate::ix!();
#[derive(Debug, Clone, Builder, Getters)]
#[builder(pattern = "owned", setter(into, strip_option), default)]
#[getset(get = "pub")]
pub struct WarriorProfileSampler {
temperature: f64,
min_similarity: f64,
}
impl Default for WarriorProfileSampler {
fn default() -> Self {
Self {
temperature: 0.25,
min_similarity: 0.75,
}
}
}
impl WarriorProfileSampler {
pub fn sample_profile<R>(
&self,
space_ratings: &WarriorCapabilityIntrinsicDimensionRatings,
rng: &mut R,
) -> WarriorCapabilityProfile
where
R: rand::Rng + ?Sized,
{
trace!("sampling profile from intrinsic‑dimension space");
WarriorCapabilityProfileBuilder::default()
.bravery(self.sample_set::<WarriorBraveryCapability, R>(space_ratings, rng))
.leadership(self.sample_set::<WarriorLeadershipCapability, R>(space_ratings, rng))
.strategy(self.sample_set::<WarriorStrategicCapability, R>(space_ratings, rng))
.protection(self.sample_set::<WarriorProtectiveCapability, R>(space_ratings, rng))
.physical(self.sample_set::<WarriorPhysicalCapability, R>(space_ratings, rng))
.resilience(self.sample_set::<WarriorResilienceCapability, R>(space_ratings, rng))
.responsiveness(self.sample_set::<WarriorResponsivenessCapability, R>(space_ratings, rng))
.exploration(self.sample_set::<WarriorExplorationCapability, R>(space_ratings, rng))
.threat_adaptation(self.sample_set::<WarriorThreatAdaptationCapability, R>(space_ratings, rng))
.conflict_mitigation(self.sample_set::<WarriorConflictMitigationCapability, R>(space_ratings, rng))
.approach(self.sample_set::<WarriorApproachCapability, R>(space_ratings, rng))
.embodiment(self.sample_set::<WarriorEmbodimentCapability, R>(space_ratings, rng))
.meta(self.sample_set::<WarriorMetaCapability, R>(space_ratings, rng))
.offensive_magic(self.sample_set::<WarriorOffensiveMagicCapability, R>(space_ratings, rng))
.defensive_magic(self.sample_set::<WarriorDefensiveMagicCapability, R>(space_ratings, rng))
.illusionary_magic(self.sample_set::<WarriorIllusionaryMagicCapability, R>(space_ratings, rng))
.cyber_defense(self.sample_set::<WarriorCyberDefenseCapability, R>(space_ratings, rng))
.tech_adaptation(self.sample_set::<WarriorTechAdaptationCapability, R>(space_ratings, rng))
.build()
.unwrap()
}
#[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()
}
#[instrument(level = "trace", skip(self, space_ratings, rng))]
fn sample_set<V, R>(
&self,
space_ratings: &WarriorCapabilityIntrinsicDimensionRatings,
rng: &mut R,
) -> 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_ratings.to_vec();
let sims: Vec<(V, f64)> = variants
.into_iter()
.map(|v| {
let sim = Self::dot(&space_vec, &v.intrinsic_ratings().to_vec());
(v.clone(), sim)
})
.collect();
let max_sim = sims.iter().map(|(_, sim)| sim).fold(f64::MIN, |a, &b| a.max(b));
let weights: Vec<f64> = sims
.iter()
.map(|(_, sim)| {
if *sim <= 0.0 || *sim < self.min_similarity {
0.0
} else {
(sim / self.temperature).exp() - 1.0
}
})
.collect();
let valid_variants: Vec<(V, f64)> = sims
.into_iter()
.zip(weights.into_iter())
.filter(|(_, weight)| *weight > 0.0)
.map(|((variant, _), weight)| (variant, weight))
.collect();
if valid_variants.is_empty() {
trace!(
category = std::any::type_name::<V>(),
max_similarity = max_sim,
"category left empty (no variants with sufficient similarity)"
);
return HashSet::new();
}
let filtered_weights: Vec<f64> = valid_variants.iter().map(|(_, w)| *w).collect();
let dist = WeightedIndex::new(&filtered_weights).expect("weights positive & finite");
let choice = valid_variants[dist.sample(rng)].0.clone();
trace!(
category = std::any::type_name::<V>(),
?choice,
max_similarity = max_sim,
"selected capability variant"
);
HashSet::from([choice])
}
}