Source code for pyEMA.normal_modes

import numpy as np

[docs]def complex_to_normal_mode(mode, max_dof=50, long=True): """Transform a complex mode shape to normal mode shape. The real mode shape should have the maximum correlation with the original complex mode shape. The vector that is most correlated with the complex mode, is the real part of the complex mode when it is rotated so that the norm of its real part is maximized. [1] ``max_dof`` and ``long`` arguments are given for modes that have a large number of degrees of freedom. See ``_large_normal_mode_approx()`` for more details. Literature: [1] Gladwell, H. Ahmadian GML, and F. Ismail. "Extracting Real Modes from Complex Measured Modes." :param mode: np.ndarray, a mode shape to be transformed. Can contain a single mode shape or a modal matrix `(n_locations, n_modes)`. :param max_dof: int, maximum number of degrees of freedom that can be in a mode shape. If larger, ``_large_normal_mode_approx()`` function is called. Defaults to 50. :param long: bool, If True, the start in stepping itartion is altered, the angles of rotation are averaged (more in ``_large_normal_mode_approx()``). This is needed only when ``max_dof`` is exceeded. The normal modes are more closely related to the ones computed with an entire matrix. Defaults to True. :return: normal mode-shape """ if mode.ndim == 1: mode = mode[None, :, None] elif mode.ndim == 2: mode = mode.T[:, :, None] else: raise Exception(f'`mode` must have 1 or 2 dimensions ({mode.ndim}).') if mode.shape[1] > max_dof: return _large_normal_mode_approx(mode[:, :, 0].T, step=int(np.ceil(mode.shape[1] / max_dof)) + 1, long=long) # Normalize modes so that norm == 1.0 _norm = np.linalg.norm(mode, axis=1)[:, None, :] mode = mode / _norm mode_T = np.transpose(mode, [0, 2, 1]) U = np.matmul(np.real(mode), np.real(mode_T)) + np.matmul(np.imag(mode), np.imag(mode_T)) val, vec = np.linalg.eig(U) i = np.argmax(np.real(val), axis=1) normal_mode = np.real([v[:, _] for v, _ in zip(vec, i)]).T return normal_mode
[docs]def _large_normal_mode_approx(mode, step, long): """Get normal mode approximation for large modes. In cases, where ``mode`` has ``n`` coordinates and ``n`` is large, this would result in a matrix ``U`` of size ``n x n``. To find eigenvalues of this non-sparse matrix is computationally expensive. The solution is to find the angle of the rotation for the vector - this is done using only every ``step`` element of ``mode``. The entire ``mode`` is then rotated, thus the full normal mode is obtained. To ensure the influence of all the coordinates, a ``long`` parameter can be used. Multiple angles of rotation are computed and then averaged. :param mode: a 2D mode shape or modal matrix ``(n_locations x n_modes)`` :param step: int, every ``step`` elemenf of ``mode`` will be taken into account for angle of rotation calculation. :param long: bool, if True, the angle of rotation is computed iteratively for different starting positions (from 0 to ``step``), when every ``step`` element is taken into account. :return: normal mode or modal matrix of ``mode``. """ if mode.ndim == 1: mode = mode[:, None] elif mode.ndim > 2: raise Exception(f'`mode` must have 1 or 2 dimensions ({mode.ndim})') mode = mode / np.linalg.norm(mode, axis=0)[None, :] if long: step_long = step else: step_long = 1 Alpha = [] for i in range(step_long): mode_step = mode[i::step] mode_normal_step = complex_to_normal_mode(mode_step) v1 = np.concatenate((np.real(mode_step)[:, :, None], np.imag(mode_step)[:, :, None]), axis=2) v2 = np.concatenate((np.real(mode_normal_step)[:, :, None], np.imag(mode_normal_step)[:, :, None]), axis=2) v1 /= np.linalg.norm(v1, axis=2)[:, :, None] v2 /= np.linalg.norm(v2, axis=2)[:, :, None] dot_product = np.array([np.matmul(np.transpose(v1[:, j, :, None], [0, 2, 1]), v2[:, j, :, None]) for j in range(v1.shape[1])]) angles = np.arccos(dot_product) alpha = np.mean(angles[:, :, 0, 0], axis=1) Alpha.append(alpha) alpha = np.mean(Alpha, axis=0)[None, :] mode_normal_full = np.real(mode)*np.cos(alpha) - np.imag(mode)*np.sin(alpha) mode_normal_full /= np.linalg.norm(mode_normal_full, axis=0)[None, :] return mode_normal_full