1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
|
#!/usr/bin/env python
# Purpose:
# Functions related to creating vtu files.
#
# Copyright:
# Copyright (C) 2025 Polytechnique Montreal
# This library is free software; you can redistribute it and/or modify it under the terms
# of the GNU Lesser General Public License as published by the Free Software Foundation;
# either version 2.1 of the License, or (at your option) any later version
#
# Author(s): Atyab A. Calloo
import numpy as np
import vtk
from math import sqrt
def create_vtu(Geom, Mcro, Trck, Flux, test_name, verbose):
"""
Uses Geom, Macr, Trck, Flux LCM objects to create the material and flux vtu
objects that can then be viewed in Paraview.
Args:
Geom : geometry LCM object
Macr : macrolib LCM object
Trck : tracking LCM object
Flux : flux LCM object
test_name : file name of the c2m procedure to be computed
verbose : controls write output level
"""
### RECOVER STATEVECTORS FROM LCM OBJECTS
SV_geom = Geom["STATE-VECTOR"]
SV_mcro = Mcro["STATE-VECTOR"]
SV_trck = Trck["STATE-VECTOR"]
SV_flux = Flux["STATE-VECTOR"]
GV_mcro = Mcro["GROUP"]
### RECOVER RELEVANT MATERIAL, TRACKING AND FLUX DATA
n_mat = SV_mcro[1]
n_reg = SV_trck[0]
n_unk = SV_trck[1]
n_dim = SV_trck[8]
spltl = SV_trck[25]
n_grp = SV_flux[0]
geom_type = SV_trck[5] # 5 - Car2D; 7 - Car3D; 8 - Hex2D; 9 - Hex3D
### RECOVER OTHER DATA FROM LCM OBJECTS
mat_array = Trck["MATCOD"]
### INITIALIZE FLUX AND NUSIGF ARRAYS
flx_array = np.zeros((n_grp,n_unk), dtype='f')
nsf_mat_array = np.zeros((n_grp,n_mat), dtype='f')
nsf_reg_array = np.zeros((n_grp,n_reg), dtype='f')
### PICK UP FLUX AND NUSIGF ARRAYS
for i_g in range(n_grp):
flx_array[i_g,:] = Flux["FLUX"][i_g]
nsf_mat_array[i_g,:] = GV_mcro[i_g]["NUSIGF"]
### CREATE NUSIGF ARRAY BY REGION
for i_reg in range(n_reg):
ind_mat = mat_array[i_reg]
if ind_mat == 0 :
nsf_reg_array[i_g,i_reg] = 0
elif ind_mat > 0 :
ind_mat=ind_mat-1
nsf_reg_array[i_g,i_reg] = nsf_mat_array[i_g,ind_mat]
### REBUILD MESHES
lx, meshx, ly, meshy, lz, meshz = rebuild_meshes(Geom)
### FIND HEXAGON CENTRES IF NEEDED
if geom_type==8 or geom_type==9:
side = Geom["SIDE"][0]
n_hex=int(n_reg/(lz*3*spltl**2))
n_ring = int((sqrt( float((4*n_hex-1)/3) )+1.)/2.)
hex_centres = compute_hex_centres(n_hex,n_ring,side)
### CALL TO MAKE_VTU GEOM-DEPENDENT
nscat = SV_trck[6]
ielem = SV_trck[7]
if (geom_type == 5) or (geom_type == 7):
if verbose > 0:
print('\n>>>>>>> GENERATING VTUs FOR ',test_name,'.')
print('-'*74 + '\n')
make_vtu_cartesian_geom(lx,meshx,ly,meshy,lz,meshz,mat_array,"material",test_name )
for i_g in range(n_grp):
make_vtu_cartesian_geom(lx,meshx,ly,meshy,lz,meshz,flx_array[i_g,:],"flux",test_name ,nscat=nscat,ielem=ielem,i_g=i_g)
elif (geom_type == 8) or (geom_type == 9):
if verbose > 0:
print('\n>>>>>>> GENERATING VTUs FOR ',test_name,'.')
print('-'*74 + '\n')
make_vtu_hexagonal_geom(n_hex,side,hex_centres,mat_array,"material",test_name ,spltl=spltl,meshz=meshz,lz=lz)
for i_g in range(n_grp):
make_vtu_hexagonal_geom(n_hex,side,hex_centres,flx_array[i_g,:],"flux",test_name ,meshz=meshz,lz=lz,nscat=nscat,ielem=ielem,spltl=spltl,i_g=i_g)
print("\n>>>>>>> VTU CREATION COMPLETED!")
print('-'*74 + '\n')
########################################################################
def rebuild_meshes(Geom):
"""
Uses Geom LCM objects to return number of mesh elements and mesh arrays along each
axis, taking split into account.
Args:
Geom : geometry LCM object
Returns:
lx : number of mesh elements along x
meshx : mesh array along x
ly : number of mesh elements along y
meshy : mesh array along y
lz : number of mesh elements along z
meshz : mesh array along z
"""
SV_geom = Geom["STATE-VECTOR"]
geom_type = SV_geom[0] # 5 - Car2D; 7 - Car3D; 8 - Hex2D; 9 - Hex3D
mesh_dict = {}
### FOR CARTESIAN GEOMETRIES
if geom_type==5 or geom_type==7:
lx = SV_geom[2]
meshx = Geom["MESHX"]
spltx = Geom["SPLITX"]
lx, meshx = get_mesh_oneaxis(lx,meshx,spltx)
ly = SV_geom[3]
meshy = Geom["MESHY"]
splty = Geom["SPLITY"]
ly, meshy = get_mesh_oneaxis(ly,meshy,splty)
else:
lx =1
meshx=1
ly =1
meshy=1
### FOR 3D GEOMETRIES
if geom_type==7 or geom_type==9:
lz = SV_geom[4]
meshz = Geom["MESHZ"]
spltz = Geom["SPLITZ"]
lz, meshz = get_mesh_oneaxis(lz,meshz,spltz)
else:
lz =1
meshz=1
spltz=1
return lx, meshx, ly, meshy, lz, meshz
########################################################################
def get_mesh_oneaxis(lx,mesh,splt):
"""
Compute number of mesh elements and mesh arrays taking split into account.
Args:
lx : number of mesh elements along axis w/o splits
meshx : mesh array along axis w/o splits
splt : split value along axis
Returns:
lx : number of mesh elements along axis w/ splits
meshx : mesh array along axis w/ splits
"""
tmp_mesh=np.empty(1, dtype='f')
val = 0.
tmp_mesh[0] = val
for i in range(len(splt)):
step=(mesh[i+1]-mesh[i])/splt[i]
for j in range(splt[i]):
val += step
tmp_mesh = np.append(tmp_mesh,val)
lx=len(tmp_mesh)-1
return lx, tmp_mesh
########################################################################
def compute_hex_centres(n_hex,n_ring,side):
"""
Compute centres of hexagons within hexagonal domain. Centre hexagon is zeroth
ring.
Args:
n_hex : number of hexagons in domain
n_ring : number of rings in domain; centre hexagon is zeroth ring
side : length of one side of hexagon
Returns:
hex_centres : 2D array with x and y coordinates of hexagon centres
"""
hex_centres = np.zeros((2,n_hex), dtype='f')
ind_hex =0
hex_centres[0, 0] = 0.0
hex_centres[1, 0] = 0.0
### LOOP OVER EACH RING OF HEXAGONS
for ind_ring in range(1, n_ring):
numhex_ring = ind_ring * 6
xcoord = ind_ring * 1.5 * side
ycoord = ind_ring * (sqrt(3) / 2) * side
hex_centres[0, ind_hex + 1] = xcoord
hex_centres[1, ind_hex + 1] = ycoord
ind_hex += 1
ind_dir = 1
ind_step = 0
numhex_perdir = ind_ring
### LOOP OVER HEXAGONS IN CURRENT RING
### For each of the six major axis directions, move in the direction
### of that axis for numhex_perdir hexagons (which happens to be the
### same as the ring index).
for i_hex in range(1, numhex_ring):
ind_step += 1
if ind_dir == 1:
xcoord -= 1.5 * side
ycoord += (sqrt(3) / 2) * side
elif ind_dir == 2:
xcoord -= 1.5 * side
ycoord -= (sqrt(3) / 2) * side
elif ind_dir == 3:
ycoord -= sqrt(3) * side
elif ind_dir == 4:
xcoord += 1.5 * side
ycoord -= (sqrt(3) / 2) * side
elif ind_dir == 5:
xcoord += 1.5 * side
ycoord += (sqrt(3) / 2) * side
elif ind_dir == 6:
ycoord += sqrt(3) * side
hex_centres[0, ind_hex + 1] = xcoord
hex_centres[1, ind_hex + 1] = ycoord
ind_hex += 1
if ind_step == numhex_perdir:
ind_dir += 1
ind_step = 0
return hex_centres
########################################################################
def writeXML(ugrid, file_name):
"""
Save unstructured grid vtk object (ugrid) to file.
Args:
ugrid : vtkUnstructuredGrid object
file_name : file name under which to save ugrid
Returns:
None
"""
writer = vtk.vtkXMLUnstructuredGridWriter()
writer.SetFileName(file_name)
writer.SetInputData(ugrid)
writer.Write()
########################################################################
def make_vtu_cartesian_geom(
lx,meshx,ly,meshy,lz,meshz,
data_np,
dataset_name,test_name,
**kwargs
):
"""
Build vtk unstructured grid object for 2D or 3D Cartesian mesh and populate with
given data, material number or flux.
Args:
lx : number of mesh elements along x
meshx : mesh array along x
ly : number of mesh elements along y
meshy : mesh array along y
lz : number of mesh elements along z
meshz : mesh array along z
data_np : given data, material number or flux
dataset_name : data name, usually 'material' or 'flux'
test_name : file name of the computed c2m procedure in case of a prior
dragon run or given name by user or default name
Returns:
None
"""
### ASSIGN OPTIONAL ARGS
nscat = kwargs.get("nscat", 1)
ielem = kwargs.get("ielem", 1)
i_g = kwargs.get("i_g", 0)
if dataset_name=="rate":
nsf_reg=kwargs.get("nusigf", 1)
### 2D CONSIDERATIONS
if lz==1: meshz = [0,0]
ndim = 2 if lz==1 else 3
### INITIALISE VTK OBJECTS
points = vtk.vtkPoints()
element = vtk.vtkVoxel()
cells = vtk.vtkCellArray()
ugrid = vtk.vtkUnstructuredGrid()
### INITIALISE SCALAR CELL DATA
DRAGONdataset = vtk.vtkFloatArray()
DRAGONdataset.SetName(dataset_name)
ugrid.GetCellData().SetScalars(DRAGONdataset)
### COMPUTE CONSTANTS
flux_offset = 1 if dataset_name=="material" else nscat*(ielem)**ndim
i_cell=-1
### LOOP OVER Z-AXIS MESH
for kk in range(lz):
### LOOP OVER Y-AXIS MESH
for jj in range(ly):
ycoord = [meshy[jj], meshy[jj], meshy[jj+1], meshy[jj+1]]
### LOOP OVER X-AXIS MESH
for ii in range(lx):
xcoord = [meshx[ii], meshx[ii+1], meshx[ii+1], meshx[ii]]
### INSERT NEW POINTS AND CELLS (DO NOT MERGE THE TWO LOOPS BELOW!!!)
for i_crd in range(0,4):
points.InsertNextPoint( xcoord[i_crd],ycoord[i_crd], meshz[kk])
for i_crd in range(0,4):
points.InsertNextPoint( xcoord[i_crd],ycoord[i_crd], meshz[kk+1])
for i in range(0, 8):
i_cell += 1
element.GetPointIds().SetId(i, i_cell)
cells.InsertNextCell(element)
ugrid.SetPoints(points)
ugrid.SetCells(vtk.VTK_HEXAHEDRON, cells)
### SET SCALAR CELL DATA
ind1 = (kk*lx*ly) + (jj*lx) + ii
ind2 = (kk*lx*ly)*flux_offset + (jj*lx)*flux_offset + ii*flux_offset
if dataset_name=="rate":
DRAGONdataset.InsertTuple1(ind1, nsf_reg[ind1]*data_np[ind2])
else:
DRAGONdataset.InsertTuple1(ind1, data_np[ind2])
print(test_name, dataset_name,' PROCESSED. SAVING ...')
filename=test_name+'_'+dataset_name+str(i_g)+".vtu"
### SAVE VTU FILE
writeXML(ugrid, filename)
return ugrid
########################################################################
def make_vtu_hexagonal_geom(
n_hex,side,hex_centres,
data_np,
dataset_name,test_name,
**kwargs
):
"""
Build vtk unstructured grid object for 2D or 3D hexagon mesh and populate with
given data, material number or flux.
Args:
n_hex : number of hexagons in domain
side : length of one side of hexagon
hex_centres : 2D array with x and y coordinates of hexagon centres
data_np : given data, material number or flux
dataset_name : data name, usually 'material' or 'flux'
test_name : file name of the computed c2m procedure in case of a prior
dragon run or given name by user or default name
Returns:
None
"""
### ASSIGN OPTIONAL ARGS
spltl = kwargs.get("spltl", 1)
lz = kwargs.get("lz", 1)
meshz = kwargs.get("meshz", 1)
nscat = kwargs.get("nscat", 1)
ielem = kwargs.get("ielem", 1)
i_g = kwargs.get("i_g", 0)
### 2D CONSIDERATIONS
if lz==1: meshz = [0,0]
ndim = 2 if lz==1 else 3
### INITIALISE VTK OBJECTS
points = vtk.vtkPoints()
lozenge = vtk.vtkVoxel()
cells = vtk.vtkCellArray()
ugrid = vtk.vtkUnstructuredGrid()
### INITIALISE SCALAR CELL DATA
DRAGONdataset = vtk.vtkFloatArray()
DRAGONdataset.SetName(dataset_name)
ugrid.GetCellData().SetScalars(DRAGONdataset)
### COMPUTE CONSTANTS
x_step=(1.0)*(side/spltl)
y_step=(sqrt(3)/2)*(side/spltl)
n_loz = 3*spltl**2
flux_offset = 1 if dataset_name=="material" else nscat*(ielem)**ndim
i_cell = -1
### LOOP OVER Z-AXIS LAYERS
for i_z in range(0,lz):
### LOOP OVER HEXAGONS
for i_hex in range(n_hex):
### FETCH CENTRE OF EACH HEX
cx, cy = hex_centres[0, i_hex], hex_centres[1, i_hex]
### LOOP OVER EACH HEX SECTION (EACH OF THE THREE MAIN LOZENGES)
for hex_section in range(3):
### SET INITIAL VALUES OF XCOORD AND YCOORD
if hex_section == 0:
x_coords = [0.0, x_step*0.5, x_step, x_step*0.5]
y_coords = [0.0, -y_step, 0.0, y_step]
elif hex_section == 1:
x_coords = [-side, -side+x_step, -side+(x_step*1.5), -side+(x_step * 0.5)]
y_coords = [0.0, 0.0, y_step, y_step]
else:
x_coords = [-side/2, (-side/2)+x_step, (-side/2)+(x_step*0.5), (-side/2)-(x_step*0.5)]
y_coords = [-side*sqrt(3)/2, -side*sqrt(3)/2, (-side*sqrt(3)/2)+y_step, (-side*sqrt(3)/2)+y_step]
### LOOP OVER Y-MESH INSIDE LOZENGE, INCREMENT XCOORD, YCOORD
for i_mshy in range(spltl):
if i_mshy != 0:
if hex_section == 0:
x_coords = x_coords - ((x_step/2)*(spltl-2))
y_coords = y_coords + y_step*(spltl)
elif hex_section == 1:
x_coords = x_coords - ((x_step)*(spltl-1.5))
y_coords = y_coords + y_step
else:
x_coords = x_coords - ((x_step)*(spltl-0.5))
y_coords = y_coords + y_step
### LOOP OVER X-MESH INSIDE LOZENGE, INCREMENT XCOORD, YCOORD
for i_mshx in range(spltl):
if i_mshx != 0:
x_coords = x_coords + (x_step/2) if hex_section == 0 else x_coords + (x_step)
y_coords = y_coords - (y_step) if hex_section == 0 else y_coords
### INSERT NEW POINTS AND CELLS (DO NOT MERGE THE TWO LOOPS BELOW!!!)
for i_crd in range(0,4):
points.InsertNextPoint(x_coords[i_crd]+cx, y_coords[i_crd]+cy, meshz[i_z])
for i_crd in range(0,4):
points.InsertNextPoint(x_coords[i_crd]+cx, y_coords[i_crd]+cy, meshz[i_z+1])
for i in range(0, 8):
i_cell += 1
lozenge.GetPointIds().SetId(i, i_cell)
cells.InsertNextCell(lozenge)
ugrid.SetPoints(points)
ugrid.SetCells(vtk.VTK_HEXAHEDRON, cells)
### SET SCALAR CELL DATA
for i_loz in range(0,n_loz):
ind1 = (i_z*n_hex*n_loz) + (i_hex*n_loz) + i_loz
ind2 = (i_z*n_hex*n_loz*flux_offset) + (i_hex*n_loz*flux_offset) + (i_loz*flux_offset)
DRAGONdataset.InsertTuple1(ind1, data_np[ind2])
print(test_name, dataset_name,' PROCESSED. SAVING ...')
filename=test_name+'_'+dataset_name+str(i_g)+".vtu"
### SAVE VTU FILE
writeXML(ugrid, filename)
return ugrid
|