[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