3030#include "libmesh/string_to_enum.h"
3131#include "libmesh/utility.h"
3232#include "libmesh/enum_to_string.h"
33+ #include <libmesh/reference_elem.h>
3334
3435// C++ includes
3536#include <functional> // std::reference_wrapper
@@ -102,7 +103,6 @@ void VariationalSmootherSystem::prepare_for_smoothing()
102103 this -> init_context (femcontext );
103104
104105 const auto & mesh = this -> get_mesh ();
105- const auto dim = mesh .mesh_dimension ();
106106
107107 Real elem_averaged_det_J_sum = 0. ;
108108
@@ -112,122 +112,27 @@ void VariationalSmootherSystem::prepare_for_smoothing()
112112 const auto & fe_map = femcontext .get_element_fe (0 )-> get_fe_map ();
113113 const auto & JxW = fe_map .get_JxW ();
114114
115- std ::map < ElemType , std ::vector < Real >> target_elem_inverse_jacobian_dets ;
115+ std ::map < ElemType , std ::vector < Real >> target_jacobians_dets ;
116116
117117 for (const auto * elem : mesh .active_local_element_ptr_range ())
118118 {
119119 femcontext .pre_fe_reinit (* this , elem );
120120 femcontext .elem_fe_reinit ();
121121
122122 // Add target element info, if applicable
123- if (_target_inverse_jacobians .find (elem -> type ()) == _target_inverse_jacobians .end ())
123+ if (_target_jacobians .find (elem -> type ()) == _target_jacobians .end ())
124124 {
125- // Create FEMap to compute target_element mapping information
126- FEMap fe_map_target ;
127-
128- // pre-request mapping derivatives
129- const auto & dxyzdxi = fe_map_target .get_dxyzdxi ();
130- const auto & dxyzdeta = fe_map_target .get_dxyzdeta ();
131- // const auto & dxyzdzeta = fe_map_target.get_dxyzdzeta();
132-
133- const auto & qrule_points = femcontext .get_element_qrule ().get_points ();
134- const auto & qrule_weights = femcontext .get_element_qrule ().get_weights ();
135- const auto nq_points = femcontext .get_element_qrule ().n_points ();
136-
137- // If the target element is the reference element, Jacobian matrix is
138- // identity, det of inverse is 1. This will only be overwritten if a
139- // different target elemen is explicitly specified.
140- target_elem_inverse_jacobian_dets [elem -> type ()] =
141- std ::vector < Real > (nq_points , 1.0 );
142-
143- const auto type_str = Utility ::enum_to_string (elem -> type ());
144-
145- // Elems deriving from Tri
146- if (type_str .compare (0 , 3 , "TRI" ) == 0 )
147- {
148-
149- // The target element will be an equilateral triangle with area equal to
150- // the area of the reference element.
151-
152- // First, let's define the nodal locations of the vertices
153- const Real sqrt_3 = std ::sqrt (3. );
154-
155- std ::vector < Point > equilateral_points {
156- Point (0. , 0. ),
157- Point (1. , 0. ),
158- Point (0.5 , 0.5 * sqrt_3 )
159- };
160-
161- // Target element
162- const auto target_elem = Elem ::build (elem -> type ());
163-
164- // Area of the reference element
165- const auto ref_area = target_elem -> reference_elem ()-> volume ();
166-
167- switch (elem -> type ())
168- {
169- case TRI3 :
170- {
171- // Nothing to do here, vertices already defined in equilateral_points
172- break ;
173- }
174-
175- case TRI6 :
176- {
177- // Define the midpoint nodes of the equilateral triangle
178- equilateral_points .emplace_back (0.50 , 0. );
179- equilateral_points .emplace_back (0.75 , 0.25 * sqrt_3 );
180- equilateral_points .emplace_back (0.25 , 0.25 * sqrt_3 );
181-
182- break ;
183- }
184-
185- default :
186- libmesh_error_msg ("Unsupported triangular element: " << type_str );
187- break ;
188- }
189-
190- // Equilateral triangle side length preserving area of the reference element
191- const auto side_length = std ::sqrt (4. / sqrt_3 * ref_area );
192-
193- std ::vector < std ::unique_ptr < Node >> owned_nodes ;
194-
195- // Set nodes of target element to form an equilateral triangle
196- for (const dof_id_type node_id : index_range (equilateral_points ))
197- {
198- // Scale the nodal positions to conserve area, store the pointer to keep it alive
199- owned_nodes .emplace_back (
200- Node ::build (equilateral_points [node_id ] * side_length , node_id ));
201- target_elem -> set_node (node_id , owned_nodes .back ().get ());
202- }
203-
204- // build map
205- fe_map_target .init_reference_to_physical_map (dim , qrule_points , target_elem .get ());
206- fe_map_target .compute_map (dim , qrule_weights , target_elem .get (), /*d2phi=*/ false);
207-
208- // Yes, triangle-to-triangle mappings have constant Jacobians, but we
209- // will keep things general for now
210- _target_inverse_jacobians [target_elem -> type ()] =
211- std ::vector < RealTensor > (nq_points );
212- for (const auto qp : make_range (nq_points ))
213- {
214- const RealTensor H_inv =
215- RealTensor (dxyzdxi [qp ](0 ), dxyzdeta [qp ](0 ), 0 , dxyzdxi [qp ](1 ),
216- dxyzdeta [qp ](1 ), 0 , 0 , 0 , 1 )
217- .inverse ();
218-
219- _target_inverse_jacobians [target_elem -> type ()][qp ] = H_inv ;
220- target_elem_inverse_jacobian_dets [target_elem -> type ()][qp ] =
221- H_inv .det ();
222- }
223- }// if Tri
125+ const auto [target_elem , target_nodes ] = get_target_elem (elem -> type ());
126+ get_target_to_reference_jacobian (target_elem .get (),
127+ femcontext ,
128+ _target_jacobians [elem -> type ()],
129+ target_jacobians_dets [elem -> type ()]);
224130 }// if find == end()
225131
226132 // Reference volume computation
227133 Real elem_integrated_det_J = 0. ;
228134 for (const auto qp : index_range (JxW ))
229- elem_integrated_det_J +=
230- JxW [qp ] * target_elem_inverse_jacobian_dets [elem -> type ()][qp ];
135+ elem_integrated_det_J += JxW [qp ] * target_jacobians_dets [elem -> type ()][qp ];
231136 const auto ref_elem_vol = elem -> reference_elem ()-> volume ();
232137 elem_averaged_det_J_sum += elem_integrated_det_J / ref_elem_vol ;
233138
@@ -378,9 +283,8 @@ bool VariationalSmootherSystem::element_time_derivative (bool request_jacobian,
378283 }
379284
380285 // Apply any applicable target element transformations
381- if (_target_inverse_jacobians .find (elem .type ()) !=
382- _target_inverse_jacobians .end ())
383- S = S * _target_inverse_jacobians [elem .type ()][qp ];
286+ if (_target_jacobians .find (elem .type ()) != _target_jacobians .end ())
287+ S = S * _target_jacobians [elem .type ()][qp ];
384288
385289 // Compute quantities needed for the smoothing algorithm
386290
@@ -724,4 +628,156 @@ bool VariationalSmootherSystem::element_time_derivative (bool request_jacobian,
724628 return request_jacobian ;
725629}
726630
631+ std ::pair < std ::unique_ptr < Elem > , std ::vector < std ::unique_ptr < Node >>>
632+ VariationalSmootherSystem ::get_target_elem (const ElemType & type )
633+ {
634+ // Build target element
635+ auto target_elem = Elem ::build (type );
636+
637+ // Volume of reference element
638+ const auto ref_vol = target_elem -> reference_elem ()-> volume ();
639+
640+ // Update the nodes of the target element, depending on type
641+ const Real sqrt_3 = std ::sqrt (Real (3 ));
642+ std ::vector < std ::unique_ptr < Node >> owned_nodes ;
643+
644+ const auto type_str = Utility ::enum_to_string (type );
645+
646+ // Elems deriving from Tri
647+ if (type_str .compare (0 , 3 , "TRI" ) == 0 )
648+ {
649+
650+ // The target element will be an equilateral triangle with area equal to
651+ // the area of the reference element.
652+
653+ // Equilateral triangle side length preserving area of the reference element
654+ const auto side_length = std ::sqrt (4. / sqrt_3 * ref_vol );
655+
656+ // Define the nodal locations of the vertices
657+ const auto & s = side_length ;
658+ // x y node_id
659+ owned_nodes .emplace_back (Node ::build (Point (0. , 0. ), 0 ));
660+ owned_nodes .emplace_back (Node ::build (Point (s , 0. ), 1 ));
661+ owned_nodes .emplace_back (Node ::build (Point (0.5 * s , 0.5 * sqrt_3 * s ), 2 ));
662+
663+ switch (type )
664+ {
665+ case TRI3 : {
666+ // Nothing to do here, vertices already added above
667+ break ;
668+ }
669+
670+ case TRI6 : {
671+ // Define the midpoint nodes of the equilateral triangle
672+ // x y node_id
673+ owned_nodes .emplace_back (Node ::build (Point (0.50 * s , 0.00 ), 3 ));
674+ owned_nodes .emplace_back (Node ::build (Point (0.75 * s , 0.25 * sqrt_3 * s ), 4 ));
675+ owned_nodes .emplace_back (Node ::build (Point (0.25 * s , 0.25 * sqrt_3 * s ), 5 ));
676+
677+ break ;
678+ }
679+
680+ default :
681+ libmesh_error_msg ("Unsupported triangular element: " << type_str );
682+ break ;
683+ }
684+ } // if Tri
685+
686+
687+ // Set the target_elem equal to the reference elem
688+ else
689+ for (const auto & node : target_elem -> reference_elem ()-> node_ref_range ())
690+ owned_nodes .emplace_back (Node ::build (node , node .id ()));
691+
692+ // Set nodes of target element
693+ for (const auto & node_ptr : owned_nodes )
694+ target_elem -> set_node (node_ptr -> id (), node_ptr .get ());
695+
696+ libmesh_assert (relative_fuzzy_equals (target_elem -> volume (), ref_vol ));
697+
698+ return std ::make_pair (std ::move (target_elem ), std ::move (owned_nodes ));
699+ }
700+
701+ void VariationalSmootherSystem ::get_target_to_reference_jacobian (
702+ const Elem * const target_elem ,
703+ const FEMContext & femcontext ,
704+ std ::vector < RealTensor > & jacobians ,
705+ std ::vector < Real > & jacobian_dets )
706+ {
707+
708+ const auto dim = target_elem -> dim ();
709+
710+ const auto & qrule_points = femcontext .get_element_qrule ().get_points ();
711+ const auto & qrule_weights = femcontext .get_element_qrule ().get_weights ();
712+ const auto nq_points = femcontext .get_element_qrule ().n_points ();
713+
714+ // If the target element is the reference element, Jacobian matrix is
715+ // identity, det of inverse is 1. These will only be overwritten if a
716+ // different target element is explicitly specified.
717+ jacobians = std ::vector < RealTensor > (nq_points , RealTensor (
718+ 1. , 0. , 0. ,
719+ 0. , 1. , 0. ,
720+ 0. , 0. , 1. ));
721+ jacobian_dets = std ::vector < Real > (nq_points , 1.0 );
722+
723+ // Don't use "if (*target_elem == *(target_elem->reference_elem()))" here, it
724+ // only compares global node ids, not the node locations themselves.
725+ bool target_equals_reference = true;
726+ const auto * ref_elem = target_elem -> reference_elem ();
727+ for (const auto local_id : make_range (target_elem -> n_nodes ()))
728+ target_equals_reference &= target_elem -> node_ref (local_id ) == ref_elem -> node_ref (local_id );
729+ if (target_equals_reference )
730+ return ;
731+
732+ // Create FEMap to compute target_element mapping information
733+ FEMap fe_map_target ;
734+
735+ // pre-request mapping derivatives
736+ const auto & dxyzdxi = fe_map_target .get_dxyzdxi ();
737+ const auto & dxyzdeta = fe_map_target .get_dxyzdeta ();
738+ const auto & dxyzdzeta = fe_map_target .get_dxyzdzeta ();
739+
740+ // build map
741+ fe_map_target .init_reference_to_physical_map (dim , qrule_points , target_elem );
742+ fe_map_target .compute_map (dim , qrule_weights , target_elem , /*d2phi=*/ false);
743+
744+ for (const auto qp : make_range (nq_points ))
745+ {
746+ // We use Larisa's H notation to denote the reference-to-target jacobian
747+ RealTensor H ;
748+ switch (dim )
749+ {
750+ case 1 : {
751+ H = RealTensor (
752+ dxyzdxi [qp ](0 ), 0. , 0. ,
753+ 0. , 1. , 0. ,
754+ 0. , 0. , 1. );
755+
756+ break ;
757+ }
758+ case 2 : {
759+ H = RealTensor (
760+ dxyzdxi [qp ](0 ), dxyzdeta [qp ](0 ), 0. ,
761+ dxyzdxi [qp ](1 ), dxyzdeta [qp ](1 ), 0. ,
762+ 0. , 0. , 1. );
763+
764+ break ;
765+ }
766+ case 3 : {
767+ H = RealTensor (dxyzdxi [qp ], dxyzdeta [qp ], dxyzdzeta [qp ]).transpose ();
768+
769+ break ;
770+ }
771+
772+ default :
773+ libmesh_error_msg ("Unsupported mesh dimension " << dim );
774+ }
775+
776+ // The target-to-reference jacobian is the inverse of the
777+ // reference-to-target jacobian
778+ jacobians [qp ] = H .inverse ();
779+ jacobian_dets [qp ] = jacobians [qp ].det ();
780+ }
781+ }
782+
727783} // namespace libMesh
0 commit comments