sim-lib-serial-core 0.1.0

Finite symbolic alphabets, aggregate rules, and certified total series transforms for SIM.
Documentation
use crate::{
    AggregateRule, AggregateRuleError, AlphabetError, AlphabetId, AlphabetRegistry, FiniteAlphabet,
    ProjectedClassSpec, ProjectionId, Series, SeriesError,
};
use sim_lib_rank::Nat;

fn gestures() -> FiniteAlphabet<&'static str> {
    FiniteAlphabet::try_new(
        AlphabetId::try_new("gesture/five-v1").expect("id"),
        vec!["rise", "fall", "hold", "turn", "rest"],
    )
    .expect("alphabet")
}

#[test]
fn five_symbol_non_pitch_alphabet_is_exhaustive_and_ranked_by_discrete_owner() {
    let series = Series::try_new(
        gestures(),
        AggregateRule::exhaustive_exactly_once(),
        vec!["turn", "rise", "rest", "fall", "hold"],
    )
    .expect("series");

    assert_eq!(series.order(), ["turn", "rise", "rest", "fall", "hold"]);
    assert!(series.ledger().is_exhaustive_exactly_once());
    assert_eq!(series.ledger().observed_count(&"rest"), Some(1));
    assert_eq!(series.permutation_rank().expect("rank"), Nat::from(76u64));
}

#[test]
fn exhaustive_no_repeat_and_free_order_have_distinct_contracts() {
    let alphabet = gestures();
    assert!(matches!(
        Series::try_new(
            alphabet.clone(),
            AggregateRule::exhaustive_exactly_once(),
            vec!["rise", "fall"]
        ),
        Err(SeriesError::WrongLength {
            expected: 5,
            found: 2
        })
    ));
    let no_repeat = Series::try_new(
        alphabet.clone(),
        AggregateRule::no_repeat(),
        vec!["rise", "turn"],
    )
    .expect("short no-repeat series");
    assert_eq!(no_repeat.ledger().omitted_symbols().len(), 3);
    assert!(matches!(
        Series::try_new(
            alphabet.clone(),
            AggregateRule::no_repeat(),
            vec!["rise", "rise"]
        ),
        Err(SeriesError::RepeatedSymbol {
            position: 1,
            first: 0
        })
    ));
    let free = Series::try_new(
        alphabet,
        AggregateRule::free_order(),
        vec!["rise", "rise", "rest"],
    )
    .expect("free order");
    assert_eq!(free.ledger().repeated_symbols(), ["rise"]);
    assert!(matches!(
        free.permutation_rank(),
        Err(SeriesError::NotPermutation(_))
    ));
}

#[test]
fn declared_multiplicity_and_omissions_are_symbol_based() {
    let alphabet = gestures();
    let multiplicity = AggregateRule::declared_multiplicity(
        &alphabet,
        [
            ("rise", 2),
            ("fall", 1),
            ("hold", 1),
            ("turn", 1),
            ("rest", 1),
        ],
    )
    .expect("multiplicity");
    let repeated = Series::try_new(
        alphabet.clone(),
        multiplicity,
        vec!["rise", "fall", "rise", "hold", "turn", "rest"],
    )
    .expect("declared repeat");
    assert_eq!(repeated.ledger().expected_count(&"rise"), Some(2));
    assert_eq!(repeated.ledger().observed_count(&"rise"), Some(2));

    let omissions =
        AggregateRule::declared_omissions(&alphabet, ["fall", "rest"]).expect("omissions");
    let shortened = Series::try_new(alphabet, omissions, vec!["rise", "hold", "turn"])
        .expect("omitted aggregate");
    assert_eq!(shortened.ledger().expected_count(&"fall"), Some(0));
    assert_eq!(shortened.ledger().omitted_symbols(), ["fall", "rest"]);
}

#[test]
fn projected_aggregate_counts_declared_classes() {
    let alphabet = gestures();
    let rule = AggregateRule::projected_aggregate(
        &alphabet,
        [
            ProjectedClassSpec::new(
                ProjectionId::try_new("motion").expect("projection"),
                vec!["rise", "fall", "turn"],
                2,
            ),
            ProjectedClassSpec::new(
                ProjectionId::try_new("stasis").expect("projection"),
                vec!["hold", "rest"],
                1,
            ),
        ],
    )
    .expect("projected rule");
    let series = Series::try_new(alphabet.clone(), rule.clone(), vec!["rise", "turn", "rest"])
        .expect("projected series");
    assert_eq!(series.ledger().projected_classes()[0].observed, 2);
    assert_eq!(series.ledger().projected_classes()[1].observed, 1);
    assert!(matches!(
        Series::try_new(alphabet, rule, vec!["rise", "hold", "rest"]),
        Err(SeriesError::ProjectionMismatch { .. })
    ));
}

#[test]
fn malformed_alphabets_members_and_rules_fail_closed() {
    assert!(matches!(
        AlphabetId::try_new("gesture id"),
        Err(AlphabetError::InvalidId { .. })
    ));
    assert!(matches!(
        FiniteAlphabet::<&str>::try_new(AlphabetId::try_new("empty").unwrap(), vec![]),
        Err(AlphabetError::Empty { .. })
    ));
    assert!(matches!(
        FiniteAlphabet::try_new(
            AlphabetId::try_new("duplicate").unwrap(),
            vec!["a", "b", "a"]
        ),
        Err(AlphabetError::DuplicateSymbol {
            first: 0,
            duplicate: 2,
            ..
        })
    ));
    assert!(matches!(
        Series::try_new(
            gestures(),
            AggregateRule::free_order(),
            vec!["rise", "foreign"]
        ),
        Err(SeriesError::ForeignSymbol { position: 1, .. })
    ));
    assert!(matches!(
        AggregateRule::declared_multiplicity(&gestures(), [("rise", 1)]),
        Err(AggregateRuleError::MissingDeclaration { .. })
    ));
    assert!(matches!(
        AggregateRule::declared_omissions(&gestures(), std::iter::empty()),
        Err(AggregateRuleError::NoOmissions)
    ));
    assert!(matches!(
        AggregateRule::declared_omissions(&gestures(), ["rise", "fall", "hold", "turn", "rest"]),
        Err(AggregateRuleError::OmitsEverything(_))
    ));
}

#[test]
fn registry_rejects_duplicate_stable_alphabet_ids() {
    let mut registry = AlphabetRegistry::new();
    registry.insert(gestures()).expect("first alphabet");
    assert!(matches!(
        registry.insert(gestures()),
        Err(AlphabetError::DuplicateId(_))
    ));
    assert_eq!(registry.len(), 1);
}

#[test]
fn rules_reject_foreign_duplicate_and_impossible_projection_data() {
    let alphabet = gestures();
    assert!(matches!(
        AggregateRule::declared_multiplicity(
            &alphabet,
            [
                ("rise", 1),
                ("rise", 1),
                ("hold", 1),
                ("turn", 1),
                ("rest", 1),
            ]
        ),
        Err(AggregateRuleError::DuplicateDeclaration { position: 0 })
    ));
    assert!(matches!(
        AggregateRule::projected_aggregate(
            &alphabet,
            [ProjectedClassSpec::new(
                ProjectionId::try_new("all").unwrap(),
                vec!["rise", "fall", "hold", "turn", "unknown"],
                1,
            )]
        ),
        Err(AggregateRuleError::ForeignSymbol { .. })
    ));
    assert!(matches!(
        AggregateRule::projected_aggregate(
            &alphabet,
            [ProjectedClassSpec::new(
                ProjectionId::try_new("none").unwrap(),
                vec!["rise", "fall", "hold", "turn", "rest"],
                0,
            )]
        ),
        Err(AggregateRuleError::OmitsEverything(_))
    ));
}