| |
1 /// Minimisation code for second-order polynomial models from Fenics. |
| |
2 #include <array> |
| |
3 #include <cmath> |
| |
4 |
| |
5 #include <basix/finite-element.h> |
| |
6 #include <dolfinx/fem/Function.h> |
| |
7 #include <dolfinx/mesh/cell_types.h> |
| |
8 #include <nanobind/nanobind.h> |
| |
9 #include <nanobind/stl/array.h> // needed for array conversions in nanobind bindings. |
| |
10 |
| |
11 #include "dolfinx_access/minmax_p2.h" |
| |
12 #include "pointsource_pde/src/dolfinx_access.rs.h" |
| |
13 |
| |
14 using namespace dolfinx::fem; |
| |
15 namespace cell = basix::cell; |
| |
16 using namespace basix::element; |
| |
17 using namespace dolfinx_access; |
| |
18 |
| |
19 namespace dolfinx_access { |
| |
20 |
| |
21 // Since Fenics documentation is poor, and it does not seem to provide an easy way to access |
| |
22 // an internal P2 model (that would not involve vomotting your guts out 🤮 dealing with the |
| |
23 // putrid horror that C++ has become (and its documentation written by lawyers and bureacrats, |
| |
24 // which is even worse than that of Fenics), our idea here is to simply assume that f is a |
| |
25 // second-order polynomial on each cell, and then evaluate it on that cell at 6 points, to |
| |
26 // construct a new model based on our existing P2 model code that is also used for minimising |
| |
27 // arbitrary sums. |
| |
28 inline CoordValuePair minmax_Function_f64_p2(Function<double> const* f, bool max) { |
| |
29 auto v = f->x(); |
| |
30 // Mesh |
| |
31 auto fp = f->function_space(); |
| |
32 auto mesh = fp->mesh(); |
| |
33 auto geom = mesh->geometry(); |
| |
34 auto topo = mesh->topology(); |
| |
35 |
| |
36 // Check that we're working with P2 elements |
| |
37 // |
| |
38 // Fenics defaults to GLL warped midpoint placement; see |
| |
39 // https://docs.fenicsproject.org/dolfinx/v0.7.0.post0/python/demos/demo_lagrange_variants.html |
| |
40 // This is not a problem with our current implementation, which works with an |
| |
41 // second-order |
| |
42 // polynomials due to duplication of effort (see above, why), but if we would directly |
| |
43 // take the cell quadrature matrix from Fenics, we should restrict el.lagrange_variant() = |
| |
44 // lagrange_variant::equispaced. |
| |
45 auto el = fp->element()->basix_element(); |
| |
46 // printf("%d %d %d %d\n", el.degree(), el.cell_type(), el.lagrange_variant(), |
| |
47 // el.family()); |
| |
48 if (el.degree() != 2 || el.cell_type() != cell::type::triangle || |
| |
49 /*el.lagrange_variant() != lagrange_variant::equispaced ||*/ el.family() != |
| |
50 family::P) { |
| |
51 throw "Only equispaced Lagrange second-order polynomial elements are supported"; |
| |
52 } |
| |
53 if (cell_num_entities(mesh::CellType::triangle, 0) != 3) { |
| |
54 throw "A triangle should have three vertices"; |
| |
55 } |
| |
56 |
| |
57 // Check that we are dealing with a scalar function |
| |
58 if (fp->value_shape().size() != 0) { |
| |
59 throw "Only scalar functions are supported, obviously."; |
| |
60 } |
| |
61 |
| |
62 // Check that we're in two dimensions |
| |
63 if (topo->dim() != 2 || geom.dim() != 2) { |
| |
64 throw "Only two-dimensional meshes are supported"; |
| |
65 } |
| |
66 |
| |
67 // Check that there are no other types of eleemnts |
| |
68 auto entity_types = topo->entity_types(2); |
| |
69 for (auto& t : entity_types) { |
| |
70 if (t != mesh::CellType::triangle) { |
| |
71 throw "Only triangular meshes are supported"; |
| |
72 } |
| |
73 } |
| |
74 |
| |
75 auto adj = topo->connectivity(2, 0); |
| |
76 auto n_cells = adj->num_nodes(); |
| |
77 |
| |
78 // assert(n_cells == n_triangles); |
| |
79 |
| |
80 // printf("cell count? %d\n", n_cells); |
| |
81 // for (auto i = 0; i < n_cells; i++) { |
| |
82 // // assert(adj->num_links(i) == 3); // Should not need this |
| |
83 // auto d = fp->dofmap()->cell_dofs(i); |
| |
84 // printf("%zd\n", d.size()); |
| |
85 // } |
| |
86 |
| |
87 // DOF coordinates (may be more than nodes, e.g., intermediate points) |
| |
88 auto gx = geom.x(); |
| |
89 // auto num_points = gx.size() / 3; |
| |
90 // Map from cells to DOF indices |
| |
91 auto geom_dofmap = geom.dofmap(); |
| |
92 assert(geom_dofmap.extent(0) == (size_t)n_cells); |
| |
93 assert(geom_dofmap.extent(1) == 3); |
| |
94 auto fp_dofmap = fp->dofmap(); |
| |
95 |
| |
96 // auto dof_coords = fp->tabulate_dof_coordinates(); |
| |
97 |
| |
98 CoordValuePair res{{{0.0, 0.0}}, INFINITY}; |
| |
99 |
| |
100 // printf("%zd %zd\n", v->array().size(), gx.size()); |
| |
101 |
| |
102 for (auto i = 0; i < n_cells; i++) { |
| |
103 // Cell corners |
| |
104 auto j0 = geom_dofmap(i, 0); |
| |
105 auto j1 = geom_dofmap(i, 1); |
| |
106 auto j2 = geom_dofmap(i, 2); |
| |
107 |
| |
108 // Construct list of coordinates. Due to f->eval, we construct this is a single list |
| |
109 // with all the Fenics weirdness of hard-coded 3D. |
| |
110 std::array<double, 9> x = {gx[3 * j0], gx[3 * j0 + 1], 0, |
| |
111 gx[3 * j1], gx[3 * j1 + 1], 0, |
| |
112 gx[3 * j2], gx[3 * j2 + 1], 0}; |
| |
113 |
| |
114 // Find the solution on the simplex. |
| |
115 auto newres = minmax_dolfinx_p2_cell(f, x, i, max); |
| |
116 |
| |
117 // printf("%f [%f, %f] [%f %f %f %f %f %f] <%f, %f> <%f, %f> <‸%f, %f>\n", newres.v, |
| |
118 // newres.x[0], newres.x[1], values[0], values[1], values[2], values[3], |
| |
119 // values[4], values[5], x[0], x[1], x[3], x[4], x[6], x[7]); |
| |
120 if (newres.v < res.v) { |
| |
121 res = newres; |
| |
122 } |
| |
123 |
| |
124 // // Full DOF data |
| |
125 // auto d = fp_dofmap->cell_dofs(i); |
| |
126 // // TODO: array() gives just local part of vector |
| |
127 // assert(d.size() == 6); |
| |
128 // for (auto k = 0; k < d.size(); k++) { |
| |
129 // auto j = d[k]; |
| |
130 // // printf("%d %d\n", 3 * j + 1, nodes.size()); |
| |
131 // assert(2 * j + 1 <= nodes.size()); |
| |
132 // auto x = {nodes[3 * j], nodes[3 * j + 1]}; |
| |
133 // } |
| |
134 } |
| |
135 |
| |
136 // Negate result if maximising. |
| |
137 if (max) { |
| |
138 res.v = -res.v; |
| |
139 } |
| |
140 return res; |
| |
141 } |
| |
142 |
| |
143 CoordValuePair min_Function_f64_p2(Function<double> const* f) { |
| |
144 return minmax_Function_f64_p2(f, false); |
| |
145 } |
| |
146 |
| |
147 CoordValuePair max_Function_f64_p2(Function<double> const* f) { |
| |
148 return minmax_Function_f64_p2(f, true); |
| |
149 } |
| |
150 |
| |
151 // Create a Python module as well |
| |
152 NB_MODULE(_dolfinx_access, m) { |
| |
153 m.def("min_Function_f64_p2", &min_Function_f64_p2); |
| |
154 m.def("max_Function_f64_p2", &max_Function_f64_p2); |
| |
155 m.def("minmax_Function_f64_p2", &minmax_Function_f64_p2); |
| |
156 m.def("eval_Function_f64", &eval_Function_f64); |
| |
157 m.def("cell_Function_f64", &cell_Function_f64); |
| |
158 // m.def("cell_Mesh_f64", &cell_Mesh_f64); |
| |
159 m.def("cell_FunctionSpace_f64", &cell_FunctionSpace_f64); |
| |
160 nanobind::class_<CoordValuePair>(m, "CoordValuePair") |
| |
161 .def_rw("x", &CoordValuePair::x) |
| |
162 .def_rw("v", &CoordValuePair::v); |
| |
163 } |
| |
164 |
| |
165 } // namespace dolfinx_access |
| |
166 |
| |
167 // Forward declaration for manual registration |
| |
168 extern "C" PyObject* PyInit__dolfinx_access(); |