[yt-svn] commit/yt: MatthewTurk: Merged in atmyers/yt (pull request #1946)

commits-noreply at bitbucket.org commits-noreply at bitbucket.org
Wed Feb 3 09:17:20 PST 2016


1 new commit in yt:

https://bitbucket.org/yt_analysis/yt/commits/5b35020ba8fb/
Changeset:   5b35020ba8fb
Branch:      yt
User:        MatthewTurk
Date:        2016-02-03 17:17:10+00:00
Summary:     Merged in atmyers/yt (pull request #1946)

Displacement Fields for ExodusII Datasets.
Affected #:  4 files

diff -r 03adb8fb971da512e3b17bfb39ad595eb59819ec -r 5b35020ba8fbcdda7134a48c6edfd019368e9248 doc/source/examining/loading_data.rst
--- a/doc/source/examining/loading_data.rst
+++ b/doc/source/examining/loading_data.rst
@@ -330,12 +330,17 @@
 Exodus II Data
 --------------
 
+.. note::
+   To load Exodus II data, you need to have the `netcdf4 <http://unidata.github.io/
+   netcdf4-python/>`_ python interface installed.
+
 Exodus II is a file format for Finite Element datasets that is used by the MOOSE
 framework for file IO. Support for this format (and for unstructured mesh data in 
-general) is a new feature as of yt 3.3, so while we aim to fully support it, we also expect 
-there to be some buggy features at present. Currently, yt can visualize first-order
-mesh types only (4-node quads, 8-node hexes, 3-node triangles, and 4-node tetrahedra).
-Development of higher-order visualization capability is a work in progress.
+general) is a new feature as of yt 3.3, so while we aim to fully support it, we 
+also expect there to be some buggy features at present. Currently, yt can visualize 
+quads, hexes, triangles, and tetrahedral element types at first order. Additionally,
+there is experimental support for the high-order visualization of 20-node hex elements.
+Development of more high-order visualization capability is a work in progress.
 
 To load an Exodus II dataset, you can use the ``yt.load`` command on the Exodus II
 file:
@@ -348,14 +353,15 @@
 Because Exodus II datasets can have multiple steps (which can correspond to time steps, 
 picard iterations, non-linear solve iterations, etc...), you can also specify a step
 argument when you load an Exodus II data that defines the index at which to look when
-you read data from the file.
+you read data from the file. Omitting this argument is the same as passing in 0, and
+setting ``step=-1`` selects the last time output in the file.
 
 You can access the connectivity information directly by doing:
 
 .. code-block:: python
     
    import yt
-   ds = yt.load("MOOSE_sample_data/out.e-s010", step=0)
+   ds = yt.load("MOOSE_sample_data/out.e-s010", step=-1)
    print(ds.index.meshes[0].connectivity_coords)
    print(ds.index.meshes[0].connectivity_indices)
    print(ds.index.meshes[1].connectivity_coords)
@@ -368,7 +374,7 @@
 .. code-block:: python
     
    import yt
-   ds = yt.load("MOOSE_sample_data/out.e-s010", step=0)
+   ds = yt.load("MOOSE_sample_data/out.e-s010")
    print(ds.field_list)
 
 This will give you a list of field names like ``('connect1', 'diffused')`` and 
@@ -380,7 +386,7 @@
 .. code-block:: python
     
    import yt
-   ds = yt.load("MOOSE_sample_data/out.e-s010", step=0)
+   ds = yt.load("MOOSE_sample_data/out.e-s010")
    ad = ds.all_data()  # geometric selection, this just grabs everything
    print(ad['connect1', 'convected'])
 
@@ -390,7 +396,7 @@
 .. code-block:: python
 
    import yt
-   ds = yt.load("MOOSE_sample_data/out.e-s010", step=0)
+   ds = yt.load("MOOSE_sample_data/out.e-s010")
    ad = ds.all_data()
    print(ad['connect1', 'convected'].shape)
 
@@ -401,7 +407,7 @@
 .. code-block:: python
 
    import yt
-   ds = yt.load("MOOSE_sample_data/out.e-s010", step=0)
+   ds = yt.load("MOOSE_sample_data/out.e-s010")
    ad = ds.all_data()
    print(ad['connect1', 'vertex_x'])
 
@@ -411,7 +417,7 @@
 .. code-block:: python
 
    import yt
-   ds = yt.load("MOOSE_sample_data/out.e-s010", step=0)
+   ds = yt.load("MOOSE_sample_data/out.e-s010")
    ad = ds.all_data()
    print(ad['connect1', 'conv_indicator'].shape)
 
@@ -420,6 +426,61 @@
 For information about visualizing unstructured mesh data, including Exodus II datasets, 
 please see :ref:`unstructured-mesh-slices` and :ref:`unstructured_mesh_rendering`. 
 
+Displacement Fields
+^^^^^^^^^^^^^^^^^^^
+
+Finite element codes often solve for the displacement of each vertex from its 
+original position as a node variable, rather than updating the actual vertex 
+positions with time. For analysis and visualization, it is often useful to turn 
+these displacements on or off, and to be able to scale them arbitrarily to 
+emphasize certain features of the solution. To allow this, if ``yt`` detects 
+displacement fields in an Exodus II dataset (using the convention that they will
+ be named ``disp_x``, ``disp_y``, etc...), it will add optionally add these to 
+the mesh vertex positions for the purposes of visualization. Displacement fields 
+can be controlled when a dataset is loaded by passing in an optional dictionary 
+to the ``yt.load`` command. This feature is turned off by default, meaning that 
+a dataset loaded as 
+
+.. code-block:: python
+
+   import yt
+   ds = yt.load("MOOSE_sample_data/mps_out.e")
+
+will not include the displacements in the vertex positions. The displacements can
+be turned on separately for each mesh in the file by passing in a a tuple of 
+(scale, offset) pairs for the meshes you want to enable displacements for. 
+For example, the following code snippet turns displacements on for the second 
+mesh, but not the first:
+
+.. code-block:: python
+
+    import yt
+    ds = yt.load("MOOSE_sample_data/mps_out.e", step=10,
+                 displacements={'connect2': (1.0, [0.0, 0.0, 0.0])})
+
+The displacements can also be scaled by an arbitrary factor before they are 
+added in to the vertex positions. The following code turns on displacements
+for both ``connect1`` and ``connect2``, scaling the former by a factor of 5.0
+and the later by a factor of 10.0:
+
+.. code-block:: python
+
+    import yt
+    ds = yt.load("MOOSE_sample_data/mps_out.e", step=10,
+                 displacements={'connect1': (5.0, [0.0, 0.0, 0.0]),
+                                'connect2': (10.0, [0.0, 0.0, 0.0])})
+
+Finally, we can also apply an arbitrary offset to the mesh vertices after 
+the scale factor is applied. For example, the following code scales all
+displacements in the second mesh by a factor of 5.0, and then shifts
+each vertex in the mesh by 1.0 unit in the z-direction:
+
+.. code-block:: python
+
+    import yt
+    ds = yt.load("MOOSE_sample_data/mps_out.e", step=10,
+                  displacements={'connect2': (5.0, [0.0, 0.0, 1.0])})
+
 
 FITS Data
 ---------

diff -r 03adb8fb971da512e3b17bfb39ad595eb59819ec -r 5b35020ba8fbcdda7134a48c6edfd019368e9248 doc/source/visualizing/unstructured_mesh_rendering.rst
--- a/doc/source/visualizing/unstructured_mesh_rendering.rst
+++ b/doc/source/visualizing/unstructured_mesh_rendering.rst
@@ -248,6 +248,43 @@
     sc.annotate_mesh_lines()
     sc.save()
 
+The dataset in the above example contains displacement fields, so this is a good
+opportunity to demonstrate their use. The following example is exactly like the
+above, except we scale the displacements by a factor of a 10.0, and additionally 
+add an offset to the mesh by 1.0 unit in the x-direction:
+
+.. python-script::
+
+    import yt
+
+    # We load the last time frame
+    ds = yt.load("MOOSE_sample_data/mps_out.e", step=-1,
+                 displacements={'connect2': (10.0, [0.01, 0.0, 0.0])})
+
+    # create a default scene
+    sc = yt.create_scene(ds, ("connect2", "temp"))
+
+    # override the default colormap. This time we also override
+    # the default color bounds
+    ms = sc.get_source(0)
+    ms.cmap = 'hot'
+    ms.color_bounds = (500.0, 1700.0)
+
+    # adjust the camera position and orientation
+    cam = sc.camera
+    camera_position = ds.arr([-1.0, 1.0, -0.5], 'code_length')
+    north_vector = ds.arr([0.0, 1.0, 1.0], 'dimensionless')
+    cam.width = ds.arr([0.05, 0.05, 0.05], 'code_length')
+    cam.set_position(camera_position, north_vector)
+    
+    # increase the default resolution
+    cam.resolution = (800, 800)
+
+    # render, draw the element boundaries, and save
+    sc.render()
+    sc.annotate_mesh_lines()
+    sc.save()
+
 As with other volume renderings in yt, you can swap out different lenses. Here is 
 an example that uses a "perspective" lens, for which the rays diverge from the 
 camera position according to some opening angle:

diff -r 03adb8fb971da512e3b17bfb39ad595eb59819ec -r 5b35020ba8fbcdda7134a48c6edfd019368e9248 yt/frontends/exodus_ii/data_structures.py
--- a/yt/frontends/exodus_ii/data_structures.py
+++ b/yt/frontends/exodus_ii/data_structures.py
@@ -42,10 +42,16 @@
 
     def _initialize_mesh(self):
         coords = self.ds._read_coordinates()
-        self.meshes = [ExodusIIUnstructuredMesh(
-            mesh_id, self.index_filename, conn_ind, coords, self)
-                       for mesh_id, conn_ind in
-                       enumerate(self.ds._read_connectivity())]
+        connectivity = self.ds._read_connectivity()
+        self.meshes = []
+        for mesh_id, conn_ind in enumerate(connectivity):
+            displaced_coords = self.ds._apply_displacement(coords, mesh_id)
+            mesh = ExodusIIUnstructuredMesh(mesh_id, 
+                                            self.index_filename,
+                                            conn_ind, 
+                                            displaced_coords, 
+                                            self)
+            self.meshes.append(mesh)
 
     def _detect_output_fields(self):
         elem_names = self.dataset.parameters['elem_names']
@@ -63,13 +69,87 @@
     def __init__(self,
                  filename,
                  step=0,
+                 displacements=None,
                  dataset_type='exodus_ii',
                  storage_filename=None,
                  units_override=None):
+        """
 
+        A class used to represent an on-disk ExodusII dataset. The initializer takes 
+        two extra optional parameters, "step" and "displacements."
+
+        Parameters
+        ----------
+
+        step : integer
+            The step tells which time index to slice at. It throws an Error if
+            the index is larger than the number of time outputs in the ExodusII
+            file. Passing step=-1 picks out the last dataframe. 
+            Default is 0.
+
+        displacements : dictionary of tuples
+            This is a dictionary that controls whether or not displacement fields
+            will be used with the meshes in this dataset. The keys of the
+            displacements dictionary should the names of meshes in the file 
+            (e.g., "connect1", "connect2", etc... ), while the values should be 
+            tuples of the form (scale, offset), where "scale" is a floating point
+            value and "offset" is an array-like with one component for each spatial
+            dimension in the dataset. When the displacements for a given mesh are
+            turned on, the coordinates of the vertices in that mesh get transformed
+            as: 
+
+                  vertex_x = vertex_x + disp_x*scale + offset_x
+                  vertex_y = vertex_y + disp_y*scale + offset_y
+                  vertex_z = vertex_z + disp_z*scale + offset_z
+
+            If no displacement 
+            fields (assumed to be named 'disp_x', 'disp_y', etc... ) are detected in
+            the output file, then this dictionary is ignored.
+
+        Examples
+        --------
+
+        This will load the Dataset at time index '0' with displacements turned off.
+
+        >>> import yt
+        >>> ds = yt.load("MOOSE_sample_data/mps_out.e")
+
+        This will load the Dataset at the final index with displacements turned off.
+
+        >>> import yt
+        >>> ds = yt.load("MOOSE_sample_data/mps_out.e", step=-1)
+
+        This will load the Dataset at index 10, turning on displacement fields for 
+        the 2nd mesh without applying any scale or offset:
+
+        >>> import yt
+        >>> ds = yt.load("MOOSE_sample_data/mps_out.e", step=10,
+                         displacements={'connect2': (1.0, [0.0, 0.0, 0.0])})
+
+        This will load the Dataset at index 10, scaling the displacements
+        in the 2nd mesh by a factor of 5 while not applying an offset:
+
+        >>> import yt
+        >>> ds = yt.load("MOOSE_sample_data/mps_out.e", step=10,
+                         displacements={'connect2': (1.0, [0.0, 0.0, 0.0])})
+        
+        This will load the Dataset at index 10, scaling the displacements for
+        the 2nd mesh by a factor of 5.0 and shifting all the vertices in 
+        the first mesh by 1.0 unit in the z direction.
+
+        >>> import yt
+        >>> ds = yt.load("MOOSE_sample_data/mps_out.e", step=10,
+                         displacements={'connect1': (0.0, [0.0, 0.0, 1.0]),
+                                        'connect2': (5.0, [0.0, 0.0, 0.0])})
+
+        """
         self.parameter_filename = filename
         self.fluid_types += self._get_fluid_types()
         self.step = step
+        if displacements is None:
+            self.displacements = {}
+        else:
+            self.displacements = displacements
         super(ExodusIIDataset, self).__init__(filename, dataset_type,
                                               units_override=units_override)
         self.index_filename = filename
@@ -102,8 +182,7 @@
         self.parameters['num_meshes'] = self._vars['eb_status'].shape[0]
         self.parameters['elem_names'] = self._get_elem_names()
         self.parameters['nod_names'] = self._get_nod_names()
-        self.domain_left_edge = self._load_domain_edge(0)
-        self.domain_right_edge = self._load_domain_edge(1)
+        self.domain_left_edge, self.domain_right_edge = self._load_domain_edge()
 
         # set up psuedo-3D for lodim datasets here
         if self.dimensionality == 2:
@@ -223,12 +302,33 @@
 
         mylog.info("Loading coordinates")
         if "coord" not in self._vars:
-            return np.array([self._vars["coord%s" % ax][:]
-                             for ax in coord_axes]).transpose().copy()
+            coords = np.array([self._vars["coord%s" % ax][:]
+                               for ax in coord_axes]).transpose().copy()
         else:
-            return np.array([coord for coord in
-                             self._vars["coord"][:]]).transpose().copy()
+            coords = np.array([coord for coord in
+                               self._vars["coord"][:]]).transpose().copy()
+        return coords
 
+    def _apply_displacement(self, coords, mesh_id):
+        
+        mesh_name = "connect%d" % (mesh_id + 1)
+        if mesh_name not in self.displacements:
+            new_coords = coords.copy()
+            return new_coords
+
+        new_coords = np.zeros_like(coords)
+        fac = self.displacements[mesh_name][0]
+        offset = self.displacements[mesh_name][1]
+
+        coord_axes = 'xyz'[:self.dimensionality]
+        for i, ax in enumerate(coord_axes):
+            if "disp_%s" % ax in self.parameters['nod_names']:
+                ind = self.parameters['nod_names'].index("disp_%s" % ax)
+                disp = self._vars['vals_nod_var%d' % (ind + 1)][self.step]
+                new_coords[:, i] = coords[:, i] + fac*disp + offset[i]
+
+        return new_coords
+        
     def _read_connectivity(self):
         """
         Loads the connectivity data for the mesh
@@ -239,17 +339,22 @@
             connectivity.append(self._vars["connect%d" % (i+1)][:].astype("i8"))
         return connectivity
 
-    def _load_domain_edge(self, domain_idx):
+    def _load_domain_edge(self):
         """
         Loads the boundaries for the domain edge
 
-        Parameters:
-        - domain_idx: 0 corresponds to the left edge, 1 corresponds to the right edge
         """
-        if domain_idx == 0:
-            return self._read_coordinates().min(axis=0)
-        if domain_idx == 1:
-            return self._read_coordinates().max(axis=0)
+        
+        coords = self._read_coordinates()
+        connectivity = self._read_connectivity()
+
+        mi = 1e300
+        ma = -1e300
+        for mesh_id, _ in enumerate(connectivity):
+            displaced_coords = self._apply_displacement(coords, mesh_id)
+            mi = np.minimum(displaced_coords.min(axis=0), mi)
+            ma = np.maximum(displaced_coords.max(axis=0), ma)
+        return mi, ma
 
     @classmethod
     def _is_valid(self, *args, **kwargs):

diff -r 03adb8fb971da512e3b17bfb39ad595eb59819ec -r 5b35020ba8fbcdda7134a48c6edfd019368e9248 yt/frontends/exodus_ii/tests/test_outputs.py
--- a/yt/frontends/exodus_ii/tests/test_outputs.py
+++ b/yt/frontends/exodus_ii/tests/test_outputs.py
@@ -18,7 +18,10 @@
     assert_array_equal, \
     requires_file
 from yt.utilities.answer_testing.framework import \
-    data_dir_load
+    data_dir_load, \
+    requires_ds, \
+    GenericArrayTest
+
 
 out = "ExodusII/out.e"
 
@@ -69,3 +72,18 @@
     field_list = [('connect1', 'forced')]
     yield assert_equal, str(ds), "gold.e"
     yield assert_array_equal, ds.field_list, field_list 
+
+big_data = "MOOSE_sample_data/mps_out.e"
+
+
+ at requires_ds(big_data)
+def test_displacement_fields():
+    displacement_dicts =[{'connect2': (5.0, [0.0, 0.0, 0.0])},
+                         {'connect1': (1.0, [1.0, 2.0, 3.0]), 
+                          'connect2': (0.0, [0.0, 0.0, 0.0])}]
+    for disp in displacement_dicts:
+        ds = data_dir_load(big_data, displacements=disp)
+        for mesh in ds.index.meshes:
+            def array_func():
+                return mesh.connectivity_coords
+            yield GenericArrayTest(ds, array_func, 12)

Repository URL: https://bitbucket.org/yt_analysis/yt/

--

This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.



More information about the yt-svn mailing list