As promised, here are a few updates on some math/music research !

Grégoire Genuys, a Ph.D. student working at IRCAM, went to the MCM 2017 conference, and presented our paper “Homometry in the Dihedral Groups: Lifting Sets from $\mathbb{Z}_n$ to $D_n$“. We extended the notion of homometry (which I promise I will expose in this blog) to non-abelian groups, in particular dihedral groups since they are of particular interest for music. Since they are non-commutative, we get two different flavors of homometry depending on whether the group acts on the left or on the right. We enumerated non-trivial homometric sets of varying cardinality in these dihedral groups, and gave some characterisation theorems for these sets. Grégoire also has a longer paper on the ArXiV: “Non-Commutative Homometry in the Dihedral Groups”.

The long paper about the categorical formalization of Klumpenhouwer networks that I worked on with Andrée Ehresmann, Moreno Andreatta, and Carlos Agon, “From K-Nets to PK-Nets: a Categorical Approach” has finally been published in Perspectives of New Music ! You can find it on JSTOR here. This is the continuation of a short paper that was presented at the MCM 2015 conference, and previously published in the MCM proceedings. Now that this is published, I plan on writing several posts to explain the theory behind these papers.

Following previous results on rhythmic canons, I extended the calculation of the lengths of rhythmic canons mod 2 with motive $A(X)=1+X+X^n$ for $n$ up to 290. Here is the corresponding graph: Of course, with lengths $10^{80}$, this is not very interesting for musical applications. I was in fact hoping to spot the unusual cases I presented previously, which lead me to the following conjectures:

Conjecture 1: In $\mathbb{F}_p[X]$ where $p$ is prime (except for $p=3$), (non-compact) canons of motive $A=1+X+X^{p^k}$ have a length of $L=p^{2^k}-1$.

Conjecture 2: In $\mathbb{F}_2[X]$, canons of motive $A=1+X+X^{2^{2k}+2^k+1}$ have a length of $L=\sum_{i=0}^{6} 2^{ki}$.

Conjecture 3: In $\mathbb{F}_2[X]$, canons of motive $A=1+X+X^{\sum_{j=0}^{3} 2^{jk}}$ have a length of $L=\sum_{i=0}^{14} 2^{ki}$.

Conjecture 4: In $\mathbb{F}_2[X]$, canons of motive $A=1+X+X^{\sum_{j=0}^{4} 2^{jk}}$ have a length of $L=\sum_{i=0}^{20} 2^{ki}$.

Conjecture 5: In $\mathbb{F}_2[X]$, canons of motive $A=1+X+X^{\sum_{j=0}^{5} 2^{jk}}$ have a length of $L=\sum_{i=0}^{62} 2^{ki}$.

In fact, I discovered that one can prove a weaker version of these conjectures, namely that the conjectured lengths are upper bounds on the actual length of the rythmic canons. Here is how we prove that. Consider first motives of the form $A(X)=1+X+X^{2^k}$. In the ring $\mathbb{F}_2[X]/A(X)$ we thus have $X^{2^k}=1+X.$

Exponentiating $X$ by $2^k$, we get ${X^{2^k}}^{2^k}=1+X^{2^k},$

and thus $X^{2^{2k}}=1+1+X=X,$

giving therefore $X^{2^{2k}-1}=1.$

Therefore, we conclude that an upper bound for the length of rhythmic canons mod 2 with motive $A(X)=1+X+X^{2^k}$ is $L=2^{2k}-1$. We know by the results of Hélianthe Caure that this is the actual length. The weaker version of conjecture 1 above is easily obtained from this result.

Similarly, assume that we have a motive of the form $A(X)=1+X+X^{{2^k}+1}$.In the ring $\mathbb{F}_2[X]/A(X)$ we thus have $X^{{2^k}+1}=1+X.$

Exponentiating $X$ by $2^k$, we get $X^{2^k}X^{4^k}=1+X^{2^k},$

and multiplying by X $XX^{2^k}X^{4^k}=X+X^{2^k+1}=X+1+X=1,$

and thus we conclude that an upper bound for the length of rhythmic canons mod 2 with motive $A(X)=1+X+X^{2^k+1}$ is $L=1+2^k+4^k$. Again, by the results of Hélianthe, we know this is the actual length.

Let’s prove the weaker version of conjecture 2 (the other conjectures are approached in a similar way). We consider motives of the form $A(X)=1+X+X^{1+2^k+4^k}$, and the corresponding ring $\mathbb{F}_2[X]/A(X)$ in which we have $X^{1+2^k+4^k}=1+X.$

Exponentiating $X$ by $2^{4k}$, we get $X^{2^{4k}}X^{2^{5k}}X^{2^{6k}}=1+X^{2^{4k}},$

and thus $X^{2^{2k}}X^{2^{3k}}X^{2^{4k}}X^{2^{5k}}X^{2^{6k}}=X^{2^{2k}}X^{2^{3k}}+X^{2^{2k}}X^{2^{3k}}X^{2^{4k}},$

equal to $X^{2^{2k}}X^{2^{3k}}X^{2^{4k}}X^{2^{5k}}X^{2^{6k}}=X^{2^{2k}}X^{2^{3k}}+1+X^{2^{2k}},$ $X^{2^{k}}X^{2^{2k}}X^{2^{3k}}X^{2^{4k}}X^{2^{5k}}X^{2^{6k}}=X^{2^{k}}X^{2^{2k}}X^{2^{3k}}+X^{2^{k}}+X^{2^{k}}X^{2^{2k}},$ $X^{2^{k}}X^{2^{2k}}X^{2^{3k}}X^{2^{4k}}X^{2^{5k}}X^{2^{6k}}=1+X^{2^{k}}+X^{2^{k}}+X^{2^{k}}X^{2^{2k}},$ $X^{2^{k}}X^{2^{2k}}X^{2^{3k}}X^{2^{4k}}X^{2^{5k}}X^{2^{6k}}=1+X^{2^{k}}X^{2^{2k}},$

and finally, $XX^{2^{k}}X^{2^{2k}}X^{2^{3k}}X^{2^{4k}}X^{2^{5k}}X^{2^{6k}}=X+XX^{2^{k}}X^{2^{2k}},$ $XX^{2^{k}}X^{2^{2k}}X^{2^{3k}}X^{2^{4k}}X^{2^{5k}}X^{2^{6k}}=X+1+X=1.$

and thus we conclude that an upper bound for the length of rhythmic canons mod 2 with motive $A(X)=1+X+X^{1+2^k+4^k}$ is $L=\sum_{i=0}^{6} 2^{ki}$. There would remain to prove that is the actual length !

And finally, I presented in a previous post a link between musical  chords, tonnetzes, and topology. And I had provided Matlab codes for calculating Betti numbers of the simplicial complexes formed by different pitch-class sets. I don’t work with Matlab anymore and wanted to update it to Python so here it is. I used an existing Python function to calculate the Smith normal form of a matrix with coefficients in $\mathbb{F}_2$. The resulting code is in fact quite general and could be used to calculate the Betti numbers of any simplicial complex.

##coding: utf-8

import itertools
import numpy as np

def reduce_matrix(matrix):
#Returns [reduced_matrix, rank, nullity]
if np.size(matrix)==0:
return [matrix,0,0]
m=matrix.shape
n=matrix.shape

def _reduce(x):
#We recurse through the diagonal entries.
#We move a 1 to the diagonal entry, then
#knock out any other 1s in the same  col/row.
#The rank is the number of nonzero pivots,
#so when we run out of nonzero diagonal entries, we will
#know the rank.
nonzero=False
#Searching for a nonzero entry then moving it to the diagonal.
for i in range(x,m):
for j in range(x,n):
if matrix[i,j]==1:
matrix[[x,i],:]=matrix[[i,x],:]
matrix[:,[x,j]]=matrix[:,[j,x]]
nonzero=True
break
if nonzero:
break
#Knocking out other nonzero entries.
if nonzero:
for i in range(x+1,m):
if matrix[i,x]==1:
matrix[i,:] = np.logical_xor(matrix[x,:], matrix[i,:])
for i in range(x+1,n):
if matrix[x,i]==1:
matrix[:,i] = np.logical_xor(matrix[:,x], matrix[:,i])
#Proceeding to next diagonal entry.
return _reduce(x+1)
else:
#Run out of nonzero entries so done.
return x
rank=_reduce(0)
return [matrix, rank, n-rank]
### Source: < https://triangleinequality.wordpress.com/2014/01/23/computing-homology/ >

################

def get_boundary_matrices(simplicial_complex):
dim_values = [len(x) for x in simplicial_complex]
max_dim = np.max(dim_values)

boundary_matrices = {}
all_simplices={}
simplices = [set(x) for x in simplicial_complex if len(x)==max_dim]

for dim in range(1,max_dim+1)[::-1]:
sub_simplices = []
for simplex in simplices:
for subsimplex in itertools.combinations(simplex,dim-1):
if not set(subsimplex) in sub_simplices:
sub_simplices.append(set(subsimplex))
for x in simplicial_complex:
if len(x)==dim-1:
if not set(x) in sub_simplices:
sub_simplices.append(set(x))

all_simplices[dim-1] = [list(x) for x in simplices]

BM_entries=[]
for i,simplex in enumerate(simplices):
for subsimplex in list(itertools.combinations(simplex,dim-1)):
for j,x in enumerate(sub_simplices):
if (x==set(subsimplex)):
BM_entries.append([j,i])

BM = np.zeros((len(sub_simplices),len(simplices)))
for i,j in BM_entries:
BM[i,j]=1
boundary_matrices[dim-1] = BM.copy()

simplices = [x for x in sub_simplices]

return boundary_matrices,all_simplices

################

def get_betti(boundary_matrices,max_betti):

betti_numbers = {}
for dim in range(max_betti+1)[::-1]:
if dim==max_betti:
betti = reduce_matrix(boundary_matrices[dim].copy())
elif dim==0:
betti = np.sum(reduce_matrix(boundary_matrices[dim].copy())[1:3])
betti -= reduce_matrix(boundary_matrices[dim+1].copy())
else:
betti = reduce_matrix(boundary_matrices[dim].copy())
betti -= reduce_matrix(boundary_matrices[dim+1].copy())
betti_numbers[dim]=betti
return betti_numbers

################

ch_type=[[0,4,7],[0,3,6]]
ch_list=[]
for x in ch_type:
for t in range(12):
ch_list.append([np.mod(x+t,12),np.mod(x+t,12),np.mod(x+t,12)])
print ch_list
mat,simp = get_boundary_matrices(ch_list)
print get_betti(mat,2)