gollum-ir 0.4.0

Intermediate Representation for the Gollum language
Documentation
//! Reasoning metadata for IR clauses and queries.

use crate::timestamp::Interval;
use gollum_ast::modal::ModalAnnotation;
use serde::{Deserialize, Serialize};

/// Reasoning metadata attached to an IR clause or query.
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
pub struct IrMetadata {
    /// ProbLog-style fact probability
    pub probability: Option<f64>,
    /// Temporal validity interval
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub temporal_interval: Option<Interval>,
    /// Structured modal annotation (e.g. necessity/possibility with optional agent)
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub modality: Option<ModalAnnotation>,
    /// Ordered type constraints for head arguments
    pub types: Option<Vec<String>>,
    /// Neural model reference for predicate evaluation
    pub neural_ref: Option<String>,
    /// Gradient propagation ID for differentiable logic
    pub differentiable_ref: Option<String>,
    /// Combined neural + differentiable reference (model_id, grad_id)
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub diff_neural_ref: Option<(String, String)>,
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::timestamp::Timestamp;

    #[test]
    fn test_default_all_none() {
        let m = IrMetadata::default();
        assert!(m.probability.is_none());
        assert!(m.temporal_interval.is_none());
        assert!(m.modality.is_none());
        assert!(m.types.is_none());
        assert!(m.neural_ref.is_none());
        assert!(m.differentiable_ref.is_none());
    }

    #[test]
    fn test_all_fields_set() {
        let m = IrMetadata {
            probability: Some(0.7),
            temporal_interval: Interval::new(
                Timestamp::new(0, 0).unwrap(),
                Timestamp::new(100, 0).unwrap(),
            ),
            modality: Some(ModalAnnotation::necessity_for("belief")),

            types: Some(vec!["int".into(), "str".into()]),
            neural_ref: Some("model_1".into()),
            differentiable_ref: Some("grad_1".into()),
            diff_neural_ref: None,
        };
        assert_eq!(m.probability, Some(0.7));
        assert!(m.temporal_interval.is_some());
        assert_eq!(
            m.modality.as_ref(),
            Some(&ModalAnnotation::necessity_for("belief"))
        );
        assert_eq!(
            m.types.as_deref(),
            Some(["int".to_string(), "str".to_string()].as_slice())
        );
        assert_eq!(m.neural_ref.as_deref(), Some("model_1"));
        assert_eq!(m.differentiable_ref.as_deref(), Some("grad_1"));
        assert!(m.diff_neural_ref.is_none());
    }

    #[test]
    fn test_serde_roundtrip() {
        let m = IrMetadata {
            probability: Some(0.5),
            temporal_interval: Interval::new(
                Timestamp::new(-100, 0).unwrap(),
                Timestamp::new(200, 0).unwrap(),
            ),
            modality: None,
            types: None,
            neural_ref: None,
            differentiable_ref: None,
            diff_neural_ref: None,
        };
        let s = ron::to_string(&m).unwrap();
        let back: IrMetadata = ron::from_str(&s).unwrap();
        assert_eq!(m, back);
    }

    #[test]
    fn test_large_values() {
        let m = IrMetadata {
            probability: None,
            temporal_interval: Interval::new(
                Timestamp::new(1_000_000_000, 0).unwrap(),
                Timestamp::new(2_000_000_000, 0).unwrap(),
            ),
            modality: None,
            types: None,
            neural_ref: None,
            differentiable_ref: None,
            diff_neural_ref: None,
        };
        let s = ron::to_string(&m).unwrap();
        let back: IrMetadata = ron::from_str(&s).unwrap();
        assert_eq!(m.temporal_interval, back.temporal_interval);
    }
}