1use crate::density::DensityType;
6use crate::error::{PicardError, Result};
7use ndarray::Array2;
8
9#[derive(Clone)]
11pub struct PicardConfig {
12 pub density: DensityType,
14
15 pub n_components: Option<usize>,
17
18 pub ortho: bool,
20
21 pub extended: Option<bool>,
24
25 pub whiten: bool,
27
28 pub centering: bool,
30
31 pub max_iter: usize,
33
34 pub tol: f64,
36
37 pub m: usize,
39
40 pub ls_tries: usize,
42
43 pub lambda_min: f64,
45
46 pub w_init: Option<Array2<f64>>,
48
49 pub fastica_it: Option<usize>,
51
52 pub jade_it: Option<usize>,
56
57 pub random_state: Option<u64>,
59
60 pub verbose: bool,
62}
63
64impl Default for PicardConfig {
65 fn default() -> Self {
66 Self {
67 density: DensityType::default(),
68 n_components: None,
69 ortho: true,
70 extended: None,
71 whiten: true,
72 centering: true,
73 max_iter: 500,
74 tol: 1e-7,
75 m: 7,
76 ls_tries: 10,
77 lambda_min: 0.01,
78 w_init: None,
79 fastica_it: None,
80 jade_it: None,
81 random_state: None,
82 verbose: false,
83 }
84 }
85}
86
87impl PicardConfig {
88 pub fn new() -> Self {
90 Self::default()
91 }
92
93 pub fn builder() -> ConfigBuilder {
95 ConfigBuilder::new()
96 }
97
98 pub fn effective_extended(&self) -> bool {
100 self.extended.unwrap_or(self.ortho)
101 }
102
103 pub fn validate(&self) -> Result<()> {
105 if self.max_iter == 0 {
106 return Err(PicardError::InvalidConfig {
107 parameter: "max_iter".into(),
108 message: "must be greater than 0".into(),
109 });
110 }
111
112 if self.tol <= 0.0 {
113 return Err(PicardError::InvalidConfig {
114 parameter: "tol".into(),
115 message: "must be positive".into(),
116 });
117 }
118
119 if self.lambda_min <= 0.0 {
120 return Err(PicardError::InvalidConfig {
121 parameter: "lambda_min".into(),
122 message: "must be positive".into(),
123 });
124 }
125
126 if self.m == 0 {
127 return Err(PicardError::InvalidConfig {
128 parameter: "m".into(),
129 message: "L-BFGS memory size must be at least 1".into(),
130 });
131 }
132
133 if self.fastica_it.is_some() && self.jade_it.is_some() {
134 return Err(PicardError::InvalidConfig {
135 parameter: "jade_it".into(),
136 message: "cannot use both fastica_it and jade_it; choose one warm start method"
137 .into(),
138 });
139 }
140
141 Ok(())
142 }
143}
144
145#[derive(Default)]
147pub struct ConfigBuilder {
148 config: PicardConfig,
149}
150
151impl ConfigBuilder {
152 pub fn new() -> Self {
154 Self {
155 config: PicardConfig::default(),
156 }
157 }
158
159 pub fn density(mut self, density: DensityType) -> Self {
161 self.config.density = density;
162 self
163 }
164
165 pub fn n_components(mut self, n: usize) -> Self {
167 self.config.n_components = Some(n);
168 self
169 }
170
171 pub fn ortho(mut self, ortho: bool) -> Self {
173 self.config.ortho = ortho;
174 self
175 }
176
177 pub fn extended(mut self, extended: bool) -> Self {
179 self.config.extended = Some(extended);
180 self
181 }
182
183 pub fn whiten(mut self, whiten: bool) -> Self {
185 self.config.whiten = whiten;
186 self
187 }
188
189 pub fn centering(mut self, centering: bool) -> Self {
191 self.config.centering = centering;
192 self
193 }
194
195 pub fn max_iter(mut self, max_iter: usize) -> Self {
197 self.config.max_iter = max_iter;
198 self
199 }
200
201 pub fn tol(mut self, tol: f64) -> Self {
203 self.config.tol = tol;
204 self
205 }
206
207 pub fn m(mut self, m: usize) -> Self {
209 self.config.m = m;
210 self
211 }
212
213 pub fn ls_tries(mut self, ls_tries: usize) -> Self {
215 self.config.ls_tries = ls_tries;
216 self
217 }
218
219 pub fn lambda_min(mut self, lambda_min: f64) -> Self {
221 self.config.lambda_min = lambda_min;
222 self
223 }
224
225 pub fn w_init(mut self, w_init: Array2<f64>) -> Self {
227 self.config.w_init = Some(w_init);
228 self
229 }
230
231 pub fn fastica_it(mut self, iterations: usize) -> Self {
235 self.config.fastica_it = Some(iterations);
236 self
237 }
238
239 pub fn jade_it(mut self, iterations: usize) -> Self {
247 self.config.jade_it = Some(iterations);
248 self
249 }
250
251 pub fn random_state(mut self, seed: u64) -> Self {
253 self.config.random_state = Some(seed);
254 self
255 }
256
257 pub fn verbose(mut self, verbose: bool) -> Self {
259 self.config.verbose = verbose;
260 self
261 }
262
263 pub fn build(self) -> PicardConfig {
265 self.config
266 }
267
268 pub fn build_validated(self) -> Result<PicardConfig> {
270 self.config.validate()?;
271 Ok(self.config)
272 }
273}