Coverage for emd/simulate.py: 27%
49 statements
« prev ^ index » next coverage.py v7.6.11, created at 2025-03-08 15:44 +0000
« prev ^ index » next coverage.py v7.6.11, created at 2025-03-08 15:44 +0000
1#!/usr/bin/python
3# vim: set expandtab ts=4 sw=4:
5"""
6Simulation functions.
8Routines:
10"""
12import logging
14import numpy as np
16from .support import ensure_equal_dims
18# Housekeeping for logging
19logger = logging.getLogger(__name__)
22# Joint Instantaneous Frequency Functions
24def compute_joint_if(freq, amp, phase, sample_rate=128, seconds=2):
25 """Compute joint instantaneous frequency from a set of oscillations.
27 This function implements a signal simulator based on the methods in Fabus
28 et al (2021) [1]_. freq, amp and phase inputs should be tuples/lists of
29 user defined values.
31 Parameters
32 ----------
33 freq, amp, phase : {tuple, list, np.ndarray}
34 Frequency, Amplitude and Phase values for each component.
36 These are lists or tuples containing a single value per component.
37 sample_rate and seconds must then also be defined.
39 sample_rate : {None, float}
40 Sampling frequency of the data used if user defined harmonic values are passed in
41 seconds : {None, float}
42 Amount of seconds to generate if user defined harmonic values are passed in
44 Returns
45 -------
46 joint_if : ndarray
47 Vector containing the joint instantaneous frequency signal
48 joint_sig : ndarray
49 Array containing the time-domain signal for each harmonic component
51 Notes
52 -----
53 Example usage - compute joint instantaneous frequency from user defined harmonic values
55 >>> f = (5, 10, 15)
56 >>> a = (1, 1/3, 1/9)
57 >>> p = (0, 0, 0)
58 >>> joint_if, joint_sig = compute_joint_if(f, a, p, 128, 10)
60 References
61 ----------
62 .. [1] Fabus, M., Woolrich, M., Warnaby, C. and Quinn, A., 2021. Understanding
63 Harmonic Structures Through Instantaneous Frequency. BiorXiv
64 https://doi.org/10.1101/2021.12.21.473676
66 """
67 time_vect = np.linspace(0, seconds, int(seconds*sample_rate))
69 # Work with numpy arrays internally
70 freq = 2*np.pi*np.array(freq)
71 amp = np.array(amp)
72 phase = np.array(phase)
74 ensure_equal_dims([freq, amp, phase], ['freq', 'amp', 'phase'], 'compute_joint_if')
75 num_comps = freq.shape[0]
77 num = np.zeros((num_comps, num_comps, time_vect.shape[0]))
78 denom_sin = np.zeros((num_comps, time_vect.shape[0]))
79 denom_cos = np.zeros((num_comps, time_vect.shape[0]))
80 sig = np.zeros((num_comps, time_vect.shape[0]))
81 for n in range(num_comps):
82 denom_cos[n, :] = amp[n] * np.cos(freq[n] * time_vect + phase[n])
83 denom_sin[n, :] = amp[n] * np.sin(freq[n] * time_vect + phase[n])
84 sig[n, :] = amp[n] * np.cos(freq[n] * time_vect + phase[n])
85 for m in range(num_comps):
86 fd = freq[n] - freq[m]
87 pd = phase[n] - phase[m]
88 num[n, m, :] = freq[m] * amp[n] * amp[m] * np.cos(fd * time_vect + pd)
90 joint_if = np.sum(num, axis=(0, 1)) / ((np.sum(denom_cos, axis=0)**2) + (np.sum(denom_sin, axis=0)**2))
91 joint_if = joint_if / (2*np.pi)
93 return joint_if, sig
96def abreu2010(f, nonlin_deg, nonlin_phi, sample_rate, seconds):
97 r"""Simulate a non-linear waveform using equation 7 in [1]_.
99 Parameters
100 ----------
101 f : float
102 Fundamental frequency of generated signal
103 nonlin_deg : float
104 Degree of non-linearity in generated signal
105 nonlin_phi : float
106 Skew in non-linearity of generated signal
107 sample_rate : float
108 The sampling frequency of the generated signal
109 seconds : float
110 The number of seconds of data to generate
112 Returns
113 -------
114 ndarray
115 Simulated signal containing non-linear wave
117 Notes
118 -----
119 This function implements equation 7 in [1]_.
121 .. math::
122 u(t) = U_wf \frac{ sin(\omega t) + \frac{r sin \phi}{1+\sqrt{1-r^2}} } {1-r cos(\omega t+ \phi)}
124 Where :math:`\phi` is nonlin_phi - a waveform parameter :math:`(-\pi/2 \leq \phi \leq \pi/2)`
125 related to the biphase and :math:`r` is nonlin_deg - an
126 index of skewness or nonlinearity :math:`(-1 \leq r \leq 1)`.
128 This equation is a generalisation of equation 14 in [2]_. This paper highlights 3 cases for :math:`\phi`.
130 * :math:`\phi = 0`, resulting in an accelerated skewed wave (sawtooth wave profile);
132 * :math:`\phi = - \pi/2`, a velocity-skewed wave (with a velocity shape similar to a 1st-order cnoidal wave);
134 * :math:`\phi = - \pi/4`, corresponding to a wave with both velocity and acceleration skewnesses
137 References
138 ----------
139 .. [1] Abreu, T., Silva, P. A., Sancho, F., & Temperville, A. (2010).
140 Analytical approximate wave form for asymmetric waves. Coastal Engineering,
141 57(7), 656-667. https://doi.org/10.1016/j.coastaleng.2010.02.005
142 .. [2] Drake, T. G., & Calantoni, J. (2001). Discrete particle model for
143 sheet flow sediment transport in the nearshore. In Journal of Geophysical
144 Research: Oceans (Vol. 106, Issue C9, pp. 19859-19868). American
145 Geophysical Union (AGU). https://doi.org/10.1029/2000jc000611
147 """
148 time_vect = np.linspace(0, seconds, int(seconds * sample_rate))
150 factor = np.sqrt(1 - nonlin_deg**2)
151 num = nonlin_deg * np.sin(nonlin_phi) / (1 + factor)
152 num = num + np.sin(2 * np.pi * f * time_vect)
154 denom = 1 - nonlin_deg * np.cos(2 * np.pi * f * time_vect + nonlin_phi)
156 return factor * (num / denom)
159def ar_oscillator(freq, sample_rate, seconds, r=.95, noise_std=None, random_seed=None):
160 """Create a simulated oscillation using an autoregressive filter.
162 A simple filter is defined by direct pole placement and applied to white
163 noise to generate a random signal with a defined oscillatory peak frequency
164 that exhibits random variability frequency, amplitude and waveform.
166 Parameters
167 ----------
168 freq : float
169 Peak resonant frequency of the simulated filter.
170 sample_rate : float
171 Sampling frequency for the simulation
172 seconds : float
173 Number of seconds of data to simulate
174 r : float (0 < r < 1)
175 Pole magnitude of simulated autoregressive resonance.
176 noise_std : float
177 Scaling of optional noise to add to simulation. Scaling is relative to
178 standard-deviation of the simulated data.
179 random_seed : int
180 Optional random seed generation
182 Returns
183 -------
184 ndarray
185 A simulated time course.
187 """
188 if random_seed is not None:
189 np.random.seed(random_seed)
191 if freq > 0:
192 freq_rads = (2 * np.pi * freq) / sample_rate
193 a1 = np.array([1, -2*r*np.cos(freq_rads), (r**2)])
194 else:
195 a1 = np.poly(r)
197 num_samples = int(sample_rate * seconds)
199 from scipy.signal import filtfilt
200 x = filtfilt(1, a1, np.random.randn(1, num_samples)).T
202 if noise_std is not None:
203 noise = np.std(x)*noise_std*np.random.randn(1, num_samples).T
204 x = x + noise
206 if random_seed is not None:
207 np.random.seed() # restore defaults
209 return x