pub const EMBEDDING_DIM: usize = 256;
#[derive(Debug, Clone, PartialEq)]
pub struct RfEmbedding(pub Vec<f32>);
impl RfEmbedding {
#[must_use]
pub fn new(v: Vec<f32>) -> Self {
debug_assert_eq!(v.len(), EMBEDDING_DIM, "embedding must be {EMBEDDING_DIM}-d");
Self(v)
}
#[must_use]
pub fn sq_dist(&self, other: &RfEmbedding) -> f32 {
self.0.iter().zip(&other.0).map(|(a, b)| (a - b).powi(2)).sum()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum TaskKind {
Pose,
Presence,
Count,
Activity,
Vitals,
Gait,
IdentityEmbedding,
}
impl TaskKind {
pub const ALL: [TaskKind; 7] = [
TaskKind::Pose,
TaskKind::Presence,
TaskKind::Count,
TaskKind::Activity,
TaskKind::Vitals,
TaskKind::Gait,
TaskKind::IdentityEmbedding,
];
}
#[derive(Debug, Clone, PartialEq)]
pub struct HeadOutput {
pub task: TaskKind,
pub values: Vec<f32>,
pub uncertainty: f32,
}
impl HeadOutput {
#[must_use]
pub fn confidence(&self) -> f32 {
1.0 / (1.0 + self.uncertainty)
}
}
#[derive(Debug, Clone)]
pub struct LinearHead {
task: TaskKind,
w: Vec<f32>,
b: Vec<f32>,
out_dim: usize,
var_w: Vec<f32>,
var_b: f32,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum RfHeadError {
WeightShape {
expected: usize,
got: usize,
},
BiasShape {
expected: usize,
got: usize,
},
VarWeightShape {
expected: usize,
got: usize,
},
}
impl std::fmt::Display for RfHeadError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::WeightShape { expected, got } => {
write!(f, "weight shape mismatch: expected {expected}, got {got}")
}
Self::BiasShape { expected, got } => {
write!(f, "bias shape mismatch: expected {expected}, got {got}")
}
Self::VarWeightShape { expected, got } => {
write!(f, "var weight shape mismatch: expected {expected}, got {got}")
}
}
}
}
impl std::error::Error for RfHeadError {}
impl LinearHead {
#[must_use]
pub fn new(task: TaskKind, out_dim: usize, w: Vec<f32>, b: Vec<f32>, var_w: Vec<f32>, var_b: f32) -> Self {
assert_eq!(w.len(), out_dim * EMBEDDING_DIM, "weight shape mismatch");
assert_eq!(b.len(), out_dim, "bias shape mismatch");
assert_eq!(var_w.len(), EMBEDDING_DIM, "var weight shape mismatch");
Self { task, w, b, out_dim, var_w, var_b }
}
pub fn try_new(
task: TaskKind,
out_dim: usize,
w: Vec<f32>,
b: Vec<f32>,
var_w: Vec<f32>,
var_b: f32,
) -> Result<Self, RfHeadError> {
let expected_w = out_dim * EMBEDDING_DIM;
if w.len() != expected_w {
return Err(RfHeadError::WeightShape { expected: expected_w, got: w.len() });
}
if b.len() != out_dim {
return Err(RfHeadError::BiasShape { expected: out_dim, got: b.len() });
}
if var_w.len() != EMBEDDING_DIM {
return Err(RfHeadError::VarWeightShape { expected: EMBEDDING_DIM, got: var_w.len() });
}
Ok(Self { task, w, b, out_dim, var_w, var_b })
}
#[must_use]
pub fn zeros(task: TaskKind, out_dim: usize) -> Self {
Self::new(
task,
out_dim,
vec![0.0; out_dim * EMBEDDING_DIM],
vec![0.0; out_dim],
vec![0.0; EMBEDDING_DIM],
0.0,
)
}
#[must_use]
pub fn forward(&self, emb: &RfEmbedding) -> HeadOutput {
let mut values = vec![0.0f32; self.out_dim];
for o in 0..self.out_dim {
let row = &self.w[o * EMBEDDING_DIM..(o + 1) * EMBEDDING_DIM];
let dot: f32 = row.iter().zip(&emb.0).map(|(wi, xi)| wi * xi).sum();
values[o] = dot + self.b[o];
}
let log_var: f32 = self.var_w.iter().zip(&emb.0).map(|(wi, xi)| wi * xi).sum::<f32>() + self.var_b;
let uncertainty = softplus(log_var);
HeadOutput { task: self.task, values, uncertainty }
}
}
const SOFTPLUS_LINEAR_THRESHOLD: f32 = 20.0;
fn softplus(x: f32) -> f32 {
if x > SOFTPLUS_LINEAR_THRESHOLD {
x
} else {
(1.0 + x.exp()).ln()
}
}
#[derive(Debug, Clone, Default)]
pub struct MultiTaskHeads {
heads: Vec<LinearHead>,
}
impl MultiTaskHeads {
#[must_use]
pub fn new() -> Self {
Self { heads: Vec::new() }
}
pub fn push(&mut self, head: LinearHead) {
self.heads.push(head);
}
#[must_use]
pub fn len(&self) -> usize {
self.heads.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.heads.is_empty()
}
#[must_use]
pub fn forward(&self, emb: &RfEmbedding) -> Vec<HeadOutput> {
self.heads.iter().map(|h| h.forward(emb)).collect()
}
#[must_use]
pub fn forward_subset(&self, emb: &RfEmbedding, enabled: &[TaskKind]) -> Vec<HeadOutput> {
self.heads
.iter()
.filter(|h| enabled.contains(&h.task))
.map(|h| h.forward(emb))
.collect()
}
}
#[must_use]
pub fn calibration_robustness_loss(under_cal_a: &RfEmbedding, under_cal_b: &RfEmbedding) -> f32 {
under_cal_a.sq_dist(under_cal_b) / EMBEDDING_DIM as f32
}
#[must_use]
pub fn triplet_loss(anchor: &RfEmbedding, positive: &RfEmbedding, negative: &RfEmbedding, margin: f32) -> f32 {
(anchor.sq_dist(positive) - anchor.sq_dist(negative) + margin).max(0.0)
}
#[derive(Debug, Clone)]
pub struct Triplet {
pub anchor: usize,
pub positive: usize,
pub negative: usize,
}
#[derive(Debug, Clone)]
pub struct ContrastiveBatcher {
state_of: Vec<u32>,
env_of: Vec<u32>,
}
impl ContrastiveBatcher {
#[must_use]
pub fn new(state_of: Vec<u32>, env_of: Vec<u32>) -> Self {
assert_eq!(state_of.len(), env_of.len(), "label vectors must align");
Self { state_of, env_of }
}
#[must_use]
pub fn triplets(&self) -> Vec<Triplet> {
let n = self.state_of.len();
let mut out = Vec::new();
for a in 0..n {
let positive = (0..n).find(|&p| {
p != a && self.state_of[p] == self.state_of[a] && self.env_of[p] != self.env_of[a]
});
let negative = (0..n).find(|&q| self.state_of[q] != self.state_of[a]);
if let (Some(positive), Some(negative)) = (positive, negative) {
out.push(Triplet { anchor: a, positive, negative });
}
}
out
}
}
#[cfg(test)]
mod tests {
use super::*;
fn emb(fill: f32) -> RfEmbedding {
RfEmbedding::new(vec![fill; EMBEDDING_DIM])
}
#[test]
fn softplus_threshold_unchanged_from_literal() {
assert_eq!(SOFTPLUS_LINEAR_THRESHOLD, 20.0_f32);
}
#[test]
fn try_new_accepts_valid_and_rejects_each_bad_shape() {
let out_dim = 2;
let w = vec![0.0; out_dim * EMBEDDING_DIM];
let b = vec![0.0; out_dim];
let var_w = vec![0.0; EMBEDDING_DIM];
let head = LinearHead::try_new(TaskKind::Presence, out_dim, w.clone(), b.clone(), var_w.clone(), 0.0)
.expect("valid shapes must construct");
let reference = LinearHead::new(TaskKind::Presence, out_dim, w.clone(), b.clone(), var_w.clone(), 0.0);
assert_eq!(head.forward(&emb(0.5)).values, reference.forward(&emb(0.5)).values);
assert_eq!(
LinearHead::try_new(TaskKind::Presence, out_dim, vec![0.0; 3], b.clone(), var_w.clone(), 0.0)
.unwrap_err(),
RfHeadError::WeightShape { expected: out_dim * EMBEDDING_DIM, got: 3 }
);
assert_eq!(
LinearHead::try_new(TaskKind::Presence, out_dim, w.clone(), vec![0.0; 1], var_w.clone(), 0.0)
.unwrap_err(),
RfHeadError::BiasShape { expected: out_dim, got: 1 }
);
assert_eq!(
LinearHead::try_new(TaskKind::Presence, out_dim, w, b, vec![0.0; 5], 0.0).unwrap_err(),
RfHeadError::VarWeightShape { expected: EMBEDDING_DIM, got: 5 }
);
}
#[test]
fn head_forward_produces_values_and_finite_uncertainty() {
let head = LinearHead::zeros(TaskKind::Presence, 2);
let out = head.forward(&emb(1.0));
assert_eq!(out.values, vec![0.0, 0.0]); assert!(out.uncertainty.is_finite() && out.uncertainty > 0.0);
assert!((out.confidence() - 1.0 / (1.0 + out.uncertainty)).abs() < 1e-6);
}
#[test]
fn uncertainty_responds_to_log_variance_weights() {
let head = LinearHead::new(
TaskKind::Vitals,
1,
vec![0.0; EMBEDDING_DIM],
vec![0.0],
vec![1.0; EMBEDDING_DIM],
0.0,
);
let out = head.forward(&emb(1.0));
assert!(out.uncertainty > 100.0, "high log-var → high uncertainty");
assert!(out.confidence() < 0.02);
}
#[test]
fn calibration_robustness_loss_zero_for_identical() {
assert_eq!(calibration_robustness_loss(&emb(0.5), &emb(0.5)), 0.0);
assert!(calibration_robustness_loss(&emb(0.0), &emb(1.0)) > 0.0);
}
#[test]
fn triplet_loss_properties() {
let a = emb(0.0);
let p = emb(0.1); let n = emb(5.0); assert_eq!(triplet_loss(&a, &p, &n, 0.5), 0.0);
assert!(triplet_loss(&a, &n, &p, 0.5) > 0.0);
}
#[test]
fn multitask_subset_ablation() {
let mut heads = MultiTaskHeads::new();
heads.push(LinearHead::zeros(TaskKind::Presence, 1));
heads.push(LinearHead::zeros(TaskKind::Pose, 51));
heads.push(LinearHead::zeros(TaskKind::Vitals, 2));
assert_eq!(heads.forward(&emb(1.0)).len(), 3);
let sub = heads.forward_subset(&emb(1.0), &[TaskKind::Presence, TaskKind::Vitals]);
assert_eq!(sub.len(), 2);
assert!(sub.iter().all(|o| o.task != TaskKind::Pose));
}
#[test]
fn contrastive_batcher_samples_cross_env_positives() {
let b = ContrastiveBatcher::new(vec![0, 0, 1], vec![0, 1, 0]);
let trips = b.triplets();
let t0 = trips.iter().find(|t| t.anchor == 0).unwrap();
assert_eq!(t0.positive, 1);
assert_eq!(t0.negative, 2);
assert!(trips.iter().all(|t| t.anchor != 2));
assert_eq!(b.triplets().len(), trips.len());
}
#[test]
fn seven_task_heads() {
assert_eq!(TaskKind::ALL.len(), 7);
}
}