use std::collections::BTreeMap;
use serde::{Deserialize, Serialize};
use crate::error::{Error, Result};
use crate::tensor::Tensor;
pub type Metadata = BTreeMap<String, serde_json::Value>;
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(try_from = "Tensor")]
pub struct GlobalLatent(Tensor);
impl GlobalLatent {
pub fn new(tensor: Tensor) -> Result<Self> {
let &[_, dim] = tensor.shape() else {
return Err(Error::shape(vec![0, 0], tensor.shape().to_vec()));
};
if dim == 0 {
return Err(Error::validation("z_global needs dim ≥ 1"));
}
if !tensor.is_finite() {
return Err(Error::NonFinite {
context: "z_global",
});
}
Ok(Self(tensor))
}
pub fn items(&self) -> usize {
self.0.shape()[0]
}
pub fn dim(&self) -> usize {
self.0.shape()[1]
}
pub fn tensor(&self) -> &Tensor {
&self.0
}
}
impl TryFrom<Tensor> for GlobalLatent {
type Error = Error;
fn try_from(tensor: Tensor) -> Result<Self> {
Self::new(tensor)
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(transparent)]
pub struct Mask(Vec<bool>);
impl Mask {
pub fn steps(&self) -> &[bool] {
&self.0
}
pub fn len(&self) -> usize {
self.0.len()
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(try_from = "TemporalRepr")]
pub struct TemporalLatent {
features: Tensor,
mask: Option<Mask>,
}
impl TemporalLatent {
pub fn new(features: Tensor) -> Result<Self> {
let &[_, channels] = features.shape() else {
return Err(Error::shape(vec![0, 0], features.shape().to_vec()));
};
if channels == 0 {
return Err(Error::validation("z_temporal needs channels ≥ 1"));
}
if !features.is_finite() {
return Err(Error::NonFinite {
context: "z_temporal",
});
}
Ok(Self {
features,
mask: None,
})
}
pub fn with_mask(mut self, steps: Vec<bool>) -> Result<Self> {
if steps.len() != self.steps() {
return Err(Error::shape(vec![self.steps()], vec![steps.len()]));
}
self.mask = Some(Mask(steps));
Ok(self)
}
pub fn steps(&self) -> usize {
self.features.shape()[0]
}
pub fn channels(&self) -> usize {
self.features.shape()[1]
}
pub fn features(&self) -> &Tensor {
&self.features
}
pub fn mask(&self) -> Option<&Mask> {
self.mask.as_ref()
}
}
#[derive(Deserialize)]
struct TemporalRepr {
features: Tensor,
mask: Option<Vec<bool>>,
}
impl TryFrom<TemporalRepr> for TemporalLatent {
type Error = Error;
fn try_from(repr: TemporalRepr) -> Result<Self> {
let latent = TemporalLatent::new(repr.features)?;
match repr.mask {
Some(mask) => latent.with_mask(mask),
None => Ok(latent),
}
}
}
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
pub struct Conditioning {
global: Option<GlobalLatent>,
temporal: Option<TemporalLatent>,
acoustic: Option<Tensor>,
event: Option<Tensor>,
noise: Option<Tensor>,
#[serde(default)]
metadata: Metadata,
}
impl Conditioning {
pub fn new() -> Self {
Self::default()
}
pub fn with_global(mut self, global: GlobalLatent) -> Self {
self.global = Some(global);
self
}
pub fn with_temporal(mut self, temporal: TemporalLatent) -> Self {
self.temporal = Some(temporal);
self
}
pub fn with_acoustic(mut self, acoustic: Tensor) -> Self {
self.acoustic = Some(acoustic);
self
}
pub fn with_event(mut self, event: Tensor) -> Self {
self.event = Some(event);
self
}
pub fn with_noise(mut self, noise: Tensor) -> Self {
self.noise = Some(noise);
self
}
pub fn with_metadata(mut self, metadata: Metadata) -> Self {
self.metadata = metadata;
self
}
pub fn global(&self) -> Option<&GlobalLatent> {
self.global.as_ref()
}
pub fn temporal(&self) -> Option<&TemporalLatent> {
self.temporal.as_ref()
}
pub fn acoustic(&self) -> Option<&Tensor> {
self.acoustic.as_ref()
}
pub fn event(&self) -> Option<&Tensor> {
self.event.as_ref()
}
pub fn noise(&self) -> Option<&Tensor> {
self.noise.as_ref()
}
pub fn metadata(&self) -> &Metadata {
&self.metadata
}
pub fn is_empty(&self) -> bool {
let Self {
global,
temporal,
acoustic,
event,
noise,
metadata: _,
} = self;
global.is_none()
&& temporal.is_none()
&& acoustic.is_none()
&& event.is_none()
&& noise.is_none()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn rank2(rows: usize, cols: usize) -> Tensor {
Tensor::zeros([rows, cols]).unwrap()
}
#[test]
fn slots_prove_their_invariants() {
assert!(GlobalLatent::new(Tensor::vector([1.0])).is_err());
assert!(GlobalLatent::new(rank2(2, 0)).is_err());
assert!(GlobalLatent::new(Tensor::new([1, 1], vec![f32::NAN]).unwrap()).is_err());
let g = GlobalLatent::new(rank2(3, 4)).unwrap();
assert_eq!((g.items(), g.dim()), (3, 4));
}
#[test]
fn mask_length_is_tied_to_steps() {
let t = TemporalLatent::new(rank2(3, 2)).unwrap();
assert!(t.clone().with_mask(vec![true, false]).is_err());
let masked = t.with_mask(vec![true, false, true]).unwrap();
assert_eq!(masked.mask().unwrap().len(), masked.steps());
}
#[test]
fn deserialization_re_proves_the_mask_tie() {
let good = r#"{"features":{"bytes":[0,0,0,0,0,0,0,0],"shape":[2,1],"dtype":"F32"},"mask":[true,false]}"#;
let t = TemporalLatent::new(rank2(2, 1))
.unwrap()
.with_mask(vec![true, false])
.unwrap();
let json = serde_json::to_string(&t).unwrap();
assert_eq!(serde_json::from_str::<TemporalLatent>(&json).unwrap(), t);
let bad = json.replace("[true,false]", "[true]");
assert!(serde_json::from_str::<TemporalLatent>(&bad).is_err());
let _ = good; }
#[test]
fn emptiness_is_total_over_slots() {
assert!(Conditioning::default().is_empty());
let with_temporal =
Conditioning::new().with_temporal(TemporalLatent::new(rank2(1, 1)).unwrap());
assert!(!with_temporal.is_empty());
let with_reserved = Conditioning::new().with_event(Tensor::vector([1.0]));
assert!(!with_reserved.is_empty());
let mut metadata = Metadata::default();
metadata.insert("k".into(), serde_json::json!("v"));
assert!(Conditioning::new().with_metadata(metadata).is_empty());
}
}