Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 90 additions & 0 deletions specs/port/trios-railway/crates/trios-igla-race/src/ema.t27
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// trios-igla-race EMA tracker — the exponential moving average.
// Port of gHashTag/trios-railway:crates/trios-igla-race/src/ema.rs
// (at 385c2143ef46, 18 lines) — Roadmap Stage 6, goal #4548, target t27c
// gen-rust.
//
// The original tracks an exponential moving average: `new` builds the
// tracker with its alpha and no value; `update` seeds the average with
// the first sample it sees, and blends every following sample as
// v * (1 - alpha) + x * alpha. The Rust `Option<f64>` value becomes the
// presence flag on the port's state, and the &mut self threading becomes
// the state chain: update takes the state and answers the next state,
// keeping both listed functions pure and tested. No transport exists in
// this port (issue #9683, "Port the decision, not the plumbing").

module port::trios_igla_race_ema;

// Original lines 1-4: `pub struct EmaTracker { alpha, value: Option<f64> }`.
pub struct EmaState {
alpha: f64,
has_value: bool,
value: f64,
}

// Original lines 6-9: `pub fn new(alpha) -> Self` — the tracker starts
// unseeded: no sample seen yet, the first update will seed it.
pub fn EmaTracker_new(alpha: f64) -> EmaState {
return EmaState { alpha: alpha, has_value: false, value: 0.0 };
}

// Original line 13: `v * (1.0 - alpha) + x * alpha` — the EMA blend.
pub fn ema_blend(v: f64, x: f64, alpha: f64) -> f64 {
return v * (1.0 - alpha) + x * alpha;
}

// Original lines 11-17: `pub fn update(&mut self, x) -> f64` — the two
// decisions: an unseeded tracker takes the sample as its value (line 14,
// the seed), and a seeded one blends the sample into the average (line
// 13). The state chain carries both.
pub fn EmaTracker_update(state: EmaState, x: f64) -> EmaState {
if (!state.has_value) {
return EmaState { alpha: state.alpha, has_value: true, value: x };
}
return EmaState {
alpha: state.alpha,
has_value: true,
value: ema_blend(state.value, x, state.alpha),
};
}

// ---------------------------------------------------------------------------
// Tests: the seed decision, the blend formula, and the tracker's
// behaviour at the alpha extremes.
// ---------------------------------------------------------------------------

test new_starts_unseeded {
const s = EmaTracker_new(0.1);
assert(!s.has_value);
assert(s.alpha == 0.1);
}

test first_update_seeds_with_the_sample {
const s = EmaTracker_update(EmaTracker_new(0.1), 5.0);
assert(s.has_value);
assert(s.value == 5.0);
}

test blend_formula {
assert(ema_blend(10.0, 20.0, 0.25) == 12.5);
assert(ema_blend(10.0, 20.0, 0.0) == 10.0);
assert(ema_blend(10.0, 20.0, 1.0) == 20.0);
}

test update_blends_after_the_seed {
const seeded = EmaTracker_update(EmaTracker_new(0.5), 2.0);
const blended = EmaTracker_update(seeded, 4.0);
assert(blended.has_value);
assert(blended.value == 3.0);
}

test zero_alpha_freezes_the_average {
const seeded = EmaTracker_update(EmaTracker_new(0.0), 7.0);
const frozen = EmaTracker_update(seeded, 100.0);
assert(frozen.value == 7.0);
}

test one_alpha_tracks_the_sample {
const seeded = EmaTracker_update(EmaTracker_new(1.0), 7.0);
const tracked = EmaTracker_update(seeded, 100.0);
assert(tracked.value == 100.0);
}
Loading