Undescribed Horrific Abuse, One Victim & Survivor of Many gmkarl at gmail.com
Wed Nov 16 17:37:31 PST 2022

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,

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.


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
(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.


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)
>>> complex_wavelet(SQUARE, .25)

the real component should have the same sign for correct behavior.

other than the cutoff point, it is even:
>>> complex_wavelet(SQUARE, .24)
>>> complex_wavelet(SQUARE, -.24)
>>> complex_wavelet(SQUARE, .26)
>>> complex_wavelet(SQUARE, -.26)

>>> SINE(0)
>>> SINE(0.5)
>>> SQUARE(0)
>>> SQUARE(0.5)

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

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.

I ended up doing other things. The matrix did look better with the
change. I would like to look at it more.

More information about the cypherpunks mailing list