def test_cbcheck_determinate(): nas = op2.rdnas2cam("tests/nas2cam_csuper/nas2cam") se = 101 maa = nas["maa"][se] kaa = nas["kaa"][se] pv = np.any(maa, axis=0) pv = np.ix_(pv, pv) maa = maa[pv] kaa = kaa[pv] uset = nas["uset"][se] bset = n2p.mksetpv(uset, "p", "b") usetb = nas["uset"][se].iloc[bset] b = n2p.mksetpv(uset, "a", "b") q = ~b b = np.nonzero(b)[0] q = np.nonzero(q)[0] center = np.mean(usetb.iloc[::6, 1:], axis=0) rb = n2p.rbgeom_uset(usetb, center.values) # transform to single pt on centerline: # [b, q]_old = T*[b, q]_new # = [[rb, 0], [0, I]] * [b, q]_new T = np.zeros((len(b) + len(q), 6 + len(q))) T[:len(b), :6] = rb T[len(b):, 6:] = np.eye(len(q)) kaa = T.T @ kaa @ T maa = T.T @ maa @ T b = np.arange(6) # write to a string: with StringIO() as f: out = cb.cbcheck(f, maa, kaa, b, b[:6], em_filt=2) s = f.getvalue() s = s.splitlines() with open("tests/nas2cam_csuper/yeti_outputs/cbcheck_yeti_101_single.out" ) as f: sy = f.read().splitlines() assert s[0] == "Mass matrix is symmetric." assert s[1] == "Mass matrix is positive definite." assert s[2] == "Warning: stiffness matrix is not symmetric." j = [10] jy = [10] assert comptable(s, sy, j, jy, label="KBB =", skip=1) compare_cbcheck_output(s, sy)
def relative_displacement_dtm(nas, node_pairs): """ Form relative displacements data recovery matrix Parameters ---------- nas : dictionary This is the nas2cam dictionary: ``nas = op2.rdnas2cam()``. For a Craig-Bampton component, `nas` will also need to have `mug1` and `tug1` entries as shown in the example usage below. The matrix `mug1` is a data recovery matrix (to recover the displacements) and `tug1` is the DOF map to `mug1`: ``[node, dof]``. These get created during an "EXTSEOUT" Nastran run. The naming convention is:: tug1 --> nas['tug1'][se] mug1 --> nas['extse'][se]['mug1'] node_pairs : 2d array_like Four column matrix where each row contains the superelement ID and node ID for two non-coincident nodes: ``[SE1, Node1, SE2, Node2]``. The relative displacement between each node pair is computed along a vector from Node1 to Node2 with positive meaning increased distance. Returns ------- reldtm : 2d ndarray This is the displacement-dependent relative displacement recovery matrix. There is one row per node pair and the order given in `node_pairs` is preserved. dist : 1d ndarray Vector of distances between node pairs. labels : list List of labels briefly describing each row in `reldtm`. For example, if one row in `node_pairs` is: ``[101, 3, 102, 30]``, the label for that row would be: 'SE102,30 - SE101,3'. Notes ----- The algorithm works as follows: 1. Forms two data recovery matrices for the X, Y and Z DOF of each node listed in `node_pairs`. One (`DTMG`) recovers from residual g-set DOF and the other (`DTMQ`) recovers from the residual q-set. 2. Multiplies `DTMG` by the g-set residual rigid-body modes. The resulting 6-column matrix is passed to :func:`pyyeti.nastran.n2p.find_xyz_triples` which calculates the location of each node and applies coordinated transforms to `DTMQ` such that it recovers in the basic coordinate system for all nodes. 3. For each pair of nodes in `node_pairs`: a. Form a new rectangular coordinate system based at Node1 with the z-axis pointing to Node2. b. Transform the Node1 and Node2 rows of `DTMQ` to output in the new coordinate system. c. Form a single row of the final `reldtm` by subtracting the Node1 Z recovery from the Node2 Z recovery: ``dtm2[2] - dtm1[2]``. This means that a positive relative displacement corresponds to an increased distance between the two nodes. As a final check, the magnitude of the rigid-body part of `reldtm` is examined. If the largest value is greater than 1e-6 a warning message is printed. Example usage:: # load nastran data: nas = op2.rdnas2cam('nas2cam') SC = 101 n2p.addulvs(nas, SC) # read in more data for SC since it is a Craig-Bampton model: if 'tug1' not in nas: nas['tug1'] = {} nas['tug1'][SC] = nastran.rddtipch('outboard.pch') if 'extse' not in nas: nas['extse'] = {} nas['extse'][SC] = nastran.op4.read('outboard.op4') node_pairs = [ [SC, 3, SC, 10], [ 0, 11, SC, 18], ] reldtm, dist, lbls = relative_displacement_dtm( nas, node_pairs) # add the above items to the data recovery: drdefs = cla.DR_Def({'se': 0}) @cla.DR_Def.addcat def _(): name = 'reldisp' desc = 'Relative Displacements' units = 'in' labels = lbls drms = {name: reldtm} drfunc = f"Vars[se]['{name}'] @ sol.d" histpv = 'all' drdefs.add(**locals()) # prepare spacecraft data recovery matrices DR = cla.DR_Event() DR.add(nas, drdefs) # initialize results (ext, mnc, mxc for all drms) results = DR.prepare_results(mission, event) # solve equations of motion: ts = ode.SolveUnc(*mbk, h) sol = ts.tsolve(genforce, static_ic=1) sol.t = t sol = DR.apply_uf(sol, *mbk, nas['nrb'], rfmodes) results.time_data_recovery(sol, nas['nrb'], caseid, DR, LC, j) # write report of results: results.rpttab() Raises ------ ValueError When Node1 and Node2 of any node pair are coincident. """ # to get locations in basic, need rb modes relative to origin: rbg = n2p.rbgeom_uset(nas["uset"][0]) # call formdrm only once per superelement: node_pairs = np.atleast_2d(node_pairs) nrel = node_pairs.shape[0] dtmq = np.empty((nrel * 6, nas["lambda"][0].shape[0])) dtmg = np.empty((nrel * 6, 6)) senodes = np.vstack((node_pairs[:, :2], node_pairs[:, 2:])) senodesdof = np.array([[se, n, i] for se, n in senodes for i in range(1, 4)]) for se in set(senodes[:, 0]): pvd = senodesdof[:, 0] == se dof = senodesdof[pvd, 1:] try: tq = n2p.formdrm(nas, se, dof)[0] tx = n2p.formdrm(nas, se, dof, gset=True)[0] except ValueError: u1x = n2p.formulvs(nas, se, gset=True) pv = n2p.mkdofpv(nas["tug1"][se], "p", dof)[0] tq = nas["extse"][se]["mug1"][pv] @ nas["ulvs"][se] tx = nas["extse"][se]["mug1"][pv] @ u1x dtmq[pvd] = tq dtmg[pvd] = tx @ rbg mats = {"dtmq": dtmq} xyz = n2p.find_xyz_triples(dtmg, mats=mats, inplace=True) reldtm = np.empty((nrel, dtmq.shape[1])) dist = np.empty(nrel) ext_coords = xyz.coords.max(axis=0) for i in range(nrel): n1 = slice(i * 3, i * 3 + 3) n2 = slice((nrel + i) * 3, (nrel + i) * 3 + 3) # make transform from basic to C: a = xyz.coords[i * 3] b = xyz.coords[(i + nrel) * 3] # check for coincident nodes: if la.norm((b - a) / ext_coords) < 1e-5: raise ValueError(f"coincident nodes detected at index {i} of " f"`node_pairs`: {node_pairs[i]}") c = a + (b - a)[[1, 2, 0]] C = np.array([[1, 1, 0], a, b, c]) To_local = n2p.mkusetcoordinfo(C, None, {})[2:].T dtm1 = To_local @ dtmq[n1] dtm2 = To_local @ dtmq[n2] # positive for moving apart (b - a > 0): reldtm[i] = dtm2[2] - dtm1[2] dist[i] = la.norm(b - a) labels = [ f"SE{se2},{n2} - SE{se1},{n1}" for se1, n1, se2, n2 in node_pairs ] return reldtm, dist, labels
def test_mk_net_drms(): pth = os.path.dirname(inspect.getfile(cb)) pth = os.path.join(pth, "..") pth = os.path.join(pth, "tests") pth = os.path.join(pth, "nas2cam_csuper") # Load the mass and stiffness from the .op4 file # This loads the data into a dict: mk = op4.load(os.path.join(pth, "inboard.op4")) maa = mk["mxx"][0] kaa = mk["kxx"][0] # Get the USET table The USET table has the boundary DOF # information (id, location, coordinate system). This is needed # for superelements with an indeterminate interface. The nastran # module has the function bulk2uset which is handy for forming the # USET table from bulk data. uset, coords = nastran.bulk2uset(os.path.join(pth, "inboard.asm")) # uset[::6, [0, 3, 4, 5]] # array([[ 3., 600., 0., 300.], # [ 11., 600., 300., 300.], # [ 19., 600., 300., 0.], # [ 27., 600., 0., 0.]]) # x s/c is axial (see figure in cbtf or cbcheck tutorial) # - make z l/v axial, but pointing down # z s/c ^ y l/v # \ | / y s/c # \|/ # <------ # x l/v sccoord = [ [900, 1, 0], [0, 0, 0], [1, 1, 0], # z is 45 deg between x & y of l/v [0, 0, -1], ] # x is -z l/v c = np.cos(45 / 180 * np.pi) Tl2s = np.array([[0, 0, -1.0], [-c, c, 0], [c, c, 0]]) # Form b-set partition vector into a-set # In this case, we already know the b-set are first: n = uset.shape[0] b = np.arange(n) # array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, # 16, 17, 18, 19, 20, 21, 22, 23]) # convert s/c from mm, kg --> m, kg ref = [600, 150, 150] conv = (0.001, 1.0) g = 9.80665 u = n2p.addgrid(None, 1, "b", sccoord, [0, 0, 0], sccoord) Tsc2lv = np.zeros((6, 6)) T = u.iloc[3:, 1:] Tsc2lv[:3, :3] = T Tsc2lv[3:, 3:] = T assert np.allclose(Tl2s.T, Tsc2lv[:3, :3]) net = cb.mk_net_drms( maa, kaa, b, uset=uset, ref=ref, sccoord=sccoord, conv=conv, g=g ) usetbq, c, bset = nastran.asm2uset(os.path.join(pth, "inboard.asm")) with assert_raises(ValueError) as cm: cb.mk_net_drms( maa, kaa, b[:3], uset=usetbq, ref=ref, sccoord=Tl2s, conv=conv, g=g ) the_msg = str(cm.exception) assert 0 == the_msg.find( f"number of rows in `uset` is {uset.shape[0]}, but must " f"equal len(b-set) (3)" ) net2 = cb.mk_net_drms( maa, kaa, b, uset=usetbq, ref=ref, sccoord=Tl2s, conv=conv, g=g ) # rb modes in system units: uset2, ref2 = cb.uset_convert(uset, ref, conv) rb = n2p.rbgeom_uset(uset2, ref2) l_sc = net.ifltma_sc[:, :n] @ rb l_lv = net.ifltma_lv[:, :n] @ rb l_scd = net.ifltmd_sc[:, :n] @ rb l_lvd = net.ifltmd_lv[:, :n] @ rb a_sc = net.ifatm_sc[:, :n] @ rb a_lv = net.ifatm_lv[:, :n] @ rb c_sc = net.cgatm_sc[:, :n] @ rb c_lv = net.cgatm_lv[:, :n] @ rb sbe = np.eye(6) sbe[:3] *= 1 / g assert np.allclose(a_sc, sbe) # calc what the interface forces should be: # - acceleration = 1 m/s**2 = 1000 mm/s**2 # - the forces should be very similar to the 6x6 mass # matrix at the reference point ... which, conveniently, # is provided in the cbtf tutorial: mass = np.array( [ [1.755, 0.0, -0.0, 0.0, 0.0, 0.0], [0.0, 1.755, -0.0, -0.0, 0.0, 772.22], [-0.0, -0.0, 1.755, 0.0, -772.22, -0.0], [0.0, -0.0, 0.0, 35905.202, -0.0, -0.0], [0.0, 0.0, -772.22, -0.0, 707976.725, 109.558], [0.0, 772.22, -0.0, -0.0, 109.558, 707976.725], ] ) sbe = mass sbe[:, :3] *= 1000 # scale up translations assert abs(sbe - l_sc).max() < 0.5 assert np.allclose(Tsc2lv @ a_sc, a_lv) assert np.allclose(Tsc2lv @ c_sc, c_lv) assert abs(l_scd).max() < 1e-6 * abs(l_sc).max() assert abs(l_lvd).max() < 1e-6 * abs(l_lv).max() scale = np.array([[1000], [1000], [1000], [1000000], [1000000], [1000000]]) assert np.allclose((1 / scale) * (Tsc2lv @ l_sc), l_lv) # height and mass values from cbcheck tutorial (and then refined): m_kg = 1.75505183 h_m = 1.039998351 - 0.6 assert abs(net.height_lv - h_m) < 0.000001 assert abs(net.weight_lv - m_kg * g) < 0.000001 assert abs(net.height_sc - 1000 * h_m) < 0.000001 * 1000 assert abs(net.weight_sc - 1000 * m_kg * g) < 0.000001 * 1000 assert net.scaxial_sc == 0 assert net.scaxial_lv == 2 compare_nets(net, net2) # check the natural unit output: net3 = cb.mk_net_drms( maa, kaa, b, uset=uset, ref=ref, sccoord=Tl2s, conv=conv, g=g, tau=("mm", "m") ) for drm in ("ifatm", "cgatm"): if drm == "ifatm": # only ifatm has 12 rows drm1 = getattr(net, drm) drm3 = getattr(net3, drm) assert np.allclose(drm3[:3], drm1[:3] * g * 1000) assert np.allclose(drm3[3:6], drm1[3:6]) assert np.allclose(drm3[6:9], drm1[6:9] * g) assert np.allclose(drm3[9:], drm1[9:]) for ext, factor in (("_sc", 1000), ("_lv", 1)): drm1 = getattr(net, drm + ext) drm3 = getattr(net3, drm + ext) assert np.allclose(drm3[:3], drm1[:3] * g * factor) assert np.allclose(drm3[3:], drm1[3:]) labels3 = [i.replace("g", "mm/s^2") for i in net.ifatm_labels[:6]] + [ i.replace("g", "m/s^2") for i in net.ifatm_labels[6:] ] print(labels3) print() print(net3.ifatm_labels) assert labels3 == net3.ifatm_labels net4 = cb.mk_net_drms( maa, kaa, b, uset=uset, ref=ref, sccoord=Tl2s, conv=conv, g=g, tau=("g", "m") ) for drm in ("ifatm", "cgatm"): if drm == "ifatm": # only ifatm has 12 rows drm1 = getattr(net, drm) drm3 = getattr(net3, drm) drm4 = getattr(net4, drm) assert np.allclose(drm4[:3], drm1[:3]) assert np.allclose(drm4[3:6], drm1[3:6]) assert np.allclose(drm4[6:9], drm3[6:9]) assert np.allclose(drm4[9:], drm3[9:]) for ext, net_ in (("_sc", net), ("_lv", net3)): drm1 = getattr(net_, drm + ext) drm4 = getattr(net4, drm + ext) assert np.allclose(drm4[:3], drm1[:3]) assert np.allclose(drm4[3:], drm1[3:]) labels4 = net.ifatm_labels[:6] + net3.ifatm_labels[6:] assert labels4 == net4.ifatm_labels # test mixed b-set/q-set: na = maa.shape[0] q = np.r_[n:na] newb = np.r_[0:12, na - 12 : na] newq = np.r_[12 : na - 12] kaa_newb = np.empty((na, na)) maa_newb = np.empty((na, na)) for newr, oldr in [(newb, b), (newq, q)]: for newc, oldc in [(newb, b), (newq, q)]: maa_newb[np.ix_(newr, newc)] = maa[np.ix_(oldr, oldc)] kaa_newb[np.ix_(newr, newc)] = kaa[np.ix_(oldr, oldc)] net5 = cb.mk_net_drms( maa_newb, kaa_newb, newb, uset=uset, ref=ref, sccoord=sccoord, conv=conv, g=g, reorder=False, ) assert np.allclose(net5.ifltma[:, newb], net.ifltma[:, b]) assert np.allclose(net5.ifltma[:, newq], net.ifltma[:, q]) net6 = cb.mk_net_drms( maa_newb, kaa_newb, newb, uset=uset, ref=ref, sccoord=sccoord, conv=conv, g=g, reorder=False, rbe3_indep_dof=123456, ) assert np.allclose(net6.ifltma, net5.ifltma) # translations match: assert np.allclose(net6.ifatm_sc[:3], net5.ifatm_sc[:3]) # rotations do not: assert not np.allclose(net6.ifatm_sc[3:], net5.ifatm_sc[3:]) vec = ytools.mkpattvec([3, 4, 5], len(newb), 6).ravel() assert (net5.ifatm_sc[:, newb[vec]] == 0.0).all() assert not (net6.ifatm_sc[:, newb[vec]] == 0.0).all()
def test_rbmultchk(): nas = op2.rdnas2cam("tests/nas2cam_csuper/nas2cam") se = 101 maa = nas["maa"][se] kaa = nas["kaa"][se] pv = np.any(maa, axis=0) pv = np.ix_(pv, pv) maa = maa[pv] kaa = kaa[pv] uset = nas["uset"][se] bset = n2p.mksetpv(uset, "p", "b") usetb = nas["uset"][se].iloc[bset] b = n2p.mksetpv(uset, "a", "b") q = ~b b = np.nonzero(b)[0] grids = [[11, 123456], [45, 123456], [60, 123456]] drm101, dof101 = n2p.formtran(nas, 101, grids) # write and read a file: f = tempfile.NamedTemporaryFile(delete=False) name = f.name f.close() rb = n2p.rbgeom_uset(usetb) cb.rbmultchk(name, drm101, "DRM101", rb) with open(name) as f: sfile = f.read() os.remove(name) # test rbscale and unit scale: with StringIO() as f: cb.rbmultchk(f, 0.00259 * drm101, "DRM101", 100 * rb) s = f.getvalue() pos = s.find(" which is: ") pos2 = s[pos:].find("\n") assert math.isclose(float(s[pos + 10 : pos + pos2]), 100) s = s.splitlines() table, nj1 = gettable(s, 15, 0, "Absolute Maximums", 3) sbe = np.array( [ [600, 300, 300, 0.00259], [600, 300, 300, 0.00259], [600, 300, 300, 0.00259], [150, -930, 150, 0.00259], [600, 300, 300, 0.00259], [150, -930, 150, 0.00259], ] ) assert np.allclose(table[:, 1:5], sbe) # write to a string: with StringIO() as f: cb.rbmultchk(f, drm101, "DRM101", rb) s = f.getvalue() assert sfile == s # add q-set rows to rb: nq = np.count_nonzero(q) rb2 = np.vstack((rb, np.zeros((nq, 6)))) with StringIO() as f: cb.rbmultchk(f, drm101, "DRM101", rb2) s2 = f.getvalue() assert s2 == s # check results when b-set are last: drm101_last = np.hstack((drm101[:, q], drm101[:, b])) with StringIO() as f: cb.rbmultchk(f, drm101_last, "DRM101", rb, bset="last") s2 = f.getvalue() assert s2 == s # check results when b-set are last ... using pv: with StringIO() as f: bsetpv = np.zeros((len(b) + nq), bool) bsetpv[-len(b) :] = True cb.rbmultchk(f, drm101_last, "DRM101", rb, bset=bsetpv) s2 = f.getvalue() assert s2 == s with StringIO() as f: assert_raises( ValueError, cb.rbmultchk, f, drm101, "asdf", rb, bset="bad string" ) # trim q-set columns out of drm: labels = [str(i[0]) + " " + str(i[1]) for i in dof101] with StringIO() as f: cb.rbmultchk( f, drm101[:, b], "DRM101", rb, drm2=drm101[:, b], prtnullrows=True, labels=labels, ) s2 = f.getvalue() # row 16 is now all zeros ... not comparable drm2 = drm101.copy() drm2[15] = 0 with StringIO() as f: cb.rbmultchk(f, drm2, "DRM101", rb, drm2=drm2, prtnullrows=True, labels=labels) s3 = f.getvalue() assert s2 == s3 s = s.splitlines() with open("tests/nas2cam_csuper/yeti_outputs/rbmultchk_yeti_101.out") as f: sy = f.read().splitlines() j = [2] jy = [2] assert comptable(s, sy, j, jy, label="Extreme ", skip=3, col=11) assert comptable(s, sy, j, jy, label=" Row ", skip=2, col=68) assert comptable(s, sy, j, jy, label=" Row ", skip=2) assert comptable(s, sy, j, jy, label=" Row ", skip=2)
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)
def test_cbcheck_determinate(): nas = op2.rdnas2cam("tests/nas2cam_csuper/nas2cam") se = 101 maa = nas["maa"][se] kaa = nas["kaa"][se] pv = np.any(maa, axis=0) pv = np.ix_(pv, pv) maa = maa[pv] kaa = kaa[pv] uset = nas["uset"][se] bset = n2p.mksetpv(uset, "p", "b") usetb = nas["uset"][se].iloc[bset] b = n2p.mksetpv(uset, "a", "b") q = ~b b = np.nonzero(b)[0] q = np.nonzero(q)[0] center = np.mean(usetb.iloc[::6, 1:], axis=0) rb = n2p.rbgeom_uset(usetb, center.values) # transform to single pt on centerline: # [b, q]_old = T*[b, q]_new # = [[rb, 0], [0, I]] * [b, q]_new T = np.zeros((len(b) + len(q), 6 + len(q))) T[: len(b), :6] = rb T[len(b) :, 6:] = np.eye(len(q)) kaa = T.T @ kaa @ T maa = T.T @ maa @ T b = np.arange(6) # write to a string: with StringIO() as f: out = cb.cbcheck(f, maa, kaa, b, b[:6], em_filt=2) s = f.getvalue() s = s.splitlines() with open("tests/nas2cam_csuper/yeti_outputs/cbcheck_yeti_101_single.out") as f: sy = f.read().splitlines() assert s[0] == "Mass matrix is symmetric." assert s[1] == "Mass matrix is positive definite." assert s[2] == "Warning: stiffness matrix is not symmetric." j = [10] jy = [10] assert comptable(s, sy, j, jy, label="KBB =", skip=1) compare_cbcheck_output(s, sy) # check with no em filter: with StringIO() as f: out2 = cb.cbcheck(f, maa, kaa, b, b[:6]) s2 = f.getvalue() s2 = s2.splitlines() s_unique = [i for i in s if i not in s2] # ['Printing only the modes with at least 2.0% effective mass.', # 'The sum includes all modes.'] s2_unique = [i for i in s2 if i not in s] # [' 5 7.025 0.00 0.00 0.00 0.00 0.88 0.00', # ' 6 7.025 0.00 0.00 0.00 0.00 0.00 0.00', # ' 7 10.913 0.00 0.00 0.00 0.00 0.00 1.52', # ' 11 25.135 0.00 0.00 0.00 0.00 0.00 0.42', # ' 12 25.140 1.03 0.00 0.00 0.00 0.00 0.00', # ' 13 42.173 0.00 0.00 0.00 0.51 0.00 0.00', # ' 14 42.193 0.00 0.00 1.04 0.00 1.02 0.00', # ' 16 46.895 0.00 0.00 0.00 0.00 0.00 0.00', # ' 17 69.173 0.00 0.13 0.00 0.00 0.00 0.99'] assert len(s2) > len(s) assert len(s_unique) == 2 assert len(s2_unique) == 9
def test_cbcheck_indeterminate(): nas = op2.rdnas2cam("tests/nas2cam_csuper/nas2cam") se = 101 maa = nas["maa"][se] kaa = nas["kaa"][se] pv = np.any(maa, axis=0) pv = np.ix_(pv, pv) maa = maa[pv] kaa = kaa[pv] uset = nas["uset"][se] bset = n2p.mksetpv(uset, "p", "b") usetb = nas["uset"][se].iloc[bset] b = n2p.mksetpv(uset, "a", "b") q = ~b b = np.nonzero(b)[0] # write and read a file: f = tempfile.NamedTemporaryFile(delete=False) name = f.name f.close() # m, k, bset, rbs, rbg, rbe, usetconv = cb.cbcheck( out = cb.cbcheck(name, maa, kaa, b, b[:6], uset, em_filt=2) with open(name) as f: sfile = f.read() os.remove(name) assert (out.m == maa).all() assert (out.k == kaa).all() assert out.uset.equals(usetb) rbg = n2p.rbgeom_uset(out.uset) assert np.allclose(rbg, out.rbg) rbg_s = np.vstack((la.solve(rbg[:6].T, rbg.T).T, np.zeros((q[q].size, 6)))) assert abs(out.rbs - rbg_s).max() < 1e-5 assert abs(out.rbe - rbg_s).max() < 1e-5 # write to a string: with StringIO() as f: out = cb.cbcheck(f, maa, kaa, b, b[:6], usetb, em_filt=2) s = f.getvalue() assert sfile == s s = s.splitlines() with open("tests/nas2cam_csuper/yeti_outputs/cbcheck_yeti_101.out") as f: sy = f.read().splitlines() assert s[0] == "Mass matrix is symmetric." assert s[1] == "Mass matrix is positive definite." assert s[2] == "Stiffness matrix is symmetric." compare_cbcheck_output(s, sy) with StringIO() as f: out = cb.cbcheck(f, maa, kaa, b, b[:6], usetb, em_filt=2, conv="e2m") out2 = cb.cbcheck(f, out.m, out.k, b, b[:6], out.uset, em_filt=2, conv="m2e") assert np.allclose(out2.uset, usetb) assert np.allclose(maa, out2.m) assert np.allclose(kaa, out2.k) # check for error catches: with StringIO() as f: assert_raises( ValueError, cb.cbcheck, f, maa, kaa, b, b[:6], usetb.iloc[:-6], em_filt=2 )
def test_cglf_moment_signs(): pth = os.path.dirname(inspect.getfile(cb)) pth = os.path.join(pth, "..") pth = os.path.join(pth, "tests") pth = os.path.join(pth, "cla_test_data") se = 101 uset, coords = nastran.bulk2uset(os.path.join(pth, "outboard.asm")) dct = op4.read(os.path.join(pth, "outboard.op4")) maa = dct["mxx"] kaa = dct["kxx"] atm = dct["mug1"] ltm = dct["mef1"] pch = os.path.join(pth, "outboard.pch") atm_labels = [ "Grid {:4d}-{:1d}".format(grid, dof) for grid, dof in nastran.rddtipch(pch) ] ltm_labels = [ "CBAR {:4d}-{:1d}".format(cbar, arg) for cbar, arg in nastran.rddtipch(pch, "tef1") ] nb = uset.shape[0] nq = maa.shape[0] - nb bset = np.arange(nb) qset = np.arange(nq) + nb ref = [600.0, 150.0, 150.0] g = 9806.65 # use addgrid to get coordinate transformations from lv to sc: cid = [1, 0, 0] A = [0, 0, 0] # define sc in terms of lv coords: # (all drawn out by hand) BC = [ [[0, 0, 1.0], [1.0, 0, 0]], # lv x is up [[0, 0, -1.0], [0, 1.0, 0]], # lv y is up [[-1.0, 0, 0.0], [0, 0, 1.0]], # lv z is up [[0, 0, -1.0], [-1.0, 0, 0]], # lv x is down [[0, 0, 1.0], [0, -1.0, 0]], # lv y is down [[0, -1.0, 0], [0, 0, -1.0]], # lv z is down ] Ts = [] nets = [] rb = n2p.rbgeom_uset(uset, ref) rbcglfa = [] for bc in BC: CI = n2p.mkusetcoordinfo([cid, A, *bc], None, {}) T = CI[2:] Ts.append(T) net = cb.mk_net_drms(maa, kaa, bset, uset=uset, ref=ref, g=g, sccoord=T) nets.append(net) rba = net.cglfa[:, :24] @ rb rbcglfa.append(rba) # sc rows: assert np.all(np.sign(rba[1, [1, 5]]) == np.sign(rba[3, [1, 5]])) assert np.all(np.sign(rba[2, [2, 4]]) == np.sign(rba[4, [2, 4]])) # lv rows: assert np.all(np.sign(rba[6, [1, 5]]) == np.sign(rba[8, [1, 5]])) assert np.all(np.sign(rba[7, [2, 4]]) == np.sign(rba[9, [2, 4]])) wh_sc = nets[0].weight_sc * nets[0].height_sc wh_lv = nets[0].weight_lv * nets[0].height_lv n = nets[0].cgatm_sc.shape[1] # x is down: cgdrm = np.vstack( ( # 5 s/c rows nets[0].cgatm_sc[:3], -nets[0].ifltma_sc[5] / wh_sc, nets[0].ifltma_sc[4] / wh_sc, # 5 l/v rows nets[0].cgatm_lv[:3], -nets[0].ifltma_lv[5] / wh_lv, nets[0].ifltma_lv[4] / wh_lv, # 4 RSS rows ... filled in during data recovery np.zeros((4, n)), ) ) assert np.allclose(cgdrm, nets[0].cglfa)
def test_mk_net_drms_6dof(): # same as above, but reduced to single point interface pth = os.path.dirname(inspect.getfile(cb)) pth = os.path.join(pth, "..") pth = os.path.join(pth, "tests") pth = os.path.join(pth, "nas2cam_csuper") mk = op4.load(os.path.join(pth, "inboard.op4")) maa = mk["mxx"][0] kaa = mk["kxx"][0] uset, coords = nastran.bulk2uset(os.path.join(pth, "inboard.asm")) n = uset.shape[0] b = np.arange(n) q = np.arange(n, maa.shape[0]) ttl = maa.shape[0] # reduce to single point interface: rb = n2p.rbgeom_uset(uset, [600, 150, 150]) # old = {b_24} = {rb.T @ b_6} = [rb.T 0_6?] {b_6} # {q_?} {q_?} [0_?6 I??] {q_?} trans = np.zeros((len(q) + 6, ttl)) trans[:6, :n] = rb.T trans[6:, n:] = np.eye(len(q)) maa = trans @ maa @ trans.T kaa = trans @ kaa @ trans.T # no conversion, no coordinate change: g = 9.80665 n = 6 b = np.arange(n) net = cb.mk_net_drms(maa, kaa, b, g=g) net2 = cb.mk_net_drms(maa, kaa, b, g=g, bsubset=b) l_sc = net.ifltma_sc[:, :n] l_lv = net.ifltma_lv[:, :n] l_scd = net.ifltmd_sc[:, :n] l_lvd = net.ifltmd_lv[:, :n] a_sc = net.ifatm_sc[:, :n] a_lv = net.ifatm_lv[:, :n] c_sc = net.cgatm_sc[:, :n] c_lv = net.cgatm_lv[:, :n] sbe = np.eye(6) sbe[:3] *= 1 / g assert np.allclose(a_sc, sbe) mass = np.array( [ [1.755, 0.0, -0.0, 0.0, 0.0, 0.0], [0.0, 1.755, -0.0, -0.0, 0.0, 772.22], [-0.0, -0.0, 1.755, 0.0, -772.22, -0.0], [0.0, -0.0, 0.0, 35905.202, -0.0, -0.0], [0.0, 0.0, -772.22, -0.0, 707976.725, 109.558], [0.0, 772.22, -0.0, -0.0, 109.558, 707976.725], ] ) sbe = mass assert abs(sbe - l_sc).max() < 0.0005 Tsc2lv = np.eye(6) assert np.allclose(Tsc2lv @ a_sc, a_lv) assert np.allclose(Tsc2lv @ c_sc, c_lv) assert abs(l_scd).max() < 1e-6 * abs(l_sc).max() assert abs(l_lvd).max() < 1e-6 * abs(l_lv).max() assert np.allclose((Tsc2lv @ l_sc), l_lv) # height and mass values from cbcheck tutorial (and then refined): m_kg = 1.75505183 h_m = 1039.998351 - 600 assert abs(net.height_lv - h_m) < 0.0001 assert abs(net.weight_lv - m_kg * g) < 0.0001 assert abs(net.height_sc - h_m) < 0.0001 assert abs(net.weight_sc - m_kg * g) < 0.0001 assert net.scaxial_sc == 0 assert net.scaxial_lv == 0 compare_nets(net, net2)