def test_undamped_2DOF(self, M1, M2, K1, K2): """ Obtain angular natural frequencies for a 2 degree of freedom undamped system as per the following sketch : ![2dof_undamped](../img/2dof_undamped.PNG) Expected results are obtained using the following equations, reproduced from John Rees' (EJR, COWI UK Bridge) TMD lecture notes: [PDF](../ref/Lecture Notes - Damping and Tuned Mass Dampers (Rev. 0.7).pdf) ![angular_freq_eqns](../img/2dof_undamped_angularFreq.PNG) """ # Create msd_chain system msd_sys = msd_chain.MSD_Chain(M_vals=[M1, M2], K_vals=[K1, K2], C_vals=[0, 0], showMsgs=False) # Obtain undamped natural frequencies w_n = msd_sys.CalcEigenproperties()["w_n"] # Note eigenvalues are in conjugate pairs w1 = numpy.abs(w_n[1]) w2 = numpy.abs(w_n[3]) # Implement equations presented in EJR's note w1_2_bar = K1 / (M1 + M2) w2_2_bar = K2 / M2 mu = M2 / M1 term1 = 0.5 * (w1_2_bar + w2_2_bar) * (1 + mu) term2 = 0.5 * (((w1_2_bar + w2_2_bar) * (1 + mu))**2 - 4 * w1_2_bar * w2_2_bar * (1 + mu))**0.5 w1_expected = (term1 - term2)**0.5 w2_expected = (term1 + term2)**0.5 # Check results agree self.assertAlmostEqual(w1, w1_expected) self.assertAlmostEqual(w2, w2_expected)
def ResponseSpectrum(accFunc, tResponse, T_vals=None, eta=0.05, makePlot=True, **kwargs): """ Function to express ground acceleration time series as a seismic response spectrum *** _Seismic response spectra_ are used to summarises the vibration response of a SDOF oscillator in response to a transient ground acceleration time series. Seismic response spectra therefore represent a useful way of quantifying and graphically illustrating the severity of a given ground acceleration time series. *** Required: * `accFunc`, function a(t) defining the ground acceleration time series (usually this is most convenient to supply via an interpolation function) * `tResponse`, time interval over which to carry out time-stepping analysis. Set this to be at least the duration of the input acceleration time series! **Important note**: This routine expects a(t) to have units of m/s<sup>2</sup>. It is common (at least in the field of seismic analysis) to quote ground accelerations in terms of 'g'. Any such time series must be pre-processed by multiplying by g=9.81m/s<sup>2</sup>) prior to using this function, such that the supplied `accFunc` returns ground acceleration in m/s<sup>2</sup>. *** Optional: * `T_vals`, _list_, periods (in seconds) at which response spectra are to be evaluated. If _None_ will be set to logarithmically span the range [0.01,10.0] seconds, which is suitable for most applications. * `eta`, damping ratio to which response spectrum obtained is applicable. 5% is used by default, as this a common default in seismic design. * `makePlot`, _boolean_, controls whether results are plotted `kwargs` may be used to pass additional arguments down to `TStep` object that is used to implement time-stepping analysis. Refer `tstep` docs for further details *** Returns: Values are returned as a dictionary, containing the following entries: * `T_vals`, periods at which spectra are evaluated. * `S_D`, relative displacement spectrum (in m) * `S_V`, relative velocity spectrum (in m/s) * `S_A`, absolute acceleration spectrum (in m/s<sup>2</sup>) * `PSV`, psuedo-velocity spectrum (in m/s) * `PSA`, psuedo-acceleration spectrum (in m/s<sup>2</sup>) In addition, if `makePlot=True`: * `fig`, figure object for plot """ # Handle optional inputs if T_vals is None: T_vals = numpy.logspace(-2,1,100) T_vals = numpy.ravel(T_vals).tolist() # Print summary of key inputs if hasattr(accFunc,"__name__"): print("Ground motion function supplied: %s" % accFunc.__name__) print("Time-stepping analysis interval: [%.2f, %.2f]" % (0,tResponse)) print("Number of SDOF oscillators to be analysed: %d" % len(T_vals)) print("Damping ratio for SDOF oscillators: {:.2%}".format(eta)) # Loop through all periods M = 1.0 # unit mass for all oscillators results_list = [] print("Obtaining SDOF responses to ground acceleration...") for i, _T in enumerate(T_vals): period_str = "Period %.2fs" % _T if i % 10 == 0: print(" Period %d of %d" % (i,len(T_vals))) # Define SDOF oscillator SDOF_sys = msd_chain.MSD_Chain(name=period_str, M_vals = M, f_vals = 1/_T, eta_vals = eta, showMsgs=False) # Add output matrix to extract results SDOF_sys.AddOutputMtrx(output_mtrx=numpy.identity(3), output_names=["RelDisp","RelVel","Acc"]) # Define forcing function def forceFunc(t): return -M*accFunc(t) # Define time-stepping analysis tstep_obj = tstep.TStep(SDOF_sys, tStart=0, tEnd=tResponse, force_func_dict={SDOF_sys:forceFunc}, retainResponseTimeSeries=True) # Run time-stepping analysis and append results results_obj = tstep_obj.run(verbose=False) results_list.append(results_obj) # Obtain absolute acceleration by adding back in ground motion results_obj.responses_list[0][2,:] += accFunc(results_obj.t.T) # Recalculate statistics results_obj.CalcResponseStats(verbose=False) # Tidy up del SDOF_sys # Collate absmax statistics print("Retrieving maximum response statistics...") S_D = numpy.asarray([x.response_stats['absmax'][0] for x in results_list]) S_V = numpy.asarray([x.response_stats['absmax'][1] for x in results_list]) S_A = numpy.asarray([x.response_stats['absmax'][2] for x in results_list]) # Evaluate psuedo-specta omega = numpy.divide(2*numpy.pi,T_vals) PSV = omega * S_D PSA = omega**2 * S_D if makePlot: fig, axarr = plt.subplots(3, sharex=True) fig.suptitle("Response spectra: {:.0%} damping".format(eta)) ax = axarr[0] ax.plot(T_vals,S_D) ax.set_ylabel("SD (m)") ax.set_title("Relative displacement") ax = axarr[1] ax.plot(T_vals,S_V) ax.plot(T_vals,PSV) ax.legend((ax.lines),("$S_V$","Psuedo $S_V$",),loc='upper right') ax.set_ylabel("SV (m/s)") ax.set_title("Relative velocity") ax = axarr[2] ax.plot(T_vals,S_A) ax.plot(T_vals,PSA) ax.legend((ax.lines),("$S_A$","Psuedo $S_A$",),loc='upper right') ax.set_ylabel("SA (m/$s^2$)") ax.set_title("Absolute acceleration") ax.set_xlim([0,numpy.max(T_vals)]) ax.set_xlabel("Oscillator natural period T (secs)") fig.tight_layout() fig.subplots_adjust(top=0.90) # Return values as dict return_dict = {} return_dict["T_vals"]=T_vals return_dict["S_D"]=S_D return_dict["S_V"]=S_V return_dict["S_A"]=S_A return_dict["PSV"]=PSV return_dict["PSA"]=PSA if makePlot: return_dict["fig"]=fig else: return_dict["fig"]=None return return_dict
# Overlay frequency transfer function for relative displacement plot_dict = PlotFrequencyResponse(f2, G_f2[:, 2, 0], label_str="Relative disp (m)", ax_magnitude=plot_dict["ax_magnitude"], ax_phase=plot_dict["ax_phase"]) print("Max |G_f|, relative disp: %.2e" % numpy.max(numpy.abs(G_f2[:, 2, 0]))) plot_dict["ax_magnitude"].axvline(f_D, color='c', alpha=0.4) plot_dict["ax_phase"].axvline(f_D, color='c', alpha=0.4) #%% # Obtain frequency response for main mass only main_sys = msd_chain.MSD_Chain(M_vals=m_M, f_vals=f_M, eta_vals=gamma_M) rslts = main_sys.CalcFreqResponse(fmax=fmax) f3 = rslts['f'] G_f3 = rslts['G_f'] print("Max |G_f|, system with no TMDs: %.2e" % numpy.max(numpy.abs(G_f3[:, 0, 0]))) # Overlay to compare frequency response PlotFrequencyResponse(f3, G_f3[:, 0, 0], label_str="using CalcFreqResponse(), no TMD", ax_magnitude=plot_dict["ax_magnitude"], ax_phase=plot_dict["ax_phase"])
resultsproc_time) return results_obj # ********************** FUNCTIONS ******************************************* # ********************** TEST ROUTINE **************************************** if __name__ == "__main__": import msd_chain # Define dynamic system mySys = msd_chain.MSD_Chain([100, 50, 100, 50], [1.2, 1.8, 2.0, 4.5], [0.03, 0.02, 0.01, 0.1], isSparse=False) mySys.AddConstraintEqns(Jnew=[[1, -1, 0, 0], [0, 0, 1, -1]], Jkey="test") mySys.PrintSystemMatrices(printShapes=True, printValues=True) # Define output matrix to return relative displacements outputMtrx = npy.asmatrix([[1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0]]) outputNames = ["Rel disp 12", "Rel disp 23"] mySys.AddOutputMtrx(output_mtrx=outputMtrx, output_names=outputNames) # Define applied forces def sine_force(t, F0, f): F0 = npy.asarray(F0)
""" Created on Fri Aug 10 19:32:48 2018 @author: whoever """ import numpy import msd_chain import tstep # Define a very basic SDOF system my_sys = msd_chain.MSD_Chain(M_vals=[1.0], f_vals=[1.0], eta_vals=[0.02]) def up_pass(t, y): return y[0] def down_pass(t, y): # if t<3.0: # return 1.0 # no events # else: # return y[0] return y[0]-0.2