14#include "arcane/utils/StringBuilder.h"
15#include "arcane/utils/Iostream.h"
16#include "arcane/utils/PlatformUtils.h"
17#include "arcane/utils/Convert.h"
18#include "arcane/utils/NotImplementedException.h"
19#include "arcane/utils/ArgumentException.h"
20#include "arcane/utils/FloatingPointExceptionSentry.h"
22#include "arcane/core/ISubDomain.h"
23#include "arcane/core/IParallelMng.h"
25#include "arcane/core/IMesh.h"
26#include "arcane/core/IMeshSubMeshTransition.h"
27#include "arcane/core/ItemGroup.h"
28#include "arcane/core/Service.h"
29#include "arcane/core/Timer.h"
30#include "arcane/core/FactoryService.h"
31#include "arcane/core/ItemPrinter.h"
32#include "arcane/core/IItemFamily.h"
33#include "arcane/core/MeshVariable.h"
34#include "arcane/core/VariableBuildInfo.h"
36#include "arcane/std/MeshPartitionerBase.h"
37#include "arcane/std/MetisMeshPartitioner_axl.h"
39#include "arcane_internal_config.h"
42#define MPICH_SKIP_MPICXX
43#define OMPI_SKIP_MPICXX
47#if (PARMETIS_MAJOR_VERSION < 4)
48#error "Your version of Parmetis is too old .Version 4.0+ is required"
51typedef real_t realtype;
54#include "arcane/std/PartitionConverter.h"
55#include "arcane/std/GraphDistributor.h"
56#include "arcane/std/internal/MetisWrapper.h"
66using MetisCallStrategy = TypesMetisMeshPartitioner::MetisCallStrategy;
67using MetisEmptyPartitionStrategy = TypesMetisMeshPartitioner::MetisEmptyPartitionStrategy;
75class MetisMeshPartitioner
96 bool m_disable_floatingexception =
false;
107 const String& Name)
const;
113MetisMeshPartitioner::
114MetisMeshPartitioner(
const ServiceBuildInfo& sbi)
115: ArcaneMetisMeshPartitionerObject(sbi)
119 if (s ==
"1" || s ==
"TRUE")
120 m_disable_floatingexception =
true;
129 Int32 nb_part = m_parallel_mng->commSize();
142 if (m_disable_floatingexception) {
161 bool force_partition =
false;
163 if (nb_part < nb_rank)
168 info() <<
"INFO: Unable to test load balancing on a single sub-domain";
176 bool force_full_repartition =
false;
178 force_full_repartition |= (m_nb_refine < 0);
180 if (nb_part != nb_rank) {
181 force_full_repartition =
true;
182 force_partition =
true;
185 info() <<
"WARNING: compensating the potential lack of 'Metis' options in case of manual instanciation";
186 Integer max_diffusion_count = 10;
187 Real imbalance_relative_tolerance = 4;
188 float tolerance_target = 1.05f;
189 bool dump_graph =
false;
190 MetisCallStrategy call_strategy = MetisCallStrategy::one_processor_per_node;
191 bool in_out_digest =
false;
196 if (call_strategy_env ==
"all-processors") {
197 call_strategy = MetisCallStrategy::all_processors;
199 else if (call_strategy_env ==
"one-processor-per-node") {
200 call_strategy = MetisCallStrategy::one_processor_per_node;
202 else if (call_strategy_env ==
"two-processors-two-nodes") {
203 call_strategy = MetisCallStrategy::two_processors_two_nodes;
205 else if (call_strategy_env ==
"two-gathered-processors") {
206 call_strategy = MetisCallStrategy::two_gathered_processors;
208 else if (call_strategy_env ==
"two-scattered-processors") {
209 call_strategy = MetisCallStrategy::two_scattered_processors;
211 else if (!call_strategy_env.
null()) {
212 ARCANE_FATAL(
"Invalid value '{0}' for ARCANE_METIS_CALL_STRATEGY environment variable", call_strategy_env);
216 in_out_digest = (v.value() != 0);
218 dump_graph = (v.value() != 0);
221 max_diffusion_count =
options()->maxDiffusiveCount();
222 imbalance_relative_tolerance =
options()->imbalanceRelativeTolerance();
223 tolerance_target = (float)(
options()->toleranceTarget());
224 dump_graph =
options()->dumpGraph();
225 call_strategy =
options()->metisCallStrategy();
226 in_out_digest =
options()->inputOutputDigest();
229 if (max_diffusion_count > 0)
230 force_full_repartition |= (m_nb_refine >= max_diffusion_count);
232 force_full_repartition |= (
imbalance() > 1.0);
235 initConstraints(
false);
241 if (is_shared_memory)
242 call_strategy = MetisCallStrategy::one_processor_per_node;
244 idxtype nb_weight = nbCellWeight();
246 info() <<
"Load balancing with Metis nb_weight=" << nb_weight <<
" initial=" << initial_partition
247 <<
" call_strategy=" << (int)call_strategy
248 <<
" is_shared_memory?=" << is_shared_memory
249 <<
" disabling_fpe?=" << m_disable_floatingexception
250 <<
" sizeof(idxtype)==" <<
sizeof(idxtype);
253 initial_partition =
true;
264 Integer nb_own_cell = nbOwnCellsWithConstraints();
266 Int32 nb_empty_part = 0;
269 metis_vtkdist[0] = 0;
270 for (
Integer i = 0; i < nb_rank; ++i) {
272 Int32 n = global_nb_own_cell[i];
276 metis_vtkdist[i + 1] =
static_cast<idxtype
>(total_nb_cell);
281 info() <<
"Total nb_cell=" << total_nb_cell <<
" nb_empty_partition=" << nb_empty_part;
282 if (total_nb_cell == 0) {
283 info() <<
"INFO: mesh '" <<
mesh->name() <<
" has no cell. No partitioning is needed";
301 Integer nb_max_face_neighbour_cell = 0;
308 if (cellUsedWithConstraints(item)) {
309 cell_metis_uid[item] = mid;
312 if (item.
hasFlags(ItemFlags::II_HasEdgeFor1DItems))
313 nb_max_face_neighbour_cell += item.
nbEdge();
315 nb_max_face_neighbour_cell += item.
nbFace();
317 cell_metis_uid.synchronize();
320 _initUidRef(cell_metis_uid);
323 cell_metis_uid.setUsed(
false);
326 metis_xadj.
reserve(nb_own_cell + 1);
329 metis_adjncy.
reserve(nb_max_face_neighbour_cell);
339 if (!cellUsedWithConstraints(item))
342 metis_xadj.
add(metis_adjncy.
size());
344 getNeighbourCellsUidWithConstraints(item, neighbour_cells, &edge_weights);
345 for (
Integer z = 0; z < neighbour_cells.
size(); ++z)
346 metis_adjncy.
add(
static_cast<idxtype
>(neighbour_cells[z]));
348 metis_xadj.
add(metis_adjncy.
size());
352 idxtype numflags = 0;
353 idxtype nparts =
static_cast<int>(nb_part);
360 if (!s.
null() && (s.
upper() ==
"PARTICLES"))
366 bool cond = (nb_weight == 1);
378 cells_weights = cellsWeightsWithConstraints(CheckedConvert::toInteger(nb_weight));
380 metis_ubvec.
resize(CheckedConvert::toInteger(nb_weight));
381 metis_ubvec.
fill(tolerance_target);
383 converter.reset(CheckedConvert::toInteger(nb_weight));
386 cond = converter.isBalancable<realtype>(cells_weights, metis_ubvec, nb_part);
388 info() <<
"We have to increase imbalance :";
389 info() << metis_ubvec;
390 for (
auto& tol : metis_ubvec) {
400 const bool do_print_weight =
false;
401 if (do_print_weight) {
403 Integer iteration =
mesh->subDomain()->commonVariables().globalIteration();
410 for (
int i = 0; i < metis_xadj.
size() - 1; ++i) {
411 dumpy <<
" Weight uid=" << i;
412 for (
int j = 0; j < nb_weight; ++j) {
413 dumpy <<
" w=" << *(metis_vwgt.
begin() + i * nb_weight + j)
414 <<
"(" << cells_weights[i * nb_weight + j] <<
")";
422 converter.computeContrib(edge_weights);
429 if (!initial_partition) {
430 cells_size = cellsSizeWithConstraints();
431 converter.computeContrib(cells_size, (
Real)itr);
435 idxtype metis_options[4];
436 metis_options[0] = 0;
444 metis_options[0] = 1;
445 metis_options[1] = 0;
446 metis_options[2] = m_random_seed;
447 metis_options[3] = 1;
453 idxtype metis_edgecut = 0;
458 std::chrono::high_resolution_clock clock;
459 auto start_time = clock.now();
471 bool redistribute =
true;
472 bool redistribute_by_wrapper =
false;
474 if (call_strategy == MetisCallStrategy::all_processors || call_strategy == MetisCallStrategy::two_scattered_processors) {
475 redistribute =
false;
478 if (call_strategy == MetisCallStrategy::two_processors_two_nodes || call_strategy == MetisCallStrategy::two_scattered_processors) {
479 redistribute_by_wrapper =
true;
496 bool gd_allow_only_one_rank =
false;
497 if (is_shared_memory || (nb_empty_part != 0 && initial_partition))
498 gd_allow_only_one_rank =
true;
503 if ((nb_empty_part + 1) == nb_rank) {
504 info() <<
"Initialize GraphDistributor with max rank=1";
505 gd.initWithMaxRank(1);
507 else if (call_strategy == MetisCallStrategy::two_gathered_processors && (nb_rank > 2)) {
509 info() <<
"Initialize GraphDistributor with max rank=2";
510 gd.initWithMaxRank(2);
513 info() <<
"Initialize GraphDistributor with one rank per node"
514 <<
" (allow_one?=" << gd_allow_only_one_rank <<
")";
520 info() <<
"Using GraphDistributor?=" << redistribute;
522 metis_xadj = gd.convert<idxtype>(metis_xadj, &metis_part,
true);
523 metis_vwgt = gd.convert<idxtype>(metis_vwgt);
524 metis_adjncy = gd.convert<idxtype>(metis_adjncy);
525 metis_ewgt = gd.convert<idxtype>(metis_ewgt);
526 if (!initial_partition)
527 metis_vsize = gd.convert<idxtype>(metis_vsize);
528 metis_pm = gd.subParallelMng();
530 metis_vtkdist.
resize(gd.size() + 1);
531 if (gd.contribute()) {
533 Integer nbVertices = metis_part.size();
535 metis_vtkdist[0] = 0;
536 for (
int i = 1; i < metis_vtkdist.
size(); ++i) {
537 metis_vtkdist[i] = metis_vtkdist[i - 1] + buffer[i - 1];
540 metis_options[3] = PARMETIS_PSR_UNCOUPLED;
546 MPI_Comm metis_mpicomm =
static_cast<MPI_Comm
>(metis_pm->
communicator());
548 bool do_call_metis =
true;
550 do_call_metis = gd.contribute();
557 _writeGraph(metis_pm, metis_vtkdist, metis_xadj, metis_adjncy, metis_vwgt, metis_ewgt, name.
toString());
561 const Integer tpwgt_size = CheckedConvert::toInteger(nb_part) * CheckedConvert::toInteger(nb_weight);
563 float fill_value = (float)(1.0 / (
double)nparts);
564 tpwgt.
fill(fill_value);
566 int retval = METIS_OK;
570 force_partition |= initial_partition;
571 force_full_repartition |= force_partition;
572 if (force_full_repartition) {
574 if (force_partition) {
575 info() <<
"Metis: use a complete partitioning.";
577 redistribute_by_wrapper,
578 metis_vtkdist.
data(),
583 &wgtflag, &numflags, &nb_weight,
584 &nparts, tpwgt.
data(),
586 metis_options, &metis_edgecut,
590 info() <<
"Metis: use a complete REpartitioning.";
592 redistribute_by_wrapper,
593 metis_vtkdist.
data(),
599 &wgtflag, &numflags, &nb_weight,
600 &nparts, tpwgt.
data(),
602 &itr, metis_options, &metis_edgecut,
610 info() <<
"Metis: use a diffusive REpartitioning";
611 retval = ParMETIS_V3_RefineKway(metis_vtkdist.
data(),
616 &wgtflag, &numflags, &nb_weight,
617 &nparts, tpwgt.
data(),
619 metis_options, &metis_edgecut,
623 if (retval != METIS_OK) {
624 ARCANE_FATAL(
"Error while computing ParMetis partitioning r='{0}'", retval);
628 metis_part = gd.convertBack<idxtype>(metis_part, nb_own_cell);
631 auto end_time = clock.now();
632 std::chrono::microseconds duration = std::chrono::duration_cast<std::chrono::microseconds>(end_time - start_time);
633 info() <<
"Time to partition using parmetis = " << duration.count() <<
" us ";
639 switch (
options()->emptyPartitionStrategy()) {
640 case MetisEmptyPartitionStrategy::DoNothing:
642 case MetisEmptyPartitionStrategy::TakeFromBiggestPartitionV1:
645 case MetisEmptyPartitionStrategy::TakeFromBiggestPartitionV2:
658 if (!cellUsedWithConstraints(item))
661 Int32 new_owner = CheckedConvert::toInt32(metis_part[index]);
663 changeCellOwner(item, cells_new_owner, new_owner);
670 cells_new_owner.synchronize();
703 for (
int i = 0; i < nb_own_cell; i++) {
704 elem_by_part[CheckedConvert::toInteger(metis_part[i])]++;
706 pm->
computeMinMaxSum(elem_by_part, min_part, max_part, sum_part, min_roc, max_proc);
709 Int32 max_part_id = -1;
710 Int32 max_part_nbr = -1;
712 for (
int i = 0; i < nb_part; i++) {
713 if (sum_part[i] == 0) {
716 if (max_part_nbr < sum_part[i]) {
717 max_part_nbr = sum_part[i];
721 info() <<
"Parmetis: number empty parts " << nb_hole;
725 if (my_rank == max_proc[max_part_id]) {
727 for (
int i = 0; i < nb_part; i++) {
729 if (sum_part[i] == 0 && offset < nb_own_cell) {
730 while (offset < nb_own_cell && metis_part[offset] != max_part_id) {
734 if (offset == nb_own_cell)
737 metis_part[offset] = i;
759 const Int32 nb_own_cell = metis_part.
size();
767 for (
int i = 0; i < nb_own_cell; i++) {
768 elem_by_part[metis_part[i]]++;
770 pm->
computeMinMaxSum(elem_by_part, min_part, max_part, sum_part, min_rank, max_rank);
775 Int32 max_part_id = -1;
776 Int64 max_part_nbr = -1;
778 Int64 total_nb_cell = 0;
779 for (
int i = 0; i < nb_part; i++) {
780 Int64 current_sum_part = sum_part[i];
781 total_nb_cell += current_sum_part;
782 if (current_sum_part == 0) {
783 empty_part_ranks.
add(i);
786 if (max_part_nbr < current_sum_part) {
787 max_part_nbr = current_sum_part;
792 ARCANE_FATAL(
"Bad value max_part_id ({0})", max_part_id);
793 info() <<
"Parmetis: check empty parts: (" << algo_iteration <<
") nb_empty_parts=" << nb_hole
794 <<
" nb_max_part=" << max_part_nbr
795 <<
" max_part_rank=" << max_part_id
796 <<
" max_proc_max_part_id=" << max_rank[max_part_id]
797 <<
" empty_part_ranks=" << empty_part_ranks
798 <<
" total_nb_cell=" << total_nb_cell;
805 if (my_rank == max_rank[max_part_id]) {
807 Int32 max_remove_cell = nb_own_cell - 1;
809 for (
int i = 0; i < nb_part; i++) {
811 if (sum_part[i] == 0 && offset < nb_own_cell) {
812 while (offset < max_remove_cell && metis_part[offset] != max_part_id) {
816 if (offset == max_remove_cell)
819 metis_part[offset] = i;
840 bool do_continue =
true;
841 Int32 last_nb_hole = 0;
842 while (do_continue) {
844 info() <<
"Parmetis: nb_empty_partition=" << nb_hole <<
" last_nb_partition=" << last_nb_hole;
851 if (last_nb_hole > 0 && last_nb_hole <= nb_hole) {
852 pwarning() <<
"Can not remove all empty partitions. This is probably because you try"
853 <<
" to cut in too many partitions";
856 last_nb_hole = nb_hole;
879 bool have_ewgt =
false;
883 info() <<
"COMM_SIZE=" << commsize <<
" RANK=" << commrank;
891 info() <<
"MetisVtkDist=" << metis_vtkdist;
892 info() <<
"MetisXAdj =" << metis_xadj;
893 info() <<
"MetisAdjncy =" << metis_adjncy;
894 info() <<
"MetisVWgt =" << metis_vwgt;
895 info() <<
"MetisEWgt =" << metis_ewgt;
897#define METIS_ERROR ARCANE_FATAL("_writeGraph")
901 filename += commrank;
904 if (metis_vtkdist.
size() != commsize + 1)
907 nvtx = metis_vtkdist[commrank + 1] - metis_vtkdist[commrank];
908 if (metis_xadj.
size() != nvtx + 1) {
909 std::cerr <<
"_writeGraph : nvtx+1 = " << nvtx <<
" != " << metis_xadj.
size() << std::endl;
914 nwgt = metis_vwgt.
size() / nvtx;
915 if (nwgt != 0 && metis_vwgt.
size() % nwgt != 0)
918 have_ewgt = (metis_ewgt.
size() != 0);
919 if (have_ewgt && metis_ewgt.
size() != metis_adjncy.
size())
925 Int64 localEdges = metis_xadj[nvtx];
928 file <<
"2" << std::endl;
929 file << commsize <<
"\t" << commrank << std::endl;
930 file << metis_vtkdist[commsize] <<
"\t" << globalEdges << std::endl;
931 file << nvtx <<
"\t" << localEdges << std::endl;
932 file <<
"0\t" <<
"0" << have_ewgt << nwgt << std::endl;
943 for (idxtype vertnum = 0; vertnum < nvtx; vertnum++) {
945 for (
int dim = 0; dim < nwgt; ++dim)
946 file << metis_vwgt[vertnum * nwgt + dim] <<
'\t';
947 file << metis_xadj[vertnum + 1] - metis_xadj[vertnum];
948 for (idxtype edgenum = metis_xadj[vertnum];
949 edgenum < metis_xadj[vertnum + 1]; ++edgenum) {
951 file <<
'\t' << metis_ewgt[edgenum];
952 file <<
'\t' << metis_adjncy[edgenum];
972#if ARCANE_DEFAULT_PARTITIONER == METIS_DEFAULT_PARTITIONER
#define ARCANE_THROW(exception_class,...)
Macro for throwing an exception with formatting.
#define ARCANE_FATAL(...)
Macro throwing a FatalErrorException.
#define ARCANE_SERVICE_INTERFACE(ainterface)
Macro to declare an interface when registering a service.
Integer size() const
Number of elements in the vector.
ArcaneMetisMeshPartitionerObject(const Arcane::ServiceBuildInfo &sbi)
Constructeur.
CaseOptionsMetisMeshPartitioner * options() const
Options du jeu de données du service.
Exception when an argument is invalid.
Conversion of an array from one type to another type.
Modifiable view of an array of type T.
constexpr Integer size() const noexcept
Returns the size of the array.
void fill(ConstReferenceType value)
Fills the array with the value value.
void resize(Int64 s)
Changes the number of elements in the array to s.
iterator begin()
Iterator over the first element of the array.
void add(ConstReferenceType val)
Adds element val to the end of the array.
const T * data() const
Access to the root of the array without any protection.
ConstArrayView< T > constView() const
Constant view of this array.
void reserve(Int64 new_capacity)
Reserves memory for new_capacity elements.
Int32 nbEdge() const
Number of edges of the cell.
Int32 nbFace() const
Number of faces of the cell.
Int32 globalIteration() const
Current iteration number.
Constant view of an array of type T.
Template class for converting a type.
Class allowing temporary activation/deactivation of exceptions of the processor.
Redistribute graph data to another "communicator".
void initWithOneRankPerNode(bool allow_only_one_rank)
Automatic distribution : one partitioning process per node.
Interface of a mesh partitioner.
Interface of a mesh partitioner.
virtual IParallelMng * parallelMng()=0
Parallelism manager.
Interface of the parallelism manager for a subdomain.
virtual bool isThreadImplementation() const =0
Indicates if the implementation uses threads.
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 Int32 commSize() const =0
Number of instances in the communicator.
virtual void allGather(ConstArrayView< char > send_buf, ArrayView< char > recv_buf)=0
Performs an all-gather operation across all processors. This is a collective operation....
virtual Parallel::Communicator communicator() const =0
MPI communicator associated with this manager.
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.
Interface of the subdomain manager.
virtual const CommonVariables & commonVariables() const =0
Information on standard variables.
virtual void flush()=0
Flushes all streams.
@ PNoDump
Indicates that the variable should not be saved.
constexpr bool hasFlags(Int32 flags) const
Returns if the flags are set for the entity.
Real imbalance() const override
Computation time imbalance.
Real maxImbalance() const override
Maximum allowed imbalance.
virtual void changeOwnersFromCells()
Positions the new owners of nodes, edges and faces based on the cells.
Mesh partitioner using the PARMetis library.
void partitionMesh(bool initial_partition) override
void _removeEmptyPartsV2(Int32 nb_part, ArrayView< idxtype > metis_part)
Fills empty partitions (version 2).
Int32 _removeEmptyPartsV2Helper(Int32 nb_part, ArrayView< idxtype > metis_part, Int32 algo_iteration)
Applies one iteration of the empty partition removal algorithm.
void build() override
Build-level construction of the service.
void _partitionMesh(bool initial_partition, Int32 nb_part)
int _writeGraph(IParallelMng *pm, Span< const idxtype > metis_vtkdist, Span< const idxtype > metis_xadj, Span< const idxtype > metis_adjncy, Span< const idxtype > metis_vwgt, Span< const idxtype > metis_ewgt, const String &Name) const
void _removeEmptyPartsV1(Int32 nb_part, Int32 nb_own_cell, ArrayView< idxtype > metis_part)
Fills empty partitions (version 1).
Wrapper around Parmetis calls.
int callPartKway(const bool print_digest, const bool gather, idx_t *vtxdist, idx_t *xadj, idx_t *adjncy, idx_t *vwgt, idx_t *adjwgt, idx_t *wgtflag, idx_t *numflag, idx_t *ncon, idx_t *nparts, real_t *tpwgts, real_t *ubvec, idx_t *options, idx_t *edgecut, idx_t *part)
Simple wrapper around the ParMetis routine "ParMETIS_V3_PartKway".
int callAdaptiveRepart(const bool print_digest, const bool gather, idx_t *vtxdist, idx_t *xadj, idx_t *adjncy, idx_t *vwgt, idx_t *vsize, idx_t *adjwgt, idx_t *wgtflag, idx_t *numflag, idx_t *ncon, idx_t *nparts, real_t *tpwgts, real_t *ubvec, real_t *ipc2redist, idx_t *options, idx_t *edgecut, idx_t *part)
Simple wrapper around the ParMetis routine "ParMETIS_V3_AdaptiveRepart".
Conversion of an array of floats to an array of integers/longs. \abstract This class manages the scal...
Structure containing the information to create a service.
Service creation properties.
1D vector of data with reference semantics.
constexpr __host__ __device__ SizeType size() const noexcept
Returns the size of the array.
View of an array of elements of type T.
Unicode character string constructor.
String toString() const
Returns the constructed character string.
Unicode character string.
bool null() const
Returns true if the string is null.
String upper() const
Transforms all characters in the string to uppercase.
const char * localstr() const
Returns the conversion of the instance into UTF-8 encoding.
Positions the name of the currently executing action.
TraceMessage info() const
Flow for an information message.
ITraceMng * traceMng() const
Trace manager.
TraceMessage pwarning() const
1D data vector with value semantics (STL style).
Parameters necessary for building a variable.
ItemGroupT< Cell > CellGroup
Group of cells.
#define ARCANE_REGISTER_SERVICE(aclass, a_service_property,...)
Macro for registering a service.
MeshVariableScalarRefT< Cell, Integer > VariableCellInteger
Quantity at the cell center of integer type.
ItemVariableScalarRefT< Int32 > VariableItemInt32
32-bit integer type quantity
Integer toInteger(Real r)
Converts a Real to Integer.
@ ReduceSum
Sum of values.
@ ReduceMax
Maximum of values.
-- tab-width: 2; indent-tabs-mode: nil; coding: utf-8-with-signature --
UniqueArray< Int64 > Int64UniqueArray
Dynamic 1D array of 64-bit integers.
std::int64_t Int64
Signed integer type of 64 bits.
Int32 Integer
Type representing an integer.
@ ST_SubDomain
The service is used at the subdomain level.
@ IK_Cell
Cell mesh entity.
double Real
Type representing a real number.
std::int32_t Int32
Signed integer type of 32 bits.