Rust Usage Examples#

Using changepoint-doctor crates directly from Rust.

Running PELT from Rust#

use cpd_core::{
    Constraints, DTypeView, ExecutionContext, MemoryLayout,
    MissingPolicy, ReproMode, TimeIndex, TimeSeriesView,
};
use cpd_costs::{CostL2Mean, CostModel, CachedCost};
use cpd_core::CachePolicy;
use cpd_offline::{Pelt, PeltConfig};
use cpd_core::{OfflineDetector, Stopping, Penalty};

fn main() -> Result<(), cpd_core::CpdError> {
    // Build a test signal: 3 segments
    let mut signal = vec![0.0f64; 50];
    signal.extend(vec![5.0; 50]);
    signal.extend(vec![-2.0; 50]);

    // Create a zero-copy view
    let view = TimeSeriesView::new(
        DTypeView::F64(&signal),
        signal.len(),
        1,  // univariate
        MemoryLayout::CContiguous,
        None,
        TimeIndex::None,
        MissingPolicy::Error,
    )?;

    // Configure constraints and execution context
    let constraints = Constraints {
        min_segment_len: 2,
        jump: 1,
        ..Constraints::default()
    };
    let ctx = ExecutionContext::new(&constraints)
        .with_repro_mode(ReproMode::Balanced);

    // Build the detector with BIC penalty
    let config = PeltConfig {
        stopping: Stopping::Penalized(Penalty::Bic),
        ..PeltConfig::default()
    };
    let detector = Pelt::<CostL2Mean>::new(config, CostL2Mean);

    // Run detection
    let result = detector.detect(&view, &ctx)?;
    println!("Breakpoints: {:?}", result.breakpoints);
    println!("Algorithm: {}", result.diagnostics.algorithm);

    Ok(())
}

Running BOCPD from Rust#

use cpd_core::{Constraints, ExecutionContext, ReproMode};
use cpd_online::{Bocpd, BocpdConfig, GaussianNigPrior, ObservationModel};
use cpd_core::OnlineDetector;

fn main() -> Result<(), cpd_core::CpdError> {
    let constraints = Constraints::default();
    let ctx = ExecutionContext::new(&constraints)
        .with_repro_mode(ReproMode::Balanced);

    let config = BocpdConfig {
        observation_model: ObservationModel::GaussianNig(GaussianNigPrior::default()),
        hazard: 1.0 / 200.0,
        max_run_length: 512,
        ..BocpdConfig::default()
    };

    let mut bocpd = Bocpd::new(config);

    // Process observations
    let observations = [0.1, 0.2, -0.1, 0.3, 5.2, 5.1, 4.9, 5.3];
    for (t, &x) in observations.iter().enumerate() {
        let step = bocpd.update(&[x], None, &ctx)?;
        if step.alert {
            println!("Alert at t={}: p_change={:.3}", t, step.p_change);
        }
    }

    // Checkpoint
    let state = bocpd.save_state();
    println!("State saved: {:?}", state);

    Ok(())
}

Custom CostModel implementation#

use cpd_core::{CachePolicy, CpdError, MissingSupport, TimeSeriesView};
use cpd_costs::CostModel;

/// Example: constant-cost model (useful as a template).
struct CostConstant {
    value: f64,
}

impl CostModel for CostConstant {
    type Cache = ();  // no precomputation needed

    fn name(&self) -> &'static str { "constant" }

    fn validate(&self, _x: &TimeSeriesView<'_>) -> Result<(), CpdError> {
        Ok(())
    }

    fn precompute(
        &self,
        _x: &TimeSeriesView<'_>,
        _policy: &CachePolicy,
    ) -> Result<Self::Cache, CpdError> {
        Ok(())
    }

    fn worst_case_cache_bytes(&self, _x: &TimeSeriesView<'_>) -> usize {
        0
    }

    fn segment_cost(&self, _cache: &Self::Cache, _start: usize, _end: usize) -> f64 {
        self.value
    }
}

The real implementation would typically:

  1. Build prefix sums in precompute() for O(1) segment queries

  2. Override penalty_params_per_segment() to match the model’s parameter count

  3. Override missing_support() if the model handles NaN values

ExecutionContext configuration#

use cpd_core::{
    CancelToken, Constraints, ExecutionContext, ReproMode,
    control::BudgetMode,
};
use std::sync::Arc;

// Basic context
let constraints = Constraints::default();
let ctx = ExecutionContext::new(&constraints);

// Full configuration
let cancel = CancelToken::new();
let ctx = ExecutionContext::new(&constraints)
    .with_cancel(&cancel)
    .with_budget_mode(BudgetMode::SoftDegrade)
    .with_repro_mode(ReproMode::Strict);

// Cancel from another thread
let cancel_clone = cancel.clone();
std::thread::spawn(move || {
    std::thread::sleep(std::time::Duration::from_secs(5));
    cancel_clone.cancel();
});