Source code for pylife.vmap.vmap_import

# Copyright (c) 2020-2023 - for information on the respective copyright owner
# see the NOTICE file and/or the repository
# https://github.com/boschresearch/pylife
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

__author__ = "Johannes Mueller"
__maintainer__ = __author__

import numpy as np
import pandas as pd

import h5py

from .exceptions import *
from . import vmap_structures


[docs] class VMAPImport: """The interface class to import a vmap file Parameters ---------- filename : string The path to the vmap file to be read Raises ------ Exception if the file cannot be read an exception is raised. So far any exception from the ``h5py`` module is passed through. """ def __init__(self, filename): self._file = h5py.File(filename, 'r') self._mesh = None self._geometry = None self._state = None def __enter__(self): return self def __exit__(self, type, value, traceback): pass
[docs] def geometries(self): """Returns a list of geometry strings of geometries present in the vmap data """ return self._file["/VMAP/GEOMETRY"].keys()
[docs] def states(self): """Returns a list of state strings of states present in the vmap data """ return self._file["/VMAP/VARIABLES/"].keys()
[docs] def node_sets(self, geometry): """Returns a list of the node_sets present in the vmap file """ return self._geometry_sets(geometry, 'nsets').keys()
[docs] def element_sets(self, geometry): """Returns a list of the element_sets present in the vmap file """ return self._geometry_sets(geometry, 'elsets').keys()
[docs] def nodes(self, geometry): """Retrieves the node positions Parameters ---------- geometry : string The geometry defined in the vmap file Returns ------- node_positions : DataFrame a DataFrame with the node numbers as index and the columns 'x', 'y' and 'z' for the node coordinates. Raises ------ KeyError if the geometry is not found of if the vmap file is corrupted """ return pd.DataFrame( self._file["/VMAP/GEOMETRY/%s/POINTS/MYCOORDINATES" % geometry][()], columns = ['x', 'y', 'z'], index = self._node_index(geometry) )
[docs] def make_mesh(self, geometry, state=None): """Makes the initial mesh Parameters ---------- geometry : string The geometry defined in the vmap file state : string, optional The load state of which the field variable is to be read. If not given, the state must be defined in ``join_variable()``. Returns ------- self Raises ------ KeyError if the ``geometry`` is not found of if the vmap file is corrupted KeyError if the ``node_set`` or ``element_set`` is not found in the geometry. APIUseError if both, a ``node_set`` and an ``element_set`` are given Notes ----- This methods defines the initial mesh to which coordinate data can be joined by ``join_coordinates()`` and field variables can be joined by ``join_variable()`` Examples -------- Get the mesh data with the coordinates of geometry '1' and the stress tensor of 'STATE-2' >>> (pylife.vmap.VMAPImport('demos/plate_with_hole.vmap') .make_mesh('1', 'STATE-2') .join_coordinates() .join_variable('STRESS_CAUCHY') .to_frame() x y z S11 S22 S33 S12 S13 S23 element_id node_id 1 1734 14.897208 5.269875 0.0 27.080811 6.927080 0.0 -13.687358 0.0 0.0 1582 14.555333 5.355806 0.0 28.319006 1.178649 0.0 -10.732705 0.0 0.0 1596 14.630658 4.908741 0.0 47.701195 5.512213 0.0 -17.866833 0.0 0.0 4923 14.726271 5.312840 0.0 27.699907 4.052865 0.0 -12.210032 0.0 0.0 4924 14.592996 5.132274 0.0 38.010101 3.345431 0.0 -14.299768 0.0 0.0 ... ... ... ... ... ... ... ... ... ... 4770 3812 -13.189782 -5.691876 0.0 36.527439 2.470588 0.0 -14.706686 0.0 0.0 12418 -13.560289 -5.278386 0.0 32.868889 3.320898 0.0 -14.260107 0.0 0.0 14446 -13.673285 -5.569107 0.0 34.291058 3.642457 0.0 -13.836027 0.0 0.0 14614 -13.389065 -5.709927 0.0 36.063541 2.828889 0.0 -13.774759 0.0 0.0 14534 -13.276068 -5.419206 0.0 33.804211 2.829817 0.0 -14.580153 0.0 0.0 """ self._mesh = pd.DataFrame(index=self._mesh_index(geometry)) self._geometry = geometry self._state = state return self
[docs] def filter_node_set(self, node_set): """Filters a node set out of the current mesh Parameters ---------- node_set : string The node set defined in the vmap file as geometry set Returns ------- self Raises ------ APIUseError if the mesh has not been initialized using ``make_mesh()`` """ self._check_mesh_for_filtering() node_set_ids = self._node_set_ids(self._geometry, node_set) self._mesh = self._mesh[self._mesh.index.isin(node_set_ids, level='node_id')] return self
[docs] def filter_element_set(self, element_set): """Filters a node set out of the current mesh Parameters ---------- element_set : string, optional The element set defined in the vmap file as geometry set Returns ------- self Raises ------ APIUseError if the mesh has not been initialized using ``make_mesh()`` """ self._check_mesh_for_filtering() element_set_ids = self._element_set_ids(self._geometry, element_set) self._mesh = self._mesh[self._mesh.index.isin(element_set_ids, level='element_id')] return self
def _check_mesh_for_filtering(self): if self._mesh is None: raise APIUseError("Need to make_mesh() before filtering node or element sets.")
[docs] def join_coordinates(self): """Join the coordinates of the predefined geometry in the mesh Returns ------- self Raises ------ APIUseError if the mesh has not been initialized using ``make_mesh()`` Examples -------- Receive the mesh with the node coordinates >>> pylife.vmap.VMAPImport('demos/plate_with_hole.vmap').make_mesh('1').join_coordinates().to_frame() x y z element_id node_id 1 1734 14.897208 5.269875 0.0 1582 14.555333 5.355806 0.0 1596 14.630658 4.908741 0.0 4923 14.726271 5.312840 0.0 4924 14.592996 5.132274 0.0 ... ... ... ... 4770 3812 -13.189782 -5.691876 0.0 12418 -13.560289 -5.278386 0.0 14446 -13.673285 -5.569107 0.0 14614 -13.389065 -5.709927 0.0 14534 -13.276068 -5.419206 0.0 [37884 rows x 3 columns] """ if self._mesh is None: raise APIUseError("Need to make_mesh() before joining the coordinates.") self._mesh = self._mesh.join(self.nodes(self._geometry)) return self
[docs] def to_frame(self): """Returns the mesh and resets the mesh Returns ------- mesh : DataFrame The mesh data joined so far Raises ------ APIUseError if there is no mesh present, i.e. make_mesh() has not been called yet or the mesh has been reset in the meantime. Notes ----- This method resets the mesh, i.e. ``make_mesh()`` must be called again in order to fetch more mesh data in another mesh. """ if self._mesh is None: raise(APIUseError("Need to make_mesh() before requesting a resulting frame.")) ret = self._mesh self._mesh = None return ret
[docs] def variables(self, geometry, state): """Ask for available variables for a certain geometry and state. Parameters ---------- geometry : string Name of the geometry state : string Name of the state Returns ------- variables : list List of available variable names for the geometry state combination Raises ------ KeyError if the geometry state combination is not available. """ self._fail_if_unknown_geometry(geometry) self._fail_if_unknown_state(state) if geometry not in self._file['/VMAP/VARIABLES/%s' % state].keys(): raise KeyError("Geometry '%s' not available in state '%s'." % (geometry, state)) return list(self._file['/VMAP/VARIABLES/%s/%s' % (state, geometry)].keys())
[docs] def join_variable(self, var_name, state=None, column_names=None): """Joins a field output variable to the mesh Parameters ---------- var_name : string The name of the field variables state : string, opional The load state of which the field variable is to be read If not given, the last defined state, either defined in ``make_mesh()`` or defeined in ``join_variable()`` is used. column_names : list of string, optional The names of the columns names to be used in the DataFrame If not provided, it will be chosen according to the list shown below. The length of the list must match the dimension of the variable. Returns ------- self Raises ------ APIUseError if the mesh has not been initialized using ``make_mesh()`` KeyError if the geometry, state or varname is not found of if the vmap file is corrupted KeyError if there are no column names given and known for the variable. ValueError if the length of the column_names does not match the dimension of the variable Notes ----- The mesh must be initialized with ``make_mesh()``. The final DataFrame can be retrieved with ``to_frame()``. If the ``column_names`` argument is not provided the following column names are chosen * 'DISPLACEMENT': ``['dx', 'dy', 'dz']`` * 'STRESS_CAUCHY': ``['S11', 'S22', 'S33', 'S12', 'S13', 'S23']`` * 'E': ``['E11', 'E22', 'E33', 'E12', 'E13', 'E23']`` If that fails a ``KeyError`` exception is risen. Examples -------- Receiving the 'DISPLACEMENT' of 'STATE-1' , the stress and strain tensors of 'STATE-2' >>> (pylife.vmap.VMAPImport('demos/plate_with_hole.vmap') .make_mesh('1') .join_variable('DISPLACEMENT', 'STATE-1') .join_variable('STRESS_CAUCHY', 'STATE-2') .join_variable('E').to_frame()) dx dy dz S11 S22 S33 S12 S13 S23 E11 E22 E33 E12 E13 E23 element_id node_id 1 1734 0.0 0.0 0.0 27.080811 6.927080 0.0 -13.687358 0.0 0.0 0.000119 -0.000006 0.0 -0.000169 0.0 0.0 1582 0.0 0.0 0.0 28.319006 1.178649 0.0 -10.732705 0.0 0.0 0.000133 -0.000035 0.0 -0.000133 0.0 0.0 1596 0.0 0.0 0.0 47.701195 5.512213 0.0 -17.866833 0.0 0.0 0.000219 -0.000042 0.0 -0.000221 0.0 0.0 4923 0.0 0.0 0.0 27.699907 4.052865 0.0 -12.210032 0.0 0.0 0.000126 -0.000020 0.0 -0.000151 0.0 0.0 4924 0.0 0.0 0.0 38.010101 3.345431 0.0 -14.299768 0.0 0.0 0.000176 -0.000038 0.0 -0.000177 0.0 0.0 ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... 4770 3812 0.0 0.0 0.0 36.527439 2.470588 0.0 -14.706686 0.0 0.0 0.000170 -0.000040 0.0 -0.000182 0.0 0.0 12418 0.0 0.0 0.0 32.868889 3.320898 0.0 -14.260107 0.0 0.0 0.000152 -0.000031 0.0 -0.000177 0.0 0.0 14446 0.0 0.0 0.0 34.291058 3.642457 0.0 -13.836027 0.0 0.0 0.000158 -0.000032 0.0 -0.000171 0.0 0.0 14614 0.0 0.0 0.0 36.063541 2.828889 0.0 -13.774759 0.0 0.0 0.000168 -0.000038 0.0 -0.000171 0.0 0.0 14534 0.0 0.0 0.0 33.804211 2.829817 0.0 -14.580153 0.0 0.0 0.000157 -0.000035 0.0 -0.000181 0.0 0.0 [37884 rows x 15 columns] TODO ---- Write a more central document about pyLife's column names. """ if self._mesh is None: raise APIUseError("Need to make_mesh() before joining a variable.") state = self._update_state(state) self._fail_if_geometry_unknown_in_state(self._geometry, state) self._state = state variable_data = (pd.DataFrame(index=self._mesh.index) .join(self._variable(self._geometry, self._state, var_name, column_names))) self._mesh = self._mesh.join(variable_data.loc[self._mesh.index]) return self
def _update_state(self, state): if state is None: state = self._state if state is None: raise APIUseError("No state name given.\n" "Must be either given in make_mesh() or in join_variable() as optional state argument.") return state def _fail_if_unknown_geometry(self, geometry): if geometry not in self.geometries(): raise KeyError("Geometry '%s' not found. Available geometries: [%s]." % (geometry, ', '.join(["'"+g+"'" for g in self.geometries()]))) def _fail_if_unknown_state(self, state): if state not in self.states(): raise KeyError("State '%s' not found. Available states: [%s]." % (state, ', '.join(["'"+s+"'" for s in self.states()]))) def _fail_if_geometry_unknown_in_state(self, geometry, state): self._fail_if_unknown_geometry(geometry) self._fail_if_unknown_state(state) if geometry not in self._file['/VMAP/VARIABLES/%s' % state].keys(): raise KeyError("Geometry '%s' not available in state '%s'." % (geometry, state)) def _mesh_index(self, geometry): self._fail_if_unknown_geometry(geometry) connectivity = self._element_connectivity(geometry).connectivity length = sum([el.shape[0] for el in connectivity]) index_np = np.empty((2, length), dtype=np.int64) i = 0 for element_id, node_ids in connectivity.items(): i_next = i + node_ids.shape[0] index_np[0, i:i_next] = element_id index_np[1, i:i_next] = node_ids i = i_next return pd.MultiIndex.from_arrays(index_np, names=['element_id', 'node_id']) def _variable(self, geometry, state, var_name, column_names): if column_names is None: try: column_names = vmap_structures.column_names[var_name][0] except KeyError: raise KeyError("No column name for variable %s. Please provide with column_names parameter." % var_name) state_group = self._file["/VMAP/VARIABLES/%s/%s" % (state, geometry)] if var_name not in state_group.keys(): raise KeyError("Variable '%s' not found in geometry '%s', '%s'." % (var_name, geometry, state)) var_tree = state_group[var_name] var_dimension = var_tree.attrs['MYDIMENSION'] if len(column_names) != var_dimension: raise ValueError("Length of column name list (%d) does not match variable dimension (%d)." % (len(column_names), var_dimension)) return pd.DataFrame( data=var_tree['MYVALUES'][()], columns=column_names, index=self._make_index(var_tree, geometry) ) def _element_connectivity(self, geometry): elements = self._file['/VMAP/GEOMETRY/' + geometry + '/ELEMENTS/MYELEMENTS'] element_connectivity = elements['myIdentifier', 'myConnectivity'][:, 0] element_ids = [elid for elid, _ in element_connectivity] connectivity = [conn for _, conn in element_connectivity] return pd.DataFrame(data={'element_id': element_ids, 'connectivity': connectivity}).set_index('element_id') def _node_index(self, geometry): return pd.Index( self._file["/VMAP/GEOMETRY/%s/POINTS/MYIDENTIFIERS" % geometry][:, 0], name='node_id' ) def _make_index(self, var_tree, geometry): location = var_tree.attrs['MYLOCATION'] if location == 2: return self._var_node_index(var_tree) if location == 3: return self._var_element_index(var_tree) if location == 6: return self._var_element_nodal_index(var_tree, geometry) raise FeatureNotSupportedError("Unsupported value location, sorry\nSupported: NODE, ELEMENT, ELEMENT NODAL") def _var_node_index(self, var_tree): return pd.Index(var_tree['MYGEOMETRYIDS'][:, 0], name='node_id') def _var_element_index(self, var_tree): return pd.Index(var_tree['MYGEOMETRYIDS'][:, 0], name='element_id') def _var_element_nodal_index(self, var_tree, geometry): mesh_index_frame = self._mesh_index(geometry).to_frame(index=False) index_frame = pd.DataFrame(var_tree['MYGEOMETRYIDS'], columns=['element_id']) return (index_frame .merge(mesh_index_frame) .set_index(['element_id', 'node_id']) .index) def _geometry_sets(self, geometry, set_type): s_type = 0 if set_type == 'nsets' else 1 geometry_sets = self._file["/VMAP/GEOMETRY/%s/GEOMETRYSETS" % geometry] return { gset.attrs['MYSETNAME'].decode('UTF-8'): gset['MYGEOMETRYSETDATA'][()] for (_, gset) in geometry_sets.items() if gset.attrs['MYSETTYPE'] == s_type }
[docs] def try_get_geometry_set(self, geometry_name, geometry_set_name): try: geometry_set = self._file["/VMAP/GEOMETRY/%s/GEOMETRYSETS/%s/MYGEOMETRYSETDATA" % (geometry_name, geometry_set_name)] return pd.Index(geometry_set[()].flatten()) except KeyError: return None
[docs] def try_get_vmap_object(self, group_full_path): try: return self._file[group_full_path] except KeyError: return None
def _node_set_ids(self, geometry, node_set): try: return self._geometry_sets(geometry, 'nsets')[node_set].T[0] except KeyError: raise KeyError("Node set '%s' not found in geometry '%s'" % (node_set, geometry)) def _element_set_ids(self, geometry, element_set): try: return self._geometry_sets(geometry, 'elsets')[element_set].T[0] except KeyError: raise KeyError("Element set '%s' not found in geometry '%s'" % (element_set, geometry))