fem_2d 0.2.2

2D Finite Element Method Toolkit
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
use super::glq::*;
use super::{HierCurlIntegral, IntegralResult};
use crate::fem_domain::basis::{HierCurlBasisFn, HierCurlBasisFnSpace};
use crate::fem_domain::domain::{
    dof::basis_spec::BasisDir, mesh::element::Materials, mesh::space::V2D,
};

/// <∇ × u, ∇ × ρ>
pub mod curl_curl {
    use super::*;

    /// The L2 Inner-Product of the Curl of two Hierarchical Basis Functions
    pub struct CurlCurl {
        u_weights: Vec<f64>,
        v_weights: Vec<f64>,
    }

    impl HierCurlIntegral for CurlCurl {
        fn with_weights(u_weights: &[f64], v_weights: &[f64]) -> Self {
            Self {
                u_weights: u_weights.to_vec(),
                v_weights: v_weights.to_vec(),
            }
        }

        fn integrate<BSpace: HierCurlBasisFnSpace>(
            &self,
            p_dir: BasisDir,
            q_dir: BasisDir,
            p_orders: [usize; 2],
            q_orders: [usize; 2],
            p_basis: &HierCurlBasisFn<BSpace>,
            q_basis: &HierCurlBasisFn<BSpace>,
            materials: &Materials,
        ) -> IntegralResult {
            IntegralResult::Full(
                (1.0 / materials.mu_rel.re)
                    // * p_basis.glq_scale()
                    // * q_basis.glq_scale()
                    * match (p_dir, q_dir) {
                        (BasisDir::U, BasisDir::U) => {
                            real_gauss_quad(&self.u_weights, &self.v_weights, |m, n| {
                                let p_curl = p_basis
                                    .f_u_d1(p_orders, [m, n], q_basis.deriv_scale())
                                    .dot_with(&CURL_OP);
                                let q_curl = q_basis
                                    .f_u_d1(q_orders, [m, n], p_basis.deriv_scale())
                                    .dot_with(&CURL_OP);

                                p_curl * q_curl * max_uv_ratios(p_basis, q_basis, [m, n])
                            })
                        }
                        (BasisDir::U, BasisDir::V) => {
                            real_gauss_quad(&self.u_weights, &self.v_weights, |m, n| {
                                let p_curl = p_basis
                                    .f_u_d1(p_orders, [m, n], q_basis.deriv_scale())
                                    .dot_with(&CURL_OP);
                                let q_curl = q_basis
                                    .f_v_d1(q_orders, [m, n], p_basis.deriv_scale())
                                    .dot_with(&CURL_OP);

                                p_curl * q_curl
                            })
                        }
                        (BasisDir::V, BasisDir::U) => {
                            real_gauss_quad(&self.u_weights, &self.v_weights, |m, n| {
                                let p_curl = p_basis
                                    .f_v_d1(p_orders, [m, n], q_basis.deriv_scale())
                                    .dot_with(&CURL_OP);
                                let q_curl = q_basis
                                    .f_u_d1(q_orders, [m, n], p_basis.deriv_scale())
                                    .dot_with(&CURL_OP);

                                p_curl * q_curl
                            })
                        }
                        (BasisDir::V, BasisDir::V) => {
                            real_gauss_quad(&self.u_weights, &self.v_weights, |m, n| {
                                let p_curl = p_basis
                                    .f_v_d1(p_orders, [m, n], q_basis.deriv_scale())
                                    .dot_with(&CURL_OP);
                                let q_curl = q_basis
                                    .f_v_d1(q_orders, [m, n], p_basis.deriv_scale())
                                    .dot_with(&CURL_OP);

                                p_curl * q_curl * max_vu_ratios(p_basis, q_basis, [m, n])
                            })
                        }
                        (_, _) => 0.0,
                    },
            )
        }

        fn integrate_by_parts<BSpace: HierCurlBasisFnSpace>(
            &self,
            p_dir: BasisDir,
            q_dir: BasisDir,
            p_orders: [usize; 2],
            q_orders: [usize; 2],
            p_basis: &HierCurlBasisFn<BSpace>,
            q_basis: &HierCurlBasisFn<BSpace>,
            materials: &Materials,
        ) -> IntegralResult {
            let surface_term = (1.0 / materials.mu_rel.re)
                * p_basis.glq_scale()
                * q_basis.glq_scale()
                * match (p_dir, q_dir) {
                    (BasisDir::U, BasisDir::U) => {
                        real_gauss_quad_inner(&self.u_weights, &self.v_weights, |m, n| {
                            let p_d2 = p_basis.f_u_d2(p_orders, [m, n], q_basis.deriv_scale());
                            let p_dd = p_basis.f_u_dd(p_orders, [m, n], q_basis.deriv_scale());

                            let p = V2D::from([p_dd[1] + p_d2[0], p_d2[1] + p_dd[0]]);
                            let q = q_basis.f_u(q_orders, [m, n]);

                            V2D::dot(p, q) / q_basis.glq_scale().powi(2)
                                * max_uv_ratios(p_basis, q_basis, [m, n])
                        }) * -1.0
                    }
                    (BasisDir::U, BasisDir::V) => {
                        real_gauss_quad_inner(&self.u_weights, &self.v_weights, |m, n| {
                            let p_d2 = p_basis.f_u_d2(p_orders, [m, n], q_basis.deriv_scale());
                            let p_dd = p_basis.f_u_dd(p_orders, [m, n], q_basis.deriv_scale());

                            let p = V2D::from([p_dd[1] + p_d2[0], p_d2[1] + p_dd[0]]);
                            let q = q_basis.f_v(q_orders, [m, n]);

                            V2D::dot(p, q) / q_basis.glq_scale().powi(2)
                        })
                    }
                    (BasisDir::V, BasisDir::U) => {
                        real_gauss_quad_inner(&self.u_weights, &self.v_weights, |m, n| {
                            let p_d2 = p_basis.f_v_d2(p_orders, [m, n], q_basis.deriv_scale());
                            let p_dd = p_basis.f_v_dd(p_orders, [m, n], q_basis.deriv_scale());

                            let p = V2D::from([p_dd[1] + p_d2[0], p_d2[1] + p_dd[0]]);
                            let q = q_basis.f_u(q_orders, [m, n]);

                            V2D::dot(p, q) / q_basis.glq_scale().powi(2)
                        })
                    }
                    (BasisDir::V, BasisDir::V) => {
                        real_gauss_quad_inner(&self.u_weights, &self.v_weights, |m, n| {
                            let p_d2 = p_basis.f_v_d2(p_orders, [m, n], q_basis.deriv_scale());
                            let p_dd = p_basis.f_v_dd(p_orders, [m, n], q_basis.deriv_scale());

                            let p = V2D::from([p_dd[1] + p_d2[0], p_d2[1] + p_dd[0]]);
                            let q = q_basis.f_v(q_orders, [m, n]);

                            V2D::dot(p, q) / q_basis.glq_scale().powi(2)
                                * max_vu_ratios(p_basis, q_basis, [m, n])
                        }) * -1.0
                    }
                    (_, _) => 0.0,
                };

            let edge_terms = (0..4)
                .map(|edge_idx| {
                    -1.0 * (1.0 / materials.mu_rel.re)
                        * p_basis.edge_glq_scale(edge_idx)
                        * q_basis.edge_glq_scale(edge_idx)
                        * match (p_dir, q_dir, edge_idx) {
                            (BasisDir::U, BasisDir::U, 0 | 1) => real_gauss_quad_edge(
                                &self.u_weights,
                                &self.v_weights,
                                edge_idx,
                                |m, n| {
                                    let p_curl = p_basis
                                        .f_u_d1(p_orders, [m, n], q_basis.deriv_scale())
                                        .dot_with(&CURL_OP);
                                    let q = q_basis
                                        .f_u(q_orders, [m, n])
                                        .dot_with(&EDGE_UNIT_VECTORS[edge_idx]);

                                    p_curl * q / q_basis.glq_scale()
                                        * max_uv_ratios(p_basis, q_basis, [m, n])
                                },
                            ),
                            (BasisDir::V, BasisDir::U, 0 | 1) => real_gauss_quad_edge(
                                &self.u_weights,
                                &self.v_weights,
                                edge_idx,
                                |m, n| {
                                    let p_curl = p_basis
                                        .f_v_d1(p_orders, [m, n], q_basis.deriv_scale())
                                        .dot_with(&CURL_OP);
                                    let q = q_basis
                                        .f_u(q_orders, [m, n])
                                        .dot_with(&EDGE_UNIT_VECTORS[edge_idx]);

                                    p_curl * q / q_basis.glq_scale()
                                },
                            ),
                            (BasisDir::U, BasisDir::V, 2 | 3) => {
                                real_gauss_quad_edge(
                                    &self.u_weights,
                                    &self.v_weights,
                                    edge_idx,
                                    |m, n| {
                                        let p_curl = p_basis
                                            .f_u_d1(p_orders, [m, n], q_basis.deriv_scale())
                                            .dot_with(&CURL_OP);
                                        let q = q_basis
                                            .f_v(q_orders, [m, n])
                                            .dot_with(&EDGE_UNIT_VECTORS[edge_idx]);

                                        p_curl * q / q_basis.glq_scale()
                                    },
                                ) * -1.0
                            }
                            (BasisDir::V, BasisDir::V, 2 | 3) => {
                                real_gauss_quad_edge(
                                    &self.u_weights,
                                    &self.v_weights,
                                    edge_idx,
                                    |m, n| {
                                        let p_curl = p_basis
                                            .f_v_d1(p_orders, [m, n], q_basis.deriv_scale())
                                            .dot_with(&CURL_OP);
                                        let q = q_basis
                                            .f_v(q_orders, [m, n])
                                            .dot_with(&EDGE_UNIT_VECTORS[edge_idx]);

                                        p_curl * q / q_basis.glq_scale()
                                            * max_vu_ratios(p_basis, q_basis, [m, n])
                                    },
                                ) * -1.0
                            }
                            (_, _, _) => 0.0,
                        }
                })
                .collect::<Vec<f64>>()
                .try_into()
                .unwrap();

            IntegralResult::ByParts(surface_term, edge_terms)
        }
    }

    const CURL_OP: V2D = V2D::from([-1.0, 1.0]);

    const EDGE_UNIT_VECTORS: [V2D; 4] = [
        V2D::from([-1.0, 0.0]),
        V2D::from([1.0, 0.0]),
        V2D::from([0.0, -1.0]),
        V2D::from([0.0, 1.0]),
    ];

    #[inline]
    fn max_uv_ratios<BSpace: HierCurlBasisFnSpace>(
        p_basis: &HierCurlBasisFn<BSpace>,
        q_basis: &HierCurlBasisFn<BSpace>,
        [m, n]: [usize; 2],
    ) -> f64 {
        ((p_basis.det_jac[m][n] >= q_basis.det_jac[m][n]) as u8) as f64 * p_basis.uv_ratio([m, n])
            + ((p_basis.det_jac[m][n] < q_basis.det_jac[m][n]) as u8) as f64
                * q_basis.uv_ratio([m, n])
        // p_basis.uv_ratio([m, n])
    }

    #[inline]
    fn max_vu_ratios<BSpace: HierCurlBasisFnSpace>(
        p_basis: &HierCurlBasisFn<BSpace>,
        q_basis: &HierCurlBasisFn<BSpace>,
        [m, n]: [usize; 2],
    ) -> f64 {
        ((p_basis.det_jac[m][n] >= q_basis.det_jac[m][n]) as u8) as f64 * p_basis.vu_ratio([m, n])
            + ((p_basis.det_jac[m][n] < q_basis.det_jac[m][n]) as u8) as f64
                * q_basis.vu_ratio([m, n])
        // p_basis.vu_ratio([m, n])
    }
}

/// <u, ρ>
pub mod inner {
    use super::*;

    /// The L2 Inner product of two Basis Functions
    pub struct L2Inner {
        u_weights: Vec<f64>,
        v_weights: Vec<f64>,
    }

    impl HierCurlIntegral for L2Inner {
        fn with_weights(u_weights: &[f64], v_weights: &[f64]) -> Self {
            Self {
                u_weights: u_weights.to_vec(),
                v_weights: v_weights.to_vec(),
            }
        }

        fn integrate<BSpace: HierCurlBasisFnSpace>(
            &self,
            p_dir: BasisDir,
            q_dir: BasisDir,
            p_orders: [usize; 2],
            q_orders: [usize; 2],
            p_basis: &HierCurlBasisFn<BSpace>,
            q_basis: &HierCurlBasisFn<BSpace>,
            materials: &Materials,
        ) -> IntegralResult {
            IntegralResult::Full(
                materials.eps_rel.re
                    * p_basis.glq_scale()
                    * q_basis.glq_scale()
                    * match (p_dir, q_dir) {
                        (BasisDir::U, BasisDir::U) => {
                            real_gauss_quad(&self.u_weights, &self.v_weights, |m, n| {
                                V2D::dot(
                                    p_basis.f_u(p_orders, [m, n]),
                                    q_basis.f_u(q_orders, [m, n]),
                                ) * partial_max(
                                    p_basis.sample_scale([m, n]),
                                    q_basis.sample_scale([m, n]),
                                )
                            })
                        }
                        (BasisDir::U, BasisDir::V) => {
                            real_gauss_quad(&self.u_weights, &self.v_weights, |m, n| {
                                V2D::dot(
                                    p_basis.f_u(p_orders, [m, n]),
                                    q_basis.f_v(q_orders, [m, n]),
                                ) * partial_max(
                                    p_basis.sample_scale([m, n]),
                                    q_basis.sample_scale([m, n]),
                                )
                            })
                        }
                        (BasisDir::V, BasisDir::U) => {
                            real_gauss_quad(&self.u_weights, &self.v_weights, |m, n| {
                                V2D::dot(
                                    p_basis.f_v(p_orders, [m, n]),
                                    q_basis.f_u(q_orders, [m, n]),
                                ) * partial_max(
                                    p_basis.sample_scale([m, n]),
                                    q_basis.sample_scale([m, n]),
                                )
                            })
                        }
                        (BasisDir::V, BasisDir::V) => {
                            real_gauss_quad(&self.u_weights, &self.v_weights, |m, n| {
                                V2D::dot(
                                    p_basis.f_v(p_orders, [m, n]),
                                    q_basis.f_v(q_orders, [m, n]),
                                ) * partial_max(
                                    p_basis.sample_scale([m, n]),
                                    q_basis.sample_scale([m, n]),
                                )
                            })
                        }
                        (_, _) => 0.0,
                    },
            )
        }

        fn integrate_by_parts<BSpace: HierCurlBasisFnSpace>(
            &self,
            p_dir: BasisDir,
            q_dir: BasisDir,
            p_orders: [usize; 2],
            q_orders: [usize; 2],
            p_basis: &HierCurlBasisFn<BSpace>,
            q_basis: &HierCurlBasisFn<BSpace>,
            materials: &Materials,
        ) -> IntegralResult {
            IntegralResult::Full(
                materials.eps_rel.re
                    * p_basis.glq_scale()
                    * q_basis.glq_scale()
                    * match (p_dir, q_dir) {
                        (BasisDir::U, BasisDir::U) => {
                            real_gauss_quad_inner(&self.u_weights, &self.v_weights, |m, n| {
                                V2D::dot(
                                    p_basis.f_u(p_orders, [m, n]),
                                    q_basis.f_u(q_orders, [m, n]),
                                ) * partial_max(
                                    p_basis.sample_scale([m, n]),
                                    q_basis.sample_scale([m, n]),
                                )
                            })
                        }
                        (BasisDir::U, BasisDir::V) => {
                            real_gauss_quad_inner(&self.u_weights, &self.v_weights, |m, n| {
                                V2D::dot(
                                    p_basis.f_u(p_orders, [m, n]),
                                    q_basis.f_v(q_orders, [m, n]),
                                ) * partial_max(
                                    p_basis.sample_scale([m, n]),
                                    q_basis.sample_scale([m, n]),
                                )
                            })
                        }
                        (BasisDir::V, BasisDir::U) => {
                            real_gauss_quad_inner(&self.u_weights, &self.v_weights, |m, n| {
                                V2D::dot(
                                    p_basis.f_v(p_orders, [m, n]),
                                    q_basis.f_u(q_orders, [m, n]),
                                ) * partial_max(
                                    p_basis.sample_scale([m, n]),
                                    q_basis.sample_scale([m, n]),
                                )
                            })
                        }
                        (BasisDir::V, BasisDir::V) => {
                            real_gauss_quad_inner(&self.u_weights, &self.v_weights, |m, n| {
                                V2D::dot(
                                    p_basis.f_v(p_orders, [m, n]),
                                    q_basis.f_v(q_orders, [m, n]),
                                ) * partial_max(
                                    p_basis.sample_scale([m, n]),
                                    q_basis.sample_scale([m, n]),
                                )
                            })
                        }
                        (_, _) => 0.0,
                    },
            )
        }
    }

    fn partial_max(v1: f64, v2: f64) -> f64 {
        std::cmp::max_by(v1, v2, |a, b| a.partial_cmp(b).unwrap())
    }
}