use alloc::vec::Vec;
pub struct HighDegreePolynomial {
degree: usize,
}
impl HighDegreePolynomial {
pub fn new(degree: usize) -> Self {
assert!(degree >= 2, "HighDegreePolynomial degree must be >= 2");
Self { degree }
}
#[inline]
pub fn degree(&self) -> usize {
self.degree
}
pub fn generate(&self, features: &[f64]) -> Vec<f64> {
let n = features.len();
let mut result = Vec::with_capacity(self.output_dim(n));
let mut indices = Vec::with_capacity(self.degree);
self.gen_recursive(features, n, 0, &mut indices, &mut result);
result
}
pub fn output_dim(&self, input_dim: usize) -> usize {
if input_dim == 0 {
return 0;
}
let p = self.degree;
let mut result: usize = 1;
for i in 0..p {
result = result * (input_dim + i) / (i + 1);
}
result
}
fn gen_recursive(
&self,
features: &[f64],
n: usize,
start: usize,
indices: &mut Vec<usize>,
result: &mut Vec<f64>,
) {
if indices.len() == self.degree {
let mut product = 1.0;
for &idx in indices.iter() {
product *= features[idx];
}
result.push(product);
return;
}
for i in start..n {
indices.push(i);
self.gen_recursive(features, n, i, indices, result);
indices.pop();
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn degree2_three_features() {
let poly = HighDegreePolynomial::new(2);
let result = poly.generate(&[1.0, 2.0, 3.0]);
assert_eq!(result.len(), 6);
let expected = [1.0, 2.0, 3.0, 4.0, 6.0, 9.0];
for (i, (&got, &exp)) in result.iter().zip(expected.iter()).enumerate() {
assert!(
(got - exp).abs() < 1e-12,
"monomial[{}]: expected {}, got {}",
i,
exp,
got,
);
}
}
#[test]
fn degree2_two_features() {
let poly = HighDegreePolynomial::new(2);
let result = poly.generate(&[3.0, 5.0]);
assert_eq!(result.len(), 3);
assert!((result[0] - 9.0).abs() < 1e-12);
assert!((result[1] - 15.0).abs() < 1e-12);
assert!((result[2] - 25.0).abs() < 1e-12);
}
#[test]
fn degree3_two_features() {
let poly = HighDegreePolynomial::new(3);
let result = poly.generate(&[2.0, 3.0]);
assert_eq!(result.len(), 4);
let expected = [8.0, 12.0, 18.0, 27.0];
for (i, (&got, &exp)) in result.iter().zip(expected.iter()).enumerate() {
assert!(
(got - exp).abs() < 1e-12,
"monomial[{}]: expected {}, got {}",
i,
exp,
got,
);
}
}
#[test]
fn output_dim_matches_generated() {
let poly = HighDegreePolynomial::new(2);
for n in 1..=8 {
let features: Vec<f64> = (0..n).map(|i| i as f64 + 1.0).collect();
let result = poly.generate(&features);
let expected_dim = poly.output_dim(n);
assert_eq!(
result.len(),
expected_dim,
"output_dim({}) = {} but generated {}",
n,
expected_dim,
result.len(),
);
}
}
#[test]
fn output_dim_degree3() {
let poly = HighDegreePolynomial::new(3);
assert_eq!(poly.output_dim(1), 1);
assert_eq!(poly.output_dim(2), 4);
assert_eq!(poly.output_dim(3), 10);
}
#[test]
fn single_feature_degree2() {
let poly = HighDegreePolynomial::new(2);
let result = poly.generate(&[7.0]);
assert_eq!(result.len(), 1);
assert!((result[0] - 49.0).abs() < 1e-12);
}
#[test]
fn empty_features_returns_empty() {
let poly = HighDegreePolynomial::new(2);
let result = poly.generate(&[]);
assert!(result.is_empty());
assert_eq!(poly.output_dim(0), 0);
}
#[test]
#[should_panic(expected = "degree must be >= 2")]
fn degree_one_panics() {
let _ = HighDegreePolynomial::new(1);
}
}