Skip to main content

SpacingFactor

Struct SpacingFactor 

Source
pub struct SpacingFactor {
    pub optimal_interval_secs: u64,
}
Expand description

SpacingFactor — forgetting curve on time since item was last seen.

Items never seen score 1.0 (full spacing benefit). Items seen very recently score near 0.0. Items seen a long time ago approach 1.0 again.

optimal_interval_secs — time after which item is considered due again. Default: 86400 (24 hours). Callers tune per domain: learning → hours/days (spaced repetition) ecommerce → days/weeks (avoid re-suggesting just-bought items) travel → months/years (re-suggest destinations)

Fields§

§optimal_interval_secs: u64

Implementations§

Source§

impl SpacingFactor

Source

pub fn new(optimal_interval_secs: u64) -> Self

Examples found in repository?
examples/learning_simulation.rs (line 23)
12fn main() {
13    println!("=== aria-core: Learning Domain Simulation ===\n");
14
15    // --- Build engine ---
16    let mut engine = Engine::new(EngineConfig {
17        exploration_rate: 0.0, // deterministic for demo
18        alpha: 0.1,            // faster skill movement so visible in 15 steps
19    });
20
21    // Register factors — caller's choice
22    engine.add_factor(Box::new(ChallengeFactor::new(0.2)));
23    engine.add_factor(Box::new(SpacingFactor::new(10))); // 10s interval for demo
24    engine.add_factor(Box::new(CoverageFactor));
25    engine.seed_rng(42);
26
27    // Register items — caller's curriculum
28    engine.add_items(vec![
29        // Math track
30        Item::new("counting",        0.05, "math"),
31        Item::new("addition",        0.15, "math"),
32        Item::new("multiplication",  0.30, "math"),
33        Item::new("fractions",       0.45, "math"),
34        Item::new("algebra_basics",  0.60, "math"),
35        Item::new("quadratics",      0.75, "math")
36            .with_prereqs(vec!["algebra_basics".into()]),
37        Item::new("calculus_intro",  0.90, "math")
38            .with_prereqs(vec!["quadratics".into()]),
39
40        // Science track (no prereqs — runs in parallel)
41        Item::new("sci_observation", 0.10, "science"),
42        Item::new("sci_hypothesis",  0.25, "science"),
43        Item::new("sci_experiment",  0.50, "science"),
44        Item::new("sci_analysis",    0.70, "science"),
45        Item::new("sci_research",    0.90, "science"),
46    ]).unwrap();
47
48    println!("Items registered: {}", engine.item_count());
49    println!("Factors registered: {}\n", engine.factor_count());
50
51    println!("{:<5} {:<22} {:<10} {:<8} {:<8} {:<10}",
52        "Step", "Item", "Category", "Success", "Effort", "Skill");
53    println!("{}", "-".repeat(70));
54
55    // --- Simulate 15 interactions ---
56    // Alternates easy successes and a few failures to show adaptation
57    let interactions: Vec<(bool, f32)> = vec![
58        (true,  0.8),  // 1  hard success
59        (true,  0.3),  // 2  easy success → optimism up
60        (true,  0.2),  // 3  easy
61        (false, 0.9),  // 4  failure → optimism eases
62        (true,  0.5),  // 5
63        (true,  0.4),  // 6
64        (true,  0.2),  // 7  easy → optimism climbs
65        (true,  0.3),  // 8
66        (false, 0.7),  // 9  failure
67        (true,  0.5),  // 10
68        (true,  0.4),  // 11
69        (true,  0.3),  // 12
70        (true,  0.2),  // 13
71        (true,  0.5),  // 14
72        (true,  0.3),  // 15
73    ];
74
75    for (step, (success, effort)) in interactions.iter().enumerate() {
76        let item = engine.suggest("alice").unwrap();
77        let item_id = item.id().to_string();
78        let category = item.category().to_string();
79
80        engine.feedback("alice", &item_id, Signal::new(*success, *effort)).unwrap();
81
82        let state = engine.get_state("alice").unwrap();
83        println!("{:<5} {:<22} {:<10} {:<8} {:<8.2} {:<.4}",
84            step + 1,
85            item_id,
86            category,
87            if *success { "✓" } else { "✗" },
88            effort,
89            state.skill,
90        );
91    }
92
93    // --- Show final state ---
94    let state = engine.get_state("alice").unwrap();
95    println!("\n=== Final State ===");
96    println!("Skill:             {:.4}", state.skill);
97    println!("Optimism bias:     {:.4}", state.optimism_bias);
98    println!("Target:            {:.4}", state.target());
99    println!("Interactions:      {}", state.interaction_count);
100    println!("Resolved items:    {:?}", state.resolved_set);
101
102    println!("\n=== Category Coverage ===");
103    let mut cats: Vec<_> = state.category_count.iter().collect();
104    cats.sort_by_key(|(k, _)| k.as_str());
105    for (cat, count) in cats {
106        println!("  {:<12} → {} interactions", cat, count);
107    }
108
109    // --- Serialise / deserialise round-trip ---
110    println!("\n=== Serialisation Round-Trip ===");
111    let encoded = Serialiser::encode(state);
112    println!("Encoded keys: {}", encoded.len());
113    let decoded = Serialiser::decode(&encoded).unwrap();
114    assert!((decoded.skill - state.skill).abs() < 1e-5, "skill mismatch after round-trip");
115    assert_eq!(decoded.interaction_count, state.interaction_count);
116    println!("Round-trip: ✓ skill={:.4} interactions={}", decoded.skill, decoded.interaction_count);
117
118    // --- Prereq demonstration ---
119    println!("\n=== Prerequisite Gating ===");
120    println!("calculus_intro requires: quadratics → algebra_basics");
121    let can_see_calculus = state.resolved_set.contains("quadratics");
122    println!("User resolved quadratics: {}", can_see_calculus);
123    println!("(calculus_intro will unlock once quadratics is resolved)");
124
125    println!("\n=== Custom Domain Example (ecommerce) ===");
126    demo_ecommerce();
127}

Trait Implementations§

Source§

impl Default for SpacingFactor

Source§

fn default() -> Self

Returns the “default value” for a type. Read more
Source§

impl Factor for SpacingFactor

Source§

fn name(&self) -> &str

Source§

fn score(&self, item: &dyn Scoreable, state: &ProfileState, now: u64) -> f32

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.