Source code for pylife.vmap.vmap_export

# 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__ = "Gyöngyvér Kiss"
__maintainer__ = __author__

import datetime
import getpass

import numpy as np
import pandas as pd
import h5py
import os

from .exceptions import *
from . import vmap_structures
from .vmap_unit_system import VMAPUnit
from .vmap_element_type import VMAPElementType
from .vmap_attribute import VMAPAttribute
from .vmap_coordinate_system import VMAPCoordinateSystem
from .vmap_section import VMAPSection
from .vmap_metadata import VMAPMetadata
from .vmap_integration_type import VMAPIntegrationType


class VMAPExportError(Exception):
    pass


[docs] class VMAPExport: """ The interface class to export a vmap file Parameters ---------- file_name : 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. """ """ These dictionaries are to provide the data to the SYSTEM datasets. This data is going to come from the import """ _element_types = { (2, 3): [0, 'VMAP_ELEM_2D_TRIANGLE_3', 'pyLife 2D 3', 3, 2, -1, -1, -1, -1, -1, [], []], (2, 6): [1, 'VMAP_ELEM_2D_TRIANGLE_6', 'pyLife 2D 6', 6, 2, -1, -1, -1, -1, -1, [], []], (2, 4): [2, 'VMAP_ELEM_2D_QUAD_4', 'pyLife 2D 4', 4, 2, -1, -1, -1, -1, -1, [], []], (2, 8): [3, 'VMAP_ELEM_2D_QUAD_8', 'pyLife 2D 8', 8, 2, -1, -1, -1, -1, -1, [], []], (3, 4): [4, 'VMAP_ELEMENT_3D_TETRAHEDRON_4', 'pyLife 3D 4', 4, 3, -1, -1, -1, -1, -1, [], []], (3, 10): [5, 'VMAP_ELEMENT_3D_TETRAHEDRON_10', 'pyLife 3D 10', 10, 3, -1, -1, -1, -1, -1, [], []], (3, 6): [6, 'VMAP_ELEMENT_3D_WEDGE_6', 'pyLife 3D 6', 6, 3, -1, -1, -1, -1, -1, [], []], (3, 15): [7, 'VMAP_ELEMENT_3D_WEDGE_15', 'pyLife 3D 15', 15, 3, -1, -1, -1, -1, -1, [], []], (3, 8): [8, 'VMAP_ELEMENT_3D_HEXAHEDRON_8', 'pyLife 3D 8', 8, 3, -1, -1, -1, -1, -1, [], []], (3, 20): [9, 'VMAP_ELEMENT_3D_HEXAHEDRON_20', 'pyLife 3D 20', 20, 3, -1, -1, -1, -1, -1, [], []] } _coordinate_systems = { 'CARTESIAN': [1, 2, [0.0, 0.0, 0.0], [1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0]] } _sections = { 'DEFAULT': [0, 'Section-ASSEMBLY_DEFAULT', 1, 0, _coordinate_systems['CARTESIAN'][0], -1, -1] } _unit_system = { 'LENGTH': [1, 1.0, 0.0, 'm', 'LENGTH'], 'MASS': [2, 1.0, 0.0, 'kg', 'MASS'], 'TIME': [3, 1.0, 0.0, 's', 'TIME'], 'ELECTRIC_CURRENT': [4, 1.0, 0.0, 'A', 'ELECTRIC CURRENT'], 'TEMPERATURE': [5, 1.0, 0.0, 'K', 'TEMPERATURE'], 'AMOUNT_OF_SUBSTANCE': [6, 1.0, 0.0, 'mol', 'AMOUNT OF SUBSTANCE'], 'LUMINOUS_INTENSITY': [7, 1.0, 0.0, 'cd', 'LUMINOUS INTENSITY'], } _metadata = { 'EXPORTER_NAME': ['ExporterName', 'pyLife'], 'FILE_DATE': ['FileDate', datetime.datetime.now().date().strftime("%Y-%m-%d")], 'FILE_TIME': ['FileTime', datetime.datetime.now().time().strftime("%H:%M:%S.%f")], 'DESCRIPTION': ['Description', ''], 'ANALYSIS_TYPE': ['Analysis Type', ''], 'USERID': ['User Id', getpass.getuser()] } def __init__(self, file_name): self._file_name = file_name try: with h5py.File(file_name, 'w') as file: self._create_fundamental_groups(file) self._dimension = 2 except OSError: if os.path.exists(self._file_name): os.remove(self._file_name) raise @property def file_name(self): """ Gets the name of the VMAP file that we are exporting """ return self._file_name
[docs] def variable_column_names(self, parameter_name): """ Gets the column names that the given parameter consists of Parameters ---------- parameter_name: string The name of the parameter Returns ------- The column names of the given parameter in the mesh """ return vmap_structures.column_names[parameter_name][0]
[docs] def variable_location(self, parameter_name): """ Gets the location of the given parameter Parameters ---------- parameter_name: string The name of the parameter Returns ------- The location of the given parameter """ return vmap_structures.column_names[parameter_name][1]
[docs] def set_group_attribute(self, object_path, key, value): """ Sets the 'MYNAME' attribute of the VMAP objects Parameters ---------- object_path: string The full path to the object that we want to rename key: string The key of the attribute that we want to set value: np.dtype The value that we want to set to the attribute Returns ------- - """ with h5py.File(self._file_name, 'a') as file: try: vmap_object = file[object_path] except KeyError: raise KeyError('VMAP object %s does not exist.' % object_path) vmap_object.attrs.create(key, value)
[docs] def add_geometry(self, geometry_name, mesh): """ Exports geometry with given name and mesh data Parameters ---------- geometry_name: string Name of the geometry to add mesh: Pandas DataFrame The Data Frame that holds the data of the mesh to export Returns ------- self """ with h5py.File(self._file_name, 'a') as file: geometry_group = file["/VMAP/GEOMETRY"] if geometry_name in geometry_group: raise KeyError('Geometry %s already exists' % geometry_name) try: geometry = self._create_geometry_groups(file, geometry_group, geometry_name) self._create_points_datasets(geometry, mesh) self._create_elements_dataset(geometry, mesh) except Exception as e: del geometry_group[geometry_name] raise VMAPExportError('An error occurred while creating geometry %s: %s' % (geometry_name, str(e))) return self
[docs] def add_node_set(self, geometry_name, indices, mesh, name=None): """ Exports node-type geometry set into given geometry Parameters ---------- geometry_name: string` The geometry to where we want to export the geometry set indices: Pandas Index List of node indices that we want to export mesh: Pandas DataFrame The Data Frame that holds the data of the mesh to export name: value of attribute MYSETNAME Returns ------- self """ node_id_set = set(mesh.index.get_level_values('node_id')) index_set = set(indices) if not index_set.issubset(node_id_set): raise KeyError('Provided index set is not a subset of the node indices.') self._create_geometry_set(geometry_name, 0, indices, name) return self
[docs] def add_element_set(self, geometry_name, indices, mesh, name=None): """ Exports element-type geometry set into given geometry Parameters ---------- geometry_name: string The geometry to where we want to export the geometry set indices: Pandas Index List of node indices that we want to export mesh: Pandas DataFrame The Data Frame that holds the data of the mesh to export name: value of attribute MYSETNAME Returns ------- self """ element_id_set = set(mesh.index.get_level_values('element_id')) index_set = set(indices) if not index_set.issubset(element_id_set): raise KeyError('Provided index set is not a subset of the element indices.') self._create_geometry_set(geometry_name, 1, indices, name) return self
[docs] def add_integration_types(self, content): """ Creates system dataset IntegrationTypes with the given content Parameters ---------- content: the content of the dataset Returns ------- self """ self._create_system_dataset(VMAPIntegrationType, content) return self
[docs] def add_variable(self, state_name, geometry_name, variable_name, mesh, column_names=None, location=None): """Exports variable into given state and geometry Parameters ---------- state_name: string State where we want to export the parameter geometry_name: string Geometry where we want to export the parameter variable_name: string The name of the variable to export mesh: Pandas DataFrame The Data Frame that holds the data of the mesh to export column_names: List, optional The columns that the parameter consists of location: Enum, optional The location of the parameter * 2 - node * 3 - element - not supported yet * 6 - element nodal Returns ------- self """ with h5py.File(self._file_name, 'a') as file: try: file["VMAP/GEOMETRY/%s" % geometry_name] except KeyError: raise KeyError("No geometry with the name %s" % geometry_name) try: state_group = file["/VMAP/VARIABLES/%s" % state_name] except KeyError: state_group = self._create_group_with_attributes(file['VMAP/VARIABLES'], state_name, VMAPAttribute('MYSTATEINCREMENT', 0), VMAPAttribute('MYSTATENAME', str.encode(state_name, 'UTF8')), VMAPAttribute('MYSTEPTIME', 0.0), VMAPAttribute('MYTOTALTIME', 0.0)) try: geometry_group = state_group[geometry_name] except KeyError: geometry_group = self._create_group_with_attributes(state_group, geometry_name, VMAPAttribute('MYSIZE', 0)) if variable_name in geometry_group: raise KeyError("Variable already exists in state %s and geometry %s" % (state_name, geometry_name)) if column_names is None: try: column_names = vmap_structures.column_names[variable_name][0] except KeyError: raise KeyError("No column name for variable %s." % variable_name) if location is None: if variable_name not in vmap_structures.column_names: raise APIUseError( "Need location for unknown variable %s. Please provide one using 'location' parameter." % variable_name) location = vmap_structures.column_names[variable_name][1] if not isinstance(location, vmap_structures.VariableLocations): raise APIUseError("location parameter needs to be of type VariableLocations.") try: variable_dataset = self._create_group_with_attributes(geometry_group, variable_name, VMAPAttribute('MYCOORDINATESYSTEM', -1), VMAPAttribute('MYDIMENSION', len(column_names)), VMAPAttribute('MYENTITY', 1), VMAPAttribute('MYIDENTIFIER', len(geometry_group)), VMAPAttribute('MYINCREMENTVALUE', 1), VMAPAttribute('MYLOCATION', location.value), VMAPAttribute('MYMULTIPLICITY', 1), VMAPAttribute('MYTIMEVALUE', 0.0), VMAPAttribute('MYUNIT', -1), VMAPAttribute('MYVARIABLEDEPENDENCY', b' '), VMAPAttribute('MYVARIABLEDESCRIPTION', str.encode( 'pyLife: %s' % variable_name, 'UTF8')), VMAPAttribute('MYVARIABLENAME', str.encode(variable_name, 'UTF8'))) if location == vmap_structures.VariableLocations.NODE: node_ids_info = mesh.groupby('node_id').first() variable_dataset.create_dataset('MYGEOMETRYIDS', data=np.array([node_ids_info.index]).T, dtype=np.int32, chunks=True) variable_dataset.create_dataset('MYVALUES', data=node_ids_info[column_names].values, chunks=True) else: element_ids = mesh.index.get_level_values('element_id').drop_duplicates().values variable_dataset.create_dataset('MYGEOMETRYIDS', data=np.array([element_ids]).T, dtype=np.int32, chunks=True) variable_dataset.create_dataset('MYVALUES', data=mesh[column_names], chunks=True) geometry_group.attrs['MYSIZE'] = geometry_group.attrs['MYSIZE'] + 1 except Exception as e: del geometry_group[variable_name] raise VMAPExportError('An error occurred while creating variable %s: %s' % (variable_name, str(e))) return self
def _create_group_with_attributes(self, parent_group, group_name, *args): group = parent_group.create_group(group_name) if args is not None: for attr in args: group.attrs.create(attr.name, attr.value) return group def _create_compound_attribute(self, parent, attr_name, field_names, field_types, field_values): dt = np.dtype({"names": field_names, "formats": field_types}) compound_attribute = np.array([field_values], dt) parent.attrs.create(attr_name, compound_attribute) def _create_fundamental_groups(self, file): vmap_group = file.create_group('VMAP') self._create_compound_attribute(vmap_group, 'VERSION', ["myMajor", "myMinor", "myPatch"], ['<i4', '<i4', '<i4'], ('0', '5', '2')) self._create_group_with_attributes(vmap_group, 'GEOMETRY') self._create_group_with_attributes(vmap_group, 'MATERIAL') self._create_group_with_attributes(vmap_group, 'SYSTEM') self._create_system_dataset(VMAPCoordinateSystem, self._coordinate_systems) self._create_system_dataset(VMAPElementType, self._element_types) self._create_system_dataset(VMAPMetadata, self._metadata) self._create_system_dataset(VMAPSection, self._sections) self._create_system_dataset(VMAPUnit, self._unit_system) self._create_group_with_attributes(vmap_group, 'VARIABLES') def _create_system_dataset(self, class_name, dataset_content): attribute_list = [] for content_key in dataset_content: dataset = class_name(*dataset_content[content_key]) attribute_list.append(dataset.attributes) name = dataset.dataset_name dt_type = dataset.dtype path = dataset.group_path compound_dataset = dataset.compound_dataset if compound_dataset: d = np.array([attribute_list], dtype=dt_type).T chunked = True else: d = np.array(attribute_list, dtype=dt_type) chunked = None with h5py.File(self._file_name, 'a') as file: system_group = file[path] if name in system_group: raise KeyError('Dataset %s already exists in SYSTEM') try: system_group.create_dataset(name, dtype=dt_type, data=d, chunks=chunked) except Exception as e: del system_group[name] raise VMAPExportError('An error occurred while creating dataset %s: %s' % (name, str(e))) return self def _create_geometry_groups(self, file, geometry_group, geometry_name): geometry = self._create_group_with_attributes(geometry_group, geometry_name) self._create_group_with_attributes(geometry, 'ELEMENTS', VMAPAttribute('MYSIZE', np.int64(0))) self._create_group_with_attributes(geometry, 'GEOMETRYSETS', VMAPAttribute('MYSIZE', 0)) self._create_group_with_attributes(geometry, 'POINTS', VMAPAttribute('MYSIZE', np.int64(0)), VMAPAttribute('MYCOORDINATESYSTEM', self._coordinate_systems['CARTESIAN'][0])) return geometry def _create_elements_dataset(self, geometry, mesh): dt_type = np.dtype({"names": ["myIdentifier", "myElementType", "myCoordinateSystem", "myMaterialType", "mySectionType", "myConnectivity"], "formats": ['<i4', '<i4', '<i4', '<i4', '<i4', h5py.special_dtype(vlen=np.dtype('int32'))]}) element_connectivities = mesh.groupby('element_id') element_ids = [] node_ids_list = [] element_types_list = [] coordinate_system = np.empty(len(element_connectivities), dtype=np.int32) coordinate_system.fill(1) material_type = np.empty(len(element_connectivities), dtype=np.int32) material_type.fill(0) section_type = np.empty(len(element_connectivities), dtype=np.int32) section_type.fill(self._sections['DEFAULT'][0]) for element_connectivity in element_connectivities: c = element_connectivity[1].index.get_level_values('node_id').values node_ids_list.append(c) element_ids.append(element_connectivity[0]) element_type = self._element_types[self._dimension, c.size][0] element_types_list.append(element_type) element_types = np.asarray(element_types_list) connectivity = np.asarray(node_ids_list) d = np.array([list(zip(element_ids, element_types, coordinate_system, material_type, section_type, connectivity))], dtype=dt_type).T elements_group = geometry['ELEMENTS'] elements_group.create_dataset("MYELEMENTS", dtype=dt_type, data=d, chunks=True) elements_group.attrs['MYSIZE'] = np.int64(len(element_ids)) def _create_points_datasets(self, geometry, mesh): node_ids_info = mesh.groupby('node_id').first() points_group = geometry['POINTS'] points_group.create_dataset('MYIDENTIFIERS', data=np.reshape(node_ids_info.index, (-1, 1)), dtype=np.int32, chunks=True) if 'z' in node_ids_info: z = node_ids_info['z'].to_numpy() if not (z[0] == z).all(): self._dimension = 3 points_group.create_dataset('MYCOORDINATES', data=node_ids_info[['x', 'y', 'z']].values, chunks=True) else: points_group.create_dataset('MYCOORDINATES', data=node_ids_info[['x', 'y']].values, chunks=True) points_group.attrs['MYSIZE'] = np.int64(node_ids_info.index.size) def _create_geometry_set(self, geometry_name, object_type, indices, name=None): if name is None: name = '' if not isinstance(name, str): raise TypeError("Invalid set name (must be a string).") with h5py.File(self._file_name, 'a') as file: try: geometry_set_group = file["VMAP/GEOMETRY/%s/GEOMETRYSETS" % geometry_name] except KeyError: raise KeyError("No geometry with the name %s" % geometry_name) try: set_size = geometry_set_group.attrs['MYSIZE'] geometry_set_name = str(f"{set_size:06d}") geometry_set = self._create_group_with_attributes(geometry_set_group, geometry_set_name, VMAPAttribute('MYIDENTIFIER', set_size), VMAPAttribute('MYSETINDEXTYPE', 1), VMAPAttribute('MYSETNAME', str.encode(name, 'UTF-8')), VMAPAttribute('MYSETTYPE', object_type)) geometry_set.create_dataset('MYGEOMETRYSETDATA', data=pd.DataFrame(indices), dtype=np.int32, chunks=True) geometry_set_group.attrs['MYSIZE'] = set_size + 1 except Exception as e: # pragma: no cover (all possible exceptions should have been caught already) del geometry_set_group[geometry_set_name] raise VMAPExportError( "An error occurred while creating geometry set %s in geometry %s: %s" "If this is reproducible, please file a bug report." % (geometry_set_name, geometry_name, str(e)) )