14 #include "libmesh/elem.h" 15 #include "libmesh/boundary_info.h" 16 #include "libmesh/mesh_base.h" 17 #include "libmesh/parallel.h" 18 #include "libmesh/parallel_algebra.h" 19 #include "libmesh/cell_tet4.h" 30 params.
addRequiredParam<MeshGeneratorName>(
"input",
"The input mesh that needs to be trimmed.");
33 "generate_transition_layer",
35 "Whether to generate a transition layer for the cut mesh. " 36 "If false, the entire input mesh will be converted to TET4 elements to facilitate the " 37 "cutting; if true, only the elements near the cut face will be converted with a transition " 38 "layer to maintain compatibility with the original mesh.");
41 "The boundary id of the face generated by the cut. An " 42 "id will be automatically assigned if not provided.");
44 "cut_face_name", BoundaryName(),
"The boundary name of the face generated by the cut.");
45 params.
addParam<SubdomainName>(
"converted_tet_element_subdomain_name_suffix",
47 "The suffix to be added to the original subdomain name for the " 48 "subdomains containing the elements converted to TET4. This is " 49 "only applicable when transition layer is generated.");
51 "converted_pyramid_element_subdomain_name_suffix",
53 "The suffix to be added to the original subdomain name for the subdomains containing the " 54 "elements converted to PYRAMID5. This is only applicable when transition layer is " 58 "This CutMeshByLevelSetGeneratorBase object is designed to be the base class of mesh " 59 "generator that cuts a 3D mesh based on an analytic level set function. The level set " 60 "function could be provided explicitly or indirectly.");
68 _input_name(getParam<MeshGeneratorName>(
"input")),
69 _generate_transition_layer(getParam<bool>(
"generate_transition_layer")),
70 _cut_face_name(getParam<BoundaryName>(
"cut_face_name")),
71 _converted_tet_element_subdomain_name_suffix(
72 getParam<SubdomainName>(
"converted_tet_element_subdomain_name_suffix")),
73 _converted_pyramid_element_subdomain_name_suffix(
74 getParam<SubdomainName>(
"converted_pyramid_element_subdomain_name_suffix")),
75 _input(getMeshByName(_input_name))
80 std::unique_ptr<MeshBase>
83 auto replicated_mesh_ptr =
dynamic_cast<ReplicatedMesh *
>(
_input.get());
84 if (!replicated_mesh_ptr)
85 paramError(
"input",
"Input is not a replicated mesh, which is required");
86 if (*(replicated_mesh_ptr->elem_dimensions().begin()) != 3 ||
87 *(replicated_mesh_ptr->elem_dimensions().rbegin()) != 3)
90 "Only 3D meshes are supported. Use XYMeshLineCutter for 2D meshes if applicable. Mixed " 91 "dimensional meshes are not supported at the moment.");
93 ReplicatedMesh &
mesh = *replicated_mesh_ptr;
103 "The cut face boundary name and id are both provided, but they are inconsistent " 104 "with an existing boundary name which has a different id.");
123 const auto block_id_to_remove = sid_shift_base * 3;
127 std::vector<std::pair<dof_id_type, bool>> cross_and_remained_elems_pre_convert;
130 std::set<subdomain_id_type> original_subdomain_ids;
131 for (
auto elem_it =
mesh.active_elements_begin(); elem_it !=
mesh.active_elements_end();
134 original_subdomain_ids.emplace((*elem_it)->subdomain_id());
135 const unsigned int & n_vertices = (*elem_it)->n_vertices();
136 unsigned short elem_vertices_counter(0);
137 for (
unsigned int i = 0; i < n_vertices; i++)
143 elem_vertices_counter++;
145 if (elem_vertices_counter == n_vertices)
146 (*elem_it)->subdomain_id() = block_id_to_remove;
150 if ((*elem_it)->default_order() != Order::FIRST)
151 mooseError(
"Only first order elements are supported for cutting.");
155 cross_and_remained_elems_pre_convert.push_back(std::make_pair((*elem_it)->id(),
true));
158 if ((*elem_it)->type() !=
TET4)
159 (*elem_it)->subdomain_id() += sid_shift_base;
162 cross_and_remained_elems_pre_convert.push_back(
163 std::make_pair((*elem_it)->id(), elem_vertices_counter > 0));
168 std::vector<dof_id_type> transition_elems_list;
169 std::vector<std::vector<unsigned int>> transition_elems_sides_list;
172 if (!
mesh.is_prepared())
173 mesh.find_neighbors();
176 for (
const auto & elem_info : cross_and_remained_elems_pre_convert)
178 for (
const auto & i_side :
make_range(
mesh.elem_ptr(elem_info.first)->n_sides()))
181 if (
mesh.elem_ptr(elem_info.first)->side_ptr(i_side)->type() ==
QUAD4)
183 if (
mesh.elem_ptr(elem_info.first)->neighbor_ptr(i_side) !=
nullptr &&
184 mesh.elem_ptr(elem_info.first)->neighbor_ptr(i_side)->subdomain_id() !=
185 block_id_to_remove &&
186 std::find(cross_and_remained_elems_pre_convert.begin(),
187 cross_and_remained_elems_pre_convert.end(),
188 std::make_pair(
mesh.elem_ptr(elem_info.first)->neighbor_ptr(i_side)->id(),
189 true)) == cross_and_remained_elems_pre_convert.end())
191 const auto & neighbor_elem_id =
192 mesh.elem_ptr(elem_info.first)->neighbor_ptr(i_side)->id();
193 const auto & neighbor_elem_side_index =
194 mesh.elem_ptr(elem_info.first)
195 ->neighbor_ptr(i_side)
196 ->which_neighbor_am_i(
mesh.elem_ptr(elem_info.first));
198 transition_elems_list.begin(), transition_elems_list.end(), neighbor_elem_id);
199 if (id_found == transition_elems_list.end())
201 transition_elems_list.push_back(neighbor_elem_id);
202 transition_elems_sides_list.push_back(
203 std::vector<unsigned int>({neighbor_elem_side_index}));
208 const auto index = std::distance(transition_elems_list.begin(), id_found);
209 transition_elems_sides_list[index].push_back(neighbor_elem_side_index);
217 std::vector<dof_id_type> converted_elems_ids_to_cut;
220 cross_and_remained_elems_pre_convert,
221 converted_elems_ids_to_cut,
226 auto & sideset_map =
mesh.get_boundary_info().get_sideset_map();
227 for (
const auto & i_elem :
index_range(transition_elems_list))
229 const auto & elem_id = transition_elems_list[i_elem];
230 const auto & side_indices = transition_elems_sides_list[i_elem];
232 std::vector<std::vector<boundary_id_type>> elem_side_info(
mesh.elem_ptr(elem_id)->n_sides());
233 auto side_range = sideset_map.equal_range(
mesh.elem_ptr(elem_id));
234 for (
auto i = side_range.first; i != side_range.second; ++i)
235 elem_side_info[i->second.first].push_back(i->second.second);
238 mesh, elem_id, side_indices, elem_side_info, sid_shift_base);
241 std::vector<const Node *> new_on_plane_nodes;
244 BoundaryInfo & boundary_info =
mesh.get_boundary_info();
245 const auto bdry_side_list = boundary_info.build_side_list();
247 for (
const auto & converted_elems_id_to_cut : converted_elems_ids_to_cut)
250 mesh, bdry_side_list, converted_elems_id_to_cut, block_id_to_remove, new_on_plane_nodes);
260 original_subdomain_ids,
265 catch (
const std::exception & e)
267 if (((std::string)e.what()).compare(26, 4,
"TET4") == 0)
268 paramError(
"converted_tet_element_subdomain_name_suffix", e.what());
270 paramError(
"converted_pyramid_element_subdomain_name_suffix", e.what());
275 for (
const auto & elem_id : transition_elems_list)
276 mesh.elem_ptr(elem_id)->subdomain_id() = block_id_to_remove;
278 for (
auto elem_it =
mesh.active_subdomain_elements_begin(block_id_to_remove);
279 elem_it !=
mesh.active_subdomain_elements_end(block_id_to_remove);
281 mesh.delete_elem(*elem_it);
284 mesh.set_isnt_prepared();
302 const Point & point2)
308 mooseError(
"At least one of the two points are on the plane.");
310 mooseError(
"The two points are on the same side of the plane.");
320 mid_point = 0.5 * (p1 + p2);
325 else if (dist_mid * dist1 < 0.0)
329 dist =
abs(dist1) +
abs(dist2);
335 dist =
abs(dist1) +
abs(dist2);
343 ReplicatedMesh & mesh,
344 std::vector<const Node *> & new_on_plane_nodes,
345 const Point & new_point)
const 347 for (
const auto & new_on_plane_node : new_on_plane_nodes)
350 return new_on_plane_node;
352 new_on_plane_nodes.push_back(
mesh.add_point(new_point));
353 return new_on_plane_nodes.back();
358 ReplicatedMesh & mesh,
359 const std::vector<libMesh::BoundaryInfo::BCTuple> & bdry_side_list,
362 std::vector<const Node *> & new_on_plane_nodes)
365 BoundaryInfo & boundary_info =
mesh.get_boundary_info();
371 std::vector<Point> elem_side_normal_list;
372 elem_side_normal_list.resize(4);
375 auto elem_side =
mesh.elem_ptr(elem_id)->side_ptr(i);
376 elem_side_normal_list[i] = (*elem_side->node_ptr(1) - *elem_side->node_ptr(0))
377 .cross(*elem_side->node_ptr(2) - *elem_side->node_ptr(1))
381 std::vector<std::vector<boundary_id_type>> elem_side_list;
383 bdry_side_list, elem_id, 4, elem_side_list);
385 std::vector<PointLevelSetRelationIndex> node_plane_relation(4);
386 std::vector<const Node *> tet4_nodes(4);
387 std::vector<const Node *> tet4_nodes_on_plane;
388 std::vector<const Node *> tet4_nodes_outside_plane;
389 std::vector<const Node *> tet4_nodes_inside_plane;
391 for (
unsigned int i = 0; i < 4; i++)
393 tet4_nodes[i] =
mesh.elem_ptr(elem_id)->node_ptr(i);
396 tet4_nodes_on_plane.push_back(tet4_nodes[i]);
398 tet4_nodes_outside_plane.push_back(tet4_nodes[i]);
400 tet4_nodes_inside_plane.push_back(tet4_nodes[i]);
402 std::vector<Elem *> elems_tet4;
403 bool new_elements_created(
false);
405 if (tet4_nodes_outside_plane.size() == 0)
407 if (tet4_nodes_on_plane.size() == 3)
410 elems_tet4.push_back(
mesh.elem_ptr(elem_id));
414 else if (tet4_nodes_inside_plane.size() == 0)
416 mesh.elem_ptr(elem_id)->subdomain_id() = block_id_to_remove;
417 if (tet4_nodes_on_plane.size() == 3)
426 new_elements_created =
true;
427 if (tet4_nodes_inside_plane.size() == 1 && tet4_nodes_outside_plane.size() == 3)
429 std::vector<const Node *> new_plane_nodes;
431 for (
const auto & tet4_node_outside_plane : tet4_nodes_outside_plane)
438 auto new_elem_tet4 = std::make_unique<Tet4>();
439 new_elem_tet4->set_node(0, const_cast<Node *>(tet4_nodes_inside_plane[0]));
440 new_elem_tet4->set_node(1, const_cast<Node *>(new_plane_nodes[0]));
441 new_elem_tet4->set_node(2, const_cast<Node *>(new_plane_nodes[1]));
442 new_elem_tet4->set_node(3, const_cast<Node *>(new_plane_nodes[2]));
443 new_elem_tet4->subdomain_id() =
mesh.elem_ptr(elem_id)->subdomain_id();
444 elems_tet4.push_back(
mesh.add_elem(std::move(new_elem_tet4)));
446 else if (tet4_nodes_inside_plane.size() == 2 && tet4_nodes_outside_plane.size() == 2)
448 std::vector<const Node *> new_plane_nodes;
450 for (
const auto & tet4_node_outside_plane : tet4_nodes_outside_plane)
452 for (
const auto & tet4_node_inside_plane : tet4_nodes_inside_plane)
460 std::vector<const Node *> new_elems_nodes = {tet4_nodes_inside_plane[1],
463 tet4_nodes_inside_plane[0],
466 std::vector<std::vector<unsigned int>> rotated_tet_face_indices;
467 std::vector<std::vector<const Node *>> optimized_node_list;
469 new_elems_nodes, rotated_tet_face_indices, optimized_node_list);
471 for (
unsigned int i = 0; i < optimized_node_list.size(); i++)
473 auto new_elem_tet4 = std::make_unique<Tet4>();
474 new_elem_tet4->set_node(0, const_cast<Node *>(optimized_node_list[i][0]));
475 new_elem_tet4->set_node(1, const_cast<Node *>(optimized_node_list[i][1]));
476 new_elem_tet4->set_node(2, const_cast<Node *>(optimized_node_list[i][2]));
477 new_elem_tet4->set_node(3, const_cast<Node *>(optimized_node_list[i][3]));
478 new_elem_tet4->subdomain_id() =
mesh.elem_ptr(elem_id)->subdomain_id();
479 elems_tet4.push_back(
mesh.add_elem(std::move(new_elem_tet4)));
482 else if (tet4_nodes_inside_plane.size() == 3 && tet4_nodes_outside_plane.size() == 1)
484 std::vector<const Node *> new_plane_nodes;
486 for (
const auto & tet4_node_inside_plane : tet4_nodes_inside_plane)
493 std::vector<const Node *> new_elems_nodes = {tet4_nodes_inside_plane[0],
494 tet4_nodes_inside_plane[1],
495 tet4_nodes_inside_plane[2],
499 std::vector<std::vector<unsigned int>> rotated_tet_face_indices;
500 std::vector<std::vector<const Node *>> optimized_node_list;
502 new_elems_nodes, rotated_tet_face_indices, optimized_node_list);
504 for (
unsigned int i = 0; i < optimized_node_list.size(); i++)
506 auto new_elem_tet4 = std::make_unique<Tet4>();
507 new_elem_tet4->set_node(0, const_cast<Node *>(optimized_node_list[i][0]));
508 new_elem_tet4->set_node(1, const_cast<Node *>(optimized_node_list[i][1]));
509 new_elem_tet4->set_node(2, const_cast<Node *>(optimized_node_list[i][2]));
510 new_elem_tet4->set_node(3, const_cast<Node *>(optimized_node_list[i][3]));
511 new_elem_tet4->subdomain_id() =
mesh.elem_ptr(elem_id)->subdomain_id();
512 elems_tet4.push_back(
mesh.add_elem(std::move(new_elem_tet4)));
515 else if (tet4_nodes_inside_plane.size() == 1 && tet4_nodes_outside_plane.size() == 1)
522 auto new_elem_tet4 = std::make_unique<Tet4>();
523 new_elem_tet4->set_node(0, const_cast<Node *>(new_plane_node));
524 new_elem_tet4->set_node(1, const_cast<Node *>(tet4_nodes_on_plane[0]));
525 new_elem_tet4->set_node(2, const_cast<Node *>(tet4_nodes_on_plane[1]));
526 new_elem_tet4->set_node(3, const_cast<Node *>(tet4_nodes_inside_plane[0]));
527 new_elem_tet4->subdomain_id() =
mesh.elem_ptr(elem_id)->subdomain_id();
528 elems_tet4.push_back(
mesh.add_elem(std::move(new_elem_tet4)));
530 else if (tet4_nodes_inside_plane.size() == 1 && tet4_nodes_outside_plane.size() == 2)
532 std::vector<const Node *> new_plane_nodes;
534 for (
const auto & tet4_node_outside_plane : tet4_nodes_outside_plane)
541 auto new_elem_tet4 = std::make_unique<Tet4>();
542 new_elem_tet4->set_node(0, const_cast<Node *>(new_plane_nodes[0]));
543 new_elem_tet4->set_node(1, const_cast<Node *>(new_plane_nodes[1]));
544 new_elem_tet4->set_node(2, const_cast<Node *>(tet4_nodes_on_plane[0]));
545 new_elem_tet4->set_node(3, const_cast<Node *>(tet4_nodes_inside_plane[0]));
546 new_elem_tet4->subdomain_id() =
mesh.elem_ptr(elem_id)->subdomain_id();
547 elems_tet4.push_back(
mesh.add_elem(std::move(new_elem_tet4)));
549 else if (tet4_nodes_inside_plane.size() == 2 && tet4_nodes_outside_plane.size() == 1)
551 std::vector<const Node *> new_plane_nodes;
553 for (
const auto & tet4_node_inside_plane : tet4_nodes_inside_plane)
560 std::vector<const Node *> new_elems_nodes = {tet4_nodes_inside_plane[0],
561 tet4_nodes_inside_plane[1],
564 tet4_nodes_on_plane[0]};
565 std::vector<std::vector<unsigned int>> rotated_tet_face_indices;
566 std::vector<std::vector<const Node *>> optimized_node_list;
568 new_elems_nodes, rotated_tet_face_indices, optimized_node_list);
570 for (
unsigned int i = 0; i < optimized_node_list.size(); i++)
572 auto new_elem_tet4 = std::make_unique<Tet4>();
573 new_elem_tet4->set_node(0, const_cast<Node *>(optimized_node_list[i][0]));
574 new_elem_tet4->set_node(1, const_cast<Node *>(optimized_node_list[i][1]));
575 new_elem_tet4->set_node(2, const_cast<Node *>(optimized_node_list[i][2]));
576 new_elem_tet4->set_node(3, const_cast<Node *>(optimized_node_list[i][3]));
577 new_elem_tet4->subdomain_id() =
mesh.elem_ptr(elem_id)->subdomain_id();
578 elems_tet4.push_back(
mesh.add_elem(std::move(new_elem_tet4)));
584 mesh.elem_ptr(elem_id)->subdomain_id() = block_id_to_remove;
587 for (
auto & elem_tet4 : elems_tet4)
589 if (new_elements_created)
591 if (elem_tet4->volume() < 0.0)
593 Node * temp = elem_tet4->node_ptr(0);
594 elem_tet4->set_node(0, elem_tet4->node_ptr(1));
595 elem_tet4->set_node(1, temp);
599 for (
unsigned int i = 0; i < 4; i++)
601 const Point & side_pt_0 = *elem_tet4->side_ptr(i)->node_ptr(0);
602 const Point & side_pt_1 = *elem_tet4->side_ptr(i)->node_ptr(1);
603 const Point & side_pt_2 = *elem_tet4->side_ptr(i)->node_ptr(2);
605 const Point side_normal = (side_pt_1 - side_pt_0).cross(side_pt_2 - side_pt_1).unit();
606 for (
unsigned int j = 0; j < 4; j++)
608 if (new_elements_created)
612 for (
const auto & side_info : elem_side_list[j])
614 boundary_info.add_side(elem_tet4, i, side_info);
std::unique_ptr< MeshBase > & _input
Reference to input mesh pointer.
const SubdomainName _converted_pyramid_element_subdomain_name_suffix
The suffix to be added to the original subdomain name for the subdomains containing the elements conv...
GenericReal< is_ad > evaluate(SymFunctionPtr &, const std::string &object_name="")
Evaluate FParser object and check EvalError.
MetaPhysicL::DualNumber< V, D, asd > abs(const MetaPhysicL::DualNumber< V, D, asd > &a)
KOKKOS_INLINE_FUNCTION const T * find(const T &target, const T *const begin, const T *const end)
Find a value in an array.
bool absoluteFuzzyEqual(const T &var1, const T2 &var2, const T3 &tol=libMesh::TOLERANCE *libMesh::TOLERANCE)
Function to check whether two variables are equal within an absolute tolerance.
bool hasBoundaryName(const MeshBase &input_mesh, const BoundaryName &name)
Whether a particular boundary name exists in the mesh.
void paramError(const std::string ¶m, Args... args) const
Emits an error prefixed with the file and line number of the given param (from the input file) along ...
SymFunctionPtr _func_level_set
function parser object describing the level set
void elementBoundaryInfoCollector(const std::vector< libMesh::BoundaryInfo::BCTuple > &bdry_side_list, const dof_id_type elem_id, const unsigned short n_elem_sides, std::vector< std::vector< boundary_id_type >> &elem_side_list)
Collect the boundary information of the given element in a mesh.
PointLevelSetRelationIndex pointLevelSetRelation(const Point &point)
Evaluate whether a point is on the level set, inside or outside the level set.
const bool _generate_transition_layer
Whether to generate a transition layer for the cut mesh.
const BoundaryName _cut_face_name
The boundary name of the surface generated by the cut.
Real levelSetEvaluator(const Point &point)
Evaluate the level set function at a given point.
BoundaryID getBoundaryID(const BoundaryName &boundary_name, const MeshBase &mesh)
Gets the boundary ID associated with the given BoundaryName.
const SubdomainName _converted_tet_element_subdomain_name_suffix
The suffix to be added to the original subdomain name for the subdomains containing the elements conv...
PointLevelSetRelationIndex
An enum class for style of input polygon size.
std::unique_ptr< MeshBase > generate() override
Generate / modify the mesh.
Point pointPairLevelSetInterception(const Point &point1, const Point &point2)
Calculate the intersection point of a level set and a line segment defined by two points separated by...
bool absoluteFuzzyLessThan(const T &var1, const T2 &var2, const T3 &tol=libMesh::TOLERANCE *libMesh::TOLERANCE)
Function to check whether a variable is less than another variable within an absolute tolerance...
static InputParameters validParams()
void convert3DMeshToAllTet4(ReplicatedMesh &mesh, const std::vector< std::pair< dof_id_type, bool >> &elems_to_process, std::vector< dof_id_type > &converted_elems_ids_to_track, const subdomain_id_type block_id_to_remove, const bool delete_block_to_remove)
Convert all the elements in a 3D mesh, consisting of only linear elements, into TET4 elements...
const Node * nonDuplicateNodeCreator(ReplicatedMesh &mesh, std::vector< const Node *> &new_on_plane_nodes, const Point &new_point) const
Check if a position on a plane has already been used as a node in the mesh.
CutMeshByLevelSetGeneratorBase(const InputParameters ¶meters)
void convertElem(ReplicatedMesh &mesh, const dof_id_type &elem_id, const std::vector< unsigned int > &side_indices, const std::vector< std::vector< boundary_id_type >> &elem_side_info, const SubdomainID &subdomain_id_shift_base)
Convert the element to an element with TRI3 side-elements on the user-specified sides by modifying th...
boundary_id_type _cut_face_id
The boundary id of the surface generated by the cut.
void assignConvertedElementsSubdomainNameSuffix(ReplicatedMesh &mesh, const std::set< subdomain_id_type > &original_subdomain_ids, const subdomain_id_type sid_shift_base, const SubdomainName &tet_suffix, const SubdomainName &pyramid_suffix)
Assign a subdomain name suffix to the converted elements created during transition layer generation...
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
void pyramidNodesToTetNodesDeterminer(std::vector< const Node *> &pyramid_nodes, std::vector< std::vector< unsigned int >> &rotated_tet_face_indices, std::vector< std::vector< const Node *>> &tet_nodes_list)
For a rotated nodes that can form a PYRAMID5 element, create a series of four-node set that can form ...
std::vector< GenericReal< is_ad > > _func_params
Array to stage the parameters passed to the functions when calling Eval.
IntRange< T > make_range(T beg, T end)
void mooseError(Args &&... args) const
Emits an error prefixed with object name and type and optionally a file path to the top-level block p...
void tet4ElemCutter(ReplicatedMesh &mesh, const std::vector< libMesh::BoundaryInfo::BCTuple > &bdry_side_list, const dof_id_type elem_id, const subdomain_id_type &block_id_to_remove, std::vector< const Node *> &new_on_plane_nodes)
For a TET4 elements crossed by the level set, keep the part of the element on the retaining side of t...
static InputParameters validParams()
bool isParamValid(const std::string &name) const
Test if the supplied parameter is valid.
static InputParameters validParams()
SubdomainID getNextFreeSubdomainID(MeshBase &input_mesh)
Checks input mesh and returns max(block ID) + 1, which represents a block ID that is not currently in...
void prismNodesToTetNodesDeterminer(std::vector< const Node *> &prism_nodes, std::vector< std::vector< unsigned int >> &rotated_tet_face_indices, std::vector< std::vector< const Node *>> &tet_nodes_list)
For a rotated nodes that can form a PRISM6 element, create a series of four-node set that can form TE...
BoundaryID getNextFreeBoundaryID(MeshBase &input_mesh)
Checks input mesh and returns the largest boundary ID in the mesh plus one, which is a boundary ID in...
MeshGenerators are objects that can modify or add to an existing mesh.
auto index_range(const T &sizable)
bool absoluteFuzzyGreaterThan(const T &var1, const T2 &var2, const T3 &tol=libMesh::TOLERANCE *libMesh::TOLERANCE)
Function to check whether a variable is greater than another variable within an absolute tolerance...