eX-Mech / pymech

A Python software suite for Nek5000 and SIMSON
https://pymech.readthedocs.io/en/stable
GNU General Public License v3.0
24 stars 24 forks source link

Support unstructured grid (Issue #125) #126

Open airwarriorg91 opened 1 month ago

airwarriorg91 commented 1 month ago

Hey ! I am looked into uxarray and attempted two methods to include support for unstructured grids. The fundamental working of both the methods is same.

Proposed Methodology

Method 1 Extend the current xarray method to uxarray as uxarray also supports the creation of dataset through load_store(). But unfortunately, it doesn't work. I get the following error.

conflicting sizes for dimension 'y': length 6 on 'xmesh' and length 36 on {'x': 'x', 'y': 'y', 'z': 'z'}.

Method 2 It is manual method to extract data from elements and store them as numpy arrays, use them to form a xarray dataset. I am able to make an xarray dataset which can be used for most post-processing stuff like plotting, POD calculations, etc. It lacks the element connectivity data, so is it required ? If not, this method can be used to handle structured and unstructured arrays both.

extract_elem_data is the implementation of Method 2. We may not require the use of uxarray.

A uxarray Grid can be constructed from the coordinates and connectivity data through uxarray.Grid.from_dataset and used with the available values to create a uxarray dataset using uxarray.UxDataset. Can someone help me in extracting the connectivity, so I can attempt creation of an uxarray dataset.

Thanks ! Gaurav

ashwinvis commented 1 month ago

You got an error with the existing _NekDataStore because of this method:

    def meshgrid_to_dim(self, mesh):
        """Reverse of np.meshgrid. This method extracts one-dimensional
        coordinates from a cubical array format for every direction
        """

I don't see a proper way of building 1D coordinate arrays for an unstructured arrays. We can add the DataStore later, but a working implementation is more important. Good that you chose to go for simpler functions.

ashwinvis commented 1 month ago

You can get the connectivity data at least for the EXODUS mesh like this:

import xarray as xr
mesh = xr.open_dataset("naca(10x4).e")
print(mesh.data_vars)
mesh.connect1

This integer data array has the dimensions connect1 (num_el_in_blk1, num_nod_per_el1) int32 766kB ... and looks like this:

array([[    3,     2,     1, ...,     5,     8,     7],
       [   10,     9,     2, ...,    11,     6,    13],
       [   15,    14,     9, ...,    16,    12,    18],
       ...,
       [72683, 72686, 32640, ..., 72732, 32642, 72731],
       [72686, 72689, 32645, ..., 72733, 32647, 72732],
       [72689, 72692, 32650, ..., 72734, 32652, 72733]], dtype=int32)

I am guessing it denotes one cell per row, and along the column denotes the 8 cells which surround it: 4 on each corner and 4 on each face (you need to double check it). Somebody wrote a short blog post looking at this using netCDF4, but above I used xarray (which also uses netCDF4 / h5netcdf behind the scenes).

https://johnfoster.pge.utexas.edu/blog/posts/extracting-exodus-information-with-netcdf-python/

ashwinvis commented 1 month ago

One thing I was not able to get it working was opening the EXODUS mesh file directly from Uxarray, although they claim it to be supported:

https://uxarray.readthedocs.io/en/latest/user-guide/grid-formats.html#exodus

airwarriorg91 commented 1 month ago

Yeah ! But I think extracting mesh or any mesh data from the exodus file will not provide us with the correct mesh data. The NEK5000 visualization file has the polynomial nodes data along with the nodes of the original mesh. Correct me, if I am wrong but I think this is the case only. So, in that case how to extract the connectivity data from the visualization file.

Thanks, I will try to implement a working function first then we can establish a class like for xarray.

airwarriorg91 commented 1 month ago

What was your error ?

One thing I was not able to get it working was opening the EXODUS mesh file directly from Uxarray, although they claim it to be supported:

https://uxarray.readthedocs.io/en/latest/user-guide/grid-formats.html#exodus

ashwinvis commented 1 month ago
import uxarray as ux
ux.open_grid("naca(10x4).e")

It assumes a 3D grid

---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
~/Sources/foss/pymech/.venv/lib/python3.12/site-packages/xarray/core/dataset.py in ?(self, name)
   1482             variable = self._variables[name]
   1483         except KeyError:
-> 1484             _, name, variable = _get_virtual_variable(self._variables, name, self.sizes)
   1485

KeyError: 'node_z'

During handling of the above exception, another exception occurred:

KeyError                                  Traceback (most recent call last)
~/Sources/foss/pymech/.venv/lib/python3.12/site-packages/xarray/core/dataset.py in ?(self, key)
   1585                 if isinstance(key, tuple):
   1586                     message += f"\nHint: use a list to select multiple variables, for example `ds[{[d for d in key]}]`"
-> 1587                 raise KeyError(message) from e
   1588

~/Sources/foss/pymech/.venv/lib/python3.12/site-packages/xarray/core/dataset.py in ?(self, name)
   1482             variable = self._variables[name]
   1483         except KeyError:
-> 1484             _, name, variable = _get_virtual_variable(self._variables, name, self.sizes)
   1485

~/Sources/foss/pymech/.venv/lib/python3.12/site-packages/xarray/core/dataset.py in ?(variables, key, dim_sizes)
    216     split_key = key.split(".", 1)
    217     if len(split_key) != 2:
--> 218         raise KeyError(key)
    219

KeyError: 'node_z'

The above exception was the direct cause of the following exception:

KeyError                                  Traceback (most recent call last)
<ipython-input-7-18c901787b96> in ?()
----> 1 ux.open_grid("naca(10x4).e")

~/Sources/foss/pymech/.venv/lib/python3.12/site-packages/uxarray/core/api.py in ?(grid_filename_or_obj, latlon, use_dual, **kwargs)
     84         try:
     85             grid_ds = xr.open_dataset(grid_filename_or_obj, **kwargs)
     86
     87             uxgrid = Grid.from_dataset(grid_ds, use_dual=use_dual)
---> 88         except ValueError:
     89             raise ValueError("Inputted grid_filename_or_obj not supported.")
     90
     91     return uxgrid

~/Sources/foss/pymech/.venv/lib/python3.12/site-packages/uxarray/grid/grid.py in ?(cls, dataset, use_dual, **kwargs)
    250             # parse to detect source grid spec
    251
    252             source_grid_spec = _parse_grid_type(dataset)
    253             if source_grid_spec == "Exodus":
--> 254                 grid_ds, source_dims_dict = _read_exodus(dataset)
    255             elif source_grid_spec == "Scrip":
    256                 grid_ds, source_dims_dict = _read_scrip(dataset)
    257             elif source_grid_spec == "UGRID":

~/Sources/foss/pymech/.venv/lib/python3.12/site-packages/uxarray/io/_exodus.py in ?(ext_ds)
    101     )
    102
    103     # populate lon/lat coordinates
    104     lon, lat = _xyz_to_lonlat_deg(
--> 105         ds["node_x"].values, ds["node_y"].values, ds["node_z"].values
    106     )
    107
    108     # populate dataset

~/Sources/foss/pymech/.venv/lib/python3.12/site-packages/xarray/core/dataset.py in ?(self, key)
   1583                 message = f"No variable named {key!r}. Variables on the dataset include {shorten_list_repr(list(self.variables.keys()), max_items=10)}"
   1584                 # If someone attempts `ds['foo' , 'bar']` instead of `ds[['foo', 'bar']]`
   1585                 if isinstance(key, tuple):
   1586                     message += f"\nHint: use a list to select multiple variables, for example `ds[{[d for d in key]}]`"
-> 1587                 raise KeyError(message) from e
   1588
   1589         if utils.iterable_of_hashable(key):
   1590             return self._copy_listed(key)

KeyError: "No variable named 'node_z'. Variables on the dataset include ['node_x', 'node_y', 'face_node_connectivity']"
ashwinvis commented 1 month ago

Yeah ! But I think extracting mesh or any mesh data from the exodus file will not provide us with the correct mesh data. The NEK5000 visualization file has the polynomial nodes data along with the nodes of the original mesh. Correct me, if I am wrong but I think this is the case only. So, in that case how to extract the connectivity data from the visualization file.

That is true. Nek5000's element numbering may be different. In that case we need its connectivity. Do you use gencon command or the genmap? If it is the latter, #40 implemented a reader for the .ma2 files. I didn't get a chance to explore it, but now is the time!

ashwinvis commented 1 month ago

It could also be that .ma2 file does not contain this information. If getting the connectivity out of Nek5000 is too hard, we could tap into uxarray's methods to do that. The test suite TestConnectivity suggest that it should be possible:

https://github.com/UXARRAY/uxarray/blob/bd8901f665d455cf324f9773790af118f2f48ef3/test/test_grid.py#L885-L900

airwarriorg91 commented 1 month ago

Yeah ! But I think extracting mesh or any mesh data from the exodus file will not provide us with the correct mesh data. The NEK5000 visualization file has the polynomial nodes data along with the nodes of the original mesh. Correct me, if I am wrong but I think this is the case only. So, in that case how to extract the connectivity data from the visualization file.

That is true. Nek5000's element numbering may be different. In that case we need its connectivity. Do you use gencon command or the genmap? If it is the latter, #40 implemented a reader for the .ma2 files. I didn't get a chance to explore it, but now is the time!

Yeah, it is essential for simulations, so we build the .ma2. Although, I am also not sure whether it will contain the exact nodes numbering as that of the visualization file. As far as I know, it only depends on the input mesh and doesn't account for the polynomial nodes since it is generated prior to simulations.

We should work on extracting connectivity data from the .f00001 files itself. The uxarray method _build_face_face_connectivity can be helpful but no mention about it in the documentation. I will look into this. Maybe ask in the discussion section of uxarray.

airwarriorg91 commented 1 month ago

@ashwinvis You probably missed my question, I am able to make an xarray dataset which can be used for most post-processing stuff like plotting, POD calculations, etc. It lacks the element connectivity data, so is it required ? If not, this method can be used to handle structured and unstructured arrays both. It is a very simple approach.

ashwinvis commented 1 month ago

I don't know. It depends on what you can do now. Without connectivity you may be able to plot, select a slice.

Sometimes you need to integrate or differentiate (by finite difference to roughly calculate a gradient) or interpolate along a direction. Without connectivity information that will be incredibly inconvenient.

airwarriorg91 commented 1 month ago

You can get the connectivity data at least for the EXODUS mesh like this:

import xarray as xr
mesh = xr.open_dataset("naca(10x4).e")
print(mesh.data_vars)
mesh.connect1

This integer data array has the dimensions connect1 (num_el_in_blk1, num_nod_per_el1) int32 766kB ... and looks like this:

array([[    3,     2,     1, ...,     5,     8,     7],
       [   10,     9,     2, ...,    11,     6,    13],
       [   15,    14,     9, ...,    16,    12,    18],
       ...,
       [72683, 72686, 32640, ..., 72732, 32642, 72731],
       [72686, 72689, 32645, ..., 72733, 32647, 72732],
       [72689, 72692, 32650, ..., 72734, 32652, 72733]], dtype=int32)

I am guessing it denotes one cell per row, and along the column denotes the 8 cells which surround it: 4 on each corner and 4 on each face (you need to double check it). Somebody wrote a short blog post looking at this using netCDF4, but above I used xarray (which also uses netCDF4 / h5netcdf behind the scenes).

https://johnfoster.pge.utexas.edu/blog/posts/extracting-exodus-information-with-netcdf-python/

I have an idea on how to build the connection data. The each face in the exodus file is further divided by polynomial nodes. So, each edge is a line with the polynomial as well as geometric nodes. We can get the slope of the edge and find which polynomial nodes lie on these edge and sort them based on the distance. This way we can find out how the nodes in each element object is form the face. This will work provided we can get connectivity of geometric nodes from exodus file.

Rough diagram of a 2D element (can be extended to 3D) image

Red nodes are the polynomial nodes that we get from the .f00001 files and blue nodes are the geometric nodes.

ashwinvis commented 2 days ago

@airwarriorg91 it has been a while. Did you work more on this?

I know it is a hard problem. It will be nice if we can try to complete this or at least document what the next steps are. Just so that we don't forget.

airwarriorg91 commented 2 days ago

@airwarriorg91 it has been a while. Did you work more on this?

I know it is a hard problem. It will be nice if we can try to complete this or at least document what the next steps are. Just so that we don't forget.

Hey ! I was a little busy with the semester end. I need to try the above idea and check its feasibility. I will probably start working on it again after Dec 15 and let you know.

Thanks, Gaurav

ashwinvis commented 2 days ago

Ok sounds good! It would make for a nice xmas project :christmas_tree: