1 /*******************************************************************/
2 /*                               XDMF                              */
3 /*                   eXtensible Data Model and Format              */
4 /*                                                                 */
5 /*  Id : Id  */
6 /*  Date : $Date$ */
7 /*  Version : $Revision$ */
8 /*                                                                 */
9 /*  Author:                                                        */
10 /*     Kenneth Leiter                                              */
11 /*     kenneth.leiter@arl.army.mil                                 */
12 /*     US Army Research Laboratory                                 */
13 /*     Aberdeen Proving Ground, MD                                 */
14 /*                                                                 */
15 /*     Copyright @ 2009 US Army Research Laboratory                */
16 /*     All Rights Reserved                                         */
17 /*     See Copyright.txt or http://www.arl.hpc.mil/ice for details */
18 /*                                                                 */
19 /*     This software is distributed WITHOUT ANY WARRANTY; without  */
20 /*     even the implied warranty of MERCHANTABILITY or FITNESS     */
21 /*     FOR A PARTICULAR PURPOSE.  See the above copyright notice   */
22 /*     for more information.                                       */
23 /*                                                                 */
24 /*******************************************************************/
25 
26 #include "XdmfExodusReader.h"
27 #include <exodusII.h>
28 
29 //
30 // Construct XdmfExodusReader.
31 //
XdmfExodusReader()32 XdmfExodusReader::XdmfExodusReader()
33 {
34   return;
35 }
36 
37 //
38 // Destroy XdmfExodusReader
39 //
~XdmfExodusReader()40 XdmfExodusReader::~XdmfExodusReader()
41 {
42   return;
43 }
44 
45 //
46 // Translate from exodus ii to xdmf topologies
47 // this was taken directly from vtkExodusIIReader and modified to fit xdmf elements
48 //
DetermineXdmfCellType(char * exoElemType,int numPointsPerCell)49 XdmfInt32 XdmfExodusReader::DetermineXdmfCellType(char * exoElemType, int numPointsPerCell)
50 {
51   // Make exoType uppercase
52   std::string elemType = exoElemType;
53   std::transform(elemType.begin(), elemType.end(), elemType.begin(), toupper);
54 
55   // Check for quadratic elements
56   if (elemType.substr(0,3) == "TRI" && numPointsPerCell == 6)
57   {
58     return XDMF_TRI_6;
59   }
60   else if (elemType.substr(0,3) == "SHE" && numPointsPerCell == 8)
61   {
62     return XDMF_QUAD_8;
63   }
64   else if (elemType.substr(0,3) == "SHE" && numPointsPerCell == 9)
65   {
66     // VTK_QUADRATIC_QUAD with 9 points
67     // Currently unsupported in Xdmf
68     return XDMF_QUAD_9;
69   }
70   else if (elemType.substr(0,3) == "TET" && numPointsPerCell == 10)
71   {
72     return XDMF_TET_10;
73   }
74   else if (elemType.substr(0,3) == "TET" && numPointsPerCell == 11)
75   {
76     // VTK_QUADRATIC_TETRA with 11 points
77     // Currently unsupported in Xdmf
78     return XDMF_NOTOPOLOGY;
79   }
80   else if (elemType.substr(0,3) == "WED" && numPointsPerCell == 15)
81   {
82     return XDMF_WEDGE_15;
83   }
84   else if (elemType.substr(0,3) == "HEX" && numPointsPerCell == 20)
85   {
86     return XDMF_HEX_20;
87   }
88   else if (elemType.substr(0,3) == "HEX" && numPointsPerCell == 21)
89   {
90     // VTK_QUADRATIC_HEXAHEDRON with 21 points
91     // Currently unsupported in Xdmf
92     return XDMF_NOTOPOLOGY;
93   }
94   else if (elemType.substr(0,3) == "HEX" && numPointsPerCell == 27)
95   {
96     return XDMF_HEX_27;
97   }
98   else if (elemType.substr(0,3) == "QUA" && numPointsPerCell == 8)
99   {
100     return XDMF_QUAD_8;
101   }
102   else if (elemType.substr(0,3) == "QUA" && numPointsPerCell == 9)
103   {
104     // VTK_BIQUADRATIC_QUAD;
105     // Currently unsupported in Xdmf
106     return XDMF_QUAD_9;
107   }
108   else if (elemType.substr(0,3) == "TRU" && numPointsPerCell == 3)
109   {
110     return XDMF_EDGE_3;
111   }
112   else if (elemType.substr(0,3) == "BEA" && numPointsPerCell == 3)
113   {
114     return XDMF_EDGE_3;
115   }
116   else if (elemType.substr(0,3) == "BAR" && numPointsPerCell == 3)
117   {
118     return XDMF_EDGE_3;
119   }
120   else if (elemType.substr(0,3) == "EDG" && numPointsPerCell == 3)
121   {
122     return XDMF_EDGE_3;
123   }
124   // Check for regular elements
125   else if (elemType.substr(0,3) == "CIR")
126   {
127     // VTK_VERTEX;
128     // Currently unsupported in Xdmf
129     return XDMF_NOTOPOLOGY;
130   }
131   else if (elemType.substr(0,3) == "SPH")
132   {
133     // VTK_VERTEX;
134     // Currently unsupported in Xdmf
135     return XDMF_NOTOPOLOGY;
136   }
137   else if (elemType.substr(0,3) == "BAR")
138   {
139     // VTK_LINE;
140     // Currently unsupported in Xdmf
141     return XDMF_NOTOPOLOGY;
142   }
143   else if (elemType.substr(0,3) == "TRU")
144   {
145     // VTK_LINE;
146     // Currently unsupported in Xdmf
147     return XDMF_NOTOPOLOGY;
148   }
149   else if (elemType.substr(0,3) == "BEA")
150   {
151     // VTK_LINE;
152     // Currently unsupported in Xdmf
153     return XDMF_NOTOPOLOGY;
154   }
155   else if (elemType.substr(0,3) == "EDG")
156   {
157     // VTK_LINE;
158     // Currently unsupported in Xdmf
159     return XDMF_NOTOPOLOGY;
160   }
161   else if (elemType.substr(0,3) == "TRI")
162   {
163     return XDMF_TRI;
164   }
165   else if (elemType.substr(0,3) == "QUA")
166   {
167     return XDMF_QUAD;
168   }
169   else if (elemType.substr(0,3) == "TET")
170   {
171     return XDMF_TET;
172   }
173   else if (elemType.substr(0,3) == "PYR")
174   {
175     return XDMF_PYRAMID;
176   }
177   else if (elemType.substr(0,3) == "WED")
178   {
179     return XDMF_WEDGE;
180   }
181   else if (elemType.substr(0,3) == "HEX")
182   {
183     return XDMF_HEX;
184   }
185   else if (elemType.substr(0,3) == "SHE" && numPointsPerCell == 3)
186   {
187     return XDMF_TRI;
188   }
189   else if (elemType.substr(0,3) == "SHE" && numPointsPerCell == 4)
190   {
191     return XDMF_QUAD;
192   }
193   else if (elemType.substr(0,8) == "STRAIGHT" && numPointsPerCell == 2)
194   {
195     // VTK_LINE;
196     // Currently unsupported in Xdmf
197     return XDMF_NOTOPOLOGY;
198   }
199   else if (elemType.substr(0,3) == "SUP")
200   {
201     return XDMF_POLYVERTEX;
202   }
203   //vtkErrorMacro("Unsupported element type: " << elemType.c_str());
204   return XDMF_NOTOPOLOGY;
205   }
206 
207 //
208 // Read contents of the exodus file and fill in xdmf grids.
209 //
read(const char * fileName,XdmfElement * parentElement)210 XdmfGrid * XdmfExodusReader::read(const char * fileName, XdmfElement * parentElement)
211 {
212   XdmfGrid * grid = new XdmfGrid();
213   parentElement->Insert(grid);
214 
215   // Read Exodus II file to XdmfGrid via Exodus II API
216 
217   float version;
218   int CPU_word_size = sizeof(double);
219   int IO_word_size = 0; // Get from file
220   int exodusHandle = ex_open(fileName, EX_READ, &CPU_word_size, &IO_word_size, &version);
221 
222   char * title = new char[MAX_LINE_LENGTH+1];
223   int num_dim, num_nodes, num_elem, num_elem_blk, num_node_sets, num_side_sets;
224   ex_get_init (exodusHandle, title, &num_dim, &num_nodes, &num_elem, &num_elem_blk, &num_node_sets, &num_side_sets);
225 
226   /*
227   cout << "Title: " << title <<
228     "\nNum Dim: " << num_dim <<
229     "\nNum Nodes: " << num_nodes <<
230     "\nNum Elem: " << num_elem <<
231     "\nNum Elem Blk: " << num_elem_blk <<
232     "\nNum Node Sets: " << num_node_sets <<
233     "\nNum Side Sets: " << num_side_sets << endl;
234   */
235 
236   // Read geometry values
237   double * x = new double[num_nodes];
238   double * y = new double[num_nodes];
239   double * z = new double[num_nodes];
240 
241   ex_get_coord(exodusHandle, x, y, z);
242 
243   XdmfGeometry * geom = grid->GetGeometry();
244   if(num_dim < 2 || num_dim > 3)
245   {
246     // Xdmf does not support geometries with less than 2 dimensions
247 		std::cout << "Exodus File contains geometry of dimension " << num_dim << "which is unsupported by Xdmf" << std::endl;
248   	return NULL;
249 	}
250 
251   // In the future we may want to do XDMF_GEOMETRY_X_Y_Z?
252   if(num_dim == 2)
253   {
254     geom->SetGeometryType(XDMF_GEOMETRY_XY);
255   }
256   else
257   {
258     geom->SetGeometryType(XDMF_GEOMETRY_XYZ);
259   }
260   geom->SetNumberOfPoints(num_nodes);
261   geom->SetDeleteOnGridDelete(true);
262 
263   XdmfArray * points = geom->GetPoints();
264   points->SetNumberType(XDMF_FLOAT64_TYPE);
265   points->SetNumberOfElements(num_nodes * num_dim);
266   for (int j=0; j<num_nodes; j++)
267   {
268     points->SetValue((j * num_dim), x[j]);
269     points->SetValue((j * num_dim) + 1, y[j]);
270     if(num_dim == 3)
271     {
272    	  points->SetValue((j * num_dim) + 2, z[j]);
273     }
274   }
275   delete [] x;
276   delete [] y;
277   delete [] z;
278 
279   int * blockIds = new int[num_elem_blk];
280   ex_get_elem_blk_ids(exodusHandle, blockIds);
281 
282   int * numElemsInBlock = new int[num_elem_blk];
283   int * numNodesPerElemInBlock = new int[num_elem_blk];
284   int * numElemAttrInBlock = new int[num_elem_blk];
285   XdmfInt32 * topTypeInBlock = new XdmfInt32[num_elem_blk];
286   int totalNumElem = 0;
287   int totalConns = 0;
288   for (int j=0; j<num_elem_blk; j++)
289   {
290     char * elem_type = new char[MAX_STR_LENGTH+1];
291     int num_nodes_per_elem, num_elem_this_blk, num_attr;
292     ex_get_elem_block(exodusHandle, blockIds[j], elem_type, &num_elem_this_blk, &num_nodes_per_elem, &num_attr);
293 
294     /*
295     cout << "Block Id: " << blockIds[j] <<
296       "\nElem Type: " << elem_type <<
297       "\nNum Elem in Blk: " << num_elem_this_blk <<
298       "\nNum Nodes per Elem: " << num_nodes_per_elem <<
299       "\nNum Attr: " << num_attr << endl;
300     */
301 
302     numElemsInBlock[j] = num_elem_this_blk;
303     numNodesPerElemInBlock[j] = num_nodes_per_elem;
304     numElemAttrInBlock[j] = num_attr;
305     topTypeInBlock[j] = this->DetermineXdmfCellType(elem_type, num_nodes_per_elem);
306     totalNumElem = totalNumElem + num_elem_this_blk;
307     totalConns = totalConns + num_elem_this_blk * num_nodes_per_elem;
308     delete [] elem_type;
309   }
310 
311   // Read connectivity from element blocks
312   // TODO: Make this work for mixed topologies?
313   XdmfInt32 topType;
314   int * conn = new int[totalConns];
315   int elemIndex = 0;
316   for (int j=0; j<num_elem_blk; ++j)
317   {
318     if (topTypeInBlock[j] != XDMF_NOTOPOLOGY)
319     {
320       topType = topTypeInBlock[j];
321       ex_get_elem_conn(exodusHandle, blockIds[j], &conn[elemIndex]);
322       elemIndex = elemIndex + numElemsInBlock[j] * numNodesPerElemInBlock[j];
323     }
324   }
325 
326   // This is taken from VTK's vtkExodusIIReader and adapted to fit Xdmf element types, which have the same ordering as VTK.
327   if(topType == XDMF_HEX_20 || topType == XDMF_HEX_27)
328   {
329     int * ptr = conn;
330     int k;
331     int itmp[4];
332 
333     // Exodus Node ordering does not match Xdmf, we must convert.
334     for (int i=0; i<totalNumElem; i++)
335     {
336       ptr += 12;
337 
338       for ( k = 0; k < 4; ++k, ++ptr)
339       {
340         itmp[k] = *ptr;
341         *ptr = ptr[4];
342       }
343 
344       for ( k = 0; k < 4; ++k, ++ptr )
345       {
346         *ptr = itmp[k];
347       }
348 
349       if(topType == XDMF_HEX_27)
350       {
351         for ( k = 0; k < 4; ++k, ++ptr )
352         {
353           itmp[k] = *ptr;
354           *ptr = ptr[3];
355         }
356         *(ptr++) = itmp[1];
357         *(ptr++) = itmp[2];
358         *(ptr++) = itmp[0];
359       }
360     }
361   }
362   else if (topType == XDMF_WEDGE_15 || topType == XDMF_WEDGE_18)
363   {
364     int * ptr = conn;
365     int k;
366     int itmp[3];
367 
368     // Exodus Node ordering does not match Xdmf, we must convert.
369     for (int i=0; i<totalNumElem; i++)
370     {
371       ptr += 9;
372 
373       for (k = 0; k < 3; ++k, ++ptr)
374       {
375         itmp[k] = *ptr;
376         *ptr = ptr[3];
377       }
378 
379       for (k = 0; k < 3; ++k, ++ptr)
380       {
381         *ptr = itmp[k];
382       }
383 
384       if(topType == XDMF_WEDGE_18)
385       {
386         itmp[0] = *(ptr);
387         itmp[1] = *(ptr+1);
388         itmp[2] = *(ptr+2);
389         *(ptr++) = itmp[1];
390         *(ptr++) = itmp[2];
391         *(ptr++) = itmp[0];
392       }
393     }
394   }
395 
396   XdmfTopology * topology = grid->GetTopology();
397   topology->SetTopologyType(topType);
398   topology->SetNumberOfElements(totalNumElem);
399   topology->SetDeleteOnGridDelete(true);
400 
401   XdmfArray * connections = topology->GetConnectivity();
402   connections->SetNumberType(XDMF_INT32_TYPE);
403   connections->SetNumberOfElements(totalConns);
404   connections->SetValues(0, conn, totalConns, 1, 1);
405   // Subtract all node ids by 1 since exodus indices start at 1
406   *connections - 1;
407   delete [] conn;
408 
409   // Get nodal map to global ids and write global ids to xdmf
410   int * node_map = new int[num_nodes];
411   ex_get_node_num_map(exodusHandle, node_map);
412 
413   XdmfAttribute * globalIds = new XdmfAttribute();
414   globalIds->SetName("GlobalNodeId");
415   globalIds->SetAttributeType(XDMF_ATTRIBUTE_TYPE_SCALAR);
416   globalIds->SetAttributeCenter(XDMF_ATTRIBUTE_CENTER_NODE);
417   globalIds->SetDeleteOnGridDelete(true);
418 
419   XdmfArray * globalNodeIdVals = globalIds->GetValues();
420   globalNodeIdVals->SetNumberType(XDMF_INT32_TYPE);
421   globalNodeIdVals->SetNumberOfElements(num_nodes);
422   globalNodeIdVals->SetValues(0, node_map, num_nodes, 1, 1);
423   // Subtract all node ids by 1 since exodus indices start at 1
424   *globalNodeIdVals - 1;
425   grid->Insert(globalIds);
426   delete [] node_map;
427 
428   // Read node sets
429   int * nodeSetIds = new int[num_node_sets];
430   ex_get_node_set_ids(exodusHandle, nodeSetIds);
431 
432   char * node_set_names[num_node_sets];
433   for (int j=0; j<num_node_sets; j++)
434   {
435     node_set_names[j] = new char[MAX_STR_LENGTH+1];
436   }
437   ex_get_names(exodusHandle, EX_NODE_SET, node_set_names);
438 
439   for (int j=0; j<num_node_sets; j++)
440   {
441     int num_nodes_in_set, num_df_in_set;
442     ex_get_node_set_param(exodusHandle, nodeSetIds[j], &num_nodes_in_set, &num_df_in_set);
443 
444     /*
445     cout << "Node Set Id: " << nodeSetIds[j] <<
446       "\nNode Set Name: " << node_set_names[j] <<
447       "\nNum Nodes in Set: "<< num_nodes_in_set <<
448       "\nNum Distrub Factors: " << num_df_in_set << endl;
449     */
450 
451     if (num_nodes_in_set > 0)
452     {
453       int * node_set_node_list = new int[num_nodes_in_set];
454       ex_get_node_set(exodusHandle, nodeSetIds[j], node_set_node_list);
455 
456       XdmfSet * set = new XdmfSet();
457       set->SetName(node_set_names[j]);
458       set->SetSetType(XDMF_SET_TYPE_NODE);
459       set->SetSize(num_nodes_in_set);
460       set->SetDeleteOnGridDelete(true);
461 
462       XdmfArray * ids = set->GetIds();
463       ids->SetNumberType(XDMF_INT32_TYPE);
464       ids->SetNumberOfElements(num_nodes_in_set);
465       ids->SetValues(0, node_set_node_list, num_nodes_in_set, 1, 1);
466       // Subtract all node ids by 1 since exodus indices start at 1
467       *ids - 1;
468       grid->Insert(set);
469 
470       /*
471       if(num_df_in_set > 0)
472       {
473         double * node_set_distribution_factors = new double[num_df_in_set];
474         ex_get_node_set_dist_fact(exodusHandle, nodeSetIds[j], node_set_distribution_factors);
475 
476         XdmfAttribute * attr = new XdmfAttribute();
477         attr->SetName("SetAttribute");
478         attr->SetAttributeType(XDMF_ATTRIBUTE_TYPE_SCALAR);
479         attr->SetAttributeCenter(XDMF_ATTRIBUTE_CENTER_NODE);
480         attr->SetDeleteOnGridDelete(true);
481 
482         XdmfArray * attrVals = attr->GetValues();
483         attrVals->SetNumberType(XDMF_FLOAT32_TYPE);
484         attrVals->SetNumberOfElements(num_df_in_set);
485         attrVals->SetValues(0, node_set_distribution_factors, num_df_in_set, 1, 1);
486         set->Insert(attr);
487         delete [] node_set_distribution_factors;
488       }
489       */
490 
491       delete [] node_set_node_list;
492     }
493     delete [] node_set_names[j];
494   }
495   delete [] nodeSetIds;
496 
497   // Read result variables (attributes)
498   int num_global_vars, num_nodal_vars, num_elem_vars;
499   ex_get_var_param(exodusHandle, "g", &num_global_vars);
500   ex_get_var_param(exodusHandle, "n", &num_nodal_vars);
501   ex_get_var_param(exodusHandle, "e", &num_elem_vars);
502 
503   /*
504   cout << "Num Global Vars: " << num_global_vars <<
505     "\nNum Nodal Vars: " << num_nodal_vars <<
506     "\nNum Elem Vars: " << num_elem_vars << endl;
507   */
508 
509   char * global_var_names[num_global_vars];
510   char * nodal_var_names[num_nodal_vars];
511   char * elem_var_names[num_elem_vars];
512   for (int j=0; j<num_global_vars; j++)
513   {
514     global_var_names[j] = new char[MAX_STR_LENGTH+1];
515   }
516   for (int j=0; j<num_nodal_vars; j++)
517   {
518     nodal_var_names[j] = new char[MAX_STR_LENGTH+1];
519   }
520   for (int j=0; j<num_elem_vars; j++)
521   {
522     elem_var_names[j] = new char[MAX_STR_LENGTH+1];
523   }
524   ex_get_var_names(exodusHandle, "g", num_global_vars, global_var_names);
525   ex_get_var_names(exodusHandle, "n", num_nodal_vars, nodal_var_names);
526   ex_get_var_names(exodusHandle, "e", num_elem_vars, elem_var_names);
527 
528   /*
529   cout << "Global Vars Names: " << endl;
530   for (int j=0; j<num_global_vars; j++)
531   {
532     cout << global_var_names[j] << endl;
533   }
534   cout << "Nodal Vars Names: " << endl;
535   for (int j=0; j<num_nodal_vars; j++)
536   {
537     cout << nodal_var_names[j] << endl;
538   }
539   cout << "Elem Vars Names: " << endl;
540   for (int j=0; j<num_elem_vars; j++)
541   {
542     cout << elem_var_names[j] << endl;
543   }
544   */
545 
546   // Get variable data
547   // TODO: do this for all timesteps?
548 
549   // Global variable data
550   double * global_var_vals = new double[num_global_vars];
551   ex_get_glob_vars(exodusHandle, 1, num_global_vars, &global_var_vals);
552   for (int j=0; j<num_global_vars; j++)
553   {
554     // Write global attribute to xdmf
555     XdmfAttribute * attr = new XdmfAttribute();
556     attr->SetName(global_var_names[j]);
557     attr->SetAttributeType(XDMF_ATTRIBUTE_TYPE_SCALAR);
558     attr->SetAttributeCenter(XDMF_ATTRIBUTE_CENTER_GRID);
559     attr->SetDeleteOnGridDelete(true);
560 
561     XdmfArray * attrVals = attr->GetValues();
562     attrVals->SetNumberType(XDMF_FLOAT64_TYPE);
563     attrVals->SetNumberOfElements(1);
564     attrVals->SetValues(0, &global_var_vals[j], 1, 1, 1);
565     grid->Insert(attr);
566     delete [] global_var_names[j];
567   }
568   delete [] global_var_vals;
569 
570   // Nodal variable data
571   for (int j=0; j<num_nodal_vars; j++)
572   {
573     // The strcmp with "GlobalNodeId" is meant to prevent errors from occuring when a nodal variable is named GlobalNodeId.
574     // A GlobalNodeId attribute was added before when adding the nodal map which means that this attribute should be ignored...
575     // This will probably only occur when doing repeated conversions --- i.e. Xdmf to Exodus to Xdmf to Exodus...
576     if (strcmp(nodal_var_names[j], "GlobalNodeId") != 0)
577     {
578       double * nodal_var_vals = new double[num_nodes];
579       ex_get_nodal_var(exodusHandle, 1, j+1, num_nodes, nodal_var_vals);
580 
581       // Write nodal attribute to xdmf
582       XdmfAttribute * attr = new XdmfAttribute();
583       attr->SetName(nodal_var_names[j]);
584       attr->SetAttributeType(XDMF_ATTRIBUTE_TYPE_SCALAR);
585       attr->SetAttributeCenter(XDMF_ATTRIBUTE_CENTER_NODE);
586       attr->SetDeleteOnGridDelete(true);
587 
588       XdmfArray * attrVals = attr->GetValues();
589       attrVals->SetNumberType(XDMF_FLOAT64_TYPE);
590       attrVals->SetNumberOfElements(num_nodes);
591       attrVals->SetValues(0, nodal_var_vals, num_nodes, 1, 1);
592       grid->Insert(attr);
593       delete [] nodal_var_vals;
594       delete [] nodal_var_names[j];
595     }
596   }
597 
598   // Element variable data
599   for (int j=0; j<num_elem_vars; j++)
600   {
601     double * elem_var_vals = new double[totalNumElem];
602     elemIndex = 0;
603     for (int k=0; k<num_elem_blk; k++)
604     {
605       ex_get_elem_var(exodusHandle, 1, j+1, blockIds[k], numElemsInBlock[j], &elem_var_vals[elemIndex]);
606       elemIndex = elemIndex + numElemsInBlock[j];
607     }
608 
609     // Write element attribute to xdmf
610     XdmfAttribute * attr = new XdmfAttribute();
611     attr->SetName(elem_var_names[j]);
612     attr->SetAttributeType(XDMF_ATTRIBUTE_TYPE_SCALAR);
613     attr->SetAttributeCenter(XDMF_ATTRIBUTE_CENTER_CELL);
614     attr->SetDeleteOnGridDelete(true);
615 
616     XdmfArray * attrVals = attr->GetValues();
617     attrVals->SetNumberType(XDMF_FLOAT64_TYPE);
618     attrVals->SetNumberOfElements(totalNumElem);
619     attrVals->SetValues(0, elem_var_vals, totalNumElem, 1, 1);
620     grid->Insert(attr);
621     delete [] elem_var_vals;
622     delete [] elem_var_names[j];
623   }
624 
625   ex_close(exodusHandle);
626   delete [] title;
627   delete [] blockIds;
628   delete [] numElemsInBlock;
629   delete [] numNodesPerElemInBlock;
630   delete [] numElemAttrInBlock;
631   delete [] topTypeInBlock;
632   return grid;
633 }
634