1use crate::{traits::GeometryMap as GeometryMapTrait, types::Scalar};
3use itertools::izip;
4use ndelement::{reference_cell, traits::MappedFiniteElement, types::ReferenceCellType};
5use rlst::{Array, DynArray, MutableArrayImpl, RlstScalar, ValueArrayImpl};
6
7pub struct GeometryMap<'a, T, B2D, C2D> {
9 geometry_points: &'a Array<B2D, 2>,
10 entities: &'a Array<C2D, 2>,
11 tdim: usize,
12 gdim: usize,
13 table: DynArray<T, 4>,
14}
15
16fn dot<T: RlstScalar, Array1Impl: ValueArrayImpl<T, 1>>(
18 a: &Array<Array1Impl, 1>,
19 b: &Array<Array1Impl, 1>,
20) -> T {
21 debug_assert!(a.shape()[0] == b.shape()[0]);
22 izip!(a.iter_value(), b.iter_value())
23 .map(|(i, j)| i * j)
24 .sum::<T>()
25}
26
27fn inverse_and_det<
31 T: RlstScalar,
32 Array2Impl: ValueArrayImpl<T, 2>,
33 Array2ImplMut: MutableArrayImpl<T, 2>,
34>(
35 mat: &Array<Array2Impl, 2>,
36 result: &mut Array<Array2ImplMut, 2>,
37) -> T {
38 let gdim = mat.shape()[0];
39 let tdim = mat.shape()[1];
40
41 debug_assert!(result.shape()[0] == tdim);
42 debug_assert!(result.shape()[1] == gdim);
43 debug_assert!(tdim <= gdim);
44
45 match tdim {
46 0 => T::one(),
47 1 => {
48 let det = dot(&mat.r().slice(1, 0), &mat.r().slice(1, 0));
49 for (r, m) in izip!(result.iter_mut(), mat.iter_value()) {
50 *r = m / det;
51 }
52 det
53 }
54 2 => {
55 let col0 = &mat.r().slice(1, 0);
56 let col1 = &mat.r().slice(1, 1);
57 let ata = [
58 dot(col0, col0),
59 dot(col0, col1),
60 dot(col1, col0),
61 dot(col1, col1),
62 ];
63 let ata_det = ata[0] * ata[3] - ata[1] * ata[2];
64 let ata_inv = [
65 ata[3] / ata_det,
66 -ata[2] / ata_det,
67 -ata[1] / ata_det,
68 ata[0] / ata_det,
69 ];
70
71 for i in 0..gdim {
72 *result.get_mut([0, i]).unwrap() = ata_inv[0] * mat.get_value([i, 0]).unwrap()
73 + ata_inv[2] * mat.get_value([i, 1]).unwrap();
74 *result.get_mut([1, i]).unwrap() = ata_inv[1] * mat.get_value([i, 0]).unwrap()
75 + ata_inv[3] * mat.get_value([i, 1]).unwrap();
76 }
77 ata_det
78 }
79 3 => {
80 let col0 = &mat.r().slice(1, 0);
81 let col1 = &mat.r().slice(1, 1);
82 let col2 = &mat.r().slice(1, 2);
83 let ata = [
84 dot(col0, col0),
85 dot(col0, col1),
86 dot(col0, col2),
87 dot(col1, col0),
88 dot(col1, col1),
89 dot(col1, col2),
90 dot(col2, col0),
91 dot(col2, col1),
92 dot(col2, col2),
93 ];
94 let ata_det = ata[0] * (ata[4] * ata[8] - ata[5] * ata[7])
95 + ata[1] * (ata[5] * ata[6] - ata[3] * ata[8])
96 + ata[2] * (ata[3] * ata[7] - ata[4] * ata[6]);
97 let ata_inv = [
98 (ata[4] * ata[8] - ata[5] * ata[7]) / ata_det,
99 (ata[2] * ata[7] - ata[1] * ata[8]) / ata_det,
100 (ata[1] * ata[5] - ata[2] * ata[4]) / ata_det,
101 (ata[5] * ata[6] - ata[3] * ata[8]) / ata_det,
102 (ata[0] * ata[8] - ata[2] * ata[6]) / ata_det,
103 (ata[2] * ata[3] - ata[0] * ata[5]) / ata_det,
104 (ata[3] * ata[7] - ata[4] * ata[6]) / ata_det,
105 (ata[1] * ata[6] - ata[0] * ata[7]) / ata_det,
106 (ata[0] * ata[4] - ata[1] * ata[3]) / ata_det,
107 ];
108
109 for i in 0..gdim {
110 *result.get_mut([0, i]).unwrap() = ata_inv[0] * mat.get_value([i, 0]).unwrap()
111 + ata_inv[3] * mat.get_value([i, 1]).unwrap()
112 + ata_inv[6] * mat.get_value([i, 2]).unwrap();
113 *result.get_mut([1, i]).unwrap() = ata_inv[1] * mat.get_value([i, 0]).unwrap()
114 + ata_inv[4] * mat.get_value([i, 1]).unwrap()
115 + ata_inv[7] * mat.get_value([i, 2]).unwrap();
116 *result.get_mut([2, i]).unwrap() = ata_inv[2] * mat.get_value([i, 0]).unwrap()
117 + ata_inv[5] * mat.get_value([i, 1]).unwrap()
118 + ata_inv[8] * mat.get_value([i, 2]).unwrap();
119 }
120 ata_det
121 }
122 _ => {
123 panic!("Unsupported dimension");
124 }
125 }
126 .sqrt()
127}
128
129fn cross<T: RlstScalar, Array2Impl: ValueArrayImpl<T, 2>, Array1ImplMut: MutableArrayImpl<T, 1>>(
130 mat: &Array<Array2Impl, 2>,
131 result: &mut Array<Array1ImplMut, 1>,
132) {
133 debug_assert!(mat.shape()[0] == mat.shape()[1] + 1);
134 match mat.shape()[1] {
135 0 => {}
136 1 => {
137 debug_assert!(result.shape()[0] == 2);
138 *result.get_mut([0]).unwrap() = mat.get_value([1, 0]).unwrap();
139 *result.get_mut([1]).unwrap() = -mat.get_value([0, 0]).unwrap();
140 }
141 2 => {
142 debug_assert!(result.shape()[0] == 3);
143 *result.get_mut([0]).unwrap() = mat.get_value([1, 0]).unwrap()
144 * mat.get_value([2, 1]).unwrap()
145 - mat.get_value([2, 0]).unwrap() * mat.get_value([1, 1]).unwrap();
146 *result.get_mut([1]).unwrap() = mat.get_value([2, 0]).unwrap()
147 * mat.get_value([0, 1]).unwrap()
148 - mat.get_value([0, 0]).unwrap() * mat.get_value([2, 1]).unwrap();
149 *result.get_mut([2]).unwrap() = mat.get_value([0, 0]).unwrap()
150 * mat.get_value([1, 1]).unwrap()
151 - mat.get_value([1, 0]).unwrap() * mat.get_value([0, 1]).unwrap();
152 }
153 _ => {
154 unimplemented!();
155 }
156 }
157}
158
159impl<'a, T: Scalar, B2D: ValueArrayImpl<T, 2>, C2D: ValueArrayImpl<usize, 2>>
160 GeometryMap<'a, T, B2D, C2D>
161{
162 pub fn new<A2D: ValueArrayImpl<T, 2>>(
164 element: &impl MappedFiniteElement<CellType = ReferenceCellType, T = T>,
165 points: &Array<A2D, 2>,
166 geometry_points: &'a Array<B2D, 2>,
167 entities: &'a Array<C2D, 2>,
168 ) -> Self {
169 let tdim = reference_cell::dim(element.cell_type());
170 debug_assert!(points.shape()[0] == tdim);
171 let gdim = geometry_points.shape()[0];
172 let npoints = points.shape()[1];
173
174 let mut table = DynArray::<T, 4>::from_shape(element.tabulate_array_shape(1, npoints));
175 element.tabulate(points, 1, &mut table);
176
177 Self {
178 geometry_points,
179 entities,
180 tdim,
181 gdim,
182 table,
183 }
184 }
185}
186
187impl<T: Scalar, B2D: ValueArrayImpl<T, 2>, C2D: ValueArrayImpl<usize, 2>> GeometryMapTrait
188 for GeometryMap<'_, T, B2D, C2D>
189{
190 type T = T;
191
192 fn entity_topology_dimension(&self) -> usize {
193 self.tdim
194 }
195 fn geometry_dimension(&self) -> usize {
196 self.gdim
197 }
198 fn point_count(&self) -> usize {
199 self.table.shape()[1]
200 }
201 fn physical_points<Array2Impl: MutableArrayImpl<T, 2>>(
202 &self,
203 entity_index: usize,
204 points: &mut Array<Array2Impl, 2>,
205 ) {
206 let npts = self.table.shape()[1];
207 debug_assert!(points.shape()[0] == self.gdim);
208 debug_assert!(points.shape()[1] == npts);
209
210 points.fill_with_value(T::default());
211 for i in 0..self.entities.shape()[0] {
212 let v = unsafe { self.entities.get_value_unchecked([i, entity_index]) };
213 for point_index in 0..npts {
214 let t = unsafe { *self.table.get_unchecked([0, point_index, i, 0]) };
215 for gd in 0..self.gdim {
216 unsafe {
217 *points.get_unchecked_mut([gd, point_index]) +=
218 self.geometry_points.get_value_unchecked([gd, v]) * t
219 };
220 }
221 }
222 }
223 }
224 fn jacobians<Array3MutImpl: MutableArrayImpl<T, 3>>(
225 &self,
226 entity_index: usize,
227 jacobians: &mut Array<Array3MutImpl, 3>,
228 ) {
229 let npts = self.table.shape()[1];
230 debug_assert!(jacobians.shape()[0] == self.gdim);
231 debug_assert!(jacobians.shape()[1] == self.tdim);
232 debug_assert!(jacobians.shape()[2] == npts);
233
234 jacobians.fill_with_value(T::zero());
235 for i in 0..self.entities.shape()[0] {
236 let v = unsafe { self.entities.get_value_unchecked([i, entity_index]) };
237 for point_index in 0..npts {
238 for td in 0..self.tdim {
239 let t = unsafe { self.table.get_value_unchecked([1 + td, point_index, i, 0]) };
240 for gd in 0..self.gdim {
241 unsafe {
242 *jacobians.get_unchecked_mut([gd, td, point_index]) +=
243 self.geometry_points.get_value_unchecked([gd, v]) * t
244 };
245 }
246 }
247 }
248 }
249 }
250
251 fn jacobians_inverses_dets<Array3MutImpl: MutableArrayImpl<T, 3>>(
252 &self,
253 entity_index: usize,
254 jacobians: &mut Array<Array3MutImpl, 3>,
255 inverse_jacobians: &mut Array<Array3MutImpl, 3>,
256 jdets: &mut [T],
257 ) {
258 let npts = self.table.shape()[1];
259 debug_assert!(jacobians.shape()[0] == self.gdim);
260 debug_assert!(jacobians.shape()[1] == self.tdim);
261 debug_assert!(jacobians.shape()[2] == npts);
262 debug_assert!(inverse_jacobians.shape()[0] == self.tdim);
263 debug_assert!(inverse_jacobians.shape()[1] == self.gdim);
264 debug_assert!(inverse_jacobians.shape()[2] == npts);
265 debug_assert!(jdets.len() == npts);
266
267 self.jacobians(entity_index, jacobians);
268
269 for point_index in 0..npts {
270 let j = &jacobians.r().slice(2, point_index);
271
272 *jdets.get_mut(point_index).unwrap() =
273 inverse_and_det(j, &mut inverse_jacobians.r_mut().slice(2, point_index));
274 }
275 }
276
277 fn jacobians_inverses_dets_normals<
278 Array2Impl: MutableArrayImpl<T, 2>,
279 Array3MutImpl: MutableArrayImpl<T, 3>,
280 >(
281 &self,
282 entity_index: usize,
283 jacobians: &mut Array<Array3MutImpl, 3>,
284 inverse_jacobians: &mut Array<Array3MutImpl, 3>,
285 jdets: &mut [T],
286 normals: &mut Array<Array2Impl, 2>,
287 ) {
288 if self.tdim + 1 != self.gdim {
289 panic!("Can only compute normal for entities where tdim + 1 == gdim");
290 }
291 let npts = self.table.shape()[1];
292 debug_assert!(jacobians.shape()[0] == self.gdim);
293 debug_assert!(jacobians.shape()[1] == self.tdim);
294 debug_assert!(jacobians.shape()[2] == npts);
295 debug_assert!(inverse_jacobians.shape()[0] == self.tdim);
296 debug_assert!(inverse_jacobians.shape()[1] == self.gdim);
297 debug_assert!(inverse_jacobians.shape()[2] == npts);
298 debug_assert!(jdets.len() == npts);
299 debug_assert!(normals.shape()[0] == self.gdim);
300 debug_assert!(normals.shape()[1] == npts);
301
302 self.jacobians(entity_index, jacobians);
303
304 for point_index in 0..npts {
305 let j = &jacobians.r().slice(2, point_index);
306 let jd = jdets.get_mut(point_index).unwrap();
307
308 *jd = inverse_and_det(j, &mut inverse_jacobians.r_mut().slice(2, point_index));
309
310 let n = &mut normals.r_mut().slice(1, point_index);
311 cross(j, n);
312 for n_i in n.iter_mut() {
313 *n_i /= *jd;
314 }
315 }
316 }
317}
318
319#[cfg(test)]
320mod test {
321 use super::*;
322 use approx::assert_relative_eq;
323 use ndelement::{
324 ciarlet::lagrange,
325 types::{Continuity, ReferenceCellType},
326 };
327 use rand::{Rng, SeedableRng};
328 use rand_chacha::ChaCha8Rng;
329 use rlst::{DynArray, Lu, rlst_dynamic_array};
330
331 fn is_singular(mat: &DynArray<f64, 2>) -> bool {
332 if mat.shape()[0] == 0 || mat.shape()[1] == 0 {
333 return false;
334 }
335 let mut mat_t = rlst_dynamic_array!(f64, [mat.shape()[1], mat.shape()[0]]);
336 mat_t.fill_from(&mat.r().transpose());
337
338 if let Ok(lu) = rlst::dot!(mat_t.r(), mat.r()).lu() {
339 lu.det().abs() < 0.1
340 } else {
341 true
342 }
343 }
344
345 fn non_singular_matrix(gdim: usize, tdim: usize) -> DynArray<f64, 2> {
346 let mut mat = rlst_dynamic_array!(f64, [gdim, tdim]);
347 let mut rng = ChaCha8Rng::seed_from_u64(2);
348 while is_singular(&mat) {
349 for m in mat.iter_mut() {
350 *m = rng.random();
351 }
352 }
353 mat
354 }
355
356 macro_rules! tests {
357 ($gdim:expr, $tdim:expr) => {
358 paste::item! {
359 #[test]
360 fn [< test_inverse_ $gdim _ $tdim >]() {
361 let mat = non_singular_matrix($gdim, $tdim);
362 let mut inv = rlst_dynamic_array!(f64, [$tdim, $gdim]);
363
364 inverse_and_det(&mat, &mut inv);
365
366 #[allow(clippy::reversed_empty_ranges)]
367 for i in 0..$tdim {
368 #[allow(clippy::reversed_empty_ranges)]
369 for j in 0..$tdim {
370 let entry = (0..$gdim)
371 .map(|k| inv[[i, k]] * mat[[k, j]])
372 .sum::<f64>();
373 assert_relative_eq!(
374 entry,
375 if i == j { 1.0 } else { 0.0 },
376 epsilon = 1e-10
377 );
378 }
379 }
380
381 }
382
383 #[test]
384 fn [< test_det_ $gdim _ $tdim >]() {
385 let mat = non_singular_matrix($gdim, $tdim);
386
387 let rlst_det = if $tdim == 0 || $gdim == 0 {
388 1.0
389 } else {
390 let mut mat_t = rlst_dynamic_array!(f64, [$tdim, $gdim]);
391 mat_t.fill_from(&mat.r().transpose());
392
393 if let Ok(lu) = rlst::dot!(mat_t.r(), mat.r()).lu() {
394 lu.det().sqrt()
395 } else {
396 0.0
397 }
398 };
399
400 let mut inv = rlst_dynamic_array!(f64, [$tdim, $gdim]);
401
402 assert_relative_eq!(
403 inverse_and_det(&mat, &mut inv),
404 rlst_det,
405 epsilon = 1e-10
406 );
407 }
408 }
409 };
410 }
411
412 tests!(0, 0);
413 tests!(1, 0);
414 tests!(1, 1);
415 tests!(2, 0);
416 tests!(2, 1);
417 tests!(2, 2);
418 tests!(3, 0);
419 tests!(3, 1);
420 tests!(3, 2);
421 tests!(3, 3);
422 tests!(4, 0);
423 tests!(4, 1);
424 tests!(4, 2);
425 tests!(4, 3);
426 tests!(5, 0);
427 tests!(5, 1);
428 tests!(5, 2);
429 tests!(5, 3);
430
431 #[test]
432 fn test_geometry_map_3_2() {
433 let e = lagrange::create::<f64, f64>(ReferenceCellType::Triangle, 1, Continuity::Standard);
434 let mut rng = ChaCha8Rng::seed_from_u64(13);
435
436 let npts = 4;
437 let mut points = rlst_dynamic_array!(f64, [2, npts]);
438
439 for p in 0..npts {
440 *points.get_mut([0, p]).unwrap() = rng.random();
441 *points.get_mut([1, p]).unwrap() =
442 rng.random::<f64>() * points.get_value([0, p]).unwrap();
443 }
444
445 *points.get_mut([0, 0]).unwrap() = 1.0;
446 *points.get_mut([0, 0]).unwrap() = 1.0;
447 let mut geo_points = rlst_dynamic_array!(f64, [3, 3]);
448 *geo_points.get_mut([0, 0]).unwrap() = 1.0;
449 *geo_points.get_mut([1, 0]).unwrap() = 0.0;
450 *geo_points.get_mut([2, 0]).unwrap() = 0.0;
451 *geo_points.get_mut([0, 1]).unwrap() = 2.0;
452 *geo_points.get_mut([1, 1]).unwrap() = 0.0;
453 *geo_points.get_mut([2, 1]).unwrap() = 1.0;
454 *geo_points.get_mut([0, 2]).unwrap() = 0.0;
455 *geo_points.get_mut([1, 2]).unwrap() = 1.0;
456 *geo_points.get_mut([2, 2]).unwrap() = 0.0;
457
458 let mut entities = rlst_dynamic_array!(usize, [2, 1]);
459 *entities.get_mut([0, 0]).unwrap() = 2;
460 *entities.get_mut([1, 0]).unwrap() = 0;
461
462 let gmap = GeometryMap::new(&e, &points, &geo_points, &entities);
463
464 let mut jacobians = rlst_dynamic_array!(f64, [3, 2, npts]);
465 let mut jinv = rlst_dynamic_array!(f64, [2, 3, npts]);
466 let mut jdets = vec![0.0; npts];
467
468 gmap.jacobians_inverses_dets(0, &mut jacobians, &mut jinv, &mut jdets);
469
470 for p in 0..npts {
471 for i in 0..2 {
472 for j in 0..2 {
473 assert_relative_eq!(
474 (0..3)
475 .map(|k| jinv[[i, k, p]] * jacobians[[k, j, p]])
476 .sum::<f64>(),
477 if i == j { 1.0 } else { 0.0 },
478 epsilon = 1e-10
479 );
480 }
481 }
482 }
483 }
484}