예제 #1
0
    def smd(self, f0=0.05, f1=0.75):
        """Compute the strong motion duration.

		Parameters
		----------
		f0 : float, optional
			Fraction of Arias intensity at the beginning of the strong motion
			duration. Default 0.05.
		f1 : float, optional
			Fraction of Arias intensity at the end of the strong motion duration.
			Default 0.75.

		Returns
		-------
		tuple
			A tuple with three floats:

			0. `smd` - the strong motion duration (``smd = t1 - t0``)
			1. `t0` - time when the Arias intensity reached `f0`.
			2. `t1` - time when the Arias intensity reached `f1`.
		"""
        if self.ordinate != 'a':
            config.vprint(
                'WARNING: class method smd() is intended for acceleration time histories'
            )
        Ea = cumtrapz(self.data**2, self.time, initial=0)
        t0, t1 = np.interp([f0 * Ea[-1], f1 * Ea[-1]], Ea, self.time)
        smd = t1 - t0
        return (smd, t0, t1)
예제 #2
0
    def differentiate(self, v=False):
        """Differentiate the time history with respect to time.
		This function uses :func:`numpy.gradient()` to perform the operation.
		Set ``v = True`` for verbose confirmations."""

        if v:
            config.vprint(
                'WARNING: differentiating a time history can '
                'degrade the information contained in the signal and lead to '
                'inaccurate results.')
        if self.dt_fixed:
            self.data = np.gradient(self.data, self.dt)
        else:
            self.data = np.gradient(self.data, self.time)
        if self.ordinate == 'd':
            if v:
                config.vprint('Time history {} has been differentiated and '
                              'now represents velocity.'.format(self.label))
            self.ordinate = 'v'
        elif self.ordinate == 'v':
            if v:
                config.vprint('Time history {} has been differentiated and '
                              'now represents acceleration.'.format(
                                  self.label))
            self.ordinate = 'a'
        elif self.ordinate == 'a':
            config.vprint('WARNING: you are differentiating an acceleration '
                          'time history.')
            self.ordinate = 'n/a'

        return self
예제 #3
0
def harmonic(dt, f=1.0, Td=None, A=1.0, ordinate='a'):
    """
	Create a sinusoidal time history.

	Parameters
	----------
	dt : float
		The time step.
	f : float, optional
		The frequency of the time history (in Hz).
	Td : float, optional
		The duration of the time history. The default value is ``Td = 100*dt``.
	A : float, optional
		The amplitude of the time history.
	ordinate : {'d', 'v', 'a'}, optional
		The physical quality of the data generated by this function,
		with 'd' = displacement, 'v' = velocity, 'a' = acceleration.

	Returns
	-------
	th : an instance of class TimeHistory

	Notes
	-----
	If ``Td/dt`` is not a whole number, then the duration will be increased
	such that ``Td = ceil(Td/dt)*dt``.
	"""

    if Td is None:
        Td = 100 * dt
    n = ceil(Td / dt)
    Tdn = n * dt
    if Tdn != Td:
        config.vprint('WARNING: the duration has been increased to {:6.2f} '
                      'seconds to accommodate {} timesteps'.format(Tdn, n))
    Td = Tdn
    w = 2 * pi * f
    time = np.linspace(0., Tdn, n)
    data = A * np.sin(w * time)
    th = TimeHistory(time, data, ordinate)
    th.setdt(dt, True, 1 / (2 * dt))

    return th
예제 #4
0
    def integrate(self, v=False):
        """Integrate the time history with respect to time. Set ``v = True``
		for verbose confirmations."""

        self.data = cumtrapz(self.data, self.time, initial=0)
        if self.ordinate == 'd':
            config.vprint('WARNING: you are integrating a displacement time '
                          'history.')
            self.ordinate = 'n/a'
        elif self.ordinate == 'v':
            if v:
                config.vprint('Time history {} has been integrated and now '
                              'represents displacement.'.format(self.label))
            self.ordinate = 'd'
        elif self.ordinate == 'a':
            if v:
                config.vprint('Time history {} has been integrated and now '
                              'represents velocity.'.format(self.label))
            self.ordinate = 'v'

        return self
예제 #5
0
def directS2S(ND,
              OD,
              z0,
              method='Jiang (2015)',
              zmin=0.001,
              INRES=True,
              **kwargs):
    """Compute an in-structure response spectrum (ISRS) using a direct
	spectrum-to-spectrum method.

	Parameters
	----------
	ND : int, {1, 2, 3}
		Number of excitation directions to take into account.
	OD : int, {0, 1, 2}
		Direction of in-structure response ('output direction').
	z0 : float
		Damping ratio of the secondary system.
	method : str, optional
		The numerical method used to compute the ISRS. Valid options are
		'Jiang (2015)' (default) and 'Der Kiureghian (1981)'.
	zmin : float, optional
		Lower bound modal damping value of the primary system.
		Useful if some primary modes have zero or unrealistically low damping.
		Default 0.001.
	INRES : bool, optional
		Include residual response in the computation. Default `True`.
		See DISRS documentation for further information.
	wd : str, optional
		Path to working directory. If this is not provided, `wd` will default
		to the current working directory.
	rslist : a list of lists of response spectra, optional
		Input spectra for ISRS computation. If `rslist` is not provided,
		the function will look for a file named `SpectralData.txt` in the
		working directory.
		The length of `rslist` must satisfy: ``len(rslist) >= ND``.
		The length of ``rslist[0]`` determines the number of damping values (`Nzb`).
		If ``rslist[1]`` is provided, then ``len(rslist[1]) == len(rslist[0])``.
		If ``rslist[2]`` is provided, then ``len(rslist[2]) == len(rslist[0])``.
	mdlist : a list of NumPy arrays, optional.
		This parameter contains the modal properties of the primary system,
		where:

		* ``mdlist[0]`` is a 1D array with natural frequencies
		  (with ``Np = len(mdlist[0])``);
		* ``mdlist[1]`` is a 1D array with modal damping ratios;
		* ``mdlist[2]`` is a 2D array with participation factors
		  (the shape of this array must be (`Np`,`ND`);
		* ``mdlist[3]`` is a 1D or 2D array with modal displacements
		  (the shape of this array must be (`Np`,`ND`) or just (`Np`,) if `ND` = 1).

	Other parameters
	----------------
	The following named parameters are used in the Jiang (2015) method:

	fc : tuple of floats, required
		Corner frequencies. See the DirectS2S Documentation.
	GT : str, optional
		Ground type ('H' for hard, 'S' for soft). Only relevant for vertical
		excitation. Default 'H'.
		
	The following named parameters are used in the Der Kiureghian (1981)
	method:

	m0 : float, optional
		Mass of secondary system. Default value 0.

	Returns
	-------
	rs : an instance of class ResponseSpectrum
		The in-structure response spectrum for response in direction `OD`.

	Notes
	-----
	A more thorough DirectS2S documentation is available as a PDF document:
	EN/NU/TECH/TGN/031, Direct Generation of In-Structure Response Spectra.
	"""
    # Time the execution
    start = time.perf_counter()
    config.vprint(
        'Computing in-structure response spectrum with method: {}'.format(
            method))

    # Check if a path has been specified; if not, assume current working directory
    if 'wd' in kwargs:
        wd = Path(kwargs['wd'])
    else:
        wd = Path.cwd()

    # Retrieve modal information (from textfile if not supplied in **kwargs)
    if 'mdlist' in kwargs:
        mdlist = kwargs['mdlist']
        fp = mdlist[0]
        zp = np.fmax(mdlist[1], zmin)
        gam = mdlist[2]
        phi = mdlist[3]
    else:
        MD = np.loadtxt(wd / 'ModalData.txt')
        fp = MD[:, 0]
        zp = np.fmax(MD[:, 1], zmin)
        gam = MD[:, 2:(2 + ND)]
        phi = MD[:, (2 + ND + OD)]

    # Retrieve response spectra at the base of the primary system.
    # If rslist is not provided, read spectral input data from file
    if 'rslist' in kwargs:
        rslist = kwargs['rslist']
        assert len(rslist) >= ND, (
            'The length of rslist is insufficient to '
            'compute response over {} directions'.format(ND))
        Nfb = rslist[0][0].ndat
        Nzb = len(rslist[0])
        fb = rslist[0][0].f
        zb = np.empty(Nzb)
        SAb = np.empty((Nfb, Nzb, ND))
        for i in range(Nzb):
            zb[i] = rslist[0][i].xi
            for j in range(ND):
                SAb[:, i, j] = rslist[j][i].sa
    else:
        SDb = np.loadtxt(wd / 'SpectralData.txt')
        Nfb = np.size(SDb, axis=0) - 1
        Nzb = (np.size(SDb, axis=1) - 1) // ND
        fb = SDb[1:Nfb + 1, 0]
        zb = SDb[0, 1:Nzb + 1]
        SAb = np.empty((Nfb, Nzb, ND))
        for d in range(ND):
            SAb[:, :, d] = SDb[1:Nfb + 1, d * Nzb + 1:(d + 1) * Nzb + 1]

    if method == 'Jiang (2015)':
        if 'fc' in kwargs:
            fc = kwargs['fc']
        else:
            raise ValueError('With method=\'Jiang (2015)\', the parameter fc '
                             'must be defined and provided.')
        if 'GT' in kwargs:
            GT = kwargs['GT']
        else:
            GT = 'H'
        SA_ISRS = dsm.j15_main(SAb, fb, zb, fp, zp, gam, phi, z0, OD, ND,
                               INRES, fc, GT)

    elif method == 'Der Kiureghian (1981)':
        if 'm0' in kwargs:
            m0 = kwargs['m0']
        else:
            m0 = 0.
        SA_ISRS = dsm.dk81_main(SAb, fb, zb, fp, zp, gam, phi, m0, z0, OD, ND,
                                False)

    else:
        raise ValueError('Method {} is not supported.'.format(method))

    rs = ResponseSpectrum(fb,
                          SA_ISRS,
                          xi=z0,
                          label='ISRS {}% Dir {}'.format(100 * z0, OD))

    # Write output to file
    outfil = Path(wd) / 'DirectS2S_Output_{}.txt'.format(OD)
    localtime = time.asctime(time.localtime(time.time()))
    head1 = ('# In-structure response spectrum computed by Qtools '
             'v. {} on {}'.format(config.version, localtime))
    head2 = ('# Method = {}, direction = {}, damping ratio = {}, '
             'residual response {}'.format(
                 method, OD, z0, 'included' if INRES else 'excluded'))
    head3 = '#'
    if method == 'Der Kiureghian (1981)':
        head3 = '# Secondary system mass = {}'.format(m0)
    header = '\n'.join([head1, head2, head3])
    np.savetxt(outfil, np.array([rs.f, rs.sa]).T, header=header)

    # End timing
    stop = time.perf_counter()
    config.vprint('Time to execute (min): {:6.2f}'.format((stop - start) / 60))

    return rs
예제 #6
0
def dk81_main(SAb, fb, zb, fp, zp, gam, phi, me, ze, OD, ND, INRES, Mi=None):
    """
	Main function for the direct spectrum-to-spectrum method developed by Der
	Kiureghian et al. (1981).
	
	Parameters
	----------
	SAb : 3D NumPy array
		Spectral accelerations at the base of the primary structure
		(the input spectrum), with ``SAb[i,j,d]`` returning the spectral 
		acceleration at frequency ``fb[i]`` and damping ratio ``zb[j]`` in
		direction `d`.
	fb : 1D NumPy array
		Frequencies corresponding to the 1st axis in `SAb`.
	zb : 1D NumPy array
		Damping ratios corresponding to the 2nd axis in `SAb`.
	fp : 1D NumPy array
		Modal frequencies of the primary system.
	zp : 1D NumPy array
		Damping ratios of the primary system.
	gam : 2D NumPy array
		Participation factors, with ``gam[i,d]`` returning the participation
		factor for mode `i` in direction `d`.
	phi : 1D NumPy array
		Modal displacements for DOF `k` (the point where the secondary
		system is attached).
	me : float
		Mass of secondary system.
	ze : float
		Damping ratio of secondary system.
	OD : int
		Output direction.
	ND : int
		Number of directions to take into account.
	INRES : bool
		If True, include the residual response. Otherwise ignore it.
	Mi : 1D NumPy array, optional
		Modal masses. The default is None, in which case it is assumed that
		all modes have been normalised to the mass matrix (in other words, it
		is assumed that ``Mi = np.ones_like(phi)``).

	Returns
	-------
	1D NumPy array
		The in-structure spectral accelerations at the frequencies defined in
		`fb`.
	"""

    with open('debug.txt', 'w') as outfil:

        # If Mi is None, then all modes have been normalised such that Mi = 1
        if Mi is None:
            Mi = np.ones_like(phi)

        # Determine lengths of input arrays
        Nfb = len(fb)
        Np = len(fp)

        # Initialise arrays
        pkis = np.empty(Np + 1)
        pnis = np.empty(Np + 1)
        fps = np.empty(Np + 1)
        zps = np.empty(Np + 1)
        Mis = np.empty(Np + 1)
        Gis = np.empty(Np + 1)
        Rt = np.empty(Np + 1)
        SA_ISRS = np.zeros(Nfb)

        # Loop over directions
        for d in range(ND):
            config.vprint(
                'Computing ISRS for excitation in direction {}'.format(d))
            # DEBUG start
            outfil.write('Direction {}\n'.format(d))
            # DEBUG end
            # Loop over frequency range:
            for i in range(Nfb):
                # Parameters
                bi = (fp**2 - fb[i]**2) / fb[i]**2
                gi = me * phi**2 / Mi
                redInd = np.argwhere(np.isclose(bi, 0) & np.isclose(gi, 0))
                if len(redInd) > 0:
                    gi[redInd] = 1e-3
                    for i in redInd[:, 0]:
                        config.vprint(
                            'WARNING: increased equipment mass in mode '
                            '{} to avoid numerical instability. New mass value: {}'
                            .format(i, gi[i] * Mi[i] / phi[i]**2))
                A1 = 1 + (bi + gi) / 2 - np.sqrt((1 + (bi + gi) / 2)**2 -
                                                 (1 + bi))
                A2 = 1 + (bi + gi) / 2 + np.sqrt((1 + (bi + gi) / 2)**2 -
                                                 (1 + bi))
                denom = np.where(bi < 0, A1 - 1, A2 - 1)
                ai = -1 / denom
                sag = np.sum(ai * gi)
                sa2g = np.sum(ai**2 * gi)
                ci = np.sqrt(1 + bi)
                l = np.argmin(np.fabs(bi))
                rn = 1 if d == OD else 0
                # New modal frequencies
                fps[0] = sqrt(1 + sag) * fb[i]
                fps[1:] = fp * np.where(bi < 0, np.sqrt(A1 / (1 + bi)),
                                        np.sqrt(A2 / (1 + bi)))
                # New modal damping ratios
                zps[0] = (np.sum(ci * ai**2 * gi * zp) +
                          (1 + sag)**2 * ze) / (1 + sa2g)
                zps[1:] = (ci * zp +
                           (1 - ai)**2 * gi * ze) / (ci * (1 + ai**2 * gi))
                # New modal displacement (DOF k)
                pkis[0] = -sag
                pkis[1:] = phi
                pkis[l + 1] = sag - ai[l] * gi[l] - 1 / ai[l]
                # New modal displacement (DOF n+1)
                pnis[0] = 1
                pnis[1:] = ai * pkis[1:]
                pnis[l + 1] = -1
                # New modal masses
                Mis[0] = (1 + sa2g) * me
                Mis[1:] = (1 + ai**2 * gi) * Mi
                a2gl = ai[l]**2 * gi[l]
                Mis[l + 1] = (1 + a2gl *
                              (1 + sa2g - a2gl)) * Mi[l] / (ai[l] * phi[l])**2
                # New participation factors
                Gis[0] = -(np.sum(ai * phi * gam[:, d]) - rn) / (1 + sa2g)
                Gis[1:] = (gam[:, d] + ai * gi * rn / phi) / (1 + ai**2 * gi)
                Gis[l + 1] = -((ai[l] * phi[l] * gam[l, d] - ai[l]**2 * gi[l] *
                                (np.sum(ai * phi * gam[:, d]) -
                                 ai[l] * phi[l] * gam[l, d] - rn)) /
                               (1 + ai[l] * gi[l] * (1 + sa2g - a2gl)))
                # Compute spectral accelerations at new frequencies
                SAp = SAfun(SAb[:, :, d], fb, zb, fps, zps)
                # Modal responses
                Rt = Gis * (pnis - pkis) * SAp / fps**2
                # Compute the double sum for direction d and add the result
                DS = doublesum(Rt, fps, zps)
                SA_ISRS[i] += fb[i]**4 * DS

    # Complete the SRSS combination and return the ISRS
    return np.sqrt(SA_ISRS)
예제 #7
0
def plotps(*args, **kwargs):
    """Function for plotting instances of class PowerSpectrum and
	FourierSpectrum.

	Parameters
	----------
	*args
		Any number of power spectra or Fourier spectra. All spectra must be
		of the same type.
	**kwargs
		Optional parameters (see below under **Other parameters**)

	Other parameters
	----------------
	style : str
		Plot style. See `Matplotlib style sheets reference <https://
		matplotlib.org/stable/gallery/style_sheets/
		style_sheets_reference.html>`_ for available styles. Default `default`.
	xscale : {'log', 'lin'}
		Specifies the scale on the x-axis (logarithmic or linear).
		Default 'lin'.
	legend : dict (or bool)
		The keys in this dictionary will be used to set the arguments in a
		call to :func:`matplotlib.pyplot.legend`. If this parameter is not
		provided, the legend will not be shown. This parameter can also be set
		to True to show the legend with default parameters ``{'loc': 'best'}``
	filename : str
		If given, save plot to file using `filename` as file name. The file
		name should include the desired extension (e.g. 'png' or 'svg'), from
		which the file format will be determined as per
		:func:`matplotlib.pyplot.savefig`.
	dpi : int
		Dots per inch to use if plot is saved to file. Default None.
	right : float
		Sets the upper limit on the x-axis.
	left : float
		Sets the lower limit on the x-axis.
	top : float
		Sets the upper limit on the y-axis.
	bottom : float
		Sets the lower limit on the y-axis.
	grid : dict
		The keys in this dictionary will be used to set the arguments in a
		call to :func:`matplotlib.pyplot.grid`. Default: ``{'which': 'major',
		'color': '0.75'}``

	Notes
	-----
	The line format can be set in the 'fmt' attribute of a spectrum.
	The line format is passed directly to :func:`matplotlib.pyplot.plot` as
	'fmt'.
	
	Examples
	--------
	See :func:`qtools.plotrs`.
	"""

    style = kwargs.get('style', 'default')
    xscale = kwargs.get('xscale', 'lin')
    if 'legend' in kwargs:
        show_legend = True
        legend = {'loc': 'best'}
        if isinstance(kwargs['legend'], dict):
            legend = kwargs['legend']
    else:
        show_legend = False
    filename = kwargs.get('filename', '')
    dpi = kwargs.get('dpi', None)
    grid = kwargs.get('grid', {'which': 'major', 'color': '0.75'})

    plt.style.use(style)

    # Get the axes of a new plot
    fig, ax = plt.subplots()

    # Set the appropriate type of plot
    if xscale == 'log':
        plot = ax.semilogx
    elif xscale == 'lin':
        plot = ax.plot

    ax.set_xlabel('Frequency [Hz]')

    # Check the validity of the arguments and set the labels on the y-axis
    if all([isinstance(ps, FourierSpectrum) for ps in args]):
        ax.set_ylabel('Fourier amplitude [{}]'.format(args[0].unit))
        yaxis = 'X'
    elif all([isinstance(ps, PowerSpectrum) for ps in args]):
        ax.set_ylabel('Spectral power density [{}]'.format(args[0].unit))
        yaxis = 'Wf'
    else:
        raise ValueError(
            'The arguments provided to plotps are not all of the'
            ' same type (either FourierSpectrum or PowerSpectrum)')
    if len(set([ps.unit for ps in args])) > 1:
        config.vprint('WARNING: in plotps, it seems that some spectra have'
                      ' different units.')

    # Create the plots
    for ps in args:
        if yaxis == 'X':
            y = ps.abs()
        else:
            y = ps.Wf
        if ps.fmt == '_default_':
            plot(ps.f, y, label=ps.label)
        else:
            plot(ps.f, y, ps.fmt, label=ps.label)

    # Set upper limit on x-axis if specified
    if 'right' in kwargs:
        ax.set_xlim(right=kwargs['right'])

    # Set lower limit on x-axis if specified
    if 'left' in kwargs:
        ax.set_xlim(left=kwargs['left'])

    # Set upper limit on y-axis if specified
    if 'top' in kwargs:
        ax.set_ylim(top=kwargs['top'])

    # Set lower limit on y-axis if specified
    if 'bottom' in kwargs:
        ax.set_ylim(bottom=kwargs['bottom'])

    if show_legend:
        ax.legend(**legend)

    ax.grid(**grid)

    if len(filename) > 0:
        plt.savefig(filename, dpi=dpi, bbox_inches='tight')

    plt.show()
예제 #8
0
def loadth(sfile,
           ordinate='a',
           dt=-1.0,
           factor=1.0,
           delimiter=None,
           comments='#',
           skiprows=0):
    """
	Create a time history from a text file.

	Parameters
	----------
	sfile : str
		The path and name of the input file.
	ordinate : {'d', 'v', 'a'}, optional
		The physical type of the data imported from `sfile`, with
		'd' = displacement, 'v' = velocity, 'a' = acceleration.
	dt : float, optional
		The time step. A positive value forces a constant time step, in which
		case `sfile` must not contain any time values. A negative value
		(e.g. ``dt = -1.0``) signifies that the time step is specified in the
		input file.
	factor : float, optional
		This argument can be used to factor the input data. Useful for
		converting from g units to |m/s2|.
	delimiter : str, optional
		The character used to separate values. The default is whitespace.
	comments : string or sequence of strings, optional
		The characters or list of characters used to indicate the start of a
		comment. None implies no comments. The default is '#'.
	skiprows : int, optional
		Skip the first `skiprows` lines. Default 0.

	Returns
	-------
	th : an instance of class TimeHistory

	Notes
	-----
	The Nyquist frequency is calculated as:

	.. math:: f_{Nyq} = \\frac{1}{2\\Delta t_{max}}

	where :math:`\Delta t_{max}` is the longest time step in the input file
	(when the function is called with `dt` < 0) or simply equal to `dt`
	(when the function is called with `dt` > 0).

	The time step can be specified in the input file by including a comment
	with the time step value. This comment must contain 'dt =' followed by
	the value of the time step in seconds. See example A below.

	The input file can have multiple data points per line. If the time step has
	been specified through `dt` or through a comment in the input file, it is
	assumed that the data in the input file are ordinates (displacements,
	velocities, accelerations) only; if the time step has not been specified,
	it is assumed that the data are given as pairs of time and ordinate.

	Examples
	--------
	*Example A*. Fixed time step specified in a comment using white space as
	delimiter with 5 data points per line::

		# dt = 0.005
		a0 a1 a2 a3 a4
		a5 a6 a7 a8 a9
		...

	*Example B*. Time points defined directly in input file with 4 data
	points per line (resulting in 8 columns of data) using comma as delimiter::

		t0, a0, t1, a1, t2, a2, t3, a3
		t4, a4, t5, a5, t6, a6, t7, a7
		...

	"""

    if type(sfile) != str:
        raise TypeError('The first argument to loadth() must be a string.')

    if dt > 0.0:
        dt_fixed = True
    else:
        dt_fixed = False

    if not dt_fixed:
        # Look for the time step definition in the input file
        with open(sfile, 'r') as fil:
            for line in fil.readlines():
                if 'dt =' in line:
                    met = line.partition('=')[2].lstrip()
                    try:
                        dt = float(met)
                    except:
                        pass
                    else:
                        dt_fixed = True
                        break

    # Now read the data from the specified file
    rawdata = np.loadtxt(sfile,
                         delimiter=delimiter,
                         comments=comments,
                         skiprows=skiprows)
    ndat = np.size(rawdata)

    if dt_fixed:
        # Constant time step - the input file contains just ordinates
        fNyq = 1 / (2 * dt)
        time = np.linspace(0, dt * (ndat - 1), num=ndat)
        data = np.reshape(rawdata, ndat)
    else:
        # The input file is defined as pairs of (time, data)
        if (np.size(rawdata[0]) % 2 != 0):
            # There is an odd number of entries in the first row.
            # This format is not supported.
            raise IndexError(
                'In loadth(): when the time points are defined in '
                'the input file, an odd number of columns in the input file is not supported.'
            )
        time = np.reshape(rawdata[:, 0::2], np.size(rawdata[:, 0::2]))
        data = np.reshape(rawdata[:, 1::2], np.size(rawdata[:, 1::2]))
        ndat //= 2
        # Find the highest frequency that can be represented by the time history
        # and check for fixed time step
        dt0 = time[1] - time[0]
        ddt = 0.0
        fNyq = 1.E6
        for i in range(ndat - 1):
            dt1 = time[i + 1] - time[i]
            ddt = max(ddt, abs(dt1 - dt0))
            fNyq = min(1 / (2 * dt1), fNyq)
        # Check whether time step really is fixed
        if ddt < 1E-8:
            dt_fixed = True
            dt = dt0

    # Factor input data
    if factor != 1.0:
        data *= factor

    # Create instance of TimeHistory
    th = TimeHistory(time, data, ordinate)
    th.setdt(dt, dt_fixed, round(fNyq, 2))

    # Set label (removing the file path and the extension, if any)
    th.setLabel(Path(sfile).stem)

    # Output information
    config.vprint('Time history successfully read from file {}.'.format(sfile))
    if th.dt_fixed:
        config.vprint(
            'Constant time step = {:6.4f} seconds will be used.'.format(th.dt))
    else:
        config.vprint('Variable time step will be used.')
    config.vprint('The duration is {:5.2f} seconds.'.format(th.Td))
    config.vprint(
        'The estimated Nyquist frequency of the time history is {:5.1f} Hz.'.
        format(th.fNyq))
    if th.fNyq < 100.0:
        config.vprint(
            'It is recommended to interpolate the time history using '
            'method interpft(k) with k >= {} for accuracy up to 100 Hz, '
            'depending on the frequency contents of the time history.'.format(
                ceil(100 / th.fNyq)))
    config.vprint('There are', th.ndat, 'points in the time history.')
    if ordinate == 'a':
        config.vprint('The PGA is {:4.2f} g.'.format(th.pga / 9.81))
    config.vprint('------------------------------------------')

    return th
예제 #9
0
    def interpft(self, k):
        """Interpolate the time history via a discrete Fourier transform of the
		data points in the time history. The result is a time history with
		a time step ``self.dt = self.dt/k``.

		Parameters
		----------
		k : int
			Divisor on the time step. If `k` is not an integer, it will be
			converted to one.

		Notes
		-----
		The time history must have fixed time step.
		If ``self.dt_fixed is False``, no action is taken by invoking this
		method. The parameter `k` must be a positive integer. A value
		``k = 1`` results in the same time history. This method uses
		:func:`numpy.fft.rfft` and :func:`numpy.fft.irfft` to perform the
		interpolation.

		Example
		-------
		The following example shows how to use this method::

			import copy
			from math import pi
			# Create a sample time hitory
			t0, dt = np.linspace(0,3*pi,20,retstep=True)
			g0 = np.sin(t0)**2*np.cos(t0)
			th0 = qt.arrayth(g0,time=t0,fmt='o')
			# Make a copy of th0 for later use
			th1 = copy.deepcopy(th0)
			th1.setLineFormat('-')
			# Interpolate and plot the two time histories
			th1.interpft(8)
			qt.plotth(th0,th1)

		"""
        k = int(k)
        if k < 1:
            raise ValueError(
                'The parameter k must be greater than or equal to 1.')

        if self.dt_fixed:
            n = self.ndat
            m = k * n
            cn = 1 / n * np.fft.rfft(self.data)
            self.data = np.fft.irfft(m * cn, m)[0:(n - 1) * k + 1]
            t0 = self.time[0]
            Td = self.Td
            self.time, self.dt = np.linspace(t0,
                                             t0 + Td, (n - 1) * k + 1,
                                             retstep=True)
            self.ndat = (n - 1) * k + 1
            self.k = 0
            self.fNyq = 1 / (2 * self.dt)
            config.vprint('Interpolating time history {}.'.format(self.label))
            config.vprint('The new time history has {} data points and a time '
                          'step of {} sec.'.format(self.ndat, self.dt))
            config.vprint('------------------------------------------')
        else:
            config.vprint('WARNING: calling interpft() on a time history with '
                          'variable time step has no effect.')
예제 #10
0
def j15_main(SAb, fb, zb, fp, zp, gam, phi, z0, OD, ND, INRES, fc, GT):
    """
	Main function for the direct spectrum-to-spectrum method developed by
	Jiang et al. (2015).
	
	Parameters
	----------
	SAb : 3D NumPy array
		Spectral accelerations at the base of the primary structure
		(the input spectrum), with ``SAb[i,j,d]`` returning the spectral 
		acceleration at frequency ``fb[i]`` and damping ratio ``zb[j]`` in
		direction `d`.
	fb : 1D NumPy array
		Frequencies corresponding to the 1st axis in `SAb`.
	zb : 1D NumPy array
		Damping ratios corresponding to the 2nd axis in `SAb`.
	fp : 1D NumPy array
		Modal frequencies of the primary system.
	zp : 1D NumPy array
		Damping ratios of the primary system.
	gam : 2D NumPy array
		Participation factors, with ``gam[i,d]`` returning the participation
		factor for mode `i` in direction `d`.
	phi : 1D NumPy array
		Modal displacements for DOF `k` (the point where the secondary
		system is attached).
	z0 : float
		Damping ratio of secondary system.
	OD : int, {0, 1, 2}
		Output direction.
	ND : int, {1, 2, 3}
		Number of directions to take into account.
	INRES : bool
		If True, include the residual response. Otherwise ignore it.
	fc : tuple of 2 floats
		Corner frequencies. See the DirectS2S Documentation.
	GT : str
		Ground type ('H' for hard, 'S' for soft). Only relevant for vertical
		excitation. Default 'H'.

	Returns
	-------
	1D NumPy array
		The in-structure spectral accelerations at the frequencies defined in
		`fb`.
	"""

    Nfb = len(fb)

    # Initialise in-structure spectral acceleration array
    SA_ISRS = np.zeros_like(SAb[:, 0, 0])

    # Loop over directions
    for d in range(ND):
        config.vprint(
            'Computing ISRS for excitation in direction {}'.format(d))
        # Compute spectral accelerations at structural frequencies and damping ratios
        SAp = SAfun(SAb[:, :, d], fb, zb, fp, zp)
        # Compute spectral acceleration at the frequency of the secondary system
        # (note: the effort is unnecessary when z0 equals one of the predefined
        # damping ratios in zb, but for general purpose, we need to to do this)
        SA0 = SAfun(SAb[:, :, d], fb, zb, fb, z0)
        # Compute peak displacements due to excitation in direction d
        u = gam[:, d] * phi
        # Add residual response
        if INRES and d == OD:
            # Append the residual modal displacement to the u array
            ur = 1 - np.sum(u)
            if ur < 0 or ur >= 1:
                config.vprint(
                    'WARNING: with ur = {}, the residual response is outside '
                    'the expected range (0 <= ur < 1)'.format(ur))
                config.vprint('The residual response is ignored (ur = 0)')
                ur = 0
            else:
                config.vprint('The residual response is ur = {}'.format(ur))
            u_r = np.append(u, ur)
            # Append appropriate values to other arrays
            # (note: the values appended to fp and zp are somewhat arbitrary)
            SAp_r = np.append(SAp, SAp[-1])
            fp_r = np.append(fp, 200)
            zp_r = np.append(zp, z0)

        # Loop over frequency range:
        for i in range(Nfb):
            if INRES and d == OD:
                # Compute the response vector
                R = resp(SAp_r, SA0[i], u_r, fb[i], fp_r, z0, zp_r, fc, d, GT,
                         INRES)
                # Compute the double sum for direction d and add the result
                DS = doublesum(R, fp_r, fb[i], zp_r, z0)
            else:
                # Compute the response vector
                R = resp(SAp, SA0[i], u, fb[i], fp, z0, zp, fc, d, GT, INRES)
                # Compute the double sum for direction d and add the result
                DS = doublesum(R, fp, fb[i], zp, z0)
            SA_ISRS[i] += DS

    # Complete the SRSS combination and return the ISRS
    return np.sqrt(SA_ISRS)