66"""Small utilities for mesh adaption (e.g. converting linear elements to
77quadratic elements)"""
88
9+ from typing import Callable , Optional
10+
911import meshio
1012import numpy as np
1113
5456 [1 , 3 ],
5557 [2 , 3 ],
5658 ],
59+ "wedge" : [
60+ [0 ],
61+ [1 ],
62+ [2 ],
63+ [3 ],
64+ [4 ],
65+ [5 ],
66+ [0 , 1 ],
67+ [1 , 2 ],
68+ [2 , 0 ],
69+ [3 , 4 ],
70+ [4 , 5 ],
71+ [5 , 3 ],
72+ [0 , 3 ],
73+ [1 , 4 ],
74+ [2 , 5 ],
75+ ],
5776 "triangle" : [
5877 [0 ],
5978 [1 ],
6887_lin_to_quad_cell_type = {
6988 "hexahedron" : "hexahedron27" ,
7089 "tetra" : "tetra10" ,
90+ "wedge" : "wedge15" ,
7191 "triangle" : "triangle6" ,
7292 "quad" : "quad9" ,
7393 "line" : "line3" ,
7494}
7595
96+ _hex8_to_hex20 = [
97+ [0 ],
98+ [1 ],
99+ [2 ],
100+ [3 ],
101+ [4 ],
102+ [5 ],
103+ [6 ],
104+ [7 ],
105+ [0 , 1 ],
106+ [1 , 2 ],
107+ [2 , 3 ],
108+ [3 , 0 ],
109+ [4 , 5 ],
110+ [5 , 6 ],
111+ [6 , 7 ],
112+ [7 , 4 ],
113+ [0 , 4 ],
114+ [1 , 5 ],
115+ [2 , 6 ],
116+ [3 , 7 ],
117+ ]
76118
77- def lin_to_quad (mesh : meshio .Mesh ) -> meshio .Mesh :
78- """Convert linear elements to quadratic elements in a mesh.
79119
80- This function returns a new mesh in which all linear elements (triangles, quadrilaterals, tetrahedra, and hexahedra)
81- are converted into their corresponding quadratic elements.
120+ def adapt_mesh (
121+ mesh : meshio .Mesh , adapting_scheme : Callable [[str ], Optional [tuple [str , list ]]]
122+ ) -> meshio .Mesh :
123+ """Adapt a mesh to a higher-order (quadratic) representation using a
124+ provided adapting scheme.
82125
83- Args:
84- mesh:
85- The input mesh containing linear elements.
126+ This function takes a mesh and, for each cell block, determines whether it should be adapted
127+ to a new cell type. If so, it generates new mid-edge nodes and updates
128+ the mesh accordingly. The adapting scheme determines the mapping from the original cell type
129+ to the new cell type and specifies how to construct new nodes.
86130
87- Returns:
88- The modified mesh with quadratic elements.
89- """
131+ Parameters
132+ ----------
133+ mesh : meshio.Mesh
134+ The input mesh to be adapted.
135+ adapting_scheme : Callable[[str], Optional[tuple[str, list]]]
136+ A function that takes a cell type string and returns either:
137+ - None, if the cell type should not be adapted,
138+ - or a tuple of (new_cell_type, node_mapping), where:
139+ new_cell_type : str
140+ The name of the quadratic cell type (e.g., "triangle6").
141+ node_mapping : list
142+ A list specifying how to map old nodes and create new mid-edge nodes.
143+ Each entry is either a tuple with one index (existing node) or multiple
144+ indices (to create a new node as the mean of those nodes).
90145
146+ Returns
147+ -------
148+ meshio.Mesh
149+ The adapted mesh with quadratic cells and updated points, point data, and cell data.
150+
151+ Notes
152+ -----
153+ - Cell blocks that are already quadratic or are not to be adapted are left unchanged.
154+ - New mid-edge nodes are created only once per unique edge and reused across cells.
155+ - Point data is interpolated for new nodes using the mean of the corresponding data.
156+ """
91157 new_points = [coord for coord in mesh .points ]
92158 new_point_data = {key : [d for d in data ] for key , data in mesh .point_data .items ()}
93159 new_cell_blocks = []
@@ -110,8 +176,17 @@ def lin_to_quad(mesh: meshio.Mesh) -> meshio.Mesh:
110176 continue
111177
112178 # determine the mapping of the nodes to the new quadratic celltype
113- node_mapping = _lin_to_quad_nodes [cell_type ]
114- new_cell_type = _lin_to_quad_cell_type [cell_type ]
179+ scheme = adapting_scheme (cell_type )
180+
181+ if not scheme :
182+ # don't adapt this block
183+ # this cell block is already quadratic, no need to convert
184+ new_cell_blocks .append (cellblock )
185+ for key , data in mesh .cell_data .items ():
186+ new_cell_data [key ].append (data [i ])
187+ continue
188+
189+ new_cell_type , node_mapping = scheme
115190
116191 for cell in cellblock .data :
117192 cell_nodes = []
@@ -135,6 +210,7 @@ def lin_to_quad(mesh: meshio.Mesh) -> meshio.Mesh:
135210 np .mean (
136211 [mesh .point_data [name ][cell [n ]] for n in node ],
137212 axis = 0 ,
213+ dtype = mesh .point_data [name ].dtype ,
138214 )
139215 )
140216
@@ -159,3 +235,53 @@ def lin_to_quad(mesh: meshio.Mesh) -> meshio.Mesh:
159235 key : [np .array (d ) for d in data ] for key , data in new_cell_data .items ()
160236 },
161237 )
238+
239+
240+ def lin_to_quad (mesh : meshio .Mesh ) -> meshio .Mesh :
241+ """Convert linear elements to quadratic elements in a mesh.
242+
243+ This function returns a new mesh in which all linear elements (triangles, quadrilaterals, tetrahedra, and hexahedra)
244+ are converted into their corresponding quadratic elements.
245+
246+ Args:
247+ mesh:
248+ The input mesh containing linear elements.
249+
250+ Returns:
251+ The modified mesh with quadratic elements.
252+ """
253+
254+ def lin_to_quad (cell_type : str ) -> Optional [tuple [str , list [list [int ]]]]:
255+ """Returns the node mapping for quadratic elements."""
256+ if cell_type in ["vertex" , "hexahedron27" , "tetra10" , "triangle6" , "quad9" ]:
257+ return None
258+
259+ return _lin_to_quad_cell_type [cell_type ], _lin_to_quad_nodes [cell_type ]
260+
261+ return adapt_mesh (mesh , lin_to_quad )
262+
263+
264+ def to_serendipity (mesh : meshio .Mesh ) -> meshio .Mesh :
265+ """Converts elements to serendipity elements in a mesh.
266+
267+ Args:
268+ mesh:
269+ The input mesh containing linear elements.
270+
271+ Returns:
272+ The modified mesh with serendipity elements.
273+ """
274+
275+ def _to_serendipity (cell_type : str ) -> Optional [tuple [str , list [list [int ]]]]:
276+ """Returns the node mapping for serendipity elements."""
277+ if cell_type in ["hexahedron20" ]:
278+ return None
279+
280+ if cell_type not in ["hexahedron" ]:
281+ raise ValueError (
282+ f"Cell type { cell_type } not supported for serendipity conversion."
283+ )
284+
285+ return "hexahedron20" , _hex8_to_hex20
286+
287+ return adapt_mesh (mesh , _to_serendipity )
0 commit comments