def test_ntfl(): freq = np.arange(0.0, 25.1, 0.1) M1 = 10.0 M2 = 30.0 M3 = 3.0 M4 = 2.0 c1 = 15.0 c2 = 15.0 c3 = 15.0 k1 = 45000.0 k2 = 25000.0 k3 = 10000.0 # 2. Solve coupled system: MASS = np.array([[M1, 0, 0, 0], [0, M2, 0, 0], [0, 0, M3, 0], [0, 0, 0, M4]]) DAMP = np.array([ [c1, -c1, 0, 0], [-c1, c1 + c2, -c2, 0], [0, -c2, c2 + c3, -c3], [0, 0, -c3, c3], ]) STIF = np.array([ [k1, -k1, 0, 0], [-k1, k1 + k2, -k2, 0], [0, -k2, k2 + k3, -k3], [0, 0, -k3, k3], ]) F = np.vstack((np.ones((1, len(freq))), np.zeros((3, len(freq))))) fs = ode.SolveUnc(MASS, DAMP, STIF, pre_eig=True) fullsol = fs.fsolve(F, freq) A_coupled = fullsol.a[1] F_coupled = (M2 / 2 * A_coupled - k2 * (fullsol.d[2] - fullsol.d[1]) - c2 * (fullsol.v[2] - fullsol.v[1])) # 3. Solve for free acceleration; SOURCE setup: [m, b, k, bdof]: ms = np.array([[M1, 0], [0, M2 / 2]]) cs = np.array([[c1, -c1], [-c1, c1]]) ks = np.array([[k1, -k1], [-k1, k1]]) source = [ms, cs, ks, [[0, 1]]] fs_source = ode.SolveUnc(ms, cs, ks, pre_eig=True) sourcesol = fs_source.fsolve(F[:2], freq) As = sourcesol.a[1:2] # free acceleration # LOAD setup: [m, b, k, bdof]: ml = np.array([[M2 / 2, 0, 0], [0, M3, 0], [0, 0, M4]]) cl = np.array([[c2, -c2, 0], [-c2, c2 + c3, -c3], [0, -c3, c3]]) kl = np.array([[k2, -k2, 0], [-k2, k2 + k3, -k3], [0, -k3, k3]]) load = [ml, cl, kl, [[1, 0, 0]]] # 4. Use NT to couple equations. First value (rigid-body motion) # should equal ``Source Mass / Total Mass = 25/45 = 0.55555...`` # Results should match the coupled method. r = frclim.ntfl(source, load, As, freq) assert np.allclose(25 / 45, abs(r.R[0, 0])) assert np.allclose(A_coupled, r.A) assert np.allclose(F_coupled, r.F) assert_raises(ValueError, frclim.ntfl, source, load, As, freq[:-1]) r2 = frclim.ntfl(r.SAM, r.LAM, As, freq) assert r.SAM is r2.SAM assert r.LAM is r2.LAM assert np.all(r.TAM == r2.TAM) assert np.allclose(r.R, r2.R) assert np.allclose(r.A, r2.A) assert np.allclose(r.F, r2.F)
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
b = 2 * 0.02 * np.sqrt(k) mbk = (m, b, k) # load in forcing functions: toes = matlab.loadmat("toes_ffns.mat", squeeze_me=True, struct_as_record=False) toes["ffns"] = toes["ffns"][:3, ::2] toes["sr"] = toes["sr"] / 2 toes["t"] = toes["t"][::2] # form force transform: T = n2p.formdrm(nas, 0, [[8, 12], [24, 13]])[0].T # do pre-calcs and loop over all cases: ts = ode.SolveUnc(*mbk, 1 / toes["sr"], rf=rfmodes) LC = toes["ffns"].shape[0] t = toes["t"] for j, force in enumerate(toes["ffns"]): print("Running {} case {}".format(event, j + 1)) genforce = T @ ([[1], [0.1], [1], [0.1]] * force[None, :]) # solve equations of motion sol = ts.tsolve(genforce, static_ic=1) sol.t = t sol = DR.apply_uf(sol, *mbk, nas["nrb"], rfmodes) caseid = "{} {:2d}".format(event, j + 1) results.time_data_recovery(sol, nas["nrb"], caseid, DR, LC, j) results.calc_stat_ext(stats.ksingle(0.99, 0.90, LC)) # save results: