///|
/// Compact sequence-learning primitives for event streams and demand signals.
pub struct SequenceWindow {
capacity : Int
values : Array[Double]
mut total : Double
mut sum_squares : Double
}
///|
pub fn SequenceWindow::new(capacity : Int) -> SequenceWindow {
{
capacity: if capacity < 1 {
1
} else {
capacity
},
values: [],
total: 0.0,
sum_squares: 0.0,
}
}
///|
pub fn SequenceWindow::push(self : SequenceWindow, value : Double) -> Unit {
self.values.push(value)
self.total += value
self.sum_squares += value * value
if self.values.length() > self.capacity {
let removed = self.values.remove(0)
self.total -= removed
self.sum_squares -= removed * removed
}
}
///|
pub fn SequenceWindow::size(self : SequenceWindow) -> Int {
self.values.length()
}
///|
pub fn SequenceWindow::capacity(self : SequenceWindow) -> Int {
self.capacity
}
///|
pub fn SequenceWindow::mean(self : SequenceWindow) -> Double {
if self.values.is_empty() {
0.0
} else {
self.total / self.values.length().to_double()
}
}
///|
pub fn SequenceWindow::variance(self : SequenceWindow) -> Double {
if self.values.is_empty() {
0.0
} else {
let mean = self.mean()
let value = self.sum_squares / self.values.length().to_double() -
mean * mean
if value < 0.0 {
0.0
} else {
value
}
}
}
///|
pub fn SequenceWindow::last(self : SequenceWindow) -> Double {
self.values.last().unwrap_or(0.0)
}
///|
pub fn SequenceWindow::values(self : SequenceWindow) -> Array[Double] {
copy_vector(self.values)
}
///|
pub fn SequenceWindow::clear(self : SequenceWindow) -> Unit {
self.values.clear()
self.total = 0.0
self.sum_squares = 0.0
}
///|
pub struct OnlineAR {
lags : Int
coefficients : Array[Double]
history : SequenceWindow
learning_rate : Double
mut observations : Int
mut squared_error : Double
}
///|
pub fn OnlineAR::new(lags : Int, learning_rate? : Double = 0.02) -> OnlineAR {
let size = if lags < 1 { 1 } else { lags }
{
lags: size,
coefficients: Array::make(size, 0.0),
history: SequenceWindow::new(size),
learning_rate: clamp(learning_rate, 1.0e-6, 1.0),
observations: 0,
squared_error: 0.0,
}
}
///|
fn OnlineAR::sequence_prediction(self : OnlineAR) -> Double {
let history = self.history.values()
let mut prediction = 0.0
let count = history.length()
for i in 0..= 0 {
prediction += self.coefficients[i] * history[offset]
}
}
prediction
}
///|
pub fn OnlineAR::predict(self : OnlineAR) -> Double {
self.sequence_prediction()
}
///|
pub fn OnlineAR::update(self : OnlineAR, value : Double) -> Double {
let prediction = self.predict()
let error = value - prediction
let history = self.history.values()
let count = history.length()
for i in 0..= 0 {
self.coefficients[i] += self.learning_rate * error * history[offset]
}
}
self.history.push(value)
self.observations += 1
self.squared_error += error * error
error
}
///|
pub fn OnlineAR::coefficients(self : OnlineAR) -> Array[Double] {
copy_vector(self.coefficients)
}
///|
pub fn OnlineAR::observations(self : OnlineAR) -> Int {
self.observations
}
///|
pub fn OnlineAR::rmse(self : OnlineAR) -> Double {
if self.observations == 0 {
0.0
} else {
(self.squared_error / self.observations.to_double()).sqrt()
}
}
///|
pub fn OnlineAR::reset(self : OnlineAR) -> Unit {
self.coefficients.fill(0.0)
self.history.clear()
self.observations = 0
self.squared_error = 0.0
}
///|
pub struct MarkovTransitionModel {
states : Int
transitions : Array[Array[Double]]
totals : Array[Double]
mut previous : Int?
}
///|
pub fn MarkovTransitionModel::new(
states : Int,
smoothing? : Double = 1.0,
) -> MarkovTransitionModel {
let size = if states < 0 { 0 } else { states }
let safe_smoothing = if smoothing < 0.0 { 0.0 } else { smoothing }
{
states: size,
transitions: Array::makei(size, _ => Array::make(size, safe_smoothing)),
totals: Array::make(size, safe_smoothing * size.to_double()),
previous: None,
}
}
///|
pub fn MarkovTransitionModel::observe(
self : MarkovTransitionModel,
state : Int,
) -> Bool {
if state < 0 || state >= self.states {
false
} else {
match self.previous {
Some(previous) => {
self.transitions[previous][state] += 1.0
self.totals[previous] += 1.0
}
None => ()
}
self.previous = Some(state)
true
}
}
///|
pub fn MarkovTransitionModel::probability(
self : MarkovTransitionModel,
from : Int,
to : Int,
) -> Double {
if from < 0 ||
from >= self.states ||
to < 0 ||
to >= self.states ||
self.totals[from] <= 0.0 {
0.0
} else {
self.transitions[from][to] / self.totals[from]
}
}
///|
pub fn MarkovTransitionModel::distribution(
self : MarkovTransitionModel,
from : Int,
) -> Array[Double] {
if from < 0 || from >= self.states {
[]
} else {
Array::makei(self.states, to => self.probability(from, to))
}
}
///|
pub fn MarkovTransitionModel::next_state(
self : MarkovTransitionModel,
from : Int,
) -> Int? {
let distribution = self.distribution(from)
if distribution.is_empty() {
None
} else {
let mut best = 0
for i in 1.. distribution[best] {
best = i
}
}
Some(best)
}
}
///|
pub fn MarkovTransitionModel::states(self : MarkovTransitionModel) -> Int {
self.states
}
///|
pub fn MarkovTransitionModel::reset(self : MarkovTransitionModel) -> Unit {
for row in self.transitions {
row.fill(0.0)
}
self.totals.fill(0.0)
self.previous = None
}
///|
pub struct SequenceFeatureBuilder {
lags : Int
include_delta : Bool
include_mean : Bool
include_variance : Bool
window : SequenceWindow
}
///|
pub fn SequenceFeatureBuilder::new(
lags : Int,
include_delta? : Bool = true,
include_mean? : Bool = true,
include_variance? : Bool = true,
) -> SequenceFeatureBuilder {
let size = if lags < 1 { 1 } else { lags }
{
lags: size,
include_delta,
include_mean,
include_variance,
window: SequenceWindow::new(size + 1),
}
}
///|
pub fn SequenceFeatureBuilder::transform(
self : SequenceFeatureBuilder,
value : Double,
) -> Array[Double] {
let previous = self.window.values()
let output : Array[Double] = []
for i in 0..= 0 { previous[offset] } else { 0.0 })
}
if self.include_delta {
output.push(
if previous.is_empty() {
0.0
} else {
value - previous.last().unwrap_or(value)
},
)
}
self.window.push(value)
if self.include_mean {
output.push(self.window.mean())
}
if self.include_variance {
output.push(self.window.variance())
}
output
}
///|
pub fn SequenceFeatureBuilder::feature_count(
self : SequenceFeatureBuilder,
) -> Int {
self.lags +
(if self.include_delta { 1 } else { 0 }) +
(if self.include_mean { 1 } else { 0 }) +
(if self.include_variance { 1 } else { 0 })
}
///|
pub fn SequenceFeatureBuilder::reset(self : SequenceFeatureBuilder) -> Unit {
self.window.clear()
}
///|
pub struct ChangePointDetector {
short : SequenceWindow
long : SequenceWindow
threshold : Double
mut changes : Int
mut last_score : Double
}
///|
pub fn ChangePointDetector::new(
short_window? : Int = 8,
long_window? : Int = 32,
threshold? : Double = 2.5,
) -> ChangePointDetector {
{
short: SequenceWindow::new(short_window),
long: SequenceWindow::new(long_window),
threshold: if threshold < 0.0 {
0.0
} else {
threshold
},
changes: 0,
last_score: 0.0,
}
}
///|
pub fn ChangePointDetector::observe(
self : ChangePointDetector,
value : Double,
) -> Bool {
self.short.push(value)
self.long.push(value)
let spread = self.long.variance().sqrt() + 1.0e-9
self.last_score = (self.short.mean() - self.long.mean()).abs() / spread
let changed = self.long.size() >= self.long.capacity() &&
self.last_score >= self.threshold
if changed {
self.changes += 1
}
changed
}
///|
pub fn ChangePointDetector::score(self : ChangePointDetector) -> Double {
self.last_score
}
///|
pub fn ChangePointDetector::changes(self : ChangePointDetector) -> Int {
self.changes
}
///|
pub fn ChangePointDetector::reset(self : ChangePointDetector) -> Unit {
self.short.clear()
self.long.clear()
self.changes = 0
self.last_score = 0.0
}