# Composing with automorphisms: the code

Quick follow-up on this post about composing with automorphisms in the ‘Cube Dance’ graph. I’m putting here the Python code I wrote to get the examples in the YouTube videos. As expected, I’m using Opycleid to get most of the job done.

Note: I’m using version 0.3.2 of Opycleid

First, we import the $M_{\mathcal{U}\mathcal{P}\mathcal{L}}$ monoid, along with some classes we will use later.

from opycleid.musicmonoids import UPL_Monoid
from opycleid.categoryaction import CatObject,CatMorphism,CategoryActionFunctor

mg = UPL_Monoid()
_,X = mg.get_object()


The variable mg now contains an instance of the $M_{\mathcal{U}\mathcal{P}\mathcal{L}}$ monoid, so we’ll know look for its automorphisms.

auts = mg.get_automorphisms()
print(len(auts))
## >>> 12


As expected, there are twelve of them. We take the first one in the list, and print the image of the generators.

aut = auts[0]
for m in ["L","P","U"]:
print(m,"->",aut.get_image_morphism(m))
## >>> L -> L
## >>> P -> P
## >>> U -> LUL


Next, we need to define an automorphism of the functor $S \colon M_{\mathcal{U}\mathcal{P}\mathcal{L}} \to \mathbf{Rel}$. We already have an automorphism $N$, so we need to define a natural transformation $\eta \colon S \to SN$. From the last post, this is defined by an appropriate permutation of the augmented triads, and by a choice of an image for a representative in each hexatonic system. The rest of the images are determined by using the naturality condition on $\eta$ for the invertible morphisms $\mathcal{P}$ and $\mathcal{L}$.

eta = CatMorphism("eta",X,X)
d={"C_aug":["G_aug"],
"G_aug":["D_aug"],
"D_aug":["F_aug"],
"F_aug":["C_aug"]}
for x,y in zip(["C_M","Eb_M","Fs_M","A_M"],
["B_m","Fs_m","A_m","C_m"]):
for m in ["L","LP","P","PL","PLP","id_."]:
p = mg.apply_operation(m,x)[0]
f = aut.get_image_morphism(m)
z = mg.apply_operation(f,y)
d[p]=z
eta.set_mapping(d)


You can print the full mapping with eta.get_mapping(). Then we define the automorphism of the functor $S$, and we verify that it is a valid one (i.e. if the automorphism of $M_{\mathcal{U}\mathcal{P}\mathcal{L}}$ is indeed a valid one, and if eta is really a natural transformation).

N = CategoryActionFunctor(mg,mg,aut,{".":eta})
print("VALIDATION :",N.is_valid())
## >>> VALIDATION : True


Now we need to define a poly-Klumpenhouwer network (PK-Net) that we will transform using

from opycleid.knetanalysis import PKNet

A = CatObject("A",["pX1"])
B = CatObject("B",["pY1"])
C = CatObject("C",["pZ1"])

f = CatMorphism("f",U,V)
f.set_mapping({"pX1":["pY1"]})

g = CatMorphism("g",V,W)
g.set_mapping({"pY1":["pZ1"]})

my_pknet = PKNet(mg)
my_pknet.set_edges([f,g])
my_pknet.set_mappings({"f":"U","g":"U"},
{"pX1":["D_M"],
"pY1":["D_aug"],
"pZ1":["G_m"]
}
)

print(my_pknet)
## >>> A -- U --> B
## >>> [['D_M']] -> [['D_aug']]
## >>> B -- U --> C
## >>> [['D_aug']] -> [['G_m']]


Everything is ready, we can apply our automorphism on this PK-Net to get the whole chord progression.

for i in range(12):
print("==================")
my_pknet = my_pknet.global_transform(N)
print(my_pknet)

# ==================
# A -- LUL --> B
# [['Cs_m']] -> [['F_aug']]
# B -- LUL --> C
# [['F_aug']] -> [['D_M']]
#
# ==================
# A -- U --> B
# [['Gs_M']] -> [['C_aug']]
# B -- U --> C
# [['C_aug']] -> [['Cs_m']]
#
# ==================
# A -- LUL --> B
# [['Eb_m']] -> [['G_aug']]
# B -- LUL --> C
# [['G_aug']] -> [['Gs_M']]
#
# ==================
# A -- U --> B
# [['Fs_M']] -> [['D_aug']]
# B -- U --> C
# [['D_aug']] -> [['Eb_m']]
#
# ==================
# A -- LUL --> B
# [['A_m']] -> [['F_aug']]
# B -- LUL --> C
# [['F_aug']] -> [['Fs_M']]
#
# ==================
# A -- U --> B
# [['C_M']] -> [['C_aug']]
# B -- U --> C
# [['C_aug']] -> [['A_m']]
#
# ==================
# A -- LUL --> B
# [['B_m']] -> [['G_aug']]
# B -- LUL --> C
# [['G_aug']] -> [['C_M']]
#
# ==================
# A -- U --> B
# [['Bb_M']] -> [['D_aug']]
# B -- U --> C
# [['D_aug']] -> [['B_m']]
#
# ==================
# A -- LUL --> B
# [['F_m']] -> [['F_aug']]
# B -- LUL --> C
# [['F_aug']] -> [['Bb_M']]
#
# ==================
# A -- U --> B
# [['E_M']] -> [['C_aug']]
# B -- U --> C
# [['C_aug']] -> [['F_m']]
#
# ==================
# A -- LUL --> B
# [['G_m']] -> [['G_aug']]
# B -- LUL --> C
# [['G_aug']] -> [['E_M']]
#
# ==================
# A -- U --> B
# [['D_M']] -> [['D_aug']]
# B -- U --> C
# [['D_aug']] -> [['G_m']]


Or equivalently, since in Opycleid morphisms of functors to $\mathbf{Rel}$ can be composed:

T=N
for i in range(12):
print("==================")
print(my_pknet.global_transform(T))
T=T*N