1use ndarray::{array, Array1, Array2};
7use num_complex::Complex64;
8use std::f64::consts::PI;
9
10use crate::core::integration::gauss::gauss_legendre;
11use crate::core::mesh::element::{cross_product, normalize};
12use crate::core::types::{ElementType, IntegrationResult, PhysicsParams};
13
14pub const MAX_SUBELEMENTS: usize = 110;
16
17#[derive(Debug, Clone, Copy)]
22pub struct QuadratureParams {
23 pub edge_gauss_order: usize,
25 pub subelement_gauss_order: usize,
27 pub edge_sections: usize,
29 pub subtriangles_per_section: usize,
31}
32
33impl Default for QuadratureParams {
34 fn default() -> Self {
35 Self {
36 edge_gauss_order: 3,
37 subelement_gauss_order: 4,
38 edge_sections: 4,
39 subtriangles_per_section: 2,
40 }
41 }
42}
43
44impl QuadratureParams {
45 pub fn for_ka(ka: f64) -> Self {
49 if ka < 0.3 {
50 Self {
52 edge_gauss_order: 3,
53 subelement_gauss_order: 4,
54 edge_sections: 4,
55 subtriangles_per_section: 2,
56 }
57 } else if ka < 1.0 {
58 Self {
60 edge_gauss_order: 4,
61 subelement_gauss_order: 5,
62 edge_sections: 6,
63 subtriangles_per_section: 2,
64 }
65 } else if ka < 2.0 {
66 Self {
68 edge_gauss_order: 5,
69 subelement_gauss_order: 6,
70 edge_sections: 8,
71 subtriangles_per_section: 3,
72 }
73 } else {
74 Self {
76 edge_gauss_order: 6,
77 subelement_gauss_order: 7,
78 edge_sections: 10,
79 subtriangles_per_section: 4,
80 }
81 }
82 }
83
84 pub fn high_accuracy() -> Self {
86 Self {
87 edge_gauss_order: 8,
88 subelement_gauss_order: 8,
89 edge_sections: 12,
90 subtriangles_per_section: 4,
91 }
92 }
93}
94
95const CSI6: [f64; 6] = [0.0, 1.0, 0.0, 0.5, 0.5, 0.0];
99const ETA6: [f64; 6] = [0.0, 0.0, 1.0, 0.0, 0.5, 0.5];
100
101const CSI8: [f64; 8] = [1.0, -1.0, -1.0, 1.0, 0.0, -1.0, 0.0, 1.0];
103const ETA8: [f64; 8] = [1.0, 1.0, -1.0, -1.0, 1.0, 0.0, -1.0, 0.0];
104
105pub fn singular_integration(
124 source_point: &Array1<f64>,
125 source_normal: &Array1<f64>,
126 element_coords: &Array2<f64>,
127 element_type: ElementType,
128 physics: &PhysicsParams,
129 bc_values: Option<&[Complex64]>,
130 bc_type: i32,
131 compute_rhs: bool,
132) -> IntegrationResult {
133 let element_size = estimate_element_size(element_coords, element_type);
135 let ka = physics.wave_number * element_size;
136 let quadrature = QuadratureParams::for_ka(ka);
137
138 singular_integration_with_params(
139 source_point,
140 source_normal,
141 element_coords,
142 element_type,
143 physics,
144 bc_values,
145 bc_type,
146 compute_rhs,
147 &quadrature,
148 )
149}
150
151pub fn singular_integration_with_params(
155 source_point: &Array1<f64>,
156 source_normal: &Array1<f64>,
157 element_coords: &Array2<f64>,
158 element_type: ElementType,
159 physics: &PhysicsParams,
160 bc_values: Option<&[Complex64]>,
161 bc_type: i32,
162 compute_rhs: bool,
163 quadrature: &QuadratureParams,
164) -> IntegrationResult {
165 let num_nodes = element_type.num_nodes();
166 let wavruim = physics.harmonic_factor * physics.wave_number;
167 let k2 = physics.wave_number * physics.wave_number;
168
169 let mut result = IntegrationResult::default();
170
171 let ngpo1 = quadrature.edge_gauss_order;
173 let (coord_gau, weit_gau) = gauss_legendre(ngpo1);
174 let nsec1 = quadrature.edge_sections;
175 let nsec2 = quadrature.subtriangles_per_section;
176
177 for ieg in 0..num_nodes {
179 let ig1 = (ieg + 1) % num_nodes;
180 let ig2 = ieg + num_nodes;
181
182 let mut diff_poi = Array1::zeros(3);
184 let mut leneg = 0.0;
185 for i in 0..3 {
186 diff_poi[i] = element_coords[[ig1, i]] - element_coords[[ieg, i]];
187 leneg += diff_poi[i] * diff_poi[i];
188 }
189 leneg = leneg.sqrt();
190
191 let diff_poo = &diff_poi / leneg;
193 let leneg_scaled = leneg / (2.0 * nsec1 as f64);
194
195 let mut zre = Complex64::new(0.0, 0.0);
197
198 let delsec = 2.0 / nsec1 as f64;
200 let mut secmid = -1.0 - delsec / 2.0;
201
202 for _isec in 0..nsec1 {
203 secmid += delsec;
204
205 for ig in 0..ngpo1 {
207 let sga = secmid + coord_gau[ig] / nsec1 as f64;
208 let wga = weit_gau[ig] * leneg_scaled;
209
210 let mut crd_gp = Array1::zeros(3);
212 let mut diff_fsp = Array1::zeros(3);
213 for i in 0..3 {
214 crd_gp[i] = element_coords[[ieg, i]] + diff_poi[i] * (sga + 1.0) / 2.0;
215 diff_fsp[i] = crd_gp[i] - source_point[i];
216 }
217
218 let (unit_r, dis_fsp) = normalize(&diff_fsp);
220
221 if dis_fsp < 1e-15 {
222 continue;
223 }
224
225 let re1 = wavruim * dis_fsp;
227 let re2 = 4.0 * PI * dis_fsp;
228 let zg = Complex64::new(re1.cos() / re2, re1.sin() / re2);
229
230 let z1 = Complex64::new(-1.0 / dis_fsp, wavruim);
232 let zg_factor = zg * z1;
233
234 let mut zdg_dy = Array1::<Complex64>::zeros(3);
236 for i in 0..3 {
237 zdg_dy[i] = zg_factor * unit_r[i];
238 }
239
240 let zwk = array![
242 zdg_dy[1] * diff_poo[2] - zdg_dy[2] * diff_poo[1],
243 zdg_dy[2] * diff_poo[0] - zdg_dy[0] * diff_poo[2],
244 zdg_dy[0] * diff_poo[1] - zdg_dy[1] * diff_poo[0]
245 ];
246
247 zre += (zwk[0] * source_normal[0]
249 + zwk[1] * source_normal[1]
250 + zwk[2] * source_normal[2])
251 * wga;
252 }
253 }
254 result.d2g_dnxdny_integral += zre;
255
256 for isec in 0..nsec2 {
258 let (ssub, tsub, aresub) = match element_type {
260 ElementType::Tri3 => {
261 let aresub = 1.0 / 24.0 / nsec2 as f64;
262 let mut ssub = [0.0; 3];
263 let mut tsub = [0.0; 3];
264
265 ssub[0] = 1.0 / 3.0;
266 tsub[0] = 1.0 / 3.0;
267
268 if isec == 0 {
269 ssub[1] = CSI6[ieg];
270 ssub[2] = CSI6[ig2];
271 tsub[1] = ETA6[ieg];
272 tsub[2] = ETA6[ig2];
273 } else {
274 ssub[1] = CSI6[ig2];
275 ssub[2] = CSI6[ig1];
276 tsub[1] = ETA6[ig2];
277 tsub[2] = ETA6[ig1];
278 }
279 (ssub, tsub, aresub)
280 }
281 ElementType::Quad4 => {
282 let aresub = 0.25 / nsec2 as f64;
283 let mut ssub = [0.0; 3];
284 let mut tsub = [0.0; 3];
285
286 ssub[0] = 0.0;
287 tsub[0] = 0.0;
288
289 if isec == 0 {
290 ssub[1] = CSI8[ieg];
291 ssub[2] = CSI8[ig2];
292 tsub[1] = ETA8[ieg];
293 tsub[2] = ETA8[ig2];
294 } else {
295 ssub[1] = CSI8[ig2];
296 ssub[2] = CSI8[ig1];
297 tsub[1] = ETA8[ig2];
298 tsub[2] = ETA8[ig1];
299 }
300 (ssub, tsub, aresub)
301 }
302 };
303
304 let ngausin = quadrature.subelement_gauss_order;
307 let quad_points = gauss_legendre(ngausin);
308 let (gau_coords, gau_weights) = quad_points;
309
310 for (i, &sga) in gau_coords.iter().enumerate() {
311 for (j, &tga) in gau_coords.iter().enumerate() {
312 let wei_gau = gau_weights[i] * gau_weights[j];
313
314 let sgg = 0.5 * (1.0 - sga) * ssub[0]
316 + 0.25 * (1.0 + sga) * ((1.0 - tga) * ssub[1] + (1.0 + tga) * ssub[2]);
317 let tgg = 0.5 * (1.0 - sga) * tsub[0]
318 + 0.25 * (1.0 + sga) * ((1.0 - tga) * tsub[1] + (1.0 + tga) * tsub[2]);
319
320 let (shape_fn, _shape_ds, _shape_dt, jacobian, el_norm, crd_poi) =
322 compute_shape_and_jacobian(element_coords, element_type, sgg, tgg);
323
324 let wga = wei_gau * (1.0 + sga) * aresub * jacobian;
325
326 let mut diff_fsp = Array1::zeros(3);
328 for i in 0..3 {
329 diff_fsp[i] = crd_poi[i] - source_point[i];
330 }
331
332 let (unit_r, dis_fsp) = normalize(&diff_fsp);
333
334 if dis_fsp < 1e-15 {
335 continue;
336 }
337
338 let re1 = wavruim * dis_fsp;
340 let re2 = wga / (4.0 * PI * dis_fsp);
341 let zg = Complex64::new(re1.cos() * re2, re1.sin() * re2);
342
343 let z1 = Complex64::new(-1.0 / dis_fsp, wavruim);
345 let zhh_base = zg * z1;
346 let re1_h = unit_r.dot(&el_norm); let re2_h = -unit_r.dot(source_normal); let zhh = zhh_base * re1_h;
349 let zht = zhh_base * re2_h;
350
351 result.g_integral += zg;
353 result.dg_dn_integral += zhh;
354 result.dg_dnx_integral += zht;
355
356 result.d2g_dnxdny_integral +=
358 zg * k2 * source_normal.dot(&el_norm);
359
360 if compute_rhs && bc_type == 0 {
362 if let Some(bc) = bc_values {
364 let mut zbgao = Complex64::new(0.0, 0.0);
365 for (ii, &n) in shape_fn.iter().enumerate() {
366 if ii < bc.len() {
367 zbgao += bc[ii] * n;
368 }
369 }
370 let gamma = physics.gamma();
371 let tau = physics.tau;
372 let beta = physics.burton_miller_beta();
373 result.rhs_contribution +=
374 (zg * gamma * tau + zht * beta) * zbgao;
375 }
376 }
377 }
378 }
379 }
380 }
381
382 if compute_rhs && bc_type == 1 {
384 if let Some(bc) = bc_values {
385 let zbgao = bc.iter().sum::<Complex64>() / bc.len() as f64;
386 let gamma = physics.gamma();
387 let tau = physics.tau;
388 let beta = physics.burton_miller_beta();
389 result.rhs_contribution = -(result.dg_dn_integral * gamma * tau
390 + result.d2g_dnxdny_integral * beta)
391 * zbgao;
392 }
393 }
394
395 result
396}
397
398fn compute_shape_and_jacobian(
400 element_coords: &Array2<f64>,
401 element_type: ElementType,
402 s: f64,
403 t: f64,
404) -> (Vec<f64>, Vec<f64>, Vec<f64>, f64, Array1<f64>, Array1<f64>) {
405 let num_nodes = element_type.num_nodes();
406
407 let (shape_fn, shape_ds, shape_dt) = match element_type {
408 ElementType::Tri3 => {
409 let shape_fn = vec![1.0 - s - t, s, t];
414 let shape_ds = vec![-1.0, 1.0, 0.0];
415 let shape_dt = vec![-1.0, 0.0, 1.0];
416 (shape_fn, shape_ds, shape_dt)
417 }
418 ElementType::Quad4 => {
419 let s1 = 0.25 * (s + 1.0);
421 let s2 = 0.25 * (s - 1.0);
422 let t1 = t + 1.0;
423 let t2 = t - 1.0;
424
425 let shape_fn = vec![s1 * t1, -s2 * t1, s2 * t2, -s1 * t2];
426 let shape_ds = vec![
427 0.25 * (t + 1.0),
428 -0.25 * (t + 1.0),
429 0.25 * (t - 1.0),
430 -0.25 * (t - 1.0),
431 ];
432 let shape_dt = vec![
433 0.25 * (s + 1.0),
434 0.25 * (1.0 - s),
435 0.25 * (s - 1.0),
436 -0.25 * (s + 1.0),
437 ];
438 (shape_fn, shape_ds, shape_dt)
439 }
440 };
441
442 let mut crd_poi = Array1::zeros(3);
444 let mut dx_ds = Array1::zeros(3);
445 let mut dx_dt = Array1::zeros(3);
446
447 for i in 0..num_nodes {
448 for j in 0..3 {
449 crd_poi[j] += shape_fn[i] * element_coords[[i, j]];
450 dx_ds[j] += shape_ds[i] * element_coords[[i, j]];
451 dx_dt[j] += shape_dt[i] * element_coords[[i, j]];
452 }
453 }
454
455 let normal = cross_product(&dx_ds, &dx_dt);
457 let jacobian = normal.dot(&normal).sqrt();
458
459 let el_norm = if jacobian > 1e-15 {
460 normal / jacobian
461 } else {
462 Array1::zeros(3)
463 };
464
465 (shape_fn, shape_ds, shape_dt, jacobian, el_norm, crd_poi)
466}
467
468#[derive(Debug, Clone)]
470pub struct Subelement {
471 pub xi_center: f64,
473 pub eta_center: f64,
475 pub factor: f64,
477 pub gauss_order: usize,
479 pub tri_vertices: Option<[(f64, f64); 3]>,
482}
483
484pub fn generate_subelements(
499 source_point: &Array1<f64>,
500 element_coords: &Array2<f64>,
501 element_type: ElementType,
502 element_area: f64,
503) -> Vec<Subelement> {
504 const MAX_NSE: usize = 60;
505 const NSE: usize = 4; const TOL_F: f64 = 3.0; const GAU_MAX: usize = 7; const GAU_MIN: usize = 4; const GAU_ACCU: f64 = 0.0005; let num_vertices = element_type.num_nodes();
514 let mut result = Vec::new();
515
516 let (csi_nodes, eta_nodes): (Vec<f64>, Vec<f64>) = match element_type {
518 ElementType::Tri3 => (CSI6[0..3].to_vec(), ETA6[0..3].to_vec()),
519 ElementType::Quad4 => (CSI8[0..4].to_vec(), ETA8[0..4].to_vec()),
520 };
521
522 let mut xi_sfp: Vec<Vec<f64>> = vec![vec![0.0; num_vertices]; MAX_NSE];
524 let mut et_sfp: Vec<Vec<f64>> = vec![vec![0.0; num_vertices]; MAX_NSE];
525
526 for j in 0..num_vertices {
528 xi_sfp[0][j] = csi_nodes[j];
529 et_sfp[0][j] = eta_nodes[j];
530 }
531
532 let mut nsfl = 1; let mut faclin = 2.0; loop {
536 let mut ndie = 0; faclin *= 0.5;
538 let arels = element_area * faclin * faclin;
539 let nsel = nsfl;
540
541 let xi_sep: Vec<Vec<f64>> = xi_sfp[..nsel].to_vec();
543 let et_sep: Vec<Vec<f64>> = et_sfp[..nsel].to_vec();
544
545 for idi in 0..nsel {
546 let scent: f64 = xi_sep[idi].iter().take(num_vertices).sum::<f64>() / num_vertices as f64;
548 let tcent: f64 = et_sep[idi].iter().take(num_vertices).sum::<f64>() / num_vertices as f64;
549
550 let crd_poip = local_to_global(element_coords, element_type, scent, tcent);
552
553 let dist = distance(&crd_poip, source_point);
555 let ratdis = dist / arels.sqrt();
556
557 if ratdis < TOL_F {
558 ndie += 1;
560 if ndie > 15 {
561 break;
563 }
564
565 nsfl = ndie * NSE;
566 let nsf0 = nsfl - NSE;
567
568 let mut xisp = vec![0.0; num_vertices * 2];
570 let mut etsp = vec![0.0; num_vertices * 2];
571
572 for j in 0..num_vertices {
573 let j1 = (j + 1) % num_vertices;
574 xisp[j] = xi_sep[idi][j];
575 xisp[j + num_vertices] = (xi_sep[idi][j] + xi_sep[idi][j1]) / 2.0;
576 etsp[j] = et_sep[idi][j];
577 etsp[j + num_vertices] = (et_sep[idi][j] + et_sep[idi][j1]) / 2.0;
578 }
579
580 for j in 0..num_vertices {
582 let nsu = nsf0 + j;
583 let j1 = j + num_vertices;
584 let j2 = if j1 > num_vertices {
585 j1 - 1
586 } else {
587 j1 + num_vertices - 1
588 };
589
590 match element_type {
591 ElementType::Quad4 => {
592 xi_sfp[nsu] = vec![xisp[j], xisp[j1], scent, xisp[j2]];
593 et_sfp[nsu] = vec![etsp[j], etsp[j1], tcent, etsp[j2]];
594 }
595 ElementType::Tri3 => {
596 xi_sfp[nsu] = vec![xisp[j], xisp[j1], xisp[j2]];
597 et_sfp[nsu] = vec![etsp[j], etsp[j1], etsp[j2]];
598
599 if j == num_vertices - 1 {
601 let nsu_center = nsf0 + NSE - 1;
602 xi_sfp[nsu_center] = vec![
603 xisp[num_vertices],
604 xisp[num_vertices + 1],
605 xisp[num_vertices + 2],
606 ];
607 et_sfp[nsu_center] = vec![
608 etsp[num_vertices],
609 etsp[num_vertices + 1],
610 etsp[num_vertices + 2],
611 ];
612 }
613 }
614 }
615 }
616 } else {
617 let (xi_center, eta_center, scale, tri_verts) = match element_type {
619 ElementType::Quad4 => {
620 let xc = xi_sep[idi].iter().sum::<f64>() / 4.0;
621 let ec = et_sep[idi].iter().sum::<f64>() / 4.0;
622 (xc, ec, faclin, None)
623 }
624 ElementType::Tri3 => {
625 let xc = (xi_sep[idi][0] + xi_sep[idi][1] + xi_sep[idi][2]) / 3.0;
627 let ec = (et_sep[idi][0] + et_sep[idi][1] + et_sep[idi][2]) / 3.0;
628 let vertices = [
629 (xi_sep[idi][0], et_sep[idi][0]),
630 (xi_sep[idi][1], et_sep[idi][1]),
631 (xi_sep[idi][2], et_sep[idi][2]),
632 ];
633 (xc, ec, faclin, Some(vertices))
634 }
635 };
636
637 let disfac = 0.5 / ratdis;
639 let gauss_order = compute_gauss_order(disfac, GAU_MIN, GAU_MAX, GAU_ACCU);
640
641 result.push(Subelement {
642 xi_center,
643 eta_center,
644 factor: scale,
645 gauss_order,
646 tri_vertices: tri_verts,
647 });
648
649 if result.len() >= MAX_SUBELEMENTS {
650 return result;
651 }
652 }
653 }
654
655 if ndie == 0 {
656 break;
657 }
658 }
659
660 result
661}
662
663fn compute_gauss_order(disfac: f64, gau_min: usize, gau_max: usize, accuracy: f64) -> usize {
665 for order in gau_min..=gau_max {
666 let err_g = estimate_error_g(order, disfac);
667 let err_h = estimate_error_h(order, disfac);
668 let err_e = estimate_error_e(order, disfac);
669
670 if err_g < accuracy && err_h < accuracy && err_e < accuracy {
671 return order;
672 }
673 }
674 gau_max
675}
676
677fn estimate_error_g(order: usize, disfac: f64) -> f64 {
679 let n = order as f64;
681 (disfac / (2.0 * n + 1.0)).powi(2 * order as i32 + 1)
682}
683
684fn estimate_error_h(order: usize, disfac: f64) -> f64 {
686 let n = order as f64;
687 (disfac / (2.0 * n + 1.0)).powi(2 * order as i32 + 2)
688}
689
690fn estimate_error_e(order: usize, disfac: f64) -> f64 {
692 let n = order as f64;
693 (disfac / (2.0 * n + 1.0)).powi(2 * order as i32 + 3)
694}
695
696fn local_to_global(
698 element_coords: &Array2<f64>,
699 element_type: ElementType,
700 s: f64,
701 t: f64,
702) -> Array1<f64> {
703 let shape_fn = match element_type {
704 ElementType::Tri3 => vec![1.0 - s - t, s, t],
705 ElementType::Quad4 => {
706 let s1 = 0.25 * (s + 1.0);
707 let s2 = 0.25 * (s - 1.0);
708 let t1 = t + 1.0;
709 let t2 = t - 1.0;
710 vec![s1 * t1, -s2 * t1, s2 * t2, -s1 * t2]
711 }
712 };
713
714 let num_nodes = element_type.num_nodes();
715 let mut result = Array1::zeros(3);
716 for i in 0..num_nodes {
717 for j in 0..3 {
718 result[j] += shape_fn[i] * element_coords[[i, j]];
719 }
720 }
721 result
722}
723
724fn distance(a: &Array1<f64>, b: &Array1<f64>) -> f64 {
726 let diff = a - b;
727 diff.dot(&diff).sqrt()
728}
729
730fn estimate_element_size(element_coords: &Array2<f64>, element_type: ElementType) -> f64 {
732 let num_nodes = element_type.num_nodes();
733 let mut total_length = 0.0;
734
735 for i in 0..num_nodes {
736 let j = (i + 1) % num_nodes;
737 let mut edge_length_sq = 0.0;
738 for k in 0..3 {
739 let diff = element_coords[[j, k]] - element_coords[[i, k]];
740 edge_length_sq += diff * diff;
741 }
742 total_length += edge_length_sq.sqrt();
743 }
744
745 total_length / num_nodes as f64
746}
747
748#[cfg(test)]
749mod tests {
750 use super::*;
751 use ndarray::Array2;
752
753 fn make_test_triangle() -> Array2<f64> {
754 Array2::from_shape_vec((3, 3), vec![0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0]).unwrap()
755 }
756
757 #[test]
758 fn test_local_to_global_triangle() {
759 let coords = make_test_triangle();
760
761 let center = local_to_global(&coords, ElementType::Tri3, 1.0 / 3.0, 1.0 / 3.0);
763 assert!((center[0] - 1.0 / 3.0).abs() < 1e-10);
764 assert!((center[1] - 1.0 / 3.0).abs() < 1e-10);
765 assert!(center[2].abs() < 1e-10);
766 }
767
768 #[test]
769 fn test_generate_subelements_far_point() {
770 let coords = make_test_triangle();
771 let source = array![10.0, 10.0, 0.0]; let subelements = generate_subelements(&source, &coords, ElementType::Tri3, 0.5);
774
775 assert!(subelements.len() <= 4);
777 }
778
779 #[test]
780 fn test_singular_integration_basic() {
781 let coords = make_test_triangle();
782 let source = array![1.0 / 3.0, 1.0 / 3.0, 0.0]; let normal = array![0.0, 0.0, 1.0];
784
785 let physics = PhysicsParams::new(10.0, 343.0, 1.21, false);
787
788 let result =
789 singular_integration(&source, &normal, &coords, ElementType::Tri3, &physics, None, 0, false);
790
791 assert!(result.g_integral.norm().is_finite());
793 assert!(result.g_integral.norm() > 0.0, "G integral was zero");
794
795 assert!(result.g_integral.re > 0.0, "G integral real part was: {} (expected positive at low freq)", result.g_integral.re);
797
798 assert!(result.dg_dn_integral.norm() < 1e-10, "H integral should be zero, was: {:?}", result.dg_dn_integral);
800 }
801
802 #[test]
803 fn test_compute_shape_and_jacobian() {
804 let coords = make_test_triangle();
805
806 let (shape, _, _, jac, normal, pos) =
807 compute_shape_and_jacobian(&coords, ElementType::Tri3, 0.5, 0.25);
808
809 let sum: f64 = shape.iter().sum();
811 assert!((sum - 1.0).abs() < 1e-10);
812
813 assert!((jac - 1.0).abs() < 1e-10);
815
816 assert!(normal[2].abs() > 0.99);
818 }
819}