ndelement/ciarlet/
raviart_thomas.rs

1//! Raviart-Thomas elements.
2
3use super::CiarletElement;
4use crate::map::ContravariantPiolaMap;
5use crate::math::orthogonalise_3;
6use crate::polynomials::{legendre_shape, polynomial_count, tabulate_legendre_polynomials};
7use crate::quadrature::gauss_jacobi_rule;
8use crate::reference_cell;
9use crate::traits::ElementFamily;
10use crate::types::{Continuity, ReferenceCellType};
11use itertools::izip;
12use rlst::dense::linalg::lapack::interface::{getrf::Getrf, getri::Getri};
13use rlst::{DynArray, RlstScalar, SliceArray, rlst_dynamic_array};
14use std::marker::PhantomData;
15
16fn create_simplex<T: RlstScalar + Getrf + Getri, TGeo: RlstScalar>(
17    cell_type: ReferenceCellType,
18    degree: usize,
19    continuity: Continuity,
20) -> CiarletElement<T, ContravariantPiolaMap, TGeo> {
21    if cell_type != ReferenceCellType::Triangle && cell_type != ReferenceCellType::Tetrahedron {
22        panic!("Invalid cell: {cell_type:?}");
23    }
24
25    if degree < 1 {
26        panic!("Degree must be at least 1");
27    }
28
29    let tdim = reference_cell::dim(cell_type);
30    let facet_type = if tdim == 2 {
31        ReferenceCellType::Interval
32    } else {
33        ReferenceCellType::Triangle
34    };
35    let pdim_minus1 = polynomial_count(cell_type, degree - 1);
36    let pdim_facet_minus1 = polynomial_count(facet_type, degree - 1);
37    let pdim_minus2 = if degree < 2 {
38        0
39    } else {
40        polynomial_count(cell_type, degree - 2)
41    };
42
43    let cell_q = gauss_jacobi_rule(cell_type, 2 * degree).unwrap();
44    let pts_t = cell_q
45        .points
46        .iter()
47        .map(|i| TGeo::from(*i).unwrap())
48        .collect::<Vec<_>>();
49    let pts = SliceArray::<TGeo, 2>::from_shape(&pts_t, [tdim, cell_q.npoints]);
50
51    let mut phi = DynArray::<T, 3>::from_shape(legendre_shape(cell_type, &pts, degree, 0));
52    tabulate_legendre_polynomials(cell_type, &pts, degree, 0, &mut phi);
53
54    let pdim = phi.shape()[1];
55
56    let mut wcoeffs = rlst_dynamic_array!(T, [pdim_minus1 * tdim + pdim_facet_minus1, tdim, pdim]);
57
58    // vector polynomials of degree <= n-1
59    for i in 0..tdim {
60        for j in 0..pdim_minus1 {
61            *wcoeffs.get_mut([i * pdim_minus1 + j, i, j]).unwrap() = T::from(1.0).unwrap();
62        }
63    }
64
65    // (px, py, pz) , where p = scalar polynomial of degree = n-1
66    for i in 0..pdim_facet_minus1 {
67        for k in pdim_minus1..pdim {
68            for j in 0..tdim {
69                *wcoeffs.get_mut([pdim_minus1 * tdim + i, j, k]).unwrap() = cell_q
70                    .weights
71                    .iter()
72                    .enumerate()
73                    .map(|(w_i, wt)| {
74                        T::from(*wt).unwrap()
75                            * phi[[0, pdim_minus2 + i, w_i]]
76                            * T::from(pts[[j, w_i]]).unwrap()
77                            * phi[[0, k, w_i]]
78                    })
79                    .sum();
80            }
81        }
82    }
83
84    orthogonalise_3(&mut wcoeffs, pdim_minus1 * tdim);
85
86    let mut x = [vec![], vec![], vec![], vec![]];
87    let mut m = [vec![], vec![], vec![], vec![]];
88
89    let entity_counts = reference_cell::entity_counts(cell_type);
90    let vertices = reference_cell::vertices::<TGeo>(cell_type);
91
92    for d in 0..tdim - 1 {
93        for _ in 0..entity_counts[d] {
94            x[d].push(rlst_dynamic_array!(TGeo, [tdim, 0]));
95            m[d].push(rlst_dynamic_array!(T, [0, tdim, 0]));
96        }
97    }
98
99    // Integral moments on facets
100    let facet_q = gauss_jacobi_rule(facet_type, 2 * degree - 1).unwrap();
101    let facet_pts_t = facet_q
102        .points
103        .iter()
104        .map(|i| TGeo::from(*i).unwrap())
105        .collect::<Vec<_>>();
106    let facet_pts = SliceArray::<TGeo, 2>::from_shape(&facet_pts_t, [tdim - 1, facet_q.npoints]);
107
108    let mut facet_phi =
109        DynArray::<T, 3>::from_shape(legendre_shape(facet_type, &facet_pts, degree - 1, 0));
110    tabulate_legendre_polynomials(facet_type, &facet_pts, degree - 1, 0, &mut facet_phi);
111
112    for (facet, normal) in izip!(
113        if tdim == 2 {
114            reference_cell::edges(cell_type)
115        } else {
116            reference_cell::faces(cell_type)
117        },
118        reference_cell::facet_normals::<TGeo>(cell_type),
119    ) {
120        let mut pts = rlst_dynamic_array!(TGeo, [tdim, facet_q.npoints]);
121        let mut mat = rlst_dynamic_array!(T, [pdim_facet_minus1, tdim, facet_q.npoints]);
122
123        for (w_i, wt) in facet_q.weights.iter().enumerate() {
124            for i in 0..tdim {
125                pts[[i, w_i]] = vertices[facet[0]][i]
126                    + izip!(
127                        facet.iter().skip(1),
128                        &facet_q.points[w_i * (tdim - 1)..(w_i + 1) * (tdim - 1)]
129                    )
130                    .map(|(v_i, pt)| {
131                        (vertices[*v_i][i] - vertices[facet[0]][i]) * TGeo::from(*pt).unwrap()
132                    })
133                    .sum();
134
135                for j in 0..pdim_facet_minus1 {
136                    mat[[j, i, w_i]] = T::from(*wt).unwrap()
137                        * facet_phi[[0, j, w_i]]
138                        * T::from(normal[i]).unwrap();
139                }
140            }
141        }
142
143        x[tdim - 1].push(pts);
144        m[tdim - 1].push(mat);
145    }
146
147    if degree == 1 {
148        for _ in 0..entity_counts[tdim] {
149            x[tdim].push(rlst_dynamic_array!(TGeo, [tdim, 0]));
150            m[tdim].push(rlst_dynamic_array!(T, [0, tdim, 0]))
151        }
152    } else {
153        let internal_q = gauss_jacobi_rule(cell_type, 2 * degree - 2).unwrap();
154        let mut pts = rlst_dynamic_array!(TGeo, [tdim, internal_q.npoints]);
155        for p in 0..internal_q.npoints {
156            for d in 0..tdim {
157                pts[[d, p]] = TGeo::from(internal_q.points[tdim * p + d]).unwrap()
158            }
159        }
160
161        let mut internal_phi =
162            DynArray::<T, 3>::from_shape(legendre_shape(cell_type, &pts, degree - 2, 0));
163        tabulate_legendre_polynomials(cell_type, &pts, degree - 2, 0, &mut internal_phi);
164
165        let mut mat = rlst_dynamic_array!(T, [tdim * pdim_minus2, tdim, internal_q.npoints]);
166        for (w_i, wt) in internal_q.weights.iter().enumerate() {
167            for i in 0..tdim {
168                for j in 0..pdim_minus2 {
169                    mat[[j + pdim_minus2 * i, i, w_i]] =
170                        T::from(*wt).unwrap() * internal_phi[[0, j, w_i]];
171                }
172            }
173        }
174
175        x[tdim].push(pts);
176        m[tdim].push(mat);
177    }
178
179    CiarletElement::create(
180        "Raviart-Thomas".to_string(),
181        cell_type,
182        degree,
183        vec![tdim],
184        wcoeffs,
185        x,
186        m,
187        continuity,
188        degree,
189        ContravariantPiolaMap {},
190    )
191}
192
193fn create_tp<T: RlstScalar + Getrf + Getri, TGeo: RlstScalar>(
194    cell_type: ReferenceCellType,
195    degree: usize,
196    continuity: Continuity,
197) -> CiarletElement<T, ContravariantPiolaMap, TGeo> {
198    if cell_type != ReferenceCellType::Quadrilateral && cell_type != ReferenceCellType::Hexahedron {
199        panic!("Invalid cell: {cell_type:?}");
200    }
201
202    if degree < 1 {
203        panic!("Degree must be at least 1");
204    }
205
206    let tdim = reference_cell::dim(cell_type);
207    let cell_q = gauss_jacobi_rule(cell_type, 2 * degree).unwrap();
208    let pts_t = cell_q
209        .points
210        .iter()
211        .map(|i| TGeo::from(*i).unwrap())
212        .collect::<Vec<_>>();
213    let pts = SliceArray::<TGeo, 2>::from_shape(&pts_t, [tdim, cell_q.npoints]);
214
215    let mut phi = DynArray::<T, 3>::from_shape(legendre_shape(cell_type, &pts, degree, 0));
216    tabulate_legendre_polynomials(cell_type, &pts, degree, 0, &mut phi);
217
218    let pdim = phi.shape()[1];
219
220    let pdim_edge = polynomial_count(ReferenceCellType::Interval, degree);
221    let pdim_edge_minus1 = polynomial_count(ReferenceCellType::Interval, degree - 1);
222    let pdim_edge_minus2 = if degree < 2 {
223        0
224    } else {
225        polynomial_count(ReferenceCellType::Interval, degree - 2)
226    };
227
228    let facet_type = if tdim == 2 {
229        ReferenceCellType::Interval
230    } else {
231        ReferenceCellType::Quadrilateral
232    };
233    let pdim_facet_minus1 = polynomial_count(facet_type, degree - 1);
234    let pdim_internal = tdim * pdim_edge_minus2 * pdim_edge_minus1.pow(tdim as u32 - 1);
235
236    let entity_counts = reference_cell::entity_counts(cell_type);
237
238    let wdim = entity_counts[tdim - 1] * pdim_facet_minus1 + entity_counts[tdim] * pdim_internal;
239    let mut wcoeffs = rlst_dynamic_array!(T, [wdim, tdim, pdim]);
240
241    // vector polynomials of degree <= n-1
242    if tdim == 2 {
243        for i in 0..pdim_edge_minus1 {
244            for j in 0..pdim_edge {
245                *wcoeffs
246                    .get_mut([j * pdim_edge_minus1 + i, 0, j * pdim_edge + i])
247                    .unwrap() = T::from(1.0).unwrap();
248                *wcoeffs
249                    .get_mut([
250                        pdim_edge_minus1 * pdim_edge + i * pdim_edge + j,
251                        1,
252                        i * pdim_edge + j,
253                    ])
254                    .unwrap() = T::from(1.0).unwrap();
255            }
256        }
257    } else {
258        for i in 0..pdim_edge_minus1 {
259            for j in 0..pdim_edge_minus1 {
260                for k in 0..pdim_edge {
261                    *wcoeffs
262                        .get_mut([
263                            k * pdim_edge_minus1.pow(2) + j * pdim_edge_minus1 + i,
264                            0,
265                            k * pdim_edge.pow(2) + j * pdim_edge + i,
266                        ])
267                        .unwrap() = T::from(1.0).unwrap();
268                    *wcoeffs
269                        .get_mut([
270                            pdim_edge * pdim_edge_minus1.pow(2)
271                                + i * pdim_edge * pdim_edge_minus1
272                                + k * pdim_edge_minus1
273                                + j,
274                            1,
275                            i * pdim_edge.pow(2) + k * pdim_edge + j,
276                        ])
277                        .unwrap() = T::from(1.0).unwrap();
278                    *wcoeffs
279                        .get_mut([
280                            pdim_edge * pdim_edge_minus1.pow(2) * 2
281                                + j * pdim_edge * pdim_edge_minus1
282                                + i * pdim_edge
283                                + k,
284                            2,
285                            j * pdim_edge.pow(2) + i * pdim_edge + k,
286                        ])
287                        .unwrap() = T::from(1.0).unwrap();
288                }
289            }
290        }
291    }
292
293    let mut x = [vec![], vec![], vec![], vec![]];
294    let mut m = [vec![], vec![], vec![], vec![]];
295
296    let vertices = reference_cell::vertices::<TGeo>(cell_type);
297
298    for d in 0..tdim - 1 {
299        for _ in 0..entity_counts[d] {
300            x[d].push(rlst_dynamic_array!(TGeo, [tdim, 0]));
301            m[d].push(rlst_dynamic_array!(T, [0, tdim, 0]));
302        }
303    }
304    // DOFs on facets
305    let facet_q = gauss_jacobi_rule(facet_type, 2 * degree - 1).unwrap();
306    let facet_pts_t = facet_q
307        .points
308        .iter()
309        .map(|i| TGeo::from(*i).unwrap())
310        .collect::<Vec<_>>();
311    let facet_pts = SliceArray::<TGeo, 2>::from_shape(&facet_pts_t, [tdim - 1, facet_q.npoints]);
312
313    let mut facet_phi =
314        DynArray::<T, 3>::from_shape(legendre_shape(facet_type, &facet_pts, degree - 1, 0));
315    tabulate_legendre_polynomials(facet_type, &facet_pts, degree - 1, 0, &mut facet_phi);
316
317    for (facet, normal) in izip!(
318        reference_cell::facets(cell_type),
319        reference_cell::facet_normals::<TGeo>(cell_type),
320    ) {
321        let mut pts = rlst_dynamic_array!(TGeo, [tdim, facet_q.npoints]);
322        let mut mat = rlst_dynamic_array!(T, [pdim_facet_minus1, tdim, facet_q.npoints]);
323
324        for (w_i, wt) in facet_q.weights.iter().enumerate() {
325            for d in 0..tdim {
326                pts[[d, w_i]] = vertices[facet[0]][d]
327                    + (0..tdim - 1)
328                        .map(|x| {
329                            (vertices[facet[usize::pow(2, x as u32)]][d] - vertices[facet[0]][d])
330                                * facet_pts[[x, w_i]]
331                        })
332                        .sum::<TGeo>();
333                for j in 0..facet_phi.shape()[1] {
334                    mat[[j, d, w_i]] = T::from(*wt).unwrap()
335                        * facet_phi[[0, j, w_i]]
336                        * T::from(normal[d]).unwrap();
337                }
338            }
339        }
340
341        x[tdim - 1].push(pts);
342        m[tdim - 1].push(mat);
343    }
344
345    // DOFs on interior
346    if degree == 1 {
347        x[tdim].push(rlst_dynamic_array!(TGeo, [tdim, 0]));
348        m[tdim].push(rlst_dynamic_array!(T, [0, tdim, 0]))
349    } else if tdim == 2 {
350        let face_q = gauss_jacobi_rule(ReferenceCellType::Quadrilateral, 2 * degree - 1).unwrap();
351        let face_pts_t = face_q
352            .points
353            .iter()
354            .map(|i| TGeo::from(*i).unwrap())
355            .collect::<Vec<_>>();
356        let face_pts = SliceArray::<TGeo, 2>::from_shape(&face_pts_t, [2, face_q.npoints]);
357
358        let mut face_phi = DynArray::<T, 3>::from_shape(legendre_shape(
359            ReferenceCellType::Quadrilateral,
360            &face_pts,
361            degree - 1,
362            0,
363        ));
364        tabulate_legendre_polynomials(
365            ReferenceCellType::Quadrilateral,
366            &face_pts,
367            degree - 1,
368            0,
369            &mut face_phi,
370        );
371
372        let mut pts = rlst_dynamic_array!(TGeo, [tdim, face_q.npoints]);
373        let mdim = 2 * pdim_edge_minus2 * pdim_edge_minus1;
374        let mut mat = rlst_dynamic_array!(T, [mdim, tdim, face_q.npoints]);
375
376        for (w_i, wt) in face_q.weights.iter().enumerate() {
377            for i in 0..tdim {
378                pts[[i, w_i]] = face_pts[[i, w_i]];
379            }
380            for i in 0..pdim_edge_minus2 {
381                for j in 0..pdim_edge_minus1 {
382                    let index = 2 * (i * pdim_edge_minus1 + j);
383                    mat[[index, 0, w_i]] =
384                        T::from(*wt).unwrap() * face_phi[[0, i * pdim_edge_minus1 + j, w_i]];
385                    mat[[index + 1, 1, w_i]] =
386                        T::from(*wt).unwrap() * face_phi[[0, j * pdim_edge_minus1 + i, w_i]];
387                }
388            }
389        }
390        x[2].push(pts);
391        m[2].push(mat);
392    } else {
393        let interior_q = gauss_jacobi_rule(ReferenceCellType::Hexahedron, 2 * degree - 1).unwrap();
394        let interior_pts_t = interior_q
395            .points
396            .iter()
397            .map(|i| TGeo::from(*i).unwrap())
398            .collect::<Vec<_>>();
399        let interior_pts =
400            SliceArray::<TGeo, 2>::from_shape(&interior_pts_t, [3, interior_q.npoints]);
401
402        let mut interior_phi = DynArray::<T, 3>::from_shape(legendre_shape(
403            ReferenceCellType::Hexahedron,
404            &interior_pts,
405            degree - 1,
406            0,
407        ));
408        tabulate_legendre_polynomials(
409            ReferenceCellType::Hexahedron,
410            &interior_pts,
411            degree - 1,
412            0,
413            &mut interior_phi,
414        );
415
416        let mut pts = rlst_dynamic_array!(TGeo, [tdim, interior_q.npoints]);
417        let mdim = 3 * pdim_edge_minus1.pow(2) * pdim_edge_minus2;
418        let mut mat = rlst_dynamic_array!(T, [mdim, tdim, interior_q.npoints]);
419
420        for (w_i, wt) in interior_q.weights.iter().enumerate() {
421            for i in 0..tdim {
422                pts[[i, w_i]] = interior_pts[[i, w_i]];
423            }
424            for i in 0..pdim_edge_minus2 {
425                for j in 0..pdim_edge_minus1 {
426                    for k in 0..pdim_edge_minus1 {
427                        let index = 3 * (i * pdim_edge_minus1.pow(2) + j * pdim_edge_minus1 + k);
428                        mat[[index, 0, w_i]] = T::from(*wt).unwrap()
429                            * interior_phi[[
430                                0,
431                                i * pdim_edge_minus1.pow(2) + j * pdim_edge_minus1 + k,
432                                w_i,
433                            ]];
434                        mat[[index + 1, 1, w_i]] = T::from(*wt).unwrap()
435                            * interior_phi[[
436                                0,
437                                k * pdim_edge_minus1.pow(2) + i * pdim_edge_minus1 + j,
438                                w_i,
439                            ]];
440                        mat[[index + 2, 2, w_i]] = T::from(*wt).unwrap()
441                            * interior_phi[[
442                                0,
443                                j * pdim_edge_minus1.pow(2) + k * pdim_edge_minus1 + i,
444                                w_i,
445                            ]];
446                    }
447                }
448            }
449        }
450        x[3].push(pts);
451        m[3].push(mat);
452    }
453
454    CiarletElement::create(
455        "Raviart-Thomas".to_string(),
456        cell_type,
457        degree,
458        vec![tdim],
459        wcoeffs,
460        x,
461        m,
462        continuity,
463        degree,
464        ContravariantPiolaMap {},
465    )
466}
467
468/// Create a Raviart-Thomas element
469pub fn create<T: RlstScalar + Getrf + Getri, TGeo: RlstScalar>(
470    cell_type: ReferenceCellType,
471    degree: usize,
472    continuity: Continuity,
473) -> CiarletElement<T, ContravariantPiolaMap, TGeo> {
474    if cell_type == ReferenceCellType::Triangle || cell_type == ReferenceCellType::Tetrahedron {
475        create_simplex(cell_type, degree, continuity)
476    } else if cell_type == ReferenceCellType::Quadrilateral
477        || cell_type == ReferenceCellType::Hexahedron
478    {
479        create_tp(cell_type, degree, continuity)
480    } else {
481        panic!("Invalid cell: {cell_type:?}");
482    }
483}
484
485/// Raviart-Thomas element family
486///
487/// A family of Raviart-Thomas elements on multiple cell types with appropriate
488/// continuity across different cell types.
489pub struct RaviartThomasElementFamily<T: RlstScalar + Getrf + Getri = f64, TGeo: RlstScalar = f64> {
490    degree: usize,
491    continuity: Continuity,
492    _t: PhantomData<T>,
493    _tgeo: PhantomData<TGeo>,
494}
495
496impl<T: RlstScalar + Getrf + Getri, TGeo: RlstScalar> RaviartThomasElementFamily<T, TGeo> {
497    /// Create new family with given `degree` and `continuity`.
498    pub fn new(degree: usize, continuity: Continuity) -> Self {
499        Self {
500            degree,
501            continuity,
502            _t: PhantomData,
503            _tgeo: PhantomData,
504        }
505    }
506}
507
508impl<T: RlstScalar + Getrf + Getri, TGeo: RlstScalar> ElementFamily
509    for RaviartThomasElementFamily<T, TGeo>
510{
511    type T = T;
512    type CellType = ReferenceCellType;
513    type FiniteElement = CiarletElement<T, ContravariantPiolaMap, TGeo>;
514    fn element(
515        &self,
516        cell_type: ReferenceCellType,
517    ) -> CiarletElement<T, ContravariantPiolaMap, TGeo> {
518        create::<T, TGeo>(cell_type, self.degree, self.continuity)
519    }
520}