New SLP-Workshop firmware variant (Synth Library Portland), built on the MEMLCelium engine + MLP shape. Ports the two post-fork learning-algorithm changes from upstream memllib InterfaceRL into the shared nisps/ml core, runtime-configurable (no compile-time switch), inert by default: - nisps/ml/jolt.hpp: Jolt — held continuous weight morph over the flat weight buffer + post-release LR ramp (kJolt* constants verbatim). - nisps/ml/ou_noise.hpp: OUNoise<N> — Ornstein-Uhlenbeck exploration walk on the output vector (theta=0.02, dt=0.001, kMaxAmplitude=0.65). Both wired into ModeBase so every mode gains jolt_press/jolt_release/ jolt_lr_scale + set_explore_intensity; gated so existing modes stay bit-identical (parity + golden tests green). Firmware surfaces them on TogB1 (Jolt) and RVX1 (explore). New SLPWorkshopMode mode + schema + codegen; firmware alias + .ino variant; playground mode registration. Tests: jolt + OU unit tests, ModeBase learning integration incl. an inert-parity test proving SLP-Workshop == MEMLCelium with features off. Verified: cpp tests, wasm build, native↔wasm parity, lint, codegen golden, playground typecheck. Firmware compile/e2e/hardware are environment-bound (no arduino-cli/submodules/browser here). Refs ergo 019f0fca.
77 lines
2.1 KiB
C++
77 lines
2.1 KiB
C++
// tests/cpp/test_mlp_ou_noise.cpp — Ornstein-Uhlenbeck exploration noise.
|
|
//
|
|
// - default state is inert: intensity 0, disabled, apply() leaves the
|
|
// output untouched (parity-safe).
|
|
// - set_intensity enables it; apply() drifts the output but keeps it in
|
|
// [0, 1].
|
|
// - same seed ⇒ identical walk (determinism / parity).
|
|
// - intensity back to 0 disables it again (inert).
|
|
|
|
#include <array>
|
|
#include <cmath>
|
|
#include <cstdint>
|
|
#include <span>
|
|
|
|
#include "test_helpers.hpp"
|
|
|
|
#include "../../nisps/ml/ou_noise.hpp"
|
|
|
|
namespace {
|
|
|
|
using nisps::ml::OUNoise;
|
|
|
|
NISPS_TEST(ou_inert_by_default) {
|
|
OUNoise<8> ou(0ull);
|
|
NISPS_EXPECT(!ou.enabled());
|
|
NISPS_EXPECT(ou.intensity() == 0.f);
|
|
std::array<float, 8> o{};
|
|
o.fill(0.5f);
|
|
ou.apply(std::span<float>(o)); // no-op while disabled
|
|
for (float v : o) NISPS_EXPECT(v == 0.5f);
|
|
}
|
|
|
|
NISPS_TEST(ou_intensity_enables_and_perturbs_bounded) {
|
|
OUNoise<8> ou(5ull);
|
|
ou.set_intensity(0.6f);
|
|
NISPS_EXPECT(ou.enabled());
|
|
|
|
bool moved = false;
|
|
for (int t = 0; t < 500; ++t) {
|
|
std::array<float, 8> o{};
|
|
o.fill(0.5f);
|
|
ou.apply(std::span<float>(o));
|
|
for (float v : o) {
|
|
NISPS_EXPECT(v >= 0.f && v <= 1.f); // clamped to param space
|
|
if (std::fabs(v - 0.5f) > 1e-4f) moved = true;
|
|
}
|
|
}
|
|
NISPS_EXPECT(moved);
|
|
}
|
|
|
|
NISPS_TEST(ou_deterministic_same_seed) {
|
|
OUNoise<4> a(42ull), b(42ull);
|
|
a.set_intensity(0.5f);
|
|
b.set_intensity(0.5f);
|
|
for (int t = 0; t < 100; ++t) {
|
|
std::array<float, 4> oa{}, ob{};
|
|
oa.fill(0.3f);
|
|
ob.fill(0.3f);
|
|
a.apply(std::span<float>(oa));
|
|
b.apply(std::span<float>(ob));
|
|
for (int i = 0; i < 4; ++i) NISPS_EXPECT(oa[i] == ob[i]);
|
|
}
|
|
}
|
|
|
|
NISPS_TEST(ou_intensity_zero_disables_again) {
|
|
OUNoise<4> ou(1ull);
|
|
ou.set_intensity(0.5f);
|
|
NISPS_EXPECT(ou.enabled());
|
|
ou.set_intensity(0.f);
|
|
NISPS_EXPECT(!ou.enabled());
|
|
std::array<float, 4> o{};
|
|
o.fill(0.2f);
|
|
ou.apply(std::span<float>(o));
|
|
for (float v : o) NISPS_EXPECT(v == 0.2f);
|
|
}
|
|
|
|
} // namespace
|