Coverage for emd/simulate.py: 27%

49 statements  

« prev     ^ index     » next       coverage.py v7.6.11, created at 2025-03-08 15:44 +0000

1#!/usr/bin/python 

2 

3# vim: set expandtab ts=4 sw=4: 

4 

5""" 

6Simulation functions. 

7 

8Routines: 

9 

10""" 

11 

12import logging 

13 

14import numpy as np 

15 

16from .support import ensure_equal_dims 

17 

18# Housekeeping for logging 

19logger = logging.getLogger(__name__) 

20 

21 

22# Joint Instantaneous Frequency Functions 

23 

24def compute_joint_if(freq, amp, phase, sample_rate=128, seconds=2): 

25 """Compute joint instantaneous frequency from a set of oscillations. 

26 

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. 

30 

31 Parameters 

32 ---------- 

33 freq, amp, phase : {tuple, list, np.ndarray} 

34 Frequency, Amplitude and Phase values for each component. 

35 

36 These are lists or tuples containing a single value per component. 

37 sample_rate and seconds must then also be defined. 

38 

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 

43 

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 

50 

51 Notes 

52 ----- 

53 Example usage - compute joint instantaneous frequency from user defined harmonic values 

54 

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) 

59 

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 

65 

66 """ 

67 time_vect = np.linspace(0, seconds, int(seconds*sample_rate)) 

68 

69 # Work with numpy arrays internally 

70 freq = 2*np.pi*np.array(freq) 

71 amp = np.array(amp) 

72 phase = np.array(phase) 

73 

74 ensure_equal_dims([freq, amp, phase], ['freq', 'amp', 'phase'], 'compute_joint_if') 

75 num_comps = freq.shape[0] 

76 

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) 

89 

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) 

92 

93 return joint_if, sig 

94 

95 

96def abreu2010(f, nonlin_deg, nonlin_phi, sample_rate, seconds): 

97 r"""Simulate a non-linear waveform using equation 7 in [1]_. 

98 

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 

111 

112 Returns 

113 ------- 

114 ndarray 

115 Simulated signal containing non-linear wave 

116 

117 Notes 

118 ----- 

119 This function implements equation 7 in [1]_. 

120 

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)} 

123 

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)`. 

127 

128 This equation is a generalisation of equation 14 in [2]_. This paper highlights 3 cases for :math:`\phi`. 

129 

130 * :math:`\phi = 0`, resulting in an accelerated skewed wave (sawtooth wave profile); 

131 

132 * :math:`\phi = - \pi/2`, a velocity-skewed wave (with a velocity shape similar to a 1st-order cnoidal wave); 

133 

134 * :math:`\phi = - \pi/4`, corresponding to a wave with both velocity and acceleration skewnesses 

135 

136 

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 

146 

147 """ 

148 time_vect = np.linspace(0, seconds, int(seconds * sample_rate)) 

149 

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) 

153 

154 denom = 1 - nonlin_deg * np.cos(2 * np.pi * f * time_vect + nonlin_phi) 

155 

156 return factor * (num / denom) 

157 

158 

159def ar_oscillator(freq, sample_rate, seconds, r=.95, noise_std=None, random_seed=None): 

160 """Create a simulated oscillation using an autoregressive filter. 

161 

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. 

165 

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 

181 

182 Returns 

183 ------- 

184 ndarray 

185 A simulated time course. 

186 

187 """ 

188 if random_seed is not None: 

189 np.random.seed(random_seed) 

190 

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) 

196 

197 num_samples = int(sample_rate * seconds) 

198 

199 from scipy.signal import filtfilt 

200 x = filtfilt(1, a1, np.random.randn(1, num_samples)).T 

201 

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 

205 

206 if random_seed is not None: 

207 np.random.seed() # restore defaults 

208 

209 return x