back to list

Regular temperament invariant

🔗Graham Breed <gbreed@gmail.com>

7/7/2007 4:08:37 AM

We know that the wedge product of a regular temperament class works as a unique key that doesn't depend on the exact tuning or the equal temperament mappings or unison vectors used to define it. As a bivector, the wedge product can be written as:

T[i][j] = A[i]*B[j] - A[j]*B[i]

where T is the wedge product, A and B are equal temperament mappings, A[i] is the ith component of a, and so on. From the definition of a wedge product, we know

T[i][j] == T[j][i] and T[i][i] == 0

Usually we don't show the redundant entries. But today I'll think of it as a list of lists. The first entry, T[0] (I'm counting octaves as 0) is the usual generator mapping (times the number of periods per octave). The simplest invariant for the temperament is T[0] with the first entry (which we know to be zero) removed. This works pretty well, but doesn't identify a unique temperament class. There are very few examples where two temperament classes with the same T[0] make the top 100 of my searches. But still, it would be nice to distinguish them.

(You can also think of the T[i] as mappings for things like equal temperaments where the ith prime is a thing like a unison vector. But most people round here don't like thinking that way.)

The full wedge product is unique, but the problem is that it gets big. In practice it does slow down the calculation considerably for high prime limits. The size is roughly quadratic in the number of prime dimensions. It's exactly the third entry in Pascal's triangle. We already know that some elements are redundant but I've never been clear how many or how to remove them.

An alternative is to take T[0] and T[1] as the invariant. As we know T[0][0]==T[1][1]==0 and T[0][1]==-T[1][0], that leaves only 2n-3 elements for n prime dimensions. These happen to be the first 2n-3 elements of the wedgie as we usually write it. (You have to standardize the sign either way.)

Most of the time this has all the information the wedge product has. It doesn't tell us which melodic pattern was intended for a contorted temperament because neither does the wedge product.

It's fairly easy to construct the full wedge product from T[0] and T[1]. All you do is pretend that T[0] and T[1] are equal temperament mappings and calculate the wedge product of the temperament that includes them both. This will give some integer multiple of the correct wedge product.

It's easy to remove torsion because you know what a lot of the entries are supposed to be, so you know what factor to divide by.

A bigger problem is when the wedge product comes out multiplied by zero. This happens when T[0] and T[1] are collinear. An example is mystery temperament:

<29, 46, 67, 81, 100] & <58, 92, 135, 163, 201]

The full wedgie is

<<0, 29, 29, 29, 46, 46, 46, -14, -33, -1]]

Truncating it gives

<<0, 29, 29, 29, 46, 46, 46]]

hence

T[0] = <0, 0, 29, 29, 29]
T[1] = <0, 0, 46, 46, 46]

This plainly can't define a temperament because the primes 2 and 3 will always map to unisons. The solution is to replace T[1] with T[i] where i is the smallest number where T[0][i] is non-zero. In this case, i is 2.

T[0] = <0, 0, 29, 29, 29]
T[2] = <-29, -46, 0, -14, -33]

Removing the redundant elements gives an invariant

<<0, 29, 29, 29, -46, -14, -33]]

(Maybe another rule would make the signs agree with a subset of the wedgie. But never mind.)

It is possible to reconstruct the wedgie from invariants such as this. There are always i-1 zero entries at the start. So you can tell here that i is 2 and we need T[2] instead of T[1].

This invariant is more efficient to calculate and use as a unique key than the full wedge product. The other thing I still use wedgies for is checking contorsion. There's a fairly simple check in that if the invariant has no common factor there's no contorsion. That follows from the elements of the invariant being a subset of the elements of the wedge product.

It is possible for the invariant to have a common factor without there being contorsion. That's because one step in calculating the wedgie from the invariant is to divide through by an erroneous common factor. There may still be a clever way to check contorsion using the invariant but I don't know how to do it because I don't really understand contorsion.

I want to get a general smallest invariant for regular temperaments in general. It should have something to do with the wedge product, and have fewer than r*d elements for a rank r dimension d temperament.

Graham

p.s. To be precise about this, here's the Python code I used to check it. It uses files from http://x31eq.com/temper/regular.zip

import regular_wedgie
from math import sqrt

def invariant(ets):
map0, mapi = reduce_mappings(ets)
assert map0[0]==0
del map0[0]
i = map0.index(-mapi[0])
del mapi[0]
assert mapi[i]==0
del mapi[i]
return tuple(normalize_sign(map0+mapi))

def expand(reduced_wedgie):
n_dimensions = (len(reduced_wedgie) + 3)/2
map0 = (0,)+reduced_wedgie[:n_dimensions-1]
mapi = [0,0]+list(reduced_wedgie[n_dimensions-1:])
i = 1
while map0[i]==0:
mapi[i] = mapi[i+1]
i += 1
mapi[0] = -map0[i]
mapi[i] = 0
raw = wedgie((map0, mapi))
torsion = raw[i-1]/map0[i]
return tuple([x/torsion for x in raw])

def reduce_mappings(ets):
map0 = mapping_without_i(ets, 0)
i=1
while map0[i]==0:
i += 1
return map0, mapping_without_i(ets, i)

def wedgie(ets):
return tuple(normalize_sign(regular_wedgie.wedgie(ets)))

def check_match(ets):
direct = invariant(ets)
direct_wedgie = wedgie(ets)
reduced = reduce_mappings(ets)
i = 1
while reduced[0][i] == 0:
i += 1
assert reduced[0][0] == reduced[1][i] == 0
assert reduced[0][i] == -reduced[1][0]
assert direct_wedgie == expand(direct)
if regular_wedgie.hcf(direct)==1:
assert regular_wedgie.hcf(direct_wedgie)==1
if i==1:
assert direct == direct_wedgie[:len(direct)]
assert direct == tuple(
normalize_sign(reduced[0][1:] + reduced[1][2:]))

def mapping_without_i((et1, et2), i):
assert len(et1)==len(et2)
return [et1[i]*et2[j] - et1[j]*et2[i]
for j in range(len(et1))]

def normalize_sign(mapping):
nonzeros = [m for m in mapping if m]
if nonzeros==[] or nonzeros[0]>0:
return mapping
return [-m for m in mapping]

def checks(primes, cutoff=0.17, nETs=20, rank=2):
cutoff /= sqrt(len(primes))
ets = regular_wedgie.getEqualTemperaments(
primes, nETs=nETs, cutoff=cutoff)
for set in regular_wedgie.combinations(rank, ets):
check_match(set)