///|
/// Explicit coordinate map. Unlisted coordinates are punctured (zero LLR).
/// Repeated transmitted coordinates are independent observations combined by LLR sum.
pub struct RatePlan {
priv n : Int32
priv transmitted : Array[Int32]
priv shortened : Array[(Int32, Int32)]
} derive(Debug)
///|
pub fn RatePlan::new(
variables : Int32,
transmitted : Array[Int32],
shortened~ : Array[(Int32, Int32)] = [],
) -> RatePlan raise CodeError {
if variables <= 0 || variables > 65536 {
raise InvalidDimensions("rate-plan variables")
}
if transmitted.length() > 1000000 {
raise ResourceLimit("transmitted observations")
}
let known = Array::make(variables, -1)
for (v, bit) in shortened {
if v < 0 || v >= variables {
raise InvalidIndex(-1, v)
}
if bit != 0 && bit != 1 {
raise InvalidBit(v, bit)
}
if known[v] != -1 {
raise DuplicateEntry(-1, v)
}
known[v] = bit
}
for v in transmitted {
if v < 0 || v >= variables {
raise InvalidIndex(-1, v)
}
if known[v] != -1 {
raise Inconsistent("shortened coordinate cannot also be transmitted")
}
}
{
n: variables,
transmitted: transmitted.copy(),
shortened: shortened.copy(),
}
}
///|
pub fn RatePlan::observation_count(self : RatePlan) -> Int32 {
self.transmitted.length()
}
///|
pub fn RatePlan::punctured_positions(self : RatePlan) -> Array[Int32] {
let present = Array::make(self.n, false)
for v in self.transmitted {
present[v] = true
}
for (v, _) in self.shortened {
present[v] = true
}
let result : Array[Int32] = []
for v in 0.. Array[Int32] raise CodeError {
ensure_bits(word, self.n)
for (v, bit) in self.shortened {
if word[v] != bit {
raise Inconsistent("word disagrees with shortening")
}
}
self.transmitted.map(fn(v) { word[v] })
}
///|
/// Each input observation is bounded before combining. Summation clips only once
/// after all observations, preserving cancellation for mixed-sign repetitions.
pub fn RatePlan::restore_llr(
self : RatePlan,
observations : Array[Double],
limit~ : Double = 50.0,
) -> Array[Double] raise CodeError {
if observations.length() != self.transmitted.length() {
raise LengthMismatch(self.transmitted.length(), observations.length())
}
validate_limit(limit)
let values = Array::make(self.n, 0.0)
let correction = Array::make(self.n, 0.0)
for i, value in observations {
if !finite(value) {
raise InvalidNumber("nonfinite rate-matched LLR")
}
let v = self.transmitted[i]
let adjusted = clamp(value, limit) - correction[v]
let next = values[v] + adjusted
correction[v] = next - values[v] - adjusted
values[v] = next
}
for v in 0.. Bool raise CodeError {
ensure_bits(word, self.n)
self.shortened.all(fn(pair) { word[pair.0] == pair.1 })
}
///|
pub extend RatePlan with @debug.Debug::{to_repr}
///|
/// Restore BEC observations; repeated known observations must agree.
/// Punctured coordinates remain erased, while shortened coordinates are exact.
pub fn RatePlan::restore_bec(
self : RatePlan,
observations : Array[Int32?],
) -> Array[Int32?] raise CodeError {
if observations.length() != self.transmitted.length() {
raise LengthMismatch(self.transmitted.length(), observations.length())
}
let values : Array[Int32?] = Array::make(self.n, None)
for i, observation in observations {
match observation {
None => ()
Some(bit) => {
if bit != 0 && bit != 1 {
raise InvalidBit(i, bit)
}
let v = self.transmitted[i]
match values[v] {
Some(previous) =>
if previous != bit {
raise Inconsistent("conflicting repeated BEC observations")
}
None => values[v] = Some(bit)
}
}
}
}
for (v, bit) in self.shortened {
values[v] = Some(bit)
}
values
}