def calcAM(S, freq): """ Calculate apparent mass Parameters ---------- S : list/tuple Contains: ``[mass, damp, stiff, bdof]`` for structure. These are the source mass, damping, and stiffness matrices (see :class:`pyyeti.ode.SolveUnc`) and `bdof`, which is described below. freq : 1d array_like Frequency vector (Hz) Returns ------- AM : 3d ndarray Apparent mass matrix in a 3d array: .. code-block:: none boundary DOF x Frequencies x boundary DOF (response) (input) Notes ----- The `bdof` input defines boundary DOF in one of two ways as follows. Let `N` be total number of DOF in mass, damping, & stiffness. 1. If `bdof` is a 2d array_like, it is interpreted to be a data recovery matrix to the b-set (number b-set = ``bdof.shape[0]``). Structure is treated generically (uses :class:`pyyeti.ode.SolveUnc` with ``pre_eig=True`` to compute apparent mass). 2. Otherwise, `bdof` is assumed to be a 1d partition vector from full `N` size to b-set and structure is assumed to be in Craig-Bampton form (uses :func:`pyyeti.cb.cbtf` to compute apparent mass). The routine :func:`ntfl` example demonstrates this function. See also -------- :func:`ntfl`. """ lf = len(freq) m = S[0] b = S[1] k = S[2] bdof = np.atleast_1d(S[3]) if bdof.ndim == 2: # bdof is treated as a drm r = bdof.shape[0] T = bdof Frc = np.zeros((r, lf)) Acc = np.empty((r, lf, r), dtype=complex) fs = ode.SolveUnc(m, b, k, pre_eig=True) for direc in range(r): Frc[direc, :] = 1.0 sol = fs.fsolve(T.T @ Frc, freq) Acc[:, :, direc] = T @ sol.a Frc[direc, :] = 0.0 AM = np.empty((r, lf, r), dtype=complex) for j in range(lf): AM[:, j, :] = la.inv(Acc[:, j, :]) else: # bdof treated as a partition vector for CB model r = len(bdof) acce = np.eye(r) # Perform Baseshake # cbtf = craig bampton transfer function; this will genenerate # the corresponding interface force required to meet imposed # acceleration AM = np.empty((r, lf, r), dtype=complex) save = {} for direc in range(r): tf = cb.cbtf(m, b, k, acce[direc, :], freq, bdof, save) AM[:, :, direc] = tf.frc return AM
def test_cbtf(): nas = op2.rdnas2cam("tests/nas2cam_csuper/nas2cam") maa = nas["maa"][102] kaa = nas["kaa"][102] uset = nas["uset"][102] b = n2p.mksetpv(uset, "a", "b") q = ~b b = np.nonzero(b)[0] rb = n2p.rbgeom_uset(uset.iloc[b], 3) freq = np.arange(1.0, 80.0, 1.0) a = rb[:, :1] a2 = a.dot(np.ones((1, len(freq)))) a3 = rb[:, 0] pv = np.any(maa, axis=0) q = q[pv] pv = np.ix_(pv, pv) maa = maa[pv] kaa = kaa[pv] baa1 = np.zeros_like(maa) baa1[q, q] = 2 * 0.05 * np.sqrt(kaa[q, q]) baa2 = 0.1 * np.random.randn(*maa.shape) baa2 = baa2.dot(baa2.T) bb = np.ix_(b, b) for baa in [baa1, baa2]: for delq in [False, True]: if delq: m = maa[bb] c = baa[bb] k = kaa[bb] else: m = maa c = baa k = kaa tf = cb.cbtf(m, c, k, a, freq, b) tf2 = cb.cbtf(m, c, k, a2, freq, b) save = {} tf3 = cb.cbtf(m, c, k, a3, freq, b, save) tf4 = cb.cbtf(m, c, k, a2, freq, b, save) assert np.all(freq == tf.freq) assert np.all(freq == tf2.freq) assert np.all(freq == tf3.freq) assert np.all(freq == tf4.freq) assert np.allclose(tf.frc, tf2.frc) assert np.allclose(tf.a, tf2.a) assert np.allclose(tf.d, tf2.d) assert np.allclose(tf.v, tf2.v) assert np.allclose(tf.frc, tf3.frc) assert np.allclose(tf.a, tf3.a) assert np.allclose(tf.d, tf3.d) assert np.allclose(tf.v, tf3.v) assert np.allclose(tf.frc, tf4.frc) assert np.allclose(tf.a, tf4.a) assert np.allclose(tf.d, tf4.d) assert np.allclose(tf.v, tf4.v) # confirm proper solution: O = 2 * np.pi * freq velo = 1j * O * tf.d acce = 1j * O * velo f = m.dot(acce) + c.dot(velo) + k.dot(tf.d) assert np.allclose(acce, tf.a) assert np.allclose(velo, tf.v) assert np.allclose(f[b], tf.frc) if not delq: assert np.allclose(f[q], 0) assert_raises(ValueError, cb.cbtf, maa, baa1, kaa, a2[:, :3], freq, b) assert_raises(ValueError, cb.cbtf, maa, baa1, kaa, a2[:3, :], freq, b)