91 lines
2.5 KiB
C
91 lines
2.5 KiB
C
/* SPDX-License-Identifier: AGPL-3.0-or-later */
|
|
/* Copyright (C) 2026 David (Corey) Herrera */
|
|
/**
|
|
* @file parity_noise.c
|
|
* @brief Implementation of the reproducible Gaussian perturbations declared in
|
|
* parity_noise.h.
|
|
*
|
|
* @status Working
|
|
* @issues None
|
|
* @todo None
|
|
*/
|
|
|
|
#include "parity_noise.h"
|
|
|
|
#include <math.h>
|
|
#include <string.h>
|
|
|
|
/** The default seed when PN_PARITY_SEED is unset or zero. */
|
|
#define PARITY_NOISE_DEFAULT_SEED 88172645463325252ULL
|
|
|
|
#define PARITY_NOISE_TWO_PI 6.283185307179586
|
|
|
|
static uint64_t noise_next(parity_noise_rng *rng)
|
|
{
|
|
uint64_t x = rng->state;
|
|
|
|
x ^= x >> 12;
|
|
x ^= x << 25;
|
|
x ^= x >> 27;
|
|
rng->state = x;
|
|
return x * 2685821657736338717ULL;
|
|
}
|
|
|
|
/**
|
|
* One Box-Muller pair, scaled by `sigma` INSIDE the radius rather than afterwards.
|
|
*
|
|
* The association is load-bearing, not stylistic: sigma*(r*cos) and (sigma*r)*cos round
|
|
* differently in double, and the recorded noise-ladder figures were produced by the second.
|
|
* Keeping it means a ladder re-run reproduces the record bit for bit.
|
|
*/
|
|
static void noise_pair(parity_noise_rng *rng, double sigma, double *a, double *b)
|
|
{
|
|
double u1 = ((double)(noise_next(rng) >> 11) + 1.0) / 9007199254740993.0;
|
|
double u2 = (double)(noise_next(rng) >> 11) / 9007199254740992.0;
|
|
double r = sigma * sqrt(-2.0 * log(u1));
|
|
|
|
*a = r * cos(PARITY_NOISE_TWO_PI * u2);
|
|
*b = r * sin(PARITY_NOISE_TWO_PI * u2);
|
|
}
|
|
|
|
void parity_noise_seed(parity_noise_rng *rng, uint64_t seed)
|
|
{
|
|
rng->state = seed ? seed : PARITY_NOISE_DEFAULT_SEED;
|
|
}
|
|
|
|
void parity_noise_add(float *dst, const float *src, int n, double sigma, parity_noise_rng *rng)
|
|
{
|
|
if (dst != src)
|
|
memcpy(dst, src, (size_t)n * sizeof(float));
|
|
if (sigma <= 0.0)
|
|
return;
|
|
for (int j = 0; j + 1 < n; j += 2) {
|
|
double a, b;
|
|
|
|
noise_pair(rng, sigma, &a, &b);
|
|
dst[j] = (float)((double)dst[j] + a);
|
|
dst[j + 1] = (float)((double)dst[j + 1] + b);
|
|
}
|
|
}
|
|
|
|
void parity_noise_direction(float *dst, int n, double norm, parity_noise_rng *rng)
|
|
{
|
|
double sum_sq = 0.0, scale;
|
|
|
|
for (int j = 0; j < n; j += 2) {
|
|
double a, b;
|
|
|
|
noise_pair(rng, 1.0, &a, &b);
|
|
dst[j] = (float)a;
|
|
if (j + 1 < n)
|
|
dst[j + 1] = (float)b;
|
|
}
|
|
for (int j = 0; j < n; j++)
|
|
sum_sq += (double)dst[j] * (double)dst[j];
|
|
if (sum_sq <= 0.0)
|
|
return;
|
|
scale = norm / sqrt(sum_sq);
|
|
for (int j = 0; j < n; j++)
|
|
dst[j] = (float)((double)dst[j] * scale);
|
|
}
|