use crate::PHI;
#[derive(Debug, Clone)]
pub struct PenroseEncoder {
pub window_radius: f64,
}
const PROJECTION: [[f64; 5]; 2] = [
[1.0, 0.30901699437494745, -0.8090169943749473, -0.8090169943749475, 0.30901699437494723],
[0.0, 0.9510565162951535, 0.5877852522924732, -0.5877852522924730, -0.9510565162951536],
];
const PERP_PROJECTION: [[f64; 5]; 2] = [
[1.0, -0.8090169943749473, 0.30901699437494745, 0.30901699437494723, -0.8090169943749475],
[0.0, 0.5877852522924732, -0.9510565162951536, -0.9510565162951535, 0.5877852522924732],
];
impl PenroseEncoder {
pub fn new(window_radius: Option<f64>) -> Self {
PenroseEncoder {
window_radius: window_radius.unwrap_or(2.0 * PHI),
}
}
pub fn project_physical(v: &[f64; 5]) -> (f64, f64) {
let x = v.iter().zip(PROJECTION[0].iter()).map(|(a, b)| a * b).sum();
let y = v.iter().zip(PROJECTION[1].iter()).map(|(a, b)| a * b).sum();
(x, y)
}
pub fn project_perp(v: &[f64; 5]) -> (f64, f64) {
let x = v.iter().zip(PERP_PROJECTION[0].iter()).map(|(a, b)| a * b).sum();
let y = v.iter().zip(PERP_PROJECTION[1].iter()).map(|(a, b)| a * b).sum();
(x, y)
}
pub fn in_acceptance_window(&self, perp_x: f64, perp_y: f64) -> bool {
let dist = (perp_x * perp_x + perp_y * perp_y).sqrt();
if dist > self.window_radius {
return false;
}
dist <= self.window_radius
}
pub fn encode(&self, v: &[f64; 5]) -> (f64, f64, bool) {
let (px, py) = Self::project_physical(v);
let (qx, qy) = Self::project_perp(v);
let accepted = self.in_acceptance_window(qx, qy);
(px, py, accepted)
}
pub fn generate_dense_tiling(&self, v: &[f64; 5], radius: i32) -> Vec<(f64, f64)> {
let mut points = Vec::new();
for i in -radius..=radius {
for j in -radius..=radius {
for k in -radius..=radius {
for l in -radius..=radius {
for m in -radius..=radius {
let zv = [
v[0] + i as f64,
v[1] + j as f64,
v[2] + k as f64,
v[3] + l as f64,
v[4] + m as f64,
];
let (px, py, accepted) = self.encode(&zv);
if accepted {
points.push((px, py));
}
}
}
}
}
}
points
}
pub fn deflate(&self, points: &[(f64, f64)]) -> Vec<(f64, f64)> {
points.iter()
.map(|(x, y)| (x * PHI, y * PHI))
.collect()
}
pub fn inflate(&self, points: &[(f64, f64)]) -> Vec<(f64, f64)> {
points.iter()
.map(|(x, y)| (x / PHI, y / PHI))
.collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_penrose_acceptance() {
let encoder = PenroseEncoder::new(None);
assert!(encoder.in_acceptance_window(0.0, 0.0));
assert!(!encoder.in_acceptance_window(10.0, 10.0));
}
#[test]
fn test_penrose_encode_basic() {
let encoder = PenroseEncoder::new(None);
let v = [1.0, 0.5, 0.7, 0.3, 0.6];
let (px, py, accepted) = encoder.encode(&v);
assert!(accepted);
assert!(px.is_finite());
assert!(py.is_finite());
}
#[test]
fn test_inflation_deflation_roundtrip() {
let encoder = PenroseEncoder::new(None);
let points = vec![(1.0, 2.0), (3.0, 4.0), (5.0, 6.0)];
let inflated = encoder.inflate(&points);
let deflated = encoder.deflate(&inflated);
for (orig, roundtripped) in points.iter().zip(deflated.iter()) {
assert!((orig.0 - roundtripped.0).abs() < 1e-10);
assert!((orig.1 - roundtripped.1).abs() < 1e-10);
}
}
#[test]
fn test_penrose_vs_eisenstein_orthogonal() {
let v = [1.0, 0.0, 0.0, 0.0, 0.0];
let encoder = PenroseEncoder::new(None);
let (px, py, _) = encoder.encode(&v);
assert!(px.is_finite());
assert!(py.is_finite());
}
#[test]
fn test_dense_tiling_produces_points() {
let encoder = PenroseEncoder::new(Some(10.0));
let v = [0.0, 0.0, 0.0, 0.0, 0.0];
let points = encoder.generate_dense_tiling(&v, 1);
assert!(points.len() > 0, "Dense tiling should produce some points");
}
}