Skip to content

Commit 7ca003e

Browse files
committed
unsorted nodes in vtk output
1 parent e278aca commit 7ca003e

File tree

8 files changed

+206
-33
lines changed

8 files changed

+206
-33
lines changed

ChangeLog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# Current (under development)
22

3+
* VTK/VTU writer now supports sparse or unsorted node tags
34
* basic example for a large-deformation mechanical case (NAFEMS GNL-5 benchmar problem)
45
* outputs from `WRITE_RESULTS` or `WRITE_MESH` in `vtu` or `vtk` for transient problems create a `.pvd` file
56
* instruction `PROBLEM_SOLVE` is not mandatory anymore, now FeenoX can guess where it should be called

src/Makefile-base.am

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ feenox_SOURCES = \
8383
./mesh/calculix.c \
8484
./mesh/vtk.c \
8585
./mesh/vtu.c \
86+
./mesh/tag_index_map.c \
8687
./mesh/elements/line2.c \
8788
./mesh/elements/line3.c \
8889
./mesh/elements/triang3.c \

src/feenox.h

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1277,6 +1277,21 @@ struct bc_data_t {
12771277
};
12781278

12791279

1280+
// hash map entry for sparse node ids
1281+
typedef struct {
1282+
size_t gmsh_tag; // Gmsh node tag (key)
1283+
size_t vtk_idx; // VTK point index (value)
1284+
UT_hash_handle hh;
1285+
} tag_index_hash_t;
1286+
1287+
// struct to encapsulate either mapping
1288+
typedef struct {
1289+
int use_array; // 1 for array, 0 for hashmap
1290+
size_t min_id, max_id;
1291+
size_t*array; // if use_array
1292+
tag_index_hash_t *hash; // if !use_array
1293+
} tag_index_map_t;
1294+
12801295
// unstructured mesh
12811296
struct mesh_t {
12821297
file_t *file;
@@ -1312,6 +1327,9 @@ struct mesh_t {
13121327
size_t *tag2index; // array to map tags to indexes (we need a -1)
13131328
// pointer to tag_min entries before so we can use the tag as an offset
13141329
size_t *tag2index_from_tag_min;
1330+
size_t node_tag_min;
1331+
size_t node_tag_max;
1332+
tag_index_map_t *index2tag;
13151333

13161334
enum {
13171335
data_type_element,
@@ -2284,6 +2302,7 @@ extern double feenox_gsl_function(double x, void *params);
22842302
// mesh.c
22852303
extern int feenox_instruction_mesh_read(void *arg);
22862304
extern int feenox_mesh_create_nodes_argument(mesh_t *);
2305+
extern int feenox_mesh_create_index2tag(mesh_t *this);
22872306
extern int feenox_mesh_free(mesh_t *);
22882307

22892308
extern int feenox_mesh_read_vtk(mesh_t *);
@@ -2392,6 +2411,12 @@ extern int feenox_mesh_init_nodal_indexes(mesh_t *, int dofs);
23922411
// cell.c
23932412
extern int feenox_mesh_element2cell(mesh_t *);
23942413

2414+
// tag_index_map.c
2415+
extern int tag_index_map_init(tag_index_map_t *map, size_t min_id, size_t max_id, size_t n_nodes, double threshold);
2416+
extern int tag_index_map_insert(tag_index_map_t *map, size_t gmsh_id, size_t vtk_idx);
2417+
extern size_t tag_index_map_lookup(tag_index_map_t *map, size_t gmsh_id);
2418+
extern int tag_index_map_free(tag_index_map_t *map);
2419+
23952420
// vtk.c
23962421
extern int feenox_mesh_read_vtk_field_node(mesh_t *mesh, FILE *fp, const char *name, unsigned int size);
23972422
extern int feenox_mesh_write_unstructured_mesh_vtk(mesh_t *mesh, FILE *file);

src/mesh/gmsh.c

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/*------------ -------------- -------- --- ----- --- -- - -
22
* feenox's mesh-related gmsh routines
33
*
4-
* Copyright (C) 2014--2024 Jeremy Theler
4+
* Copyright (C) 2014--2025 Jeremy Theler
55
*
66
* This file is part of feenox.
77
*
@@ -399,9 +399,7 @@ int feenox_mesh_read_gmsh(mesh_t *this) {
399399
}
400400

401401
// en msh2 si no es sparse, los tags son indices pero si es sparse es otro cantar
402-
if (j+1 != tag) {
403-
this->sparse = 1;
404-
}
402+
this->sparse |= (j+1 != tag);
405403

406404
this->node[j].tag = tag;
407405
if (tag < tag_min) {
@@ -416,6 +414,7 @@ int feenox_mesh_read_gmsh(mesh_t *this) {
416414

417415
// finished reading the node data, check if they are sparse
418416
// if they are, build tag2index
417+
// TODO: use the vtk hashmap
419418
if (this->sparse) {
420419
if (tag_max < this->n_nodes) {
421420
feenox_push_error_message("maximum node tag %d is less that number of nodes %d", tag_max, this->n_nodes);
@@ -433,9 +432,19 @@ int feenox_mesh_read_gmsh(mesh_t *this) {
433432

434433
} else if (version_maj == 4) {
435434
// number of blocks and nodes
436-
size_t num_blocks;
435+
size_t num_blocks = 0;
437436
if (version_min == 0) {
438-
// in 4.0 there is no min/ max
437+
// in 4.0 there's no min/max, I asked for this in
438+
// https://onelab.info/pipermail/gmsh/2018/012432.html
439+
//
440+
// > I would need to know the biggest tag of the nodes or elements before
441+
// > readuing them, not just the number of non-zero elements. Otherwise I
442+
// > need to either double-parse the file or reallocate the array each time.
443+
//
444+
// we could indeed add min/max vertex/element tags in the section header
445+
// in a future revision of the format
446+
//
447+
// see also https://gitlab.onelab.info/gmsh/gmsh/-/issues/444
439448
size_t data[2] = {0,0};
440449
feenox_call(feenox_gmsh_read_data_size_t(this, 2, data, binary));
441450
num_blocks = data[0];
@@ -457,7 +466,8 @@ int feenox_mesh_read_gmsh(mesh_t *this) {
457466
feenox_check_alloc(this->node = calloc(this->n_nodes, sizeof(node_t)));
458467

459468
if (tag_max != 0) {
460-
// we can do this as a one single pass because we have tag_max (I asked for this in v4.1)
469+
// we can do this as a one single pass because we have tag_max
470+
// (I asked for this in v4.1, see links above)
461471
if (tag_max < this->n_nodes) {
462472
feenox_push_error_message("maximum node tag %d is less that number of nodes %d", tag_max, this->n_nodes);
463473
}
@@ -496,12 +506,15 @@ int feenox_mesh_read_gmsh(mesh_t *this) {
496506
return FEENOX_ERROR;
497507
}
498508

509+
if (this->node[node_index].tag < tag_min) {
510+
tag_min = this->node[node_index].tag;
511+
}
499512
if (this->node[node_index].tag > tag_max) {
500513
tag_max = this->node[node_index].tag;
501514
}
502-
503-
// in msh4 the tags are the indices of the global mesh
504515
this->node[node_index].index_mesh = node_index;
516+
// we don't have tag2index yet, but we can check if it is sparse
517+
this->sparse |= (this->node[node_index].tag != node_index+1);
505518
node_index++;
506519
}
507520
} else {
@@ -530,14 +543,18 @@ int feenox_mesh_read_gmsh(mesh_t *this) {
530543
return FEENOX_ERROR;
531544
}
532545
this->tag2index_from_tag_min[this->node[node_index].tag] = node_index;
546+
this->sparse |= (this->node[node_index].tag != node_index+1);
533547
node_index++;
534548
}
535549
feenox_free(node_coords);
536550
}
537551
}
538552

553+
this->node_tag_min = tag_min;
554+
this->node_tag_max = tag_max;
539555
if (version_min == 0) {
540-
// for v4.0 we need an extra loop because we did not have the actual tag_max
556+
// for v4.0 we need an extra loop because we did not have
557+
// the actual tag_min/tag_max before reading
541558
feenox_mesh_tag2index_alloc(this, tag_min, tag_max);
542559

543560
for (size_t j = 0; j < this->n_nodes; j++) {
@@ -668,7 +685,6 @@ int feenox_mesh_read_gmsh(mesh_t *this) {
668685
} else if (version_maj == 4) {
669686
size_t num_blocks;
670687
if (version_min == 0) {
671-
// in 4.0 there's no min/max (I asked for this)
672688
tag_min = 0;
673689
tag_max = 0;
674690
if (fscanf(fp, "%lu %lu", &num_blocks, &this->n_elements) < 2) {

src/mesh/mesh.c

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -671,8 +671,12 @@ int feenox_mesh_free(mesh_t *mesh) {
671671
// feenox_free(physical_group->element);
672672
// feenox_free(physical_group);
673673
}
674-
*/
674+
*/
675675

676+
if (mesh->index2tag != NULL) {
677+
feenox_call(tag_index_map_free(mesh->index2tag));
678+
feenox_free(mesh->index2tag);
679+
}
676680
mesh->initialized = 0;
677681

678682
return FEENOX_OK;
@@ -700,3 +704,22 @@ int feenox_mesh_create_nodes_argument(mesh_t *this) {
700704

701705
return FEENOX_OK;
702706
}
707+
708+
int feenox_mesh_create_index2tag(mesh_t *this) {
709+
if (this->index2tag != NULL) {
710+
feenox_call(tag_index_map_free(this->index2tag));
711+
feenox_free(this->index2tag);
712+
}
713+
this->index2tag = calloc(1, sizeof(tag_index_map_t));
714+
tag_index_map_init(this->index2tag, this->node_tag_min, this->node_tag_max, this->n_nodes, 4);
715+
size_t index = 0;
716+
size_t n_nodes = 0;
717+
for (size_t tag = this->node_tag_min; tag <= this->node_tag_max; tag++) {
718+
if ((index = this->tag2index_from_tag_min[tag]) != SIZE_MAX) {
719+
tag_index_map_insert(this->index2tag, tag, index);
720+
n_nodes++;
721+
}
722+
}
723+
724+
return FEENOX_OK;
725+
}

src/mesh/tag_index_map.c

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
/*------------ -------------- -------- --- ----- --- -- - -
2+
* feenox's routines to perform array or hash-based lookups
3+
*
4+
* Copyright (C) 2025 Jeremy Theler
5+
*
6+
* This file is part of feenox.
7+
*
8+
* feenox is free software: you can redistribute it and/or modify
9+
* it under the terms of the GNU General Public License as published by
10+
* the Free Software Foundation, either version 3 of the License, or
11+
* (at your option) any later version.
12+
*
13+
* FeenoX is distributed in the hope that it will be useful,
14+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
15+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16+
* GNU General Public License for more details.
17+
*
18+
* You should have received a copy of the GNU General Public License
19+
* along with feenox. If not, see <http://www.gnu.org/licenses/>.
20+
*
21+
*
22+
* Portions of this file were developed with the assistance of GitHub Copilot.
23+
*------------------- ------------ ---- -------- -- - - -
24+
*/
25+
26+
#include "feenox.h"
27+
28+
int tag_index_map_init(tag_index_map_t *map, size_t min_id, size_t max_id, size_t n_nodes, double threshold) {
29+
map->min_id = min_id;
30+
map->max_id = max_id;
31+
size_t range = max_id - min_id + 1;
32+
if (range <= threshold * n_nodes) {
33+
map->use_array = 1;
34+
feenox_check_alloc(map->array = (size_t*)malloc(range * sizeof(size_t)));
35+
for (size_t i = 0; i < range; ++i) {
36+
map->array[i] = SIZE_MAX;
37+
}
38+
map->hash = NULL;
39+
} else {
40+
map->use_array = 0;
41+
map->array = NULL;
42+
map->hash = NULL;
43+
}
44+
return FEENOX_OK;
45+
}
46+
47+
int tag_index_map_insert(tag_index_map_t *map, size_t gmsh_tag, size_t vtk_idx) {
48+
if (map->use_array) {
49+
map->array[gmsh_tag - map->min_id] = vtk_idx;
50+
} else {
51+
tag_index_hash_t *entry = NULL;
52+
HASH_FIND(hh, map->hash, &gmsh_tag, sizeof(size_t), entry);
53+
if (!entry) {
54+
entry = (tag_index_hash_t*)malloc(sizeof(tag_index_hash_t));
55+
entry->gmsh_tag = gmsh_tag;
56+
entry->vtk_idx = vtk_idx;
57+
HASH_ADD(hh, map->hash, gmsh_tag, sizeof(size_t), entry);
58+
} else {
59+
entry->vtk_idx = vtk_idx; // Overwrite if needed
60+
}
61+
}
62+
return FEENOX_OK;
63+
}
64+
65+
size_t tag_index_map_lookup(tag_index_map_t *map, size_t gmsh_tag) {
66+
if (map) {
67+
if (map->use_array) {
68+
return map->array[gmsh_tag - map->min_id];
69+
} else {
70+
tag_index_hash_t *entry = NULL;
71+
HASH_FIND_INT(map->hash, &gmsh_tag, entry);
72+
if (entry) {
73+
return entry->vtk_idx;
74+
} else {
75+
return SIZE_MAX; // Not found
76+
}
77+
}
78+
} else {
79+
return gmsh_tag-1;
80+
}
81+
}
82+
83+
int tag_index_map_free(tag_index_map_t *map) {
84+
if (map->use_array) {
85+
feenox_free(map->array);
86+
} else {
87+
tag_index_hash_t *cur, *tmp;
88+
HASH_ITER(hh, map->hash, cur, tmp) {
89+
HASH_DEL(map->hash, cur);
90+
feenox_free(cur);
91+
}
92+
}
93+
return FEENOX_OK;
94+
}

0 commit comments

Comments
 (0)