| Crates.io | karma |
| lib.rs | karma |
| version | 1.0.0 |
| created_at | 2017-02-06 20:46:01.995164+00 |
| updated_at | 2025-12-16 14:13:30.27933+00 |
| description | A sophisticated Hidden Markov Model (HMM) implementation using the Baum-Welch algorithm |
| homepage | |
| repository | https://github.com/psychonautwiki/karma |
| max_upload_size | |
| id | 8414 |
| size | 118,897 |
A sophisticated and efficient Hidden Markov Model (HMM) implementation in Rust using the Baum-Welch algorithm for training and the Forward algorithm for evaluation.
Hidden Markov Models (HMMs) are statistical models used to represent systems that transition between hidden states while producing observable outputs. They're widely used in:
An HMM consists of:
Add this to your Cargo.toml:
[dependencies]
karma = "0.2"
For serialization support:
[dependencies]
karma = { version = "0.2", features = ["serde"] }
use karma::HiddenMarkovModel;
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create an HMM with 3 hidden states and 4 possible observations
let mut hmm = HiddenMarkovModel::new(3, 4)?;
// Train on observation sequences
hmm.train(&[0, 1, 2, 3, 1, 0], None)?;
hmm.train(&[1, 2, 1, 0], Some(0.1))?; // Custom learning rate
// Evaluate probability of a new sequence
let probability = hmm.evaluate(&[0, 1, 2])?;
println!("P(sequence|model) = {}", probability);
Ok(())
}
use karma::HiddenMarkovModel;
// Create model with 20 states and 10 observations
let mut hmm = HiddenMarkovModel::new(20, 10)?;
// Optionally randomize initial probabilities to break symmetry
hmm.randomize_initial_probabilities();
// Train on multiple sequences
let training_data = vec![
vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
vec![0, 1, 4, 5, 7, 9, 0, 4, 7, 8, 2],
];
for sequence in training_data {
hmm.train(&sequence, Some(0.05))?; // 0.05 learning rate
}
// Evaluate test sequences
let prob = hmm.evaluate(&[5, 6, 7, 8])?;
println!("Probability: {}", prob);
use karma::HiddenMarkovModel;
let mut hmm = HiddenMarkovModel::builder()
.states(10)
.observations(5)
.randomize(true) // Randomize initial probabilities
.build()?;
// Train and evaluate as normal
hmm.train(&[0, 1, 2, 3, 4], None)?;
Train an HMM to distinguish between different types of sequences:
use karma::HiddenMarkovModel;
let mut hmm = HiddenMarkovModel::new(15, 4)?;
// Train on alternating patterns
for _ in 0..20 {
hmm.train(&[0, 1, 0, 1, 0, 1], Some(0.1))?;
}
// Train on sequential patterns
for _ in 0..20 {
hmm.train(&[0, 1, 2, 3], Some(0.1))?;
}
// Classify new sequences
let prob_alternating = hmm.evaluate(&[0, 1, 0, 1])?;
let prob_sequential = hmm.evaluate(&[0, 1, 2, 3])?;
// Higher probability indicates better match to training data
use karma::HiddenMarkovModel;
let mut hmm = HiddenMarkovModel::new(5, 3)?;
hmm.train(&[0, 1, 2, 0, 1, 2], Some(0.1))?;
// Serialize to JSON
let json = serde_json::to_string(&hmm)?;
// Save to file
std::fs::write("model.json", json)?;
// Load from file
let loaded_json = std::fs::read_to_string("model.json")?;
let loaded_hmm: HiddenMarkovModel = serde_json::from_str(&loaded_json)?;
HiddenMarkovModelnew(n_states, n_observations) - Create with uniform initial probabilitiesbuilder() - Get a builder for custom configurationtrain(&observations, learning_rate) - Train on an observation sequence
observations: Slice of observation indices [0, n_observations)learning_rate: Optional f64 in (0.0, 1.0], defaults to 0.05Result<(), HmmError>evaluate(&observations) - Compute P(observations | model)
Result<f64, HmmError>randomize_initial_probabilities() - Break symmetry in initial state probabilitiesn_states() - Get number of hidden statesn_observations() - Get number of possible observationsinitial_probabilities() - Get initial state probability vector πtransition_probabilities() - Get state transition matrix Aemission_probabilities() - Get emission probability matrix BAll operations return Result<T, HmmError> with detailed error messages:
use karma::{HiddenMarkovModel, HmmError};
let mut hmm = HiddenMarkovModel::new(3, 4)?;
match hmm.train(&[0, 1, 5], None) {
Ok(_) => println!("Training successful"),
Err(HmmError::InvalidObservation(obs, max)) => {
println!("Invalid observation {} (max: {})", obs, max);
}
Err(e) => println!("Error: {}", e),
}
The Baum-Welch algorithm is an Expectation-Maximization (EM) algorithm for estimating HMM parameters:
The learning rate controls how much the parameters are updated in each iteration (like a step size in gradient descent).
Efficiently computes P(observations | model) using dynamic programming:
Time complexity: O(N² × T) where N is the number of states and T is the sequence length.
The implementation is optimized for performance:
Run benchmarks with:
cargo bench
Example benchmark results (will vary by hardware):
model_creation/new/20 time: [125 ns ... 135 ns]
training/20states_10obs time: [45 μs ... 50 μs]
evaluation/100 time: [8 μs ... 9 μs]
Run the included examples:
# Basic usage example
cargo run --example basic_usage
# Pattern classification example
cargo run --example classification
# Advanced: DNA sequence analysis (bioinformatics)
cargo run --example dna_sequence_analysis
The dna_sequence_analysis example demonstrates a sophisticated real-world application: identifying CpG islands in DNA sequences using HMMs. This example shows:
Perfect for understanding how HMMs are used in computational biology!
The project includes comprehensive tests:
# Run unit and integration tests
cargo test
# Run tests with output
cargo test -- --nocapture
# Run property-based tests
cargo test prop_
# Run with all features
cargo test --all-features
This is a complete rewrite of the original 2017 implementation with significant improvements:
| Aspect | Old (2017) | New (2025) |
|---|---|---|
| Rust Edition | 2015 | 2021 |
| rand version | 0.3.15 (2016) | 0.8 (2024) |
| Error Handling | Panics | Result types with custom errors |
| API Design | Direct struct access | Builder pattern + getters |
| Type Safety | Mixed i64/usize | Consistent usize for indices |
| Memory | Box<Vec> | Direct Vec (more efficient) |
| Documentation | Minimal | Comprehensive with examples |
| Tests | 1 basic test | 25+ tests + property-based |
| Benchmarks | None | Full criterion benchmark suite |
| Dependencies | rand only | +thiserror, +serde (optional) |
Old API:
extern crate karma;
let mut hmm = karma::Karma::new(20, 10);
hmm.randomize();
hmm.train(&vec![0, 1, 2], None); // Takes &Vec, no error handling
let prob = hmm.evaluate(&vec![0, 1]); // Returns f64 directly
New API:
use karma::HiddenMarkovModel;
let mut hmm = HiddenMarkovModel::new(20, 10)?;
hmm.randomize_initial_probabilities();
hmm.train(&[0, 1, 2], None)?; // Takes &[usize], returns Result
let prob = hmm.evaluate(&[0, 1])?; // Returns Result<f64>
Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
# Clone the repository
git clone https://github.com/psychonautwiki/karma
cd karma
# Run tests
cargo test
# Run benchmarks
cargo bench
# Build documentation
cargo doc --open
MIT License - see LICENSE file for details.
Copyright (c) 2017 Kenan Sulayman Copyright (c) 2017 The PsychonautWiki Contributors Copyright (c) 2025 Modern Rust Implementation
Original implementation by Kenan Sulayman and PsychonautWiki contributors. This modern rewrite maintains the core algorithms while bringing the codebase up to modern Rust standards.
Made with Rust 🦀