according to numpy my square wave matrix is singular. something for me to think about! i'm guessing a big issue is that my wavelet function doesn't have a way to shift phase. i don't really know much about wavelets; it's a word i remember from being a teenager, learning about these things in the resources i knew how to find. i don't know if it's the cause, but my wavelet will need a way to have phase. i could interpret the complex argument in terms of angle and magnitude, and simply phase shift the output. this seems like it would work fine, at first. but i'm not sure if it would survive the whole process, with no complex output. the trick with the sinusoids is that in frequency space, the phase is held as an angle in the complex plane. when the freq2time matrix is created, where does the complex output come from, and what meaning does it hold? it has only real-domain frequencies, and yet produces a matrix that converts from complex frequency. it takes the frequencies, and scales them by the offsets, and turns that into time parameters it passes the time parameters to the sinusoids. this produces a matrix that can process complex frequency data, and convert it to time data. each frequency is a magnitude and a phase. i can make a function that takes complex frequency data but the frequencies that are passed in are not complex: they are negative and positive. where the negative is the complex conjugate i.e. the angle iand hence phase is treated as negative. there are various things i could try but i'm trying to figure out what's going on in general. exp(2i pi x) produces a complex number rotated with an angle x how does this produce real-domain data? if i pass in a single frequency with a 0 angle, what does it do? i tried this: (Pdb) p np.array([0,1,0,1]) @ create_freq2time(4) array([ 5.00000000e-01+0.j, 3.06161700e-17+0.j, -5.00000000e-01+0.j, -9.18485099e-17+0.j]) so how does that work? (Pdb) p create_freq2time(4).round(2) array([[ 0.25+0.j , 0.25+0.j , 0.25+0.j , 0.25+0.j ], [ 0.25+0.j , 0. +0.25j, -0.25+0.j , -0. -0.25j], [ 0.25+0.j , -0.25-0.j , 0.25+0.j , -0.25-0.j ], [ 0.25+0.j , 0. -0.25j, -0.25-0.j , -0. +0.25j]]) columns are freq, rows are samps The first sample gets 0.25 + 0.25 = 0.5 The second sample is the only one with complex domain stuff, and its 0.25 components cancel each other. The third has -0.25 at the two indices. The fourth cancels again. I'm going to paste it again. array([[ 0.25+0.j , 0.25+0.j , 0.25+0.j , 0.25+0.j ], [ 0.25+0.j , 0. +0.25j, -0.25+0.j , -0. -0.25j], [ 0.25+0.j , -0.25-0.j , 0.25+0.j , -0.25-0.j ], [ 0.25+0.j , 0. -0.25j, -0.25-0.j , -0. +0.25j]]) It's 4 complex sinusoids, but they oppose each other in the complex domain. Because a real-valued signal has both negative and complex frequencies equal, and because the complex component of the wavelet function is odd, the two cancel. That would work fine for phaseless square wave data. You'd provide an even function square wave for the real component, and an odd function square wave for the imaginary component. What about phase offset? (Pdb) p (np.array([0,1+1j,0,1+1j]) @ create_freq2time(4)).round(3) array([ 0.5+0.5j, 0. +0.j , -0.5-0.5j, -0. -0.j ]) When I specify complex numbers for the frequencies, it actually produces a wave with the same phase, that has complex value, not a phased wave ! I thought the angle of the frequency was the phase, but it isn't? (Pdb) p np.fft.ifft(np.array([0,1+1j,0,1+1j])).round(3) array([ 0.5+0.5j, 0. +0.j , -0.5-0.5j, 0. +0.j ]) That appears to be so. That doesn't seem very useful to me, it seems it would be more useful if it were. Ohhhh I need to use the complex conjugate! (Pdb) p (np.array([0,1+1j,0,1-1j]) @ create_freq2time(4)).round(3) array([ 0.5+0.j, -0.5+0.j, -0.5+0.j, 0.5+0.j]) There we go. I offset it by 45 degrees? (Pdb) p (np.array([0,1,0,1]) @ create_freq2time(4)).round(3) array([ 0.5+0.j, 0. +0.j, -0.5+0.j, -0. +0.j]) I'm guessing the peaks for 45 degrees were between the samples. 90 might be clearer. Yeah here's 90 degrees offset: (Pdb) p (np.array([0,1j,0,-1j]) @ create_freq2time(4)).round(3) array([ 0. +0.j, -0.5+0.j, -0. +0.j, 0.5+0.j]) So how does that happen? (Pdb) p create_freq2time(4).round(3) array([[ 0.25+0.j , 0.25+0.j , 0.25+0.j , 0.25+0.j ], [ 0.25+0.j , 0. +0.25j, -0.25+0.j , -0. -0.25j], [ 0.25+0.j , -0.25-0.j , 0.25+0.j , -0.25-0.j ], [ 0.25+0.j , 0. -0.25j, -0.25-0.j , -0. +0.25j]]) Passing (0,1j,0,-1j) ... The first two frequencies have opposing components so they cancel. That would work with a 90 degree square wave. In the second .. it's 1j and -1j. the j's multiply to -1, and we get -.25 and -.25, which sum to 0.5 . the 90 degree case would work with square waves. but why does it work for sinusoids? cos(x) + sin(x)j = exp(2j pi x) is applied, not to the input data x, but rather to the frequency. then when the input data comes in, it is multiplied against that result. so if it's real, the sinusoid components cancel and the cosine is extracted. this then produces a real cosine wave. if it's imaginary and self-opposing, the cosines cancel from the self-opposingness, whereas the sines amplify, producing a wave that is 90 degrees off. how would a 45 degree wave be produced? what is that? it seems it would be a sum of the 0 degree and 90 degree waves, not sure. i looked at trigonometric identities and i found a relation that looked complex. let's see how it goes (Pdb) p create_freq2time(4).round(3) array([[ 0.25+0.j , 0.25+0.j , 0.25+0.j , 0.25+0.j ], [ 0.25+0.j , 0. +0.25j, -0.25+0.j , -0. -0.25j], [ 0.25+0.j , -0.25-0.j , 0.25+0.j , -0.25-0.j ], [ 0.25+0.j , 0. -0.25j, -0.25-0.j , -0. +0.25j]]) (Pdb) p (np.array([0,1+1j,0,1-1j]) @ create_freq2time(4)).round(3) array([ 0.5+0.j, -0.5+0.j, -0.5+0.j, 0.5+0.j]) first sample: (1+1j)(0.25) + (1-1j)(0.25) = 0.5 second sample: (1+1j)(0.25j) + (1-1j)(-0.25j) = -0.5 so the first sample is just the real component then again the second sample is just the complex component it really is two waves, rising at different times, offset by 90 degrees, or such (Pdb) p (np.array([0,0.5+1j,0,0.5-1j]) @ create_freq2time(4)).round(3) array([ 0.25+0.j, -0.5 +0.j, -0.25+0.j, 0.5 +0.j]) One could think of it as outputting the data in pairs. It outputs a real component, then a complex component, both in the real domain. Because one is offset by 90 degrees, this can provide for the construction of waves smoothly. It may work for the square wave. I guess I'll try it. 1600 I made a complex square wave. Basically the real and imaginary parts are juts off by 1/4 . The forward matrix it produces has an inverse, and the pseudoinverse passes np.allclose() with it. The assertion still doesn't pass, but again it should now be a closed system. 1603 I'm looking a little at the real-domain matrix produced for the square wave, and I'm not yet sure about it. (Pdb) list 174 forward_mat = np.linalg.pinv(inverse_mat) 175 if not is_complex: 176 # todo: remove test data 177 time_data = np.random.random(inverse_mat.shape[1]) 178 freq_data = time_data @ forward_mat 179 -> forward_mat = np.concatenate([ 180 forward_mat[:,:tail_idx], 181 forward_mat[:,tail_idx:neg_start_idx].conj() 182 ], axis=1) 183 return forward_mat 184 (Pdb) normal_forward_mat = np.linalg.pinv(create_freq2time(time_count, freqs)) (Pdb) p normal_forward_mat[6].round(3) array([ 1. +0.j , -0.707-0.707j, -0. +1.j , 0.707-0.707j, -1. -0.j , 0.707+0.707j, -0. -1.j , -0.707+0.707j, 1. -0.j , -0.707-0.707j, -0. +1.j , 0.707-0.707j, -1. -0.j , 0.707+0.707j, 0. -1.j , -0.707+0.707j]) (Pdb) p forward_mat[6].round(3) array([-0.5+0.5j, 0.5+0.5j, -0.5-0.5j, -0.5+0.5j, 0.5-0.5j, -0.5-0.5j, 0.5+0.5j, 0.5-0.5j, -0.5+0.5j, 0.5+0.5j, -0.5-0.5j, -0.5+0.5j, 0.5-0.5j, -0.5-0.5j, 0.5+0.5j, 0.5-0.5j]) In the normal dft matrix, the negative frequencies that occupy the second half, are the reverse-order complex conjugates of the positive frequencies that occupy the first half. In the forward_mat here, where the the square wave is being used, this complex conjugate situation does not hold. It's 1609. Basically, the even-numbered indices are conjugates, whereas the odds are opposites. It might be possible this could relate to the evaluation of the square wave at its discontinuous points. With real frequencies, the implication is that there are never frequencies equal to 0.125, for example. Looking a little further I'm not immediately discerning a pattern. 1640 having trouble developing behaviors and energy and stuff around this next step. i'm looking at inverse_mat and it seems to have a similar issue. i'm thinking that the real component of the waveform is not an even function, and that that is the issue here. trying out numbers, it kind of looks like the wavelet is getting indexed in a way that is rounding negative, rather than toward zero. that's something i can look into! this is what I have: SINE = lambda x: np.sin(2 * np.pi * x) SQUARE = lambda x: np.floor(x * 2) * 2 - 1 def complex_wavelet(wavelet, x): return wavelet((x + 0.25) % 1) + wavelet(x % 1) * 1j and I'm getting this:
complex_wavelet(SQUARE, -.25) (-1+1j) complex_wavelet(SQUARE, .25) (1-1j)
the real component should have the same sign for correct behavior. other than the cutoff point, it is even:
complex_wavelet(SQUARE, .24) (-1-1j) complex_wavelet(SQUARE, -.24) (-1+1j) complex_wavelet(SQUARE, .26) (1-1j) complex_wavelet(SQUARE, -.26) (1+1j)
SINE(0) 0.0 SINE(0.5) 1.2246467991473532e-16 SQUARE(0) -1.0 SQUARE(0.5) 1.0
The sine function has 0 and 1/2 equal, whereas the square function does not. If the square function had 1.0 | x==0 or 1.0 | x > 0.5, it would work, but instead it has 1.0 | x >= 0.5 . The difference stems from floor(x * 2). With the floor function, lower values are included in the range, and upper values are not. I wonder if it would be appropriate to offset things by 0.125 simply to make the behavior look even when sampled at the points : but this wouldn't work for denser sampling, of course, which I imagine is needed. 1703 I changed SQUARE to (x > 0.5) * 2 - 1, which kinda works because True is treated like 1 and False treated like 0. Assertion still failing. Note: if an even function is required for a wavelet to work, it would make sense to define them as even functions. 2036 I ended up doing other things. The matrix did look better with the change. I would like to look at it more.