Skip to content

Latest commit

 

History

History
305 lines (219 loc) · 11.1 KB

File metadata and controls

305 lines (219 loc) · 11.1 KB

Q-SETUN Formal API Reference & Specification

Version: 2.1.0
Complexity Guarantees: Strict $\mathcal{O}(1)$ time complexity, $0$ dynamic heap allocation (malloc = 0), $100%$ integer fixed-point arithmetic in the primary inference pathway.


Types

QTrit — Balanced Ternary State

enum class QTrit : int8_t {
    Negative = -1,  // Repolarization / S-dip / Deflection below baseline
    Zero     =  0,  // Ground equilibrium / Isoelectric baseline
    Positive =  1   // Depolarization / Action potential / Excitation above baseline
};

Cast to integer: static_cast<int8_t>(state.current_trit) → returns -1, 0, or +1.


QState — Inference Output

Returned by every call to feed().

struct QState {
    bool     is_anomaly;           // True if the last completed cycle was anomalous
    bool     beat_classified;      // True if a full excursion cycle just completed
    bool     noise_annihilated;    // True if cellular apoptosis dissolved a noise spike
    uint8_t  anomaly_score_pct;    // 0 (healthy) to 98 (anomaly). Integer, zero-FLOP.
    float    anomaly_score;        // 0.0 to 0.98. Backward-compatible float copy.
    int16_t  charge;               // Net topological charge of the last completed cycle
    uint16_t cycle_width;          // Width of last cycle in samples
    uint16_t wave_energy;          // v2.1: accumulated |diff| inside the cycle (>> 8, saturated)
    uint8_t  wave_trit_density_pct;// v2.1: share of non-zero trits in the cycle (0..100)
    QTrit    current_trit;         // Discretized trit for the current sample
    uint32_t cycles_count;         // Total completed cycles since reset
};

Key fields explained:

Field When it changes What it means
is_anomaly On cycle completion The completed cycle had abnormal width (>14 samples) or unbalanced charge (≥charge_limit)
beat_classified On cycle completion A full positive→zero excursion just ended. Check charge and cycle_width for its shape.
noise_annihilated Every sample High-frequency jitter was dissolved: either opposing trits (+1 then -1) cancelled, or a 1-sample glitch was erased.
anomaly_score_pct On cycle completion 2 = normal, 98 = anomaly. Stays at last value between completions.
wave_energy On cycle completion v2.1: total `
wave_trit_density_pct On cycle completion v2.1: 100 × non_zero_trits / width. Together with wave_energy separates sharp-strong from wide-weak cycles.
current_trit Every sample The filtered ternary state after apoptosis: -1, 0, or +1.

Class: QSetun

Constructor

QSetun qsetun;

Creates an engine instance. Calls reset() internally. Memory footprint: 84 bytes of flat static state (measured with avr-g++ 7.3, -Os). No heap allocation.


begin() — Initialize Thresholds

Three overloads:

Float (Backward-Compatible)

void begin(float pos_threshold = 0.35f, float neg_threshold = -0.25f, int16_t charge_limit = 6);

For normalized sensor values in the range [-1.0, 1.0]. Internally scales to Q8 fixed-point.

// Default clinical ECG thresholds
qsetun.begin();

// Custom thresholds for a sensitive piezo
qsetun.begin(0.15f, -0.10f, 4);

Integer Q8 (Recommended for Embedded)

void begin(int32_t pos_threshold_q8, int32_t neg_threshold_q8, int16_t charge_limit = 6);

Pass pre-scaled Q8 values (multiply your raw ADC threshold by 256).

// Threshold = 30 ADC counts → 30 << 8 = 7680
qsetun.begin((int32_t)(30 << 8), (int32_t)(-20 << 8), 6);

Int Convenience (AVR-Safe)

void begin(int pos_threshold_q8, int neg_threshold_q8, int charge_limit = 6);

Prevents ambiguous overload resolution on 16-bit AVR platforms.


calibrate() — Zero-FLOP Auto-Calibration

void calibrate(int16_t (*read_fn)(), uint16_t samples = 128, uint8_t n_sigma = 3);

The recommended way to initialize Q-SETUN. Reads samples ambient readings via read_fn, computes integer mean + integer standard deviation (digit-by-digit square root, 0 FLOPs), and sets:

  • Baseline EMA = mean
  • Positive threshold = +n_sigma × σ
  • Negative threshold = −n_sigma × σ

Parameters:

Param Default Description
read_fn required Function pointer: int16_t myReadFunc(). Must return one raw sensor sample.
samples 128 Number of calibration samples. 128 = ~130 ms on Arduino, ~0.1 ms per sample. More = more precise.
n_sigma 3 Noise envelope multiplier. 3 = 99.7% confidence (3-sigma rule). Use 2 for more sensitive, 4 for less sensitive.

Example:

int16_t readSensor() {
    return (int16_t)analogRead(A0);
}

void setup() {
    qsetun.calibrate(readSensor, 256, 3);
    // Engine is now calibrated to ambient noise floor.
    // Any signal exceeding 3σ of the noise will trigger detection.
}

Important:

  • calibrate() preserves calibrated baseline and variance — it only clears wave-tracking state.
  • Keep the sensor still and quiet during calibration (no movement, no signal source).
  • If read_fn is nullptr or samples is 0, calibrate silently returns without changing state.

feed() — Core Inference Step

QState feed(int16_t raw_value);   // Primary: raw integer input
QState feed(float raw_value);      // Wrapper: auto-scales normalized floats

The main function. Call once per sample. Returns a QState with the current classification.

Integer Version (Recommended)

int16_t raw = analogRead(A0);     // 0..4095 on ESP32, 0..1023 on AVR
QState state = qsetun.feed(raw);

Execution time: ~1.0 μs on ESP32, ~6 μs on 16 MHz AVR. Deterministic O(1). Zero floating-point operations.

Float Version (Backward-Compatible)

float normalized = (analogRead(A0) - 2048) / 2048.0f;  // -1.0 to +1.0
QState state = qsetun.feed(normalized);

Internally scales by 256 and calls the integer version. Values in [-10.0, 10.0] are treated as normalized; outside that range, cast directly to int16_t.


setThresholds() — Dynamic Threshold Update

void setThresholds(int32_t pos_threshold_q8, int32_t neg_threshold_q8, int16_t charge_limit = 6);

Change thresholds at runtime without resetting the engine state. Useful for adaptive sensitivity.

// Increase sensitivity at night (lower thresholds)
qsetun.setThresholds(5 << 8, -(5 << 8), 4);

// Decrease sensitivity during daytime noise
qsetun.setThresholds(50 << 8, -(40 << 8), 8);

configure() — v2.1 Advanced Tuning (Optional)

void configure(uint16_t hysteresis_q8 = 0, uint8_t live_sigma = 0);

Both parameters default to exact v2.0 behavior — omit the call (or call with defaults) and the engine is bit-for-bit v2.0.

Param Default Description
hysteresis_q8 0 Ternary state memory width in Q8 diff units. Once a trit enters +1, it HOLDS until diff falls below pos − hyst; mirroring for −1 (hold above neg + hyst). A deep crossing of the opposite threshold is still a legit instant sign flip. Verified: −34.3 % trit chatter, 90.0 % waves preserved.
live_sigma 0 Continuous threshold self-reinforcement: with > 0, both thresholds are re-derived every feed() as pos = live_sigma × variance_ema. Tracks jumps of the ambient noise floor without re-calibration. Verified: 16× noise jump → 1530 false beats (fixed) vs 4 (live_sigma = 3).

The two options are combinable and independent of begin() / calibrate() / setThresholds().

// v2.0 behavior, bit-for-bit
qsetun.configure();

// Quiet trit chatter around the boundary (hold band ≈ 47 raw units)
qsetun.configure(12000, 0);

// Make thresholds follow the noise floor continuously (3-sigma style)
qsetun.configure(0, 3);

// Both
qsetun.configure(12000, 3);

configure() does not reset baseline, variance or wave-tracking state.


reset() — Full State Reset

void reset();

Clears all internal state: baseline, variance, ring buffer, wave tracking, cycle count, anomaly flags. Returns the engine to construction-time defaults. Call this when switching sensor channels or after a major configuration change.


Telemetry Getters

int16_t  getBaseline()      const;  // Current adaptive baseline (raw units, de-scaled from Q8)
bool     isAnomaly()        const;  // Last anomaly flag
uint8_t  getScorePct()      const;  // Last anomaly score 0..100
float    getScore()         const;  // Last anomaly score 0.0..1.0
uint32_t getCyclesCount()   const;  // Total completed cycles
// v2.1 (parity with the lab fork)
int32_t  getVarianceEMA()   const;  // Raw variance EMA (Q8)
int32_t  getPosThreshold()  const;  // Current positive threshold (Q8)
int32_t  getNegThreshold()  const;  // Current negative threshold (Q8)
int16_t  getChargeLimit()   const;  // Current charge limit
int32_t  getBaselineEMA()   const;  // Raw baseline EMA (Q8)
const QTrit* getTritRing()  const;  // Pointer to the 32-trit ring buffer
uint8_t  getRingHead()      const;  // Current ring head index
void     setBaselineEMA(int32_t val);  // Seed baseline (Q8)
void     setVarianceEMA(int32_t val);  // Seed variance (Q8)

These return the last computed values. They don't trigger a new inference step.


Memory Layout

Total static footprint: 84 bytes (v2.1, measured with avr-g++ 7.3 on ATmega328P at -Os).

┌─────────────────────────────────────┐
│  Thresholds (pos, neg, charge_limit)│  10 bytes
│  Baseline EMA (Q8 int32_t)          │   4 bytes
│  Variance EMA (Q8 int32_t)          │   4 bytes
│  Trit Ring Buffer [32]              │  32 bytes
│  Ring head + prev_trit + prev_diff  │   6 bytes
│  Wave tracking (in_wave, width, Q)  │   5 bytes
│  Anomaly state + scores             │   7 bytes
│  Last charge + last width           │   4 bytes
│  Cycles count                       │   4 bytes
│  Hysteresis + live_sigma  (v2.1)    │   3 bytes
│  Wave energy + trit counter (v2.1)  │   6 bytes
├─────────────────────────────────────┤
│  Total: 84 bytes actual (v2.1)      │
└─────────────────────────────────────┘

No malloc(). No new. No std::vector. No heap. Ever.


Platform Notes

Platform ADC Range feed() Input Latency
Arduino Uno (ATmega328P) 0–1023 (10-bit) int16_t ~6 μs
Arduino Mega (ATmega2560) 0–1023 (10-bit) int16_t ~6 μs
ESP32 0–4095 (12-bit) int16_t ~1.0 μs
ESP32-S3 0–4095 (12-bit) int16_t ~0.8 μs
STM32 BluePill (F103) 0–4095 (12-bit) int16_t ~1.5 μs
STM32 BlackPill (F411) 0–4095 (12-bit) int16_t ~0.9 μs
RP2040 (Pico) 0–4095 (12-bit) int16_t ~1.2 μs

All platforms use the same qsetun.h with zero #ifdef branching in the inference path.