Arcane  4.1.12.0
Developer documentation
Loading...
Searching...
No Matches
UnstructuredMeshUtilities.cc
1// -*- tab-width: 2; indent-tabs-mode: nil; coding: utf-8-with-signature -*-
2//-----------------------------------------------------------------------------
3// Copyright 2000-2026 CEA (www.cea.fr) IFPEN (www.ifpenergiesnouvelles.com)
4// See the top-level COPYRIGHT file for details.
5// SPDX-License-Identifier: Apache-2.0
6//-----------------------------------------------------------------------------
7/*---------------------------------------------------------------------------*/
8/* UnstructuredMeshUtilities.cc (C) 2000-2025 */
9/* */
10/* Utility functions for a mesh. */
11/*---------------------------------------------------------------------------*/
12/*---------------------------------------------------------------------------*/
13
14#include "arcane/utils/InvalidArgumentException.h"
15#include "arcane/utils/FatalErrorException.h"
16#include "arcane/utils/ParallelFatalErrorException.h"
17#include "arcane/utils/ArgumentException.h"
18#include "arcane/utils/Array.h"
19#include "arcane/utils/Real3.h"
20#include "arcane/utils/HashTableMap.h"
21#include "arcane/utils/OStringStream.h"
22#include "arcane/utils/ScopedPtr.h"
23#include "arcane/utils/StringBuilder.h"
24#include "arcane/utils/CheckedConvert.h"
25#include "arcane/utils/PlatformUtils.h"
26#include "arcane/utils/SmallArray.h"
27#include "arcane/utils/NotImplementedException.h"
28
29#include "arcane/core/IMesh.h"
30#include "arcane/core/IMeshWriter.h"
31#include "arcane/core/IParallelMng.h"
33#include "arcane/core/IItemFamily.h"
34#include "arcane/core/ItemPrinter.h"
35#include "arcane/core/VariableTypes.h"
36#include "arcane/core/ItemPairGroup.h"
37#include "arcane/core/ISubDomain.h"
38#include "arcane/core/ServiceBuilder.h"
39#include "arcane/core/IParallelReplication.h"
40#include "arcane/core/Timer.h"
41#include "arcane/core/IMeshPartitioner.h"
42#include "arcane/core/IPrimaryMesh.h"
43#include "arcane/core/IMeshChecker.h"
44#include "arcane/core/IItemFamilyNetwork.h"
45#include "arcane/core/NodesOfItemReorderer.h"
46
47#include "arcane/mesh/UnstructuredMeshUtilities.h"
48#include "arcane/ConnectivityItemVector.h"
49#include "arcane/mesh/NewItemOwnerBuilder.h"
50#include "arcane/mesh/ParticleFamily.h"
51#include "arcane/mesh/GraphDoFs.h"
52#include "arcane/mesh/BasicItemPairGroupComputeFunctor.h"
53#include "arcane/mesh/MeshNodeMerger.h"
54#include "arcane/mesh/ConnectivityNewWithDependenciesTypes.h"
55#include "arcane/mesh/ItemsOwnerBuilder.h"
56
57#include <algorithm>
58
59/*---------------------------------------------------------------------------*/
60/*---------------------------------------------------------------------------*/
61
62namespace Arcane
63{
64
65/*---------------------------------------------------------------------------*/
66/*---------------------------------------------------------------------------*/
67
68UnstructuredMeshUtilities::
69UnstructuredMeshUtilities(IMesh* mesh)
70: TraceAccessor(mesh->traceMng())
71, m_mesh(mesh)
72, m_compute_adjacency_functor(new BasicItemPairGroupComputeFunctor(traceMng()))
73{
74}
75
76/*---------------------------------------------------------------------------*/
77/*---------------------------------------------------------------------------*/
78
79UnstructuredMeshUtilities::
80~UnstructuredMeshUtilities()
81{
82 delete m_compute_adjacency_functor;
83}
84
85/*---------------------------------------------------------------------------*/
86/*---------------------------------------------------------------------------*/
87
88void UnstructuredMeshUtilities::
89changeOwnersFromCells()
90{
91 // We assume that we know the new owners of the cells, which
92 // are found in cells_owner. We must
93 // now determine the new owners of the nodes and
94 // faces. Pending an algorithm that better balances
95 // messages, we apply the following:
96 // - each subdomain is responsible for determining the new
97 // owner of the nodes and faces belonging to it.
98 // - for nodes, the new owner is the new owner
99 // of the cell connected to this node whose uniqueId() is the smallest.
100 // - for faces, the new owner is the new owner
101 // of the cell behind this face if it is an internal face
102 // and of the connected cell if it is a boundary face.
103 // - for dual nodes, the new owner is the new owner
104 // of the cell connected to the dual element
105 // - for links, the new owner is the new owner
106 // of the cell connected to the first dual node, that is, the owner
107 // of the link's first dual node
108
109 // Utility for assigning owners to items
110 mesh::NewItemOwnerBuilder owner_builder;
111
112 VariableItemInt32& nodes_owner(m_mesh->nodeFamily()->itemsNewOwner());
113 VariableItemInt32& edges_owner(m_mesh->edgeFamily()->itemsNewOwner());
114 VariableItemInt32& faces_owner(m_mesh->faceFamily()->itemsNewOwner());
115 VariableItemInt32& cells_owner(m_mesh->cellFamily()->itemsNewOwner());
116
117 // Determine the new owners of nodes
118 {
119 ENUMERATE_NODE (i_node, m_mesh->ownNodes()) {
120 const Node node = *i_node;
121 const Cell cell = owner_builder.connectedCellOfItem(node);
122#ifdef ARCANE_DEBUG_LOAD_BALANCING
123 if (nodes_owner[node] != cells_owner[cell]) {
124 info() << "New owner for node: " << ItemPrinter(node) << " cell=" << ItemPrinter(cell)
125 << " old_owner=" << nodes_owner[node]
126 << " current_cell_owner=" << cell.owner()
127 << " new_owner=" << cells_owner[cell];
128 }
129#endif /* ARCANE_DEBUG_LOAD_BALANCING */
130 nodes_owner[node] = cells_owner[cell];
131 }
132 nodes_owner.synchronize();
133 }
134
135 // Determine the new owners of edges
136 {
137 ENUMERATE_EDGE (i_edge, m_mesh->ownEdges()) {
138 const Edge edge = *i_edge;
139 const Cell cell = owner_builder.connectedCellOfItem(edge);
140#ifdef ARCANE_DEBUG_LOAD_BALANCING
141 if (edges_owner[edge] != cells_owner[cell]) {
142 info() << "New owner for edge: " << ItemPrinter(edge) << " cell=" << ItemPrinter(cell)
143 << " old_owner=" << edges_owner[edge]
144 << " current_cell_owner=" << cell.owner()
145 << " new_owner=" << cells_owner[cell];
146 }
147#endif /* ARCANE_DEBUG_LOAD_BALANCING */
148 edges_owner[edge] = cells_owner[cell];
149 }
150 edges_owner.synchronize();
151 }
152
153 // Determine the new owners of faces
154 {
155 ENUMERATE_FACE (i_face, m_mesh->ownFaces()) {
156 const Face face = *i_face;
157 const Cell cell = owner_builder.connectedCellOfItem(face);
158 faces_owner[face] = cells_owner[cell];
159 }
160 faces_owner.synchronize();
161 }
162
163 // Determine the new owners of particles
164 // Particles have the same owner as the cell they are in.
165 for (IItemFamily* family : m_mesh->itemFamilies()) {
166 if (family->itemKind() != IK_Particle)
167 continue;
168 // Position the new owners of particles
169 VariableItemInt32& particles_owner(family->itemsNewOwner());
170 ENUMERATE_PARTICLE (i_particle, family->allItems()) {
171 Particle particle = *i_particle;
172 particles_owner[particle] = cells_owner[particle.cell()];
173 }
174 }
175
176 // GraphOnDoF
177 if (m_mesh->itemFamilyNetwork()) {
178 // Dof with Mesh Item connectivity
179 for (IItemFamily* family : m_mesh->itemFamilies()) {
180 if (family->itemKind() != IK_DoF || family->name() == mesh::GraphDoFs::linkFamilyName())
181 continue;
182 VariableItemInt32& dofs_new_owner(family->itemsNewOwner());
183 std::array<Arcane::eItemKind, 5> dualitem_kinds = { IK_Cell, IK_Face, IK_Edge, IK_Node, IK_Particle };
184 for (auto dualitem_kind : dualitem_kinds) {
185 IItemFamily* dualitem_family = dualitem_kind == IK_Particle ? m_mesh->findItemFamily(dualitem_kind, mesh::ParticleFamily::defaultFamilyName(), false) : m_mesh->itemFamily(dualitem_kind);
186 if (dualitem_family) {
187
188 VariableItemInt32& dualitems_new_owner(dualitem_family->itemsNewOwner());
189 auto connectivity_name = mesh::connectivityName(family, dualitem_family);
190 bool is_dof2dual = true;
191 auto connectivity = m_mesh->itemFamilyNetwork()->getConnectivity(family, dualitem_family, connectivity_name);
192 if (!connectivity) {
193 connectivity = m_mesh->itemFamilyNetwork()->getConnectivity(dualitem_family, family, connectivity_name);
194 is_dof2dual = false;
195 }
196
197 if (connectivity) {
198 ConnectivityItemVector accessor(connectivity);
199 if (is_dof2dual) {
200 ENUMERATE_ITEM (item, family->allItems().own()) {
201 auto connected_items = accessor.connectedItems(ItemLocalId(item));
202 if (connected_items.size() > 0) {
203 dofs_new_owner[*item] = dualitems_new_owner[connected_items[0]];
204 }
205 }
206 }
207 else {
208 ENUMERATE_ITEM (item, dualitem_family->allItems()) {
209 ENUMERATE_ITEM (connected_item, accessor.connectedItems(ItemLocalId(item))) {
210 dofs_new_owner[*connected_item] = dualitems_new_owner[*item];
211 }
212 }
213 }
214 }
215 }
216 }
217 }
218 // Dof with DoF connectivity
219 IItemFamily* links_family = m_mesh->findItemFamily(IK_DoF, mesh::GraphDoFs::linkFamilyName(), false);
220 if (links_family) {
221 VariableItemInt32& links_new_owner(links_family->itemsNewOwner());
222 IItemFamily* dualnodes_family = m_mesh->findItemFamily(IK_DoF, mesh::GraphDoFs::dualNodeFamilyName(), false);
223 if (dualnodes_family) {
224 VariableItemInt32& dualnodes_new_owner(dualnodes_family->itemsNewOwner());
225 auto connectivity_name = mesh::connectivityName(links_family, dualnodes_family);
226 auto connectivity = m_mesh->itemFamilyNetwork()->getConnectivity(links_family, dualnodes_family, connectivity_name);
227 if (connectivity) {
228 ConnectivityItemVector accessor(connectivity);
229 ENUMERATE_ITEM (item, links_family->allItems().own()) {
230 auto connected_items = accessor.connectedItems(ItemLocalId(item));
231 if (connected_items.size() > 0) {
232 links_new_owner[*item] = dualnodes_new_owner[connected_items[0]];
233 }
234 }
235 }
236 }
237 }
238 }
239}
240
241/*---------------------------------------------------------------------------*/
242/*---------------------------------------------------------------------------*/
243
244void UnstructuredMeshUtilities::
245localIdsFromConnectivity(eItemKind item_kind,
246 IntegerConstArrayView items_nb_node,
247 Int64ConstArrayView items_connectivity,
248 Int32ArrayView local_ids,
249 bool allow_null)
250{
251 Integer nb_item = items_nb_node.size();
252 if (item_kind != IK_Face && item_kind != IK_Cell)
253 throw InvalidArgumentException(A_FUNCINFO, "item_kind",
254 "IK_Cell or IK_Face expected",
255 (int)item_kind);
256 if (item_kind == IK_Cell)
257 throw NotImplementedException(A_FUNCINFO, "not implemented for cells");
258
259 if (nb_item != local_ids.size())
260 throw InvalidArgumentException(A_FUNCINFO, "local_ids",
261 "Size different from 'items_nb_node'",
262 (int)item_kind);
263
264 Integer item_connectivity_index = 0;
266 buf.reserve(256);
267 ItemInternalList nodes(m_mesh->itemsInternal(IK_Node));
268 for (Integer i = 0; i < nb_item; ++i) {
269 Integer current_nb_node = items_nb_node[i];
270 Int64ConstArrayView current_nodes(current_nb_node, items_connectivity.data() + item_connectivity_index);
271 item_connectivity_index += current_nb_node;
272 if (item_kind == IK_Face) {
273 buf.resize(current_nb_node);
274 mesh_utils::reorderNodesOfFace(current_nodes, buf);
275 Int64 first_node_uid = buf[0];
276 Int64ArrayView first_node_uid_array(1, &first_node_uid);
277 Int32 first_node_lid = CheckedConvert::toInt32(buf[0]);
278 Int32ArrayView first_node_lid_array(1, &first_node_lid);
279 m_mesh->nodeFamily()->itemsUniqueIdToLocalId(first_node_lid_array, first_node_uid_array, !allow_null);
280 if (first_node_lid == NULL_ITEM_LOCAL_ID) {
281 if (allow_null) {
282 local_ids[i] = NULL_ITEM_LOCAL_ID;
283 }
284 else {
285 StringBuilder sb("Face with nodes (");
286 for (Integer j = 0; j < current_nb_node; ++j) {
287 if (j != 0)
288 sb += " ";
289 sb += current_nodes[j];
290 }
291 sb += ") not found (first node ";
292 sb += first_node_uid;
293 sb += " not found)";
295 }
296 continue;
297 }
298
299 Node node(nodes[first_node_lid]);
300 Face face(mesh_utils::getFaceFromNodesUnique(node, buf));
301 if (face.null()) {
302 if (allow_null) {
303 local_ids[i] = NULL_ITEM_LOCAL_ID;
304 }
305 else {
306 StringBuilder sb("Face with nodes (");
307 for (Integer j = 0; j < current_nb_node; ++j) {
308 if (j != 0)
309 sb += " ";
310 sb += current_nodes[j];
311 }
312 sb += ") not found";
314 }
315 }
316 else
317 local_ids[i] = face.localId();
318 }
319 }
320}
321
322/*---------------------------------------------------------------------------*/
323/*---------------------------------------------------------------------------*/
324
325void UnstructuredMeshUtilities::
326getFacesLocalIdFromConnectivity(ConstArrayView<ItemTypeId> items_type,
327 ConstArrayView<Int64> items_connectivity,
328 ArrayView<Int32> local_ids,
329 bool allow_null)
330{
331 Int32 nb_item = items_type.size();
332
333 if (nb_item != local_ids.size())
334 throw InvalidArgumentException(A_FUNCINFO, "local_ids",
335 "Size different from 'items_type'");
336
337 Int32 item_connectivity_index = 0;
338 ItemTypeMng* item_type_mng = m_mesh->itemTypeMng();
339 NodesOfItemReorderer face_reorderer(item_type_mng);
340
341 ItemInternalList nodes(m_mesh->itemsInternal(IK_Node));
342 for (Integer i = 0; i < nb_item; ++i) {
343 ItemTypeId current_type_id = items_type[i];
344 Int32 current_nb_node = item_type_mng->typeFromId(current_type_id)->nbLocalNode();
345 Int64ConstArrayView current_nodes(current_nb_node, items_connectivity.data() + item_connectivity_index);
346 item_connectivity_index += current_nb_node;
347
348 face_reorderer.reorder(current_type_id, current_nodes);
349 ConstArrayView<Int64> buf(face_reorderer.sortedNodes());
350 Int64 first_node_uid = buf[0];
351 Int64ArrayView first_node_uid_array(1, &first_node_uid);
352 Int32 first_node_lid = CheckedConvert::toInt32(buf[0]);
353 Int32ArrayView first_node_lid_array(1, &first_node_lid);
354 m_mesh->nodeFamily()->itemsUniqueIdToLocalId(first_node_lid_array, first_node_uid_array, !allow_null);
355 if (first_node_lid == NULL_ITEM_LOCAL_ID) {
356 if (allow_null) {
357 local_ids[i] = NULL_ITEM_LOCAL_ID;
358 }
359 else {
360 StringBuilder sb("Face with nodes (");
361 for (Integer j = 0; j < current_nb_node; ++j) {
362 if (j != 0)
363 sb += " ";
364 sb += current_nodes[j];
365 }
366 sb += ") not found (first node ";
367 sb += first_node_uid;
368 sb += " not found)";
370 }
371 continue;
372 }
373
374 Node node(nodes[first_node_lid]);
375 Face face(MeshUtils::getFaceFromNodesUnique(node, buf));
376 if (face.null()) {
377 if (allow_null) {
378 local_ids[i] = NULL_ITEM_LOCAL_ID;
379 }
380 else {
381 StringBuilder sb("Face with nodes (");
382 for (Integer j = 0; j < current_nb_node; ++j) {
383 if (j != 0)
384 sb += " ";
385 sb += current_nodes[j];
386 }
387 sb += ") not found";
389 }
390 }
391 else
392 local_ids[i] = face.localId();
393 }
394}
395
396/*---------------------------------------------------------------------------*/
397/*---------------------------------------------------------------------------*/
398
399Real3 UnstructuredMeshUtilities::
400_round(Real3 value)
401{
402 Real3 rvalue = value;
403 // Avoid numerical rounding issues
404 if (math::isNearlyZero(value.x))
405 rvalue.x = 0.0;
406 if (math::isNearlyZero(value.y))
407 rvalue.y = 0.0;
408 if (math::isNearlyZero(value.z))
409 rvalue.z = 0.0;
410
411 if (math::isNearlyEqual(value.x, 1.0))
412 rvalue.x = 1.0;
413 if (math::isNearlyEqual(value.y, 1.0))
414 rvalue.y = 1.0;
415 if (math::isNearlyEqual(value.z, 1.0))
416 rvalue.z = 1.0;
417
418 if (math::isNearlyEqual(value.x, -1.0))
419 rvalue.x = -1.0;
420 if (math::isNearlyEqual(value.y, -1.0))
421 rvalue.y = -1.0;
422 if (math::isNearlyEqual(value.z, -1.0))
423 rvalue.z = -1.0;
424
425 return rvalue;
426}
427
428/*---------------------------------------------------------------------------*/
429/*---------------------------------------------------------------------------*/
430
431Real3 UnstructuredMeshUtilities::
432computeNormal(const FaceGroup& face_group, const VariableNodeReal3& nodes_coord)
433{
434 String func_name = "UnstructuredMeshUtilities::computeNormal";
435 IParallelMng* pm = m_mesh->parallelMng();
436 Integer rank = pm->commRank();
437 //Integer nb_item = face_group.size();
438 Integer nb_node = 0;
439 Integer mesh_dimension = m_mesh->dimension();
440 // 'global_normal' and 'total_global_normal' are used to determine
441 // a direction for the surface assuming it is
442 // an external surface. The direction is then used to orient
443 // the calculated normal in the outward normal direction.
444 Real3 global_normal = Real3::null();
445 Real nb_global_normal_used_node = 0.0;
446 ENUMERATE_FACE (iface, face_group) {
447 Face face = *iface;
448 Integer face_nb_node = face.nbNode();
449 nb_node += face_nb_node;
450
451 if (face.isSubDomainBoundary() && face.isOwn()) {
452 Real3 face_normal;
453 if (mesh_dimension == 3) {
454 Real3 v1 = nodes_coord[face.node(1)] - nodes_coord[face.node(0)];
455 Real3 v2 = nodes_coord[face.node(2)] - nodes_coord[face.node(0)];
456 face_normal = math::vecMul(v1, v2);
457 }
458 else if (mesh_dimension == 2) {
459 Real3 dir = nodes_coord[face.node(0)] - nodes_coord[face.node(1)];
460 face_normal.x = -dir.y;
461 face_normal.y = dir.x;
462 face_normal.z = 0.0;
463 }
464 if (face.boundaryCell() == face.frontCell())
465 face_normal = -face_normal;
466 //info() << "ADD NORMAL normal=" << face_normal;
467 global_normal = global_normal + face_normal;
468 nb_global_normal_used_node += 1.0;
469 }
470 }
471 // The algorithm attempts to determine the nodes at the ends of the surface
472 // It assumes these are nodes that belong to only one
473 // face of the surface. This works well if all faces have at least
474 // four edges. If there are triangles, we assume this is not
475 // everywhere, and in this case, we have at least two known extreme nodes.
476 typedef HashTableMapT<Int32, Int32> NodeOccurenceMap;
477 // Range in the table for each node the number of surface faces
478 // to which it is connected.
479 NodeOccurenceMap nodes_occurence(nb_node * 2, true, nb_node);
480 ENUMERATE_FACE (iface, face_group) {
481 Face face = *iface;
482 for (NodeLocalId inode : face.nodeIds()) {
483 NodeOccurenceMap::Data* v = nodes_occurence.lookup(inode);
484 if (v)
485 ++v->value();
486 else {
487 //info() << " ADD NODE " << ItemPrinter(*inode) << " coord=" << nodes_coord[inode];
488 nodes_occurence.add(inode, 1);
489 }
490 }
491 }
492 UniqueArray<Node> single_nodes;
493 NodeLocalIdToNodeConverter nodes_internal(m_mesh->nodeFamily());
494 nodes_occurence.each([&](NodeOccurenceMap::Data* d) {
495 if (d->value() == 1) {
496 Node node = nodes_internal[d->key()];
497 // In parallel, only process the nodes that belong to us
498 if (node.owner() == rank) {
499 single_nodes.add(node);
500 //info() << "SINGLE NODE OWNER lid=" << d->key() << " " << ItemPrinter(node)
501 // << " coord=" << nodes_coord[node];
502 }
503 }
504 });
505 // Each sub-domain collects the coordinates of the nodes from the other sub-domains
506 Integer nb_single_node = single_nodes.size();
507 //info() << "NB SINGLE NODE= " << nb_single_node;
508 Integer total_nb_single_node = pm->reduce(Parallel::ReduceSum, nb_single_node);
509 Real3 total_global_normal;
510 {
511 Real all_min = 0.0;
512 Real all_max = 0.0;
513 Real all_sum = 0.0;
514 Int32 min_rank = -1;
515 Int32 max_rank = -1;
516 pm->computeMinMaxSum(nb_global_normal_used_node, all_min, all_max, all_sum, min_rank, max_rank);
517 Real3 buf;
518 if (max_rank == rank) {
519 // I am the one with the farthest point. I send it to the others.
520 buf = global_normal;
521 }
522 pm->broadcast(Real3ArrayView(1, &buf), max_rank);
523 total_global_normal = buf;
524 }
525
526 info() << "TOTAL SINGLE NODE= " << total_nb_single_node << " surface=" << face_group.name()
527 << " global_normal = " << total_global_normal;
528 if (total_nb_single_node < 2) {
529 ARCANE_FATAL("not enough nodes connected to only one face");
530 }
531 Real3UniqueArray coords(nb_single_node);
532 for (Integer i = 0; i < nb_single_node; ++i) {
533 coords[i] = nodes_coord[single_nodes[i]];
534 }
535 //Array<Real> all_nodes_coord_real;
536 UniqueArray<Real3> all_nodes_coord;
537 pm->allGatherVariable(coords, all_nodes_coord);
538 //info() << " ALL NODES COORD n=" << all_nodes_coord_real.size();
539 //Array<Real3> all_nodes_coord(total_nb_single_node);
540 Real3 barycentre = Real3::null();
541 for (Integer i = 0; i < total_nb_single_node; ++i) {
542 barycentre += all_nodes_coord[i];
543 }
544 barycentre /= (Real)total_nb_single_node;
545 if (total_nb_single_node == 2 && m_mesh->dimension() == 3) {
546 // We only have two nodes. We need at least a third one to determine
547 // the normal to the plane. For this, each processor searches for the node
548 // on the surface that is farthest from the barycenter of the two nodes already
549 // found. This farthest node will serve as the third point for the
550 // plane.
551 Real max_distance = 0.0;
552 Node farthest_node;
553 Real3 s0 = all_nodes_coord[0];
554 Real3 s1 = all_nodes_coord[1];
555 nodes_occurence.each([&](NodeOccurenceMap::Data* d) {
556 Node node = nodes_internal[d->key()];
557 Real3 coord = nodes_coord[node];
558 // Do not process the two nodes already found
559 if (math::isNearlyEqual(coord, s0))
560 return;
561 if (math::isNearlyEqual(coord, s1))
562 return;
563 Real distance = (coord - barycentre).squareNormL2();
564 if (distance > max_distance) {
565 Real3 normal = math::cross(coord - s0, s1 - s0);
566 // We only take the node if it is not aligned with the other two.
567 if (!math::isNearlyZero(normal.squareNormL2())) {
568 max_distance = distance;
569 farthest_node = node;
570 }
571 }
572 });
573
574 if (!farthest_node.null()) {
575 info() << " FARTHEST NODE= " << ItemPrinter(farthest_node) << " dist=" << max_distance;
576 }
577 {
578 Real3 farthest_coord = _broadcastFarthestNode(max_distance, farthest_node, nodes_coord);
579 info() << " FARTHEST NODE ALL coord=" << farthest_coord;
580 all_nodes_coord.add(farthest_coord);
581 }
582 }
583 // Sort the nodes so that the calculation does not depend on the order of
584 // parallel operations.
585 std::sort(std::begin(all_nodes_coord), std::end(all_nodes_coord));
586 Integer nb_final_node = all_nodes_coord.size();
587 Real3 full_normal = Real3::null();
588 info() << " NB FINAL NODE=" << nb_final_node;
589 for (Integer i = 0; i < nb_final_node; ++i)
590 info() << " NODE=" << all_nodes_coord[i];
591 if (m_mesh->dimension() == 2) {
592 if (nb_final_node != 2) {
593 ARCANE_FATAL("should have 2 border nodes in 2D mesh");
594 }
595 Real3 direction = all_nodes_coord[1] - all_nodes_coord[0];
596 full_normal.x = -direction.y;
597 full_normal.y = direction.x;
598 }
599 else if (m_mesh->dimension() == 3) {
600 //nb_final_node = 3; // We only take the first 3 points.
601 // NOTE: we could take all points, because if the first three are
602 // aligned, the normal will not be correct.
603 // If we take all points, we must ensure they are ordered
604 // such that the normal of three consecutive points is always
605 // in the same direction. This means if we have four points, for example,
606 // that these 4 points form a non-crossed quadrangle (not in the shape
607 // of a butterfly)
608 Real3 first_normal = math::vecMul(all_nodes_coord[2] - all_nodes_coord[0],
609 all_nodes_coord[1] - all_nodes_coord[0]);
610 for (Integer i = 0; i < nb_final_node; ++i) {
611 Real3 s0 = all_nodes_coord[i];
612 Real3 s1 = all_nodes_coord[(i + nb_final_node - 1) % nb_final_node];
613 Real3 s2 = all_nodes_coord[(i + 1) % nb_final_node];
614 Real3 normal = math::vecMul(s2 - s0, s1 - s0);
615 info() << " ADD NORMAL: " << normal;
616 full_normal += normal;
617 info() << " FULL: " << full_normal;
618 }
619 if (math::isNearlyZero(full_normal.squareNormL2()))
620 full_normal = first_normal;
621 }
622 else
623 ARCANE_FATAL("invalid mesh dimension (should be 2 or 3)");
624 Real a = full_normal.normL2();
625 if (math::isZero(a))
626 ARCANE_FATAL("invalid value for normal");
627 full_normal /= a;
628 Real b = total_global_normal.normL2();
629 Real dir = 0.0;
630 info() << " Normal is " << full_normal;
631 if (!math::isZero(b)) {
632 total_global_normal /= b;
633 dir = math::scaMul(full_normal, total_global_normal);
634 if (dir < 0.0)
635 full_normal = -full_normal;
636 }
637 full_normal = _round(full_normal);
638 info() << " Final normal is " << full_normal << " dir=" << dir;
639 return full_normal;
640}
641
642/*---------------------------------------------------------------------------*/
643/*---------------------------------------------------------------------------*/
651Real3 UnstructuredMeshUtilities::
652computeDirection(const NodeGroup& node_group,
653 const VariableNodeReal3& nodes_coord,
654 Real3* n1, Real3* n2)
655{
656 String func_name = "UnstructuredMeshUtilities::computeDirection";
657 IParallelMng* pm = m_mesh->parallelMng();
658 //Integer rank = pm->commRank();
659 //Integer nb_node_own = node_group.own().size();
660 //Integer total_nb_node_own = pm->reduce(Parallel::ReduceSum,nb_node_own);
661 Real3 barycentre = Real3::null();
662 ENUMERATE_NODE (inode, node_group.own()) {
663 barycentre += nodes_coord[inode];
664 }
665 Real r_barycentre[3];
666 r_barycentre[0] = barycentre.x;
667 r_barycentre[1] = barycentre.y;
668 r_barycentre[2] = barycentre.z;
669 RealArrayView rav(3, r_barycentre);
670 pm->reduce(Parallel::ReduceSum, rav);
671 barycentre = Real3(rav[0], rav[1], rav[2]);
672 debug() << " BARYCENTRE COMPUTE DIRECTION = " << barycentre;
673 pm->barrier();
674
675 // Determines the node farthest from the barycenter
676 Real3 first_boundary_coord;
677 {
678 Real max_distance = 0.0;
679 Node farthest_node;
680 ENUMERATE_NODE (inode, node_group.own()) {
681 Node node = *inode;
682 Real3 coord = nodes_coord[node];
683 Real distance = (coord - barycentre).squareNormL2();
684 if (distance > max_distance) {
685 max_distance = distance;
686 farthest_node = node;
687 }
688 }
689 first_boundary_coord = _broadcastFarthestNode(max_distance, farthest_node, nodes_coord);
690 if (n1)
691 *n1 = first_boundary_coord;
692 }
693 Real3 second_boundary_coord;
694 {
695 Real max_distance = 0.0;
696 Node farthest_node;
697 ENUMERATE_NODE (inode, node_group.own()) {
698 Node node = *inode;
699 Real3 coord = nodes_coord[node];
700 Real distance = (coord - first_boundary_coord).squareNormL2();
701 if (distance > max_distance) {
702 max_distance = distance;
703 farthest_node = node;
704 }
705 }
706 second_boundary_coord = _broadcastFarthestNode(max_distance, farthest_node, nodes_coord);
707 if (n2)
708 *n2 = second_boundary_coord;
709 }
710 Real3 direction = second_boundary_coord - first_boundary_coord;
711 Real norm = direction.normL2();
712 if (math::isZero(norm)) {
713 ARCANE_FATAL("Direction is null for group '{0}' first_coord={1} second_coord={2}",
714 node_group.name(), first_boundary_coord, second_boundary_coord);
715 }
716 return direction / norm;
717}
718
719/*---------------------------------------------------------------------------*/
720/*---------------------------------------------------------------------------*/
721
722Real3 UnstructuredMeshUtilities::
723_broadcastFarthestNode(Real distance, const Node& farthest_node,
724 const VariableNodeReal3& nodes_coord)
725{
726 String func_name = "UnstructuredMeshUtilities::_broadcastFarthestNode()";
727 IParallelMng* pm = m_mesh->parallelMng();
728 Integer rank = pm->commRank();
729
730 Real all_min_distance = 0.0;
731 Real all_max_distance = 0.0;
732 Real all_sum_distance = 0.0;
733 Int32 min_rank = -1;
734 Int32 max_rank = -1;
735 pm->computeMinMaxSum(distance, all_min_distance, all_max_distance, all_sum_distance, min_rank, max_rank);
736 Real3 buf;
737 if (!farthest_node.null())
738 debug() << " FARTHEST NODE myself coord=" << nodes_coord[farthest_node]
739 << " distance=" << distance
740 << " rank=" << max_rank
741 << " max_distance=" << all_max_distance;
742 else
743 debug() << " FARTHEST NODE myself coord=none"
744 << " distance=" << distance
745 << " rank=" << max_rank
746 << " max_distance=" << all_max_distance;
747
748 if (max_rank == rank) {
749 if (farthest_node.null())
750 ARCANE_FATAL("can not find farthest node");
751 // I am the one with the farthest point. I send it to the others.
752 buf = nodes_coord[farthest_node];
753 debug() << " I AM FARTHEST NODE ALL coord=" << buf;
754 }
755 pm->broadcast(Real3ArrayView(1, &buf), max_rank);
756 Real3 farthest_coord(buf);
757 debug() << " FARTHEST NODE ALL coord=" << farthest_coord
758 << " rank=" << max_rank
759 << " max_distance=" << all_max_distance;
760 return farthest_coord;
761}
762
763/*---------------------------------------------------------------------------*/
764/*---------------------------------------------------------------------------*/
765
766void UnstructuredMeshUtilities::
767computeAdjency(ItemPairGroup adjency_array, eItemKind link_kind, Integer nb_layer)
768{
769 m_compute_adjacency_functor->computeAdjacency(adjency_array, link_kind, nb_layer);
770}
771
772/*---------------------------------------------------------------------------*/
773/*---------------------------------------------------------------------------*/
774
775bool UnstructuredMeshUtilities::
776writeToFile(const String& file_name, const String& service_name)
777{
778 ServiceBuilder<IMeshWriter> sb(m_mesh->handle());
779 auto mesh_writer = sb.createReference(service_name, SB_AllowNull);
780
781 if (!mesh_writer) {
782 UniqueArray<String> available_names;
783 sb.getServicesNames(available_names);
784 warning() << String::format("The specified service '{0}' to write the mesh is not available."
785 " Valid names are {1}",
786 service_name, available_names);
787 return true;
788 }
789 mesh_writer->writeMeshToFile(m_mesh, file_name);
790 return false;
791}
792
793/*---------------------------------------------------------------------------*/
794/*---------------------------------------------------------------------------*/
795
796void UnstructuredMeshUtilities::
797partitionAndExchangeMeshWithReplication(IMeshPartitionerBase* partitioner,
798 bool initial_partition)
799{
800 IPrimaryMesh* primary_mesh = partitioner->primaryMesh();
801 IMesh* mesh = primary_mesh;
802 if (mesh != this->m_mesh)
803 throw ArgumentException(A_FUNCINFO, "partitioner->mesh() != this->m_mesh");
804
805 IParallelMng* pm = mesh->parallelMng();
806 ITimeStats* ts = pm->timeStats();
808 bool has_replication = pr->hasReplication();
809
810 // In replication mode, only the first replica performs the balancing.
811 // It must then send this mesh information to the others
812 // so that they have the same mesh.
813
814 info() << "Partition start date=" << platform::getCurrentDateTime();
815 if (pr->isMasterRank()) {
816 Timer::Action ts_action1(ts, "MeshPartition", true);
817 info() << "Partitioning the mesh (initial?=" << initial_partition << ")";
818 partitioner->partitionMesh(initial_partition);
819 }
820 else
821 info() << "Waiting for partition information from the master replica";
822
823 if (has_replication) {
824 pm->barrier();
825
826 // Checks that all families are the same.
827 mesh->checker()->checkValidReplication();
828
829 Int32 replica_master_rank = pr->masterReplicationRank();
830 IParallelMng* rep_pm = pr->replicaParallelMng();
831
832 // Only the master replica has the correct owners. It must update
833 // the others from this. To do this, we synchronize
834 // the owners of the cells and then update the other families
835 // from the cell owners.
836 {
837 Int32ArrayView owners = mesh->cellFamily()->itemsNewOwner().asArray();
838 rep_pm->broadcast(owners, replica_master_rank);
839 if (!pr->isMasterRank()) {
841 }
842 }
843 }
844 info() << "Partition end date=" << platform::getCurrentDateTime();
845 {
846 Timer::Action ts_action2(ts, "MeshExchange", true);
847 primary_mesh->exchangeItems();
848 }
849 info() << "Exchange end date=" << platform::getCurrentDateTime();
850 partitioner->notifyEndPartition();
851
852 // It must be compacted to return to the same situation as
853 // if we had just read a mesh directly (which performs a prepareForDump()
854 // when calling endAllocate()).
855 // We do this also in case of replication to avoid potential
856 // inconsistencies if later some replicas call this
857 // method and others do not.
858 // TODO: check if it should also be done in case of repartitioning.
859 if (initial_partition || has_replication)
860 mesh->prepareForDump();
861}
862
863/*---------------------------------------------------------------------------*/
864/*---------------------------------------------------------------------------*/
865
866void UnstructuredMeshUtilities::
867mergeNodes(Int32ConstArrayView nodes_local_id,
868 Int32ConstArrayView nodes_to_merge_local_id,
869 bool allow_non_corresponding_face)
870{
871 mesh::MeshNodeMerger merger(m_mesh);
872 merger.mergeNodes(nodes_local_id, nodes_to_merge_local_id, allow_non_corresponding_face);
873}
874
875/*---------------------------------------------------------------------------*/
876/*---------------------------------------------------------------------------*/
877
878void UnstructuredMeshUtilities::
879computeAndSetOwnersForNodes()
880{
881 mesh::ItemsOwnerBuilder owner_builder(m_mesh);
882 owner_builder.computeNodesOwner();
883}
884
885/*---------------------------------------------------------------------------*/
886/*---------------------------------------------------------------------------*/
887
888void UnstructuredMeshUtilities::
889computeAndSetOwnersForEdges()
890{
891 mesh::ItemsOwnerBuilder owner_builder(m_mesh);
892 owner_builder.computeEdgesOwner();
893}
894
895/*---------------------------------------------------------------------------*/
896/*---------------------------------------------------------------------------*/
897
898void UnstructuredMeshUtilities::
899computeAndSetOwnersForFaces()
900{
901 mesh::ItemsOwnerBuilder owner_builder(m_mesh);
902 owner_builder.computeFacesOwner();
903}
904
905/*---------------------------------------------------------------------------*/
906/*---------------------------------------------------------------------------*/
907namespace
908{
909 void _recomputeUniqueIds(IItemFamily* family)
910 {
911 SmallArray<Int64> unique_ids;
912 ENUMERATE_ (ItemWithNodes, iitem, family->allItems()) {
913 ItemWithNodes item = *iitem;
914 Int32 index = 0;
915 unique_ids.resize(item.nbNode());
916 for (Node node : item.nodes()) {
917 unique_ids[index] = node.uniqueId();
918 ++index;
919 }
920 Int64 new_uid = MeshUtils::generateHashUniqueId(unique_ids);
921 item.mutableItemBase().setUniqueId(new_uid);
922 }
924 }
925} // namespace
926
927/*---------------------------------------------------------------------------*/
928/*---------------------------------------------------------------------------*/
929
930void UnstructuredMeshUtilities::
931recomputeItemsUniqueIdFromNodesUniqueId()
932{
933 IMesh* mesh = m_mesh;
935 ITraceMng* tm = mesh->traceMng();
936
937 tm->info() << "Calling RecomputeItemsUniqueIdFromNodesUniqueId()";
938 // First indicate that the nodes have changed to potentially
939 // update the orientation of the faces.
940 mesh->nodeFamily()->notifyItemsUniqueIdChanged();
941 _recomputeUniqueIds(mesh->edgeFamily());
942 _recomputeUniqueIds(mesh->faceFamily());
943 _recomputeUniqueIds(mesh->cellFamily());
944}
945
946/*---------------------------------------------------------------------------*/
947/*---------------------------------------------------------------------------*/
948
949} // End namespace Arcane
950
951/*---------------------------------------------------------------------------*/
952/*---------------------------------------------------------------------------*/
#define ARCANE_CHECK_POINTER(ptr)
Macro returning the pointer ptr if it is not null or throwing an exception if it is null.
#define ARCANE_FATAL(...)
Macro throwing a FatalErrorException.
#define ENUMERATE_FACE(name, group)
Generic enumerator for a face group.
#define ENUMERATE_(type, name, group)
Generic enumerator for an entity group.
#define ENUMERATE_PARTICLE(name, group)
Generic enumerator for a particle group.
#define ENUMERATE_EDGE(name, group)
Generic enumerator for an edge group.
#define ENUMERATE_ITEM(name, group)
Generic enumerator for a node group.
#define ENUMERATE_NODE(name, group)
Generic enumerator for a node group.
Utility functions for the mesh.
bool reorderNodesOfFace(Int64ConstArrayView before_ids, Int64ArrayView after_ids)
Reorders the nodes of a face.
Integer size() const
Number of elements in the vector.
Modifiable view of an array of type T.
constexpr Integer size() const noexcept
Returns the size of the array.
void resize(Int64 s)
Changes the number of elements in the array to s.
void add(ConstReferenceType val)
Adds element val to the end of the array.
void reserve(Int64 new_capacity)
Reserves memory for new_capacity elements.
Cell of a mesh.
Definition Item.h:1300
Manages the retrieval of connectivity information.
ItemVectorView connectedItems(ItemLocalId item)
Returns the entities connected to item.
Constant view of an array of type T.
constexpr const_pointer data() const noexcept
Pointer to the allocated memory.
constexpr Integer size() const noexcept
Number of elements in the array.
Edge of a cell.
Definition Item.h:875
Face of a cell.
Definition Item.h:1032
Cell frontCell() const
Cell in front of the face (null cell if none).
Definition Item.h:1780
bool isSubDomainBoundary() const
Indicates if the face is on the subdomain boundary (i.e nbCell()==1).
Definition Item.h:1148
Cell boundaryCell() const
Cell associated with this boundary face (null cell if none).
Definition Item.h:1768
Hash table for associative arrays.
Interface of an entity family.
Definition IItemFamily.h:83
virtual void notifyItemsUniqueIdChanged()=0
Notifies that the unique IDs of the entities have been modified.
virtual ItemGroup allItems() const =0
Group of all entities.
virtual VariableItemInt32 & itemsNewOwner()=0
Variable containing the number of the new subdomain owning the entity.
Interface of a mesh partitioner.
virtual IPrimaryMesh * primaryMesh()=0
Associated mesh.
virtual void partitionMesh(bool initial_partition)=0
virtual void notifyEndPartition()=0
Notification when a re-partitioning finishes (after entity exchange).
Interface of the parallelism manager for a subdomain.
virtual void computeMinMaxSum(char val, char &min_val, char &max_val, char &sum_val, Int32 &min_rank, Int32 &max_rank)=0
Calculates the sum, min, and max of a value in one operation.
virtual Int32 commRank() const =0
Rank of this instance in the communicator.
virtual IParallelReplication * replication() const =0
Replication information.
virtual ITimeStats * timeStats() const =0
Associated statistics manager (can be null).
virtual void allGatherVariable(ConstArrayView< char > send_buf, Array< char > &recv_buf)=0
Performs an all-gather operation across all processors.
virtual char reduce(eReduceType rt, char v)=0
Performs a reduction of type rt on the real v and returns the value.
virtual void barrier()=0
Performs a barrier.
Brief information on parallel subdomain replication.
virtual bool hasReplication() const =0
Indicates if replication is active.
virtual IParallelMng * replicaParallelMng() const =0
Communicator associated with all replicas representing the same subdomain.
virtual Int32 masterReplicationRank() const =0
Rank in the master replication.
virtual bool isMasterRank() const =0
Indicates if this replication rank is the master.
virtual void exchangeItems()=0
Changes the owning subdomains of entities.
Interface managing execution time statistics.
Definition ITimeStats.h:44
virtual TraceMessage info()=0
Stream for an information message.
Exception when a fatal error has occurred.
const String & name() const
Group name.
Definition ItemGroup.h:81
ItemGroup own() const
Group equivalent to this one but containing only the local elements of the subdomain.
Definition ItemGroup.cc:182
Index of an Item in a variable.
Definition ItemLocalId.h:42
Table of entity lists.
Utility class for printing information about an entity.
Definition ItemPrinter.h:35
Type of an entity (Item).
Definition ItemTypeId.h:33
Integer nbLocalNode() const
Number of nodes of the entity.
Mesh entity type manager.
Definition ItemTypeMng.h:66
ItemTypeInfo * typeFromId(Integer id) const
Type corresponding to the number id.
Mesh element based on nodes (Edge,Face,Cell).
Definition Item.h:773
Node node(Int32 i) const
i-th node of the entity
Definition Item.h:840
NodeConnectedListViewType nodes() const
List of nodes of the entity.
Definition Item.h:843
Int32 nbNode() const
Number of nodes of the entity.
Definition Item.h:837
NodeLocalIdView nodeIds() const
List of nodes of the entity.
Definition Item.h:846
impl::MutableItemBase mutableItemBase() const
Mutable internal part of the entity.
Definition Item.h:394
constexpr Int32 localId() const
Local identifier of the entity in the processor subdomain.
Definition Item.h:233
Int32 owner() const
Owner subdomain number of the entity.
Definition Item.h:252
constexpr bool null() const
true if the entity is null (i.e. not connected to the mesh)
Definition Item.h:230
constexpr bool isOwn() const
true if the entity belongs to the subdomain
Definition Item.h:267
Class to convert a NodeLocalId to an edge.
Node of a mesh.
Definition Item.h:598
Utility class to reorder the nodes of an entity.
Particle.
Definition Item.h:1529
Cell cell() const
Cell to which the particle belongs. You must call setCell() before calling this function....
Definition Item.h:1606
Class managing a 3-dimensional real vector.
Definition Real3.h:132
__host__ __device__ Real normL2() const
Returns the L2 norm of the triplet $ .
Definition Real3.h:585
constexpr __host__ __device__ Real squareNormL2() const
Returns the square of the L2 norm of the triplet $ .
Definition Real3.h:455
Utility class for instantiating a service of a given interface.
Ref< InterfaceType > createReference(const String &name, eServiceBuilderProperties properties=SB_None)
Creates an instance implementing the InterfaceType interface.
void getServicesNames(Array< String > &names) const
Fills names with the names of services available to instantiate this interface.
1D data array with pre-allocated stack buffer.
Unicode character string constructor.
String toString() const
Returns the constructed character string.
Positions the name of the currently executing action.
Definition Timer.h:119
TraceMessageDbg debug(Trace::eDebugLevel=Trace::Medium) const
Flow for a debug message.
TraceMessage info() const
Flow for an information message.
TraceMessage warning() const
Flow for a warning message.
1D data vector with value semantics (STL style).
void changeOwnersFromCells() override
Positions the new owners of nodes, edges, and faces based on the cells.
Generic class for calculating entity owners.
Merging nodes of a mesh.
void mergeNodes(Int32ConstArrayView nodes_local_id, Int32ConstArrayView nodes_to_merge_local_id, bool allow_non_corresponding_face=false)
__host__ __device__ Real3 vecMul(Real3 u, Real3 v)
Vector cross product of u by v in .
Definition MathUtils.h:48
__host__ __device__ Real scaMul(Real2 u, Real2 v)
Dot product of u by v in .
Definition MathUtils.h:112
__host__ __device__ Real3 cross(Real3 v1, Real3 v2)
Cross product of two 3-component vectors.
Definition MathUtils.h:759
ItemGroupT< Face > FaceGroup
Group of faces.
Definition ItemTypes.h:179
ItemGroupT< Node > NodeGroup
Group of nodes.
Definition ItemTypes.h:168
MeshVariableScalarRefT< Node, Real3 > VariableNodeReal3
Coordinate type quantity at node.
ItemVariableScalarRefT< Int32 > VariableItemInt32
32-bit integer type quantity
constexpr __host__ __device__ bool isNearlyEqual(const _Type &a, const _Type &b)
Tests if two values are approximately equal. For integer types, this function is equivalent to IsEqua...
Definition Numeric.h:219
bool isZero(const BuiltInProxy< _Type > &a)
Tests if a value is exactly equal to zero.
String getCurrentDateTime()
Current date and time in ISO 8601 format.
-- tab-width: 2; indent-tabs-mode: nil; coding: utf-8-with-signature --
ArrayView< Int64 > Int64ArrayView
C equivalent of a 1D array of 64-bit integers.
Definition UtilsTypes.h:451
@ SB_AllowNull
Allows the service to be absent.
UniqueArray< Int64 > Int64UniqueArray
Dynamic 1D array of 64-bit integers.
Definition UtilsTypes.h:339
std::int64_t Int64
Signed integer type of 64 bits.
ArrayView< Real3 > Real3ArrayView
C equivalent of a 1D array of Real3.
Definition UtilsTypes.h:467
Int32 Integer
Type representing an integer.
UniqueArray< Real3 > Real3UniqueArray
Dynamic 1D array of rank 3 vectors.
Definition UtilsTypes.h:363
ConstArrayView< Int32 > Int32ConstArrayView
C equivalent of a 1D array of 32-bit integers.
Definition UtilsTypes.h:482
ConstArrayView< ItemInternal * > ItemInternalList
Type of the internal list of entities.
Definition ItemTypes.h:466
ConstArrayView< Int64 > Int64ConstArrayView
C equivalent of a 1D array of 64-bit integers.
Definition UtilsTypes.h:480
ArrayView< Int32 > Int32ArrayView
C equivalent of a 1D array of 32-bit integers.
Definition UtilsTypes.h:453
eItemKind
Mesh entity type.
@ IK_Particle
Particle mesh entity.
@ IK_Node
Node mesh entity.
@ IK_Cell
Cell mesh entity.
@ IK_Face
Face mesh entity.
@ IK_DoF
Degree of Freedom mesh entity.
@ IK_Edge
Edge mesh entity.
double Real
Type representing a real number.
ConstArrayView< Integer > IntegerConstArrayView
C equivalent of a 1D array of integers.
Definition UtilsTypes.h:486
ArrayView< Real > RealArrayView
C equivalent of a 1D array of reals.
Definition UtilsTypes.h:459
std::int32_t Int32
Signed integer type of 32 bits.
Real y
second component of the triplet
Definition Real3.h:36
Real z
third component of the triplet
Definition Real3.h:37
Real x
first component of the triplet
Definition Real3.h:35