14#include "libmesh/dense_matrix.h"
15#include "libmesh/dense_vector.h"
16#include "libmesh/dof_map.h"
17#include "libmesh/elem.h"
18#include "libmesh/enum_elem_type.h"
19#include "libmesh/enum_fe_family.h"
20#include "libmesh/enum_order.h"
21#include "libmesh/enum_point_locator_type.h"
22#include "libmesh/enum_to_string.h"
23#include "libmesh/fe_base.h"
24#include "libmesh/fe_compute_data.h"
25#include "libmesh/fe_interface.h"
26#include "libmesh/fe_map.h"
27#include "libmesh/int_range.h"
28#include "libmesh/linear_implicit_system.h"
29#include "libmesh/linear_solver.h"
30#include "libmesh/node.h"
31#include "libmesh/numeric_vector.h"
32#include "libmesh/quadrature_gauss.h"
33#include "libmesh/sparse_matrix.h"
34#include "libmesh/utility.h"
45constexpr Real dirichlet_penalty = 1.0e10;
48constexpr Real linear_solver_tolerance = 1.0e-10;
51constexpr Real singular_magnitude = 0.1;
54constexpr Real minimum_magnitude = 1.0e-12;
57boundaryCrossField(
const std::map<dof_id_type, Real> & boundary_tangent_angles)
60 for (
const auto & [node_id, tangent_angle] : boundary_tangent_angles)
61 boundary_cross_field.emplace(node_id,
std::polar(1.0, 4.0 * tangent_angle));
62 return boundary_cross_field;
67 const std::map<dof_id_type, Real> & boundary_tangent_angles)
68 : _communicator(MPI_COMM_SELF),
70 _boundary_cross_field(boundaryCrossField(boundary_tangent_angles)),
74 _imaginary_variable(0),
80 mooseError(
"XYCrossFieldSolver: The cross field background mesh must be replicated.");
88 for (
const auto & elem :
_mesh.element_ptr_range())
89 if (elem->type() !=
TRI3)
90 mooseError(
"XYCrossFieldSolver: The cross field background mesh must contain only TRI3 "
91 "elements, but it contains an element of type ",
97 mooseError(
"XYCrossFieldSolver: A boundary tangent angle was supplied for node ",
99 ", which is not a node of the cross field background mesh.");
121 mooseError(
"XYCrossFieldSolver: The cross field solve failed to converge with reason: ",
140 fe->attach_quadrature_rule(&qrule);
142 const std::vector<Real> & JxW = fe->get_JxW();
143 const std::vector<std::vector<RealGradient>> & dphi = fe->get_dphi();
148 std::vector<dof_id_type> real_dof_indices;
149 std::vector<dof_id_type> imaginary_dof_indices;
153 for (
const auto & elem :
_mesh.active_local_element_ptr_range())
159 const auto n_dofs = real_dof_indices.size();
160 Ke.
resize(n_dofs, n_dofs);
162 Fe_imaginary.
resize(n_dofs);
167 Ke(i, j) += JxW[qp] * (dphi[i][qp] * dphi[j][qp]);
173 mooseAssert(n_dofs == elem->n_nodes(),
174 "The nodal penalty indexes degrees of freedom by local node number, which linear "
175 "Lagrange on TRI3 must supply one of per element node.");
182 Ke(i, i) += dirichlet_penalty;
183 Fe_real(i) += dirichlet_penalty * boundary_value->second.real();
184 Fe_imaginary(i) += dirichlet_penalty * boundary_value->second.imag();
189 system_matrix.add_matrix(Ke, real_dof_indices);
190 system_matrix.add_matrix(Ke, imaginary_dof_indices);
205 for (
const auto & node :
_mesh.node_ptr_range())
208 const std::complex<Real> z(solution(node->dof_number(system_number,
_real_variable, 0)),
210 const Real magnitude = std::abs(z);
212 if (magnitude < singular_magnitude)
217 const auto normalized =
218 magnitude > minimum_magnitude ? z / magnitude : std::complex<Real>(1.0, 0.0);
228 mooseAssert(
_solved,
"solve() must be called before the cross field can be queried.");
230 const Elem * elem = (*_point_locator)(point);
232 mooseError(
"XYCrossFieldSolver: No element was found to contain point ", point);
239 "Linear Lagrange on TRI3 must supply one shape function per element node.");
243 std::complex<Real> interpolated(0.0, 0.0);
246 const Real shape_value = fe_data.
shape[i];
259std::pair<Point, Point>
263 const Point u(std::cos(angle), std::sin(angle), 0.0);
264 return {u,
Point(-u(1), u(0), 0.0)};
void mooseError(Args &&... args)
Emit an error message with the given stringified, concatenated args and terminate the application.
Solves for a cross field, the smooth field of 4-fold symmetric directions that gives XYFrontalDelauna...
std::pair< Point, Point > crossFrame(const Point &point) const
static void assembleSystem(libMesh::EquationSystems &es, const std::string &system_name)
libMesh assembly callback; forwards to assembleLaplace() on the solver stashed in es.
std::map< dof_id_type, std::complex< Real > > NodalCrossField
Values of the complex cross field z at the nodes of the background mesh, keyed by node id.
const libMesh::FEType _fe_type
Linear Lagrange, the discretization of both components of z.
void solve()
Assemble and solve the two Laplace problems, then normalize the nodal field and collect the singular ...
unsigned int _real_variable
Variable number of the real part of z.
XYCrossFieldSolver(const libMesh::MeshBase &background_mesh, const std::map< dof_id_type, Real > &boundary_tangent_angles)
Build a cross field solver on a background triangulation.
std::unique_ptr< libMesh::PointLocatorBase > _point_locator
Locates the element containing a query point.
NodalCrossField _nodal_cross_field
Unit-magnitude nodal values of z, keyed by node id.
std::unique_ptr< libMesh::EquationSystems > _equation_systems
Holds the solve; kept past solve() because shape function evaluation needs it.
void assembleLaplace()
Assemble the Laplace operator for both variables and the nodal penalty Dirichlet conditions.
libMesh::ReplicatedMesh _mesh
Private copy of the background triangulation, sharing the caller's node ids.
const NodalCrossField _boundary_cross_field
Dirichlet value exp(4 i theta_t) of each boundary node, keyed by node id.
bool _solved
Whether solve() has produced the results the queries read.
void extractNodalCrossField()
Normalize the solved nodal values of z and record the nodes where z vanishes.
libMesh::LinearImplicitSystem * _system
System holding both components of z; owned by _equation_systems.
std::complex< Real > interpolatedCrossField(const Point &point) const
std::vector< dof_id_type > _singular_nodes
Ascending ids of the nodes where z vanishes.
unsigned int _imaginary_variable
Variable number of the imaginary part of z.
Real theta(const Point &point) const
void resize(const unsigned int new_m, const unsigned int new_n)
void resize(const unsigned int n)
void dof_indices(const Elem *const elem, std::vector< dof_id_type > &di) const
virtual unsigned int n_nodes() const=0
virtual unsigned short dim() const=0
dof_id_type node_id(const unsigned int i) const
NumericVector< Number > * rhs
std::vector< Number > shape
std::unique_ptr< FEGenericBase< Real > > build(const unsigned int dim, const FEType &fet)
static void compute_data(const unsigned int dim, const FEType &fe_t, const Elem *elem, FEComputeData &data)
static Point inverse_map(const unsigned int dim, const Elem *elem, const Point &p, const Real tolerance=TOLERANCE, const bool secure=true, const bool extra_checks=true)
Order default_quadrature_order() const
const SparseMatrix< Number > & get_system_matrix() const
virtual LinearSolver< Number > * get_linear_solver() const override
virtual void solve() override
virtual LinearConvergenceReason get_converged_reason() const=0
unsigned int mesh_dimension() const
virtual bool is_replicated() const
void allow_renumbering(bool allow)
void prepare_for_use(const bool skip_renumber_nodes_and_elements, const bool skip_find_neighbors)
virtual void add_vector(const T *v, const std::vector< numeric_index_type > &dof_indices)
const T & get(std::string_view) const
static std::unique_ptr< PointLocatorBase > build(PointLocatorType t, const MeshBase &mesh, const PointLocatorBase *master=nullptr)
unsigned int n_points() const
virtual const Node * query_node_ptr(const dof_id_type i) const override final
void attach_assemble_function(void fptr(EquationSystems &es, const std::string &name))
unsigned int add_variable(std::string_view var, const FEType &type, const std::set< subdomain_id_type > *const active_subdomains=nullptr)
std::unique_ptr< NumericVector< Number > > solution
const DofMap & get_dof_map() const
unsigned int number() const
virtual void copy_nodes_and_elements(const MeshBase &other_mesh, const bool skip_find_neighbors=false, dof_id_type element_id_offset=0, dof_id_type node_id_offset=0, unique_id_type unique_id_offset=0, std::unordered_map< subdomain_id_type, subdomain_id_type > *id_remapping=nullptr, const bool skip_preparation=false)
std::string enum_to_string(const T e)
The following methods are specializations for using the libMesh::Parallel::packed_range_* routines fo...
auto index_range(const T &sizable)
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
IntRange< T > make_range(T beg, T end)