use crate::error::PrivacyError;
use crate::types::{AttractorKind, ChaosTelemetry};
use serde::{Deserialize, Serialize};
extern crate alloc;
use alloc::vec::Vec;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RosslerParams {
pub a: f64,
pub b: f64,
pub c: f64,
pub dt: f64,
}
impl Default for RosslerParams {
fn default() -> Self {
Self { a: 0.2, b: 0.2, c: 5.7, dt: 0.01 }
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct RosslerState {
pub x: f64,
pub y: f64,
pub z: f64,
}
impl RosslerState {
pub fn default_ic() -> Self {
Self { x: 0.1, y: 0.0, z: 0.0 }
}
}
#[derive(Debug, Clone)]
pub struct RosslerAttractor {
pub params: RosslerParams,
pub state: RosslerState,
pub lyapunov: f64,
steps: u64,
variance_acc: f64,
prev_x: f64,
pub active: bool,
}
impl RosslerAttractor {
pub fn new() -> Self {
Self::with_params(RosslerParams::default(), RosslerState::default_ic())
}
pub fn with_params(params: RosslerParams, ic: RosslerState) -> Self {
Self {
params,
state: ic,
lyapunov: 4.5,
steps: 0,
variance_acc: 0.0,
prev_x: ic.x,
active: false,
}
}
fn derivatives(&self, s: &RosslerState) -> (f64, f64, f64) {
let dxdt = -s.y - s.z;
let dydt = s.x + self.params.a * s.y;
let dzdt = self.params.b + s.z * (s.x - self.params.c);
(dxdt, dydt, dzdt)
}
#[allow(clippy::manual_clamp)]
pub fn step(&mut self) {
let dt = self.params.dt;
let s = self.state;
let (dxdt, dydt, dzdt) = self.derivatives(&s);
self.state.x += dt * dxdt;
self.state.y += dt * dydt;
self.state.z += dt * dzdt;
let dx = (self.state.x - self.prev_x).abs();
self.variance_acc = 0.99 * self.variance_acc + 0.01 * dx;
self.prev_x = self.state.x;
if self.variance_acc > 1e-10 {
self.lyapunov = (self.variance_acc * 1000.0).ln().max(0.0).min(10.0);
}
self.steps += 1;
}
pub fn activate(&mut self) {
self.active = true;
}
pub fn deactivate(&mut self) {
self.active = false;
}
pub fn sample_bits(&mut self, n: usize) -> Vec<u8> {
let mut bits = Vec::with_capacity(n.div_ceil(8));
let mut byte = 0u8;
let mut bit_pos = 0u8;
for _ in 0..n {
self.step();
let bit = ((self.state.x > 0.0) ^ (self.state.y > 0.0)) as u8;
byte |= bit << bit_pos;
bit_pos += 1;
if bit_pos == 8 {
bits.push(byte);
byte = 0;
bit_pos = 0;
}
}
if bit_pos > 0 {
bits.push(byte);
}
bits
}
pub fn perturbation(&self) -> f64 {
let raw = self.state.x * 0.6 + self.state.y * 0.4;
raw.tanh()
}
pub fn is_stalled(&self) -> bool {
self.steps > 100 && self.variance_acc < 0.005
}
pub fn telemetry(&self) -> ChaosTelemetry {
ChaosTelemetry {
lyapunov: self.lyapunov,
h_min: if self.is_stalled() { 0.0 } else { 0.97 },
passed: !self.is_stalled() && self.lyapunov >= 4.0,
attractor: AttractorKind::Rossler,
}
}
pub fn validate(&self) -> Result<(), PrivacyError> {
if self.is_stalled() {
return Err(PrivacyError::AllAttractorsStalled);
}
Ok(())
}
}
impl Default for RosslerAttractor {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_rossler_steps() {
let mut r = RosslerAttractor::new();
for _ in 0..500 {
r.step();
}
assert!(r.state.x.abs() > 1e-10 || r.state.y.abs() > 1e-10);
}
#[test]
fn test_perturbation_bounded() {
let mut r = RosslerAttractor::new();
for _ in 0..200 {
r.step();
}
let p = r.perturbation();
assert!(p > -1.0 && p < 1.0);
}
#[test]
fn test_activation() {
let mut r = RosslerAttractor::new();
assert!(!r.active);
r.activate();
assert!(r.active);
r.deactivate();
assert!(!r.active);
}
}