1use faer::linalg::matmul::matmul;
2use faer::linalg::solvers::{Llt, Solve};
3use faer::{Accum, Col, Mat, Par, Side};
4use rand::{Rng, RngExt};
5use rand_distr::{Distribution, StandardNormal, uniform::SampleUniform};
6
7use super::Scalar;
8use super::cl_scaling::{
9 BoxAffineScaling, cl_scaling_pair, max_feasible_step_component,
10 project_strictly_inside_component,
11};
12use super::linalg::{
13 AddDiagonalVectorInPlace, DenseMatrixFromFn, GeneralRankOneUpdate, GramMatrix,
14 LinearSolveError, LinearSolveSpd, MatDiagonal, MatTransposeVec, MatVec, MatrixFromDiagonal,
15 MatrixIdentity, MaxDiagonal, RankOneUpdate, SymmetricEigen, SymmetricEigenError,
16};
17use super::sample::{SampleStandardNormal, SampleUniformBox, assert_finite_box};
18use super::{
19 ClampInPlace, ComponentDivAssign, ComponentMaxAssign, ComponentMulAssign, Dot,
20 FloorZerosInPlace, NegInPlace, NormInfinity, NormSquared, ScaleInPlace, ScaledAdd, VectorIndex,
21 VectorLen,
22};
23
24impl<F: Scalar> ScaledAdd<F> for Col<F> {
30 fn scaled_add(&mut self, scalar: F, other: &Self) {
31 assert_eq!(self.nrows(), other.nrows(), "scaled_add: shape mismatch");
32 faer::zip!(self.as_mut(), other.as_ref())
33 .for_each(|faer::unzip!(x, y)| *x = *x + scalar * *y);
34 }
35}
36
37impl<F: Scalar> NormSquared<F> for Col<F> {
38 fn norm_squared(&self) -> F {
39 self.iter().map(|x| *x * *x).sum()
40 }
41}
42
43impl<F: Scalar> NormInfinity<F> for Col<F> {
44 fn norm_infinity(&self) -> F {
45 self.iter().map(|x| x.abs()).fold(F::zero(), F::max)
46 }
47}
48
49impl<F: Scalar> Dot<F> for Col<F> {
50 fn dot(&self, other: &Self) -> F {
51 assert_eq!(self.nrows(), other.nrows(), "dot: shape mismatch");
52 self.iter().zip(other.iter()).map(|(a, b)| *a * *b).sum()
53 }
54}
55
56impl<F: Scalar> NegInPlace for Col<F> {
57 fn neg_in_place(&mut self) {
58 faer::zip!(self.as_mut()).for_each(|faer::unzip!(x)| *x = -*x);
59 }
60}
61
62impl<F: Scalar + SampleUniform> SampleUniformBox for Col<F> {
63 fn sample_uniform_box<R: Rng + ?Sized>(lower: &Self, upper: &Self, rng: &mut R) -> Self {
64 assert_eq!(
65 lower.nrows(),
66 upper.nrows(),
67 "sample_uniform_box: bounds length mismatch"
68 );
69 assert_finite_box(lower, upper);
70 Self::from_fn(lower.nrows(), |i| rng.random_range(lower[i]..=upper[i]))
71 }
72}
73
74impl<F: Scalar> VectorLen for Col<F> {
75 fn vec_len(&self) -> usize {
76 self.nrows()
77 }
78}
79
80impl<F: Scalar> VectorIndex<F> for Col<F> {
81 fn get_scalar(&self, i: usize) -> F {
82 self[i]
83 }
84 fn set_scalar(&mut self, i: usize, value: F) {
85 self[i] = value;
86 }
87}
88
89impl<F: Scalar> SampleStandardNormal for Col<F>
90where
91 StandardNormal: Distribution<F>,
92{
93 fn sample_standard_normal<R: Rng + ?Sized>(template: &Self, rng: &mut R) -> Self {
94 Self::from_fn(template.nrows(), |_| StandardNormal.sample(rng))
95 }
96}
97
98impl<F: Scalar> ScaleInPlace<F> for Col<F> {
99 fn scale_in_place(&mut self, scalar: F) {
100 faer::zip!(self.as_mut()).for_each(|faer::unzip!(x)| *x = *x * scalar);
101 }
102}
103
104impl<F: Scalar> ComponentMulAssign for Col<F> {
105 fn component_mul_assign(&mut self, other: &Self) {
106 assert_eq!(
107 self.nrows(),
108 other.nrows(),
109 "component_mul_assign: shape mismatch"
110 );
111 faer::zip!(self.as_mut(), other.as_ref()).for_each(|faer::unzip!(x, y)| *x = *x * *y);
112 }
113}
114
115impl<F: Scalar> ComponentMaxAssign for Col<F> {
116 fn component_max_assign(&mut self, other: &Self) {
117 assert_eq!(
118 self.nrows(),
119 other.nrows(),
120 "component_max_assign: shape mismatch"
121 );
122 faer::zip!(self.as_mut(), other.as_ref()).for_each(|faer::unzip!(x, y)| *x = x.max(*y));
123 }
124}
125
126impl<F: Scalar> FloorZerosInPlace<F> for Col<F> {
127 fn floor_zeros_in_place(&mut self, value: F) {
128 faer::zip!(self.as_mut()).for_each(|faer::unzip!(x)| {
129 if *x <= F::zero() {
130 *x = value;
131 }
132 });
133 }
134}
135
136impl<F: Scalar> ComponentDivAssign for Col<F> {
137 fn component_div_assign(&mut self, other: &Self) {
138 assert_eq!(
139 self.nrows(),
140 other.nrows(),
141 "component_div_assign: shape mismatch"
142 );
143 faer::zip!(self.as_mut(), other.as_ref()).for_each(|faer::unzip!(x, y)| *x = *x / *y);
144 }
145}
146
147impl<F: Scalar> ClampInPlace for Col<F> {
148 fn clamp_in_place(&mut self, lower: &Self, upper: &Self) {
149 assert_eq!(
150 self.nrows(),
151 lower.nrows(),
152 "clamp_in_place: lower shape mismatch"
153 );
154 assert_eq!(
155 self.nrows(),
156 upper.nrows(),
157 "clamp_in_place: upper shape mismatch"
158 );
159 faer::zip!(self.as_mut(), lower.as_ref(), upper.as_ref())
160 .for_each(|faer::unzip!(x, lo, hi)| *x = (*x).max(*lo).min(*hi));
163 }
164}
165
166impl<F: Scalar + faer_traits::ComplexField> BoxAffineScaling<F> for Col<F> {
167 fn compute_cl_scaling(
168 &self,
169 gradient: &Self,
170 lower: &Self,
171 upper: &Self,
172 d_sq: &mut Self,
173 c_diag: &mut Self,
174 ) {
175 let n = self.nrows();
176 assert_eq!(
177 n,
178 gradient.nrows(),
179 "compute_cl_scaling: gradient shape mismatch"
180 );
181 assert_eq!(n, lower.nrows(), "compute_cl_scaling: lower shape mismatch");
182 assert_eq!(n, upper.nrows(), "compute_cl_scaling: upper shape mismatch");
183 assert_eq!(n, d_sq.nrows(), "compute_cl_scaling: d_sq shape mismatch");
184 assert_eq!(
185 n,
186 c_diag.nrows(),
187 "compute_cl_scaling: c_diag shape mismatch"
188 );
189 for i in 0..n {
191 let (d_sq_i, c_i) = cl_scaling_pair::<F>(self[i], gradient[i], lower[i], upper[i]);
192 d_sq[i] = d_sq_i;
193 c_diag[i] = c_i;
194 }
195 }
196
197 fn max_feasible_step(&self, step: &Self, lower: &Self, upper: &Self) -> F {
198 let n = self.nrows();
199 assert_eq!(n, step.nrows(), "max_feasible_step: step shape mismatch");
200 assert_eq!(n, lower.nrows(), "max_feasible_step: lower shape mismatch");
201 assert_eq!(n, upper.nrows(), "max_feasible_step: upper shape mismatch");
202 let mut tau = F::infinity();
203 for i in 0..n {
204 let t = max_feasible_step_component::<F>(self[i], step[i], lower[i], upper[i]);
205 if t < tau {
206 tau = t;
207 }
208 }
209 tau
210 }
211
212 fn cl_kkt_inf_norm(&self, d_sq: &Self) -> F {
213 assert_eq!(
214 self.nrows(),
215 d_sq.nrows(),
216 "cl_kkt_inf_norm: shape mismatch"
217 );
218 self.iter()
219 .zip(d_sq.iter())
220 .map(|(&v, &d)| <F as num_traits::Float>::abs(v) / d)
221 .fold(F::zero(), |a, b| if b > a { b } else { a })
222 }
223
224 fn weighted_norm_squared(&self, weights: &Self) -> F {
225 assert_eq!(
226 self.nrows(),
227 weights.nrows(),
228 "weighted_norm_squared: shape mismatch"
229 );
230 self.iter()
231 .zip(weights.iter())
232 .map(|(&v, &w)| v * v * w)
233 .sum()
234 }
235
236 fn project_strictly_inside(&mut self, lower: &Self, upper: &Self, rstep: F) {
237 let n = self.nrows();
238 assert_eq!(
239 n,
240 lower.nrows(),
241 "project_strictly_inside: lower shape mismatch"
242 );
243 assert_eq!(
244 n,
245 upper.nrows(),
246 "project_strictly_inside: upper shape mismatch"
247 );
248 for i in 0..n {
249 self[i] = project_strictly_inside_component::<F>(self[i], lower[i], upper[i], rstep);
250 }
251 }
252}
253
254impl<F> MatVec<Col<F>> for Mat<F>
260where
261 F: Scalar + faer_traits::ComplexField,
262{
263 fn matvec(&self, x: &Col<F>) -> Col<F> {
264 assert_eq!(
265 self.ncols(),
266 x.nrows(),
267 "matvec: A.ncols ({}) != x.nrows ({})",
268 self.ncols(),
269 x.nrows()
270 );
271 let mut y = Col::<F>::zeros(self.nrows());
272 matmul(
273 y.as_mut(),
274 Accum::Replace,
275 self.as_ref(),
276 x.as_ref(),
277 F::one(),
278 Par::Seq,
279 );
280 y
281 }
282}
283
284impl<F> MatTransposeVec<Col<F>> for Mat<F>
285where
286 F: Scalar + faer_traits::ComplexField,
287{
288 fn mat_transpose_vec(&self, x: &Col<F>) -> Col<F> {
289 assert_eq!(
290 self.nrows(),
291 x.nrows(),
292 "mat_transpose_vec: A.nrows ({}) != x.nrows ({})",
293 self.nrows(),
294 x.nrows()
295 );
296 let mut y = Col::<F>::zeros(self.ncols());
297 matmul(
298 y.as_mut(),
299 Accum::Replace,
300 self.transpose(),
301 x.as_ref(),
302 F::one(),
303 Par::Seq,
304 );
305 y
306 }
307}
308
309impl<F> GramMatrix for Mat<F>
310where
311 F: Scalar + faer_traits::ComplexField,
312{
313 fn gram(&self) -> Self {
314 let n = self.ncols();
315 let mut g = Self::zeros(n, n);
316 matmul(
317 g.as_mut(),
318 Accum::Replace,
319 self.transpose(),
320 self.as_ref(),
321 F::one(),
322 Par::Seq,
323 );
324 g
325 }
326}
327
328impl<F: Scalar> MaxDiagonal<F> for Mat<F> {
329 fn max_diagonal(&self) -> F {
330 assert_eq!(
331 self.nrows(),
332 self.ncols(),
333 "max_diagonal: matrix must be square, got {}x{}",
334 self.nrows(),
335 self.ncols()
336 );
337 (0..self.nrows())
338 .map(|i| self[(i, i)])
339 .fold(F::neg_infinity(), F::max)
340 }
341}
342
343impl<F: Scalar> MatDiagonal<Col<F>> for Mat<F> {
344 fn diagonal(&self) -> Col<F> {
345 assert_eq!(
346 self.nrows(),
347 self.ncols(),
348 "diagonal: matrix must be square, got {}x{}",
349 self.nrows(),
350 self.ncols()
351 );
352 Col::from_fn(self.nrows(), |i| self[(i, i)])
353 }
354}
355
356impl<F: Scalar> AddDiagonalVectorInPlace<Col<F>> for Mat<F> {
357 fn add_diagonal_vector_in_place(&mut self, diag: &Col<F>) {
358 assert_eq!(
359 self.nrows(),
360 self.ncols(),
361 "add_diagonal_vector_in_place: matrix must be square, got {}x{}",
362 self.nrows(),
363 self.ncols()
364 );
365 assert_eq!(
366 self.nrows(),
367 diag.nrows(),
368 "add_diagonal_vector_in_place: matrix is {}x{} but diag has length {}",
369 self.nrows(),
370 self.ncols(),
371 diag.nrows()
372 );
373 for i in 0..self.nrows() {
374 let entry = &mut self[(i, i)];
375 *entry = *entry + diag[i];
376 }
377 }
378}
379
380impl<F: Scalar> ScaleInPlace<F> for Mat<F> {
381 fn scale_in_place(&mut self, scalar: F) {
382 faer::zip!(self.as_mut()).for_each(|faer::unzip!(x)| *x = *x * scalar);
383 }
384}
385
386impl<F> MatrixIdentity for Mat<F>
387where
388 F: Scalar + faer_traits::ComplexField,
389{
390 fn identity(n: usize) -> Self {
391 Self::identity(n, n)
392 }
393}
394
395impl<F: Scalar> MatrixFromDiagonal<Col<F>> for Mat<F> {
396 fn from_diagonal(diag: &Col<F>) -> Self {
397 let n = diag.nrows();
398 Self::from_fn(n, n, |i, j| if i == j { diag[i] } else { F::zero() })
399 }
400}
401
402impl<F: Scalar> DenseMatrixFromFn<F> for Col<F> {
403 type Matrix = Mat<F>;
404 fn dense_from_fn<G: FnMut(usize, usize) -> F>(rows: usize, cols: usize, f: G) -> Mat<F> {
405 Mat::from_fn(rows, cols, f)
406 }
407}
408
409impl<F> SymmetricEigen<Col<F>> for Mat<F>
410where
411 F: Scalar + faer_traits::ComplexField<Real = F>,
412{
413 fn try_eigh(&self) -> Result<(Self, Col<F>), SymmetricEigenError> {
414 assert_eq!(
415 self.nrows(),
416 self.ncols(),
417 "try_eigh: matrix must be square, got {}x{}",
418 self.nrows(),
419 self.ncols()
420 );
421 let eig = self
425 .self_adjoint_eigen(Side::Lower)
426 .map_err(|_| SymmetricEigenError::Failed)?;
427 let n = self.nrows();
428 let u_ref = eig.U();
429 let s_ref = eig.S();
430 let mut u_mat = Self::zeros(n, n);
433 for j in 0..n {
434 for i in 0..n {
435 u_mat[(i, j)] = u_ref[(i, j)];
436 }
437 }
438 let s_col = Col::<F>::from_fn(n, |i| s_ref[i]);
439 Ok((u_mat, s_col))
440 }
441}
442
443impl<F> RankOneUpdate<Col<F>, F> for Mat<F>
444where
445 F: Scalar + faer_traits::ComplexField,
446{
447 fn rank_one_update(&mut self, alpha: F, v: &Col<F>) {
448 assert_eq!(
449 self.nrows(),
450 self.ncols(),
451 "rank_one_update: matrix must be square, got {}x{}",
452 self.nrows(),
453 self.ncols()
454 );
455 assert_eq!(
456 self.nrows(),
457 v.nrows(),
458 "rank_one_update: matrix is {}x{} but v has length {}",
459 self.nrows(),
460 self.ncols(),
461 v.nrows()
462 );
463 matmul(
466 self.as_mut(),
467 Accum::Add,
468 v.as_mat(),
469 v.transpose().as_mat(),
470 alpha,
471 Par::Seq,
472 );
473 }
474}
475
476impl<F> GeneralRankOneUpdate<Col<F>, F> for Mat<F>
477where
478 F: Scalar + faer_traits::ComplexField,
479{
480 fn general_rank_one_update(&mut self, alpha: F, u: &Col<F>, v: &Col<F>) {
481 assert_eq!(
482 self.nrows(),
483 self.ncols(),
484 "general_rank_one_update: matrix must be square, got {}x{}",
485 self.nrows(),
486 self.ncols()
487 );
488 assert_eq!(
489 self.nrows(),
490 u.nrows(),
491 "general_rank_one_update: matrix is {}x{} but u has length {}",
492 self.nrows(),
493 self.ncols(),
494 u.nrows()
495 );
496 assert_eq!(
497 self.ncols(),
498 v.nrows(),
499 "general_rank_one_update: matrix is {}x{} but v has length {}",
500 self.nrows(),
501 self.ncols(),
502 v.nrows()
503 );
504 matmul(
507 self.as_mut(),
508 Accum::Add,
509 u.as_mat(),
510 v.transpose().as_mat(),
511 alpha,
512 Par::Seq,
513 );
514 }
515}
516
517impl<F> LinearSolveSpd<Col<F>> for Mat<F>
518where
519 F: Scalar + faer_traits::ComplexField,
520{
521 fn solve_spd(&self, b: &Col<F>) -> Result<Col<F>, LinearSolveError> {
522 assert_eq!(
523 self.nrows(),
524 self.ncols(),
525 "solve_spd: matrix must be square, got {}x{}",
526 self.nrows(),
527 self.ncols()
528 );
529 assert_eq!(
530 self.nrows(),
531 b.nrows(),
532 "solve_spd: A.nrows ({}) != b.nrows ({})",
533 self.nrows(),
534 b.nrows()
535 );
536 let llt = Llt::new(self.as_ref(), Side::Lower)
537 .map_err(|_| LinearSolveError::NotPositiveDefinite)?;
538 let mut x = b.clone();
539 llt.solve_in_place(&mut x);
540 Ok(x)
541 }
542}
543
544#[cfg(test)]
545mod tests {
546 use super::*;
547
548 fn approx_eq(a: f64, b: f64, tol: f64) -> bool {
549 (a - b).abs() < tol
550 }
551
552 fn mat2(row0: [f64; 2], row1: [f64; 2]) -> Mat<f64> {
553 let rows = [row0, row1];
554 Mat::from_fn(2, 2, |i, j| rows[i][j])
555 }
556
557 #[test]
558 fn matvec_known_values() {
559 let a = mat2([1.0, 2.0], [3.0, 4.0]);
560 let x = Col::<f64>::from_fn(2, |i| [5.0, 6.0][i]);
561 let y = a.matvec(&x);
562 assert_eq!(y.nrows(), 2);
563 assert!(approx_eq(y[0], 17.0, 1e-12));
564 assert!(approx_eq(y[1], 39.0, 1e-12));
565 }
566
567 #[test]
568 fn mat_transpose_vec_known_values() {
569 let a = mat2([1.0, 2.0], [3.0, 4.0]);
570 let x = Col::<f64>::from_fn(2, |i| [5.0, 6.0][i]);
571 let y = a.mat_transpose_vec(&x);
572 assert_eq!(y.nrows(), 2);
573 assert!(approx_eq(y[0], 23.0, 1e-12));
575 assert!(approx_eq(y[1], 34.0, 1e-12));
576 }
577
578 #[test]
579 fn gram_known_values() {
580 let a = mat2([1.0, 2.0], [3.0, 4.0]);
581 let g = a.gram();
582 assert_eq!(g.nrows(), 2);
584 assert_eq!(g.ncols(), 2);
585 assert!(approx_eq(g[(0, 0)], 10.0, 1e-12));
586 assert!(approx_eq(g[(0, 1)], 14.0, 1e-12));
587 assert!(approx_eq(g[(1, 0)], 14.0, 1e-12));
588 assert!(approx_eq(g[(1, 1)], 20.0, 1e-12));
589 }
590
591 #[test]
592 fn solve_spd_happy_path() {
593 let a = mat2([4.0, 1.0], [1.0, 3.0]);
594 let b = Col::<f64>::from_fn(2, |i| [1.0, 2.0][i]);
595 let x = a.solve_spd(&b).expect("SPD system must solve");
596 assert!(approx_eq(x[0], 1.0 / 11.0, 1e-12));
598 assert!(approx_eq(x[1], 7.0 / 11.0, 1e-12));
599 }
600
601 #[test]
602 fn solve_spd_indefinite_returns_error() {
603 let a = mat2([1.0, 2.0], [2.0, 1.0]);
604 let b = Col::<f64>::from_fn(2, |i| [1.0, 1.0][i]);
605 let err = a.solve_spd(&b).expect_err("indefinite must fail");
606 assert_eq!(err, LinearSolveError::NotPositiveDefinite);
607 }
608
609 #[test]
610 fn gram_of_rank_deficient_is_singular() {
611 let a = mat2([1.0, 2.0], [2.0, 4.0]);
612 let g = a.gram();
613 let b = Col::<f64>::from_fn(2, |i| [1.0, 1.0][i]);
614 let err = g.solve_spd(&b).expect_err("rank-deficient gram must fail");
615 assert_eq!(err, LinearSolveError::NotPositiveDefinite);
616 }
617
618 #[test]
619 fn add_diagonal_regularizes_singular_gram() {
620 let a = mat2([1.0, 2.0], [2.0, 4.0]);
621 let mut g = a.gram();
622 let b = Col::<f64>::from_fn(2, |i| [1.0, 1.0][i]);
623 assert!(g.clone().solve_spd(&b).is_err());
624 g.add_diagonal_vector_in_place(&Col::<f64>::from_fn(2, |_| 1e-3));
625 let x = g.solve_spd(&b).expect("damped gram must be SPD");
626 assert_eq!(x.nrows(), 2);
627 }
628
629 #[test]
630 fn matrix_identity_is_diagonal_ones() {
631 let i: Mat<f64> = MatrixIdentity::identity(3);
632 assert_eq!((i.nrows(), i.ncols()), (3, 3));
633 for r in 0..3 {
634 for c in 0..3 {
635 let want = if r == c { 1.0 } else { 0.0 };
636 assert!(approx_eq(i[(r, c)], want, 1e-12));
637 }
638 }
639 }
640
641 #[test]
642 fn matrix_from_diagonal_places_vector_on_diagonal() {
643 let d = Col::<f64>::from_fn(3, |i| [2.0, 3.0, 5.0][i]);
644 let m: Mat<f64> = MatrixFromDiagonal::from_diagonal(&d);
645 assert_eq!((m.nrows(), m.ncols()), (3, 3));
646 for r in 0..3 {
647 for c in 0..3 {
648 let want = if r == c { d[r] } else { 0.0 };
649 assert!(approx_eq(m[(r, c)], want, 1e-12));
650 }
651 }
652 }
653
654 #[test]
655 fn rank_one_update_outer_product() {
656 let mut a = Mat::<f64>::zeros(3, 3);
657 let v = Col::<f64>::from_fn(3, |i| [1.0, 2.0, 3.0][i]);
658 a.rank_one_update(2.0, &v);
659 assert!(approx_eq(a[(0, 0)], 2.0, 1e-12));
660 assert!(approx_eq(a[(0, 1)], 4.0, 1e-12));
661 assert!(approx_eq(a[(0, 2)], 6.0, 1e-12));
662 assert!(approx_eq(a[(1, 1)], 8.0, 1e-12));
663 assert!(approx_eq(a[(2, 2)], 18.0, 1e-12));
664 }
665
666 #[test]
667 fn symmetric_eigen_recovers_factorization() {
668 let c = mat2([2.0, 1.0], [1.0, 2.0]);
670 let (b, lambda) = c.try_eigh().expect("eigendecomposition");
671 let mut bd = b.clone();
673 for j in 0..2 {
674 for i in 0..2 {
675 bd[(i, j)] *= lambda[j];
676 }
677 }
678 let mut recomposed = Mat::<f64>::zeros(2, 2);
679 matmul(
680 recomposed.as_mut(),
681 Accum::Replace,
682 bd.as_ref(),
683 b.transpose(),
684 1.0,
685 Par::Seq,
686 );
687 for r in 0..2 {
688 for c_idx in 0..2 {
689 assert!(approx_eq(recomposed[(r, c_idx)], c[(r, c_idx)], 1e-10));
690 }
691 }
692 }
693
694 #[test]
695 fn add_diagonal_vector_in_place_adds_per_index() {
696 let mut a = Mat::<f64>::from_fn(3, 3, |i, j| (i * 3 + j + 1) as f64);
697 a.add_diagonal_vector_in_place(&Col::<f64>::from_fn(3, |i| [10.0, 100.0, 1000.0][i]));
698 assert!(approx_eq(a[(0, 0)], 11.0, 1e-12));
700 assert!(approx_eq(a[(1, 1)], 105.0, 1e-12));
701 assert!(approx_eq(a[(2, 2)], 1009.0, 1e-12));
702 assert!(approx_eq(a[(0, 1)], 2.0, 1e-12));
703 assert!(approx_eq(a[(2, 1)], 8.0, 1e-12));
704 }
705}