38 using Clock = std::chrono::steady_clock;
53 bool operator==(
Key const &other)
const noexcept {
54 return mode == other.mode &&
freqBin == other.freqBin &&
88 : m_enabled(enabled) {
91 <<
"soft-combining disabled (JS8_SOFT_COMBINING=0)";
106 Key makeKey(
int mode,
float f1,
float dt, std::array<float, N>
const &llr0,
107 std::array<float, N>
const &llr1)
const {
108 return Key{mode,
static_cast<int>(std::lround(f1)),
109 static_cast<int>(std::lround(dt * 10.0f)),
110 signature(llr0, llr1)};
122 std::array<float, N>
const &llr1,
123 std::chrono::seconds ttl) {
127 return Combined{key, llr0, llr1, 1,
false};
130 auto &bucket = m_entries[keyForLookup(key)];
131 auto it = findEntry(bucket, key.
signature);
133 if (it == bucket.end()) {
134 bucket.push_back(makeEntry(key.
signature, llr0, llr1));
135 return Combined{key, llr0, llr1, 1,
false};
138 for (std::size_t i = 0; i < llr0.size(); ++i) {
139 it->llr0[i] += llr0[i];
140 it->llr1[i] += llr1[i];
144 it->lastSeen = Clock::now();
147 <<
"soft-combining repeats" << it->repeats <<
"mode" << key.
mode
150 return Combined{key, it->llr0, it->llr1, it->repeats,
true};
161 auto lookup = keyForLookup(key);
162 auto it = m_entries.find(lookup);
164 if (it == m_entries.end())
167 auto &bucket = it->second;
168 bucket.erase(std::remove_if(bucket.begin(), bucket.end(),
169 [&key](Entry
const &entry) {
170 return entry.signature == key.signature;
182 void flush(std::chrono::seconds ttl) {
186 auto const now = Clock::now();
188 for (
auto it = m_entries.begin(); it != m_entries.end();) {
189 auto &bucket = it->second;
191 bucket.erase(std::remove_if(bucket.begin(), bucket.end(),
192 [now, ttl](Entry
const &entry) {
193 return now - entry.lastSeen > ttl;
198 it = m_entries.erase(it);
211 bool operator==(CoarseKey
const &other)
const noexcept {
212 return mode == other.mode && freqBin == other.freqBin &&
213 dtBin == other.dtBin;
218 std::size_t operator()(CoarseKey
const &key)
const noexcept {
219 std::size_t
const h1 = std::hash<int>{}(key.mode);
220 std::size_t
const h2 = std::hash<int>{}(key.freqBin);
221 std::size_t
const h3 = std::hash<int>{}(key.dtBin);
222 return h1 ^ (h2 << 1) ^ (h3 << 2);
231 std::array<float, N> llr0;
232 std::array<float, N> llr1;
234 Clock::time_point lastSeen;
237 using Bucket = std::vector<Entry>;
245 static constexpr auto signatureIndices() {
246 std::array<int, 32> indices{};
248 for (std::size_t i = 0; i < indices.size(); ++i) {
249 value = (value + 37) %
static_cast<int>(N);
261 static typename Bucket::iterator findEntry(Bucket &bucket, uint32_t signature) {
262 constexpr int MAX_HAMMING =
266 bucket.begin(), bucket.end(), [signature](Entry
const &entry) {
267 return hamming(signature, entry.signature) <= MAX_HAMMING;
275 static bool defaultEnabled() {
277 int value = qEnvironmentVariableIntValue(
"JS8_SOFT_COMBINING", &ok);
278 return ok ? value != 0 :
true;
284 static uint32_t signature(std::array<float, N>
const &llr0,
285 std::array<float, N>
const &llr1) {
286 static constexpr auto INDICES = signatureIndices();
289 for (std::size_t i = 0; i < INDICES.size(); ++i) {
290 auto const idx = INDICES[i];
291 float const v = 0.5f * (llr0[idx] + llr1[idx]);
299 static int hamming(uint32_t a, uint32_t b) {
312 static void maybeRunSelfTest() {
313 static std::once_flag once;
314 std::call_once(once, []() {
315 if (!qEnvironmentVariableIsSet(
"JS8_SOFT_COMBINING_TEST"))
320 std::array<float, N> baseline{};
321 for (std::size_t i = 0; i < baseline.size(); ++i) {
322 baseline[i] = (i % 2 == 0) ? 2.0f : -2.0f;
325 auto noisy = [](std::array<float, N> base,
int flipStride) {
326 for (std::size_t i = 0; i < base.size(); ++i) {
327 if (i % flipStride == 0) {
336 auto llrA = noisy(baseline, 7);
337 auto llrB = noisy(baseline, 11);
339 auto key = combiner.makeKey(0, 1500.0f, 1.0f, llrA, llrB);
341 combiner.combine(key, llrA, llrA, std::chrono::seconds{30});
343 combiner.combine(key, llrB, llrB, std::chrono::seconds{30});
345 auto countMatches = [](std::array<float, N>
const &llr,
346 std::array<float, N>
const &reference) {
348 for (std::size_t i = 0; i < llr.size(); ++i) {
349 if (llr[i] * reference[i] > 0)
355 int const matchesA = countMatches(first.llr0, baseline);
356 int const matchesB = countMatches(llrB, baseline);
357 int const matchesCombined = countMatches(second.llr0, baseline);
360 <<
"soft-combining self-test: A matches" << matchesA
361 <<
"B matches" << matchesB <<
"combined matches"
362 << matchesCombined <<
"repeats" << second.repeats;
367 static Entry makeEntry(uint32_t signature, std::array<float, N>
const &llr0,
368 std::array<float, N>
const &llr1) {
369 return Entry{signature, llr0, llr1, 1, Clock::now()};
373 CoarseKey keyForLookup(
Key const &key)
const {
374 return CoarseKey{key.mode, key.freqBin, key.dtBin};
376 std::unordered_map<CoarseKey, Bucket, CoarseHash> m_entries;
Combined combine(Key const &key, std::array< float, N > const &llr0, std::array< float, N > const &llr1, std::chrono::seconds ttl)
Attempt to combine incoming LLRs with cached repeats.
Definition soft_combiner.h:121
Key makeKey(int mode, float f1, float dt, std::array< float, N > const &llr0, std::array< float, N > const &llr1) const
Create a Key for a candidate from its parameters and LLRs.
Definition soft_combiner.h:106