Source code for Karana.FEMBridge.ReadH5PhysicalModalBody

# Copyright (c) 2024-2025 Karana Dynamics Pty Ltd. All rights reserved.
#
# NOTICE TO USER:
#
# This source code and/or documentation (the "Licensed Materials") is
# the confidential and proprietary information of Karana Dynamics Inc.
# Use of these Licensed Materials is governed by the terms and conditions
# of a separate software license agreement between Karana Dynamics and the
# Licensee ("License Agreement"). Unless expressly permitted under that
# agreement, any reproduction, modification, distribution, or disclosure
# of the Licensed Materials, in whole or in part, to any third party
# without the prior written consent of Karana Dynamics is strictly prohibited.
#
# THE LICENSED MATERIALS ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND.
# KARANA DYNAMICS DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING
# BUT NOT LIMITED TO WARRANTIES OF MERCHANTABILITY, NON-INFRINGEMENT, AND
# FITNESS FOR A PARTICULAR PURPOSE.
#
# IN NO EVENT SHALL KARANA DYNAMICS BE LIABLE FOR ANY DAMAGES WHATSOEVER,
# INCLUDING BUT NOT LIMITED TO LOSS OF PROFITS, DATA, OR USE, EVEN IF
# ADVISED OF THE POSSIBILITY OF SUCH DAMAGES, WHETHER IN CONTRACT, TORT,
# OR OTHERWISE ARISING OUT OF OR IN CONNECTION WITH THE LICENSED MATERIALS.
#
# U.S. Government End Users: The Licensed Materials are a "commercial item"
# as defined at 48 C.F.R. 2.101, and are provided to the U.S. Government
# only as a commercial end item under the terms of this license.
#
# Any use of the Licensed Materials in individual or commercial software must
# include, in the user documentation and internal source code comments,
# this Notice, Disclaimer, and U.S. Government Use Provision.

"""Classes associated with creating a ModalBodyDS from an H5 file."""

import h5py
import numpy as np
from Karana.KUtils.DataStruct import DataStruct
from Karana.Dynamics.SOAFlexDyn_types import ModalBodyDS, ModalNodeDS
from Karana.Dynamics.SOADyn_types import BodyDS
from Karana.Dynamics import HINGE_TYPE
from Karana.Dynamics.SOADyn_types import HingeDS
from pathlib import Path
from Karana.KUtils.Ktyping import NonNegativeFloat, Vec
from numpy.typing import NDArray


[docs] class H5PhysicalModalBodyDS(DataStruct): """Configuration options for ReadH5PhysicalModalBody. Parameters ---------- filename: Path | str The name of the H5 file to read the flexible body data from. mass_conversion : float = 1.0 Conversion factor for mass. distance_conversion : float = 1.0 Conversion factor for distance. p_node_grid : int | None The grid point number associated with the pnode. This can be None if and only if the hinge type is 6 DoF. sensor_nodes : dict[int, str] This dictionary defines the sensor nodes to add to the body. The keys are grid point numbers from the NASTRAN file, and the values are the names of the sensor nodes that will be created and are associated with those grid point numbers. force_nodes : dict[int, str] This dictionary defines the force nodes to add to the body. The keys are grid point numbers from the NASTRAN file, and the values are the names of the force nodes that will be created and are associated with those grid point numbers. constraint_nodes: dict[int, str] This dictionary defines the constraint nodes to add to the body. The keys are grid point numbers from the NASTRAN file, and the values are the names of the constraint nodes that will be created and are associated with those grid point numbers. damping : NonNegativeFloat | Vec Defines the damping. If given as a NonNegativeFloat, then the damping vector will be computed as 2*zeta*w, where w are the individual frequencies in rad/s. If given as a Vec, then this will be used as the damping vector directly. """ filename: Path | str mass_conversion: float = 1.0 distance_conversion: float = 1.0 p_node_grid: int | None sensor_nodes: dict[int, str] = {} force_nodes: dict[int, str] = {} constraint_nodes: dict[int, str] = {} damping: NonNegativeFloat | Vec = 0.0
[docs] class ReadH5PhysicalModalBody: """Create a ModalBodyDS from an H5 file. Also, can be used to add inboard body information for child bodies of this modal body. """ def __init__(self, c: H5PhysicalModalBodyDS): """Create a ReadH5PhysicalModalBody. Parameters ---------- c : H5PhysicalModalBodyDS Configuration options. """ self._c = c self.f = h5py.File(self._c.filename, "r") self.n_rigid_modes = self.f["modal_analysis"]["n_rigid_modes"][()]
[docs] def getNodalMatrix(self, grid: int) -> NDArray: """Retrieve the nodal matrix associated with the given grid point ID. Parameters ---------- grid : int The ID associated with the grid point to get the nodal matrix for. Returns ------- NDArray Nodal matrix for the given grid point. """ nm = self.f["grids"][str(grid)]["nodal_matrix"][:, self.n_rigid_modes :] nm[0:3, :] *= 1.0 / (self._c.distance_conversion) nm *= 1.0 / (np.sqrt(self._c.mass_conversion)) return nm
[docs] def addInbNodeToDS(self, body_ds: BodyDS, grid_id: int): """Add inboard body node (hinge o-node) to incoming BodyDS. Parameters ---------- body_ds : BodyDS The BodyDS to add the inboard node information to. grid_id : int The grid ID to use as the inboard node. """ body_ds.inb_nodal_matrix = self.getNodalMatrix(grid_id) body_ds.inb_to_joint = self.f["grids"][str(grid_id)]["pos"][:]
[docs] def getModalBodyDS(self, name: str, hinge: HingeDS) -> ModalBodyDS: """Create the ModalBodyDS from the H5 file given in the configuration. Parameters ---------- name : str The name to use for the ModalBodyDS. hinge : HingeDS The HingeDS to use for the ModalBodyDS. Returns ------- ModalBodyDS The ModalBodyDS associated with the configuration and method options. """ if isinstance(self._c.damping, float) or isinstance(self._c.damping, int): freq_rad = self.f["modal_analysis"]["freq_rad"][self.n_rigid_modes :] damping = 2 * self._c.damping * freq_rad else: damping = self._c.damping[self.n_rigid_modes :] # Get the nodal matrix if hinge.hinge_type == HINGE_TYPE.FULL6DOF: nodal_matrix = None body_to_joint = np.zeros(3) else: if self._c.p_node_grid is None: raise ValueError( "The pnode grid point cannot be none if the joint type is not FULL6DOF" ) nodal_matrix = self.getNodalMatrix(self._c.p_node_grid) body_to_joint = self.f["grids"][str(self._c.p_node_grid)]["pos"][:] # Add sensor nodes sensor_nodes = [] for gid, nm in self._c.sensor_nodes.items(): sensor_nodes.append( ModalNodeDS( name=nm, constraint_node=False, force_node=False, nodal_matrix=self.getNodalMatrix(gid), translation=self.f["grids"][str(gid)]["pos"][:], ) ) # Add force nodes force_nodes = [] for gid, nm in self._c.force_nodes.items(): force_nodes.append( ModalNodeDS( name=nm, constraint_node=False, force_node=True, nodal_matrix=self.getNodalMatrix(gid), translation=self.f["grids"][str(gid)]["pos"][:], ) ) # Add constraint nodes constraint_nodes = [] for gid, nm in self._c.constraint_nodes.items(): constraint_nodes.append( ModalNodeDS( name=nm, constraint_node=True, force_node=True, nodal_matrix=self.getNodalMatrix(gid), translation=self.f["grids"][str(gid)]["pos"][:], ) ) return ModalBodyDS( name=name, hinge=hinge, stiffness_vector=self.f["modal_analysis"]["stiffness"][self.n_rigid_modes :], damping_vector=damping, mass=self.f["rigid_mass_props"]["mass"][()] * self._c.mass_conversion, b2cm=self.f["rigid_mass_props"]["center_of_mass"][:] * self._c.distance_conversion, inertia=self.f["rigid_mass_props"]["inertia"][:] * self._c.mass_conversion * self._c.distance_conversion**2, nodal_matrix=nodal_matrix, body_to_joint=body_to_joint, sensor_nodes=sensor_nodes, force_nodes=force_nodes, constraint_nodes=constraint_nodes, )