linfa_reduction/random_projection/
hyperparams.rs

1use std::{fmt::Debug, marker::PhantomData};
2
3use linfa::ParamGuard;
4
5use rand::Rng;
6
7use crate::ReductionError;
8
9use super::methods::ProjectionMethod;
10
11/// Random projection hyperparameters
12///
13/// The main hyperparameter of random projections is
14/// the dimension of the embedding.
15/// This dimension is usually determined by the desired precision (or distortion) `eps`,
16/// using the [Johnson-Lindenstrauss Lemma](https://en.wikipedia.org/wiki/Johnson%E2%80%93Lindenstrauss_lemma).
17/// However, this lemma makes a very conservative estimate of the required dimension,
18/// and does not leverage the structure of the data, therefore it is also possible
19/// to manually specify the dimension of the embedding.
20///
21/// As this algorithm is randomized, it also accepts an [`Rng`] as parameter,
22/// to be used to sample coordinate of the projection matrix.
23pub struct RandomProjectionParams<Proj: ProjectionMethod, R: Rng + Clone>(
24    pub(crate) RandomProjectionValidParams<Proj, R>,
25);
26
27impl<Proj: ProjectionMethod, R: Rng + Clone> RandomProjectionParams<Proj, R> {
28    /// Set the dimension of output of the embedding.
29    ///
30    /// Setting the target dimension with this function
31    /// discards the precision parameter if it had been set previously.
32    pub fn target_dim(mut self, dim: usize) -> Self {
33        self.0.params = RandomProjectionParamsInner::Dimension { target_dim: dim };
34
35        self
36    }
37
38    /// Set the precision parameter (distortion, `eps`) of the embedding.
39    ///
40    /// Setting `eps` with this function
41    /// discards the target dimension parameter if it had been set previously.
42    pub fn eps(mut self, eps: f64) -> Self {
43        self.0.params = RandomProjectionParamsInner::Epsilon { eps };
44
45        self
46    }
47
48    /// Specify the random number generator to use to generate the projection matrix.
49    pub fn with_rng<R2: Rng + Clone>(self, rng: R2) -> RandomProjectionParams<Proj, R2> {
50        RandomProjectionParams(RandomProjectionValidParams {
51            params: self.0.params,
52            rng,
53            marker: PhantomData,
54        })
55    }
56}
57
58/// Random projection hyperparameters
59///
60/// The main hyperparameter of random projections is
61/// the dimension of the embedding.
62/// This dimension is usually determined by the desired precision (or distortion) `eps`,
63/// using the [Johnson-Lindenstrauss Lemma](https://en.wikipedia.org/wiki/Johnson%E2%80%93Lindenstrauss_lemma).
64/// However, this lemma makes a very conservative estimate of the required dimension,
65/// and does not leverage the structure of the data, therefore it is also possible
66/// to manually specify the dimension of the embedding.
67///
68/// As this algorithm is randomized, it also accepts an [`Rng`] as parameter,
69/// to be used to sample coordinate of the projection matrix.
70#[derive(Debug, Clone, PartialEq)]
71pub struct RandomProjectionValidParams<Proj: ProjectionMethod, R: Rng + Clone> {
72    pub(super) params: RandomProjectionParamsInner,
73    pub(super) rng: R,
74    pub(crate) marker: PhantomData<Proj>,
75}
76
77/// Internal data structure that either holds the dimension or the embedding,
78/// or the precision, which can be used later to compute the dimension
79/// (see [super::common::johnson_lindenstrauss_min_dim]).
80#[derive(Debug, Clone, PartialEq)]
81pub(crate) enum RandomProjectionParamsInner {
82    Dimension { target_dim: usize },
83    Epsilon { eps: f64 },
84}
85
86impl RandomProjectionParamsInner {
87    fn target_dim(&self) -> Option<usize> {
88        use RandomProjectionParamsInner::*;
89        match self {
90            Dimension { target_dim } => Some(*target_dim),
91            Epsilon { .. } => None,
92        }
93    }
94
95    fn eps(&self) -> Option<f64> {
96        use RandomProjectionParamsInner::*;
97        match self {
98            Dimension { .. } => None,
99            Epsilon { eps } => Some(*eps),
100        }
101    }
102}
103
104impl<Proj: ProjectionMethod, R: Rng + Clone> RandomProjectionValidParams<Proj, R> {
105    pub fn target_dim(&self) -> Option<usize> {
106        self.params.target_dim()
107    }
108
109    pub fn eps(&self) -> Option<f64> {
110        self.params.eps()
111    }
112
113    pub fn rng(&self) -> &R {
114        &self.rng
115    }
116}
117
118impl<Proj: ProjectionMethod, R: Rng + Clone> ParamGuard for RandomProjectionParams<Proj, R> {
119    type Checked = RandomProjectionValidParams<Proj, R>;
120    type Error = ReductionError;
121
122    fn check_ref(&self) -> Result<&Self::Checked, Self::Error> {
123        match self.0.params {
124            RandomProjectionParamsInner::Dimension { target_dim } => {
125                if target_dim == 0 {
126                    return Err(ReductionError::NonPositiveEmbeddingSize);
127                }
128            }
129            RandomProjectionParamsInner::Epsilon { eps } => {
130                if eps <= 0. || eps >= 1. {
131                    return Err(ReductionError::InvalidPrecision);
132                }
133            }
134        };
135        Ok(&self.0)
136    }
137
138    fn check(self) -> Result<Self::Checked, Self::Error> {
139        self.check_ref()?;
140        Ok(self.0)
141    }
142}