You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
578 lines
16 KiB
Python
578 lines
16 KiB
Python
#!/usr/bin/env python
|
|
# coding: utf-8
|
|
|
|
# In[2]:
|
|
|
|
|
|
import itertools as it
|
|
import networkx as nx
|
|
from collections import Counter
|
|
import copy
|
|
import matplotlib.pyplot as plt
|
|
import random
|
|
from fractions import Fraction
|
|
import math
|
|
|
|
def collapse(fraction):
|
|
if fraction < 1:
|
|
while fraction < 1:
|
|
fraction *= Fraction(2, 1)
|
|
elif fraction >= 2:
|
|
while fraction >= 2:
|
|
fraction *= Fraction(1, 2)
|
|
return fraction
|
|
|
|
def hsPointToFR(point):
|
|
fraction = Fraction(1, 1)
|
|
for dim in point:
|
|
if dim > 0:
|
|
fraction = fraction * dim
|
|
else:
|
|
fraction = fraction * 1/abs(dim)
|
|
return fraction
|
|
|
|
def pitches(iterable, r):
|
|
for base in it.combinations_with_replacement(iterable, r - 1):
|
|
split = tuple(list(g) for k, g in it.groupby(tuple(b for b in base if b != 1)))
|
|
mults = list(it.product([-1, 1], repeat = len(split)))
|
|
for mult in mults:
|
|
yield tuple(it.chain(*[[val * mult[idx] for val in g] for idx, g in enumerate(split)]))
|
|
|
|
def expandPitch(pitch):
|
|
num = 1;
|
|
den = 1;
|
|
expandedPitch = list(pitch)
|
|
for dim in pitch:
|
|
if dim > 0:
|
|
num *= dim
|
|
else:
|
|
den *= abs(dim)
|
|
fraction = num/den
|
|
if fraction < 1:
|
|
while fraction < 1:
|
|
fraction *= 2
|
|
expandedPitch = [2] + expandedPitch
|
|
elif fraction >= 2:
|
|
while fraction >= 2:
|
|
fraction *= 1/2
|
|
expandedPitch = [-2] + expandedPitch
|
|
return tuple(expandedPitch)
|
|
|
|
def expandChord(chord):
|
|
return tuple([expandPitch(p) for p in chord])
|
|
|
|
|
|
def transposePitch(pitch, trans):
|
|
transposedPitch = list(pitch)
|
|
for t in trans:
|
|
if (t * -1) in transposedPitch:
|
|
transposedPitch.remove(t * -1)
|
|
else:
|
|
transposedPitch.append(t)
|
|
transposedPitch.sort(key=lambda val: abs(val))
|
|
return transposedPitch
|
|
|
|
def transposeChord(chord, trans):
|
|
transposedChord = list(chord)
|
|
for pdx, pitch in enumerate(chord):
|
|
transposedPitch = transposePitch(pitch, trans)
|
|
transposedChord[pdx] = tuple(transposedPitch)
|
|
return tuple(transposedChord)
|
|
|
|
def chords(pitches, r):
|
|
def is_connected(iterable):
|
|
points = comparitors = list(iterable)
|
|
connectedPoints = []
|
|
base = points[0]
|
|
bIdxScroll = 0
|
|
while True:
|
|
for comp in comparitors:
|
|
comps = sorted([base, comp], key=len, reverse=True)
|
|
if ((Counter(comps[0]) - Counter(comps[1])).total() == 1) and (len(comps[0]) - len(comps[1]) == 1):
|
|
comparitors = connectedPoints = connectedPoints + comps
|
|
points.remove(base)
|
|
if comp in points:
|
|
points.remove(comp)
|
|
if(len(points) == 0):
|
|
return True
|
|
else:
|
|
base = points[0]
|
|
bIdxScroll = 0
|
|
break
|
|
else:
|
|
if bIdxScroll < (len(points) - 1):
|
|
bIdxScroll += 1
|
|
base = points[bIdxScroll]
|
|
else:
|
|
return False
|
|
def is_centered(iterable):
|
|
return len(list(iterable)[0]) == 0
|
|
#return filter(is_connected, it.takewhile(is_centered, it.combinations(pitches, r)))
|
|
return {c for c in it.takewhile(is_centered, it.combinations(pitches, r)) if is_connected(c)}
|
|
|
|
def pitchDifference(frs):
|
|
cents1 = (1200 * math.log(hsPointToFR(frs[0]), 2))
|
|
cents2 = (1200 * math.log(hsPointToFR(frs[1]), 2))
|
|
return abs(cents2 - cents1)
|
|
|
|
def difference(p1, p2):
|
|
return transposePitch(p1, [p * -1 for p in p2])
|
|
|
|
def edges(chords):
|
|
def edgeDict(transposition, symDiff):
|
|
dict = {}
|
|
dict['melodic_movement'] = pitchDifference(symDiff)
|
|
dict['transposition'] = transposition
|
|
return dict
|
|
def reverseDict(dict):
|
|
revDict = copy.deepcopy(dict)
|
|
if revDict['transposition'] != ():
|
|
revDict['transposition'] = tuple(t * -1 for t in revDict['transposition'])
|
|
return revDict
|
|
def edgeData(iterable):
|
|
[base, comp] = list(iterable)
|
|
expandedBase = expandChord(base)
|
|
expandedComp = expandChord(comp)
|
|
transpositions = set([tuple(difference(pair[0], pair[1])) for pair in set(it.product(expandedBase, expandedComp))])
|
|
edges = [(expandedBase, expandedComp, edgeDict(t, symDiff)) for t in transpositions if len(symDiff := list(set(expandedBase) ^ set(tChord := transposeChord(expandedComp, t)))) == 2]
|
|
edges = edges + [(e[1], e[0], reverseDict(e[2])) for e in edges]
|
|
if edges != []:
|
|
return edges
|
|
else:
|
|
return None
|
|
return list(it.chain(*[e for c in it.combinations(chords, 2) if (e := edgeData(c)) is not None]))
|
|
|
|
def graph(edges):
|
|
G = nx.MultiDiGraph()
|
|
G.add_edges_from(edges)
|
|
return G
|
|
|
|
def hamiltonian(G):
|
|
F = [(G,[list(G.nodes())[0]])]
|
|
n = G.number_of_nodes()
|
|
while F:
|
|
graph,path = F.pop()
|
|
confs = []
|
|
neighbors = (node for node in graph.neighbors(path[-1])
|
|
if node != path[-1]) #exclude self loops
|
|
for neighbor in neighbors:
|
|
conf_p = path[:]
|
|
conf_p.append(neighbor)
|
|
conf_g = nx.Graph(graph)
|
|
conf_g.remove_node(path[-1])
|
|
confs.append((conf_g,conf_p))
|
|
for g,p in confs:
|
|
if len(p)==n:
|
|
return p
|
|
else:
|
|
F.append((g,p))
|
|
return None
|
|
|
|
def stochastic_hamiltonian(graph):
|
|
check_graph = graph.copy()
|
|
#next_node = random.choice(list(graph.nodes()))
|
|
next_node = list(graph.nodes())[0]
|
|
check_graph.remove_node(next_node)
|
|
path = [next_node]
|
|
while (nx.number_of_nodes(check_graph) > 0) and (len(path) < 5000):
|
|
neighbors = graph[next_node]
|
|
nd_list = list(graph.degree(list(neighbors)))
|
|
neighbors, weights = zip(*[[n, 1/pow(d, 2) if n not in path else 0.0000001] for n, d in nd_list])
|
|
next_node = random.choices(neighbors, weights=weights)[0]
|
|
path.append(next_node)
|
|
if next_node in check_graph.nodes:
|
|
check_graph.remove_node(next_node)
|
|
return [path, check_graph]
|
|
|
|
def stochastic_hamiltonian(graph):
|
|
check_graph = graph.copy()
|
|
#next_node = random.choice(list(graph.nodes()))
|
|
next_node = list(graph.nodes())[0]
|
|
check_graph.remove_node(next_node)
|
|
path = []
|
|
while (nx.number_of_nodes(check_graph) > 0) and (len(path) < 5000):
|
|
outEdges = list(graph.out_edges(next_node, data=True))
|
|
weights = [(1 if e[2]['melodic_movement'] < 200 else 0.001) * (1 if e[1] not in [pE[0] for pE in path] else 0.0000001) for e in outEdges]
|
|
edge = random.choices(outEdges, weights=weights)[0]
|
|
next_node = edge[1]
|
|
path.append(edge)
|
|
if next_node in check_graph.nodes:
|
|
check_graph.remove_node(next_node)
|
|
return path
|
|
|
|
|
|
# In[3]:
|
|
|
|
|
|
pSet = pitches([1, 3, 5], 4)
|
|
#print(len(list(pSet)))
|
|
cSet = chords(pSet, 4)
|
|
#print(cSet)
|
|
eSet = edges(cSet)
|
|
#for e in eSet:
|
|
# print(e)
|
|
testGraph = graph(eSet)
|
|
|
|
|
|
# In[4]:
|
|
|
|
|
|
len(testGraph.nodes)
|
|
|
|
|
|
# In[5]:
|
|
|
|
|
|
len(testGraph.edges)
|
|
|
|
|
|
# In[6]:
|
|
|
|
|
|
sGraph = nx.Graph(testGraph)
|
|
pos = nx.draw_spring(sGraph, node_size=5, width=0.1)
|
|
|
|
# larger figure size
|
|
plt.figure(1, figsize=(12,12))
|
|
nx.draw(sGraph, pos, node_size=5, width=0.1)
|
|
#plt.show()
|
|
plt.savefig('compact_sets.png', dpi=150)
|
|
|
|
|
|
# In[7]:
|
|
|
|
|
|
def reconcilePath(ham):
|
|
def sortByOther(c1, c2, trans):
|
|
indices = list(range(len(c1)))
|
|
sortedChord = copy.deepcopy(c2)
|
|
for pitch in c2:
|
|
transposedPitch = tuple(transposePitch(pitch, trans))
|
|
if transposedPitch in c1:
|
|
index = c1.index(transposedPitch)
|
|
sortedChord[index] = pitch
|
|
indices.remove(index)
|
|
else:
|
|
diff = pitch
|
|
sortedChord[indices[0]] = diff
|
|
return sortedChord
|
|
rPath = [[[], [list(p) for p in ham[0][0]]]]
|
|
for cdx in range(len(ham)-1):
|
|
c1 = list(ham[cdx][0])
|
|
c2 = list(ham[cdx][1])
|
|
trans = list(ham[cdx][2]['transposition'])
|
|
c2 = sortByOther(c1, c2, trans)
|
|
ham[cdx+1][0] = c2
|
|
rPath.append([trans, [list(p) for p in c2]])
|
|
return rPath
|
|
|
|
ham = stochastic_hamiltonian(testGraph)
|
|
ham = [list(e) for e in ham]
|
|
print(len(ham))
|
|
for e in ham:
|
|
print(e)
|
|
rPath = reconcilePath(ham)
|
|
rPath
|
|
|
|
|
|
# In[8]:
|
|
|
|
|
|
def pathToChords(path):
|
|
curRoot = Fraction(1, 1)
|
|
chords = []
|
|
for trans, points in path:
|
|
curRoot = curRoot * hsPointToFR(trans)
|
|
chord = [float(curRoot * hsPointToFR(p)) for p in points]
|
|
chords.append(chord)
|
|
return chords
|
|
|
|
fPath = pathToChords(rPath)
|
|
len(set([tuple(p) for p in fPath]))
|
|
fPath
|
|
|
|
|
|
# In[284]:
|
|
|
|
|
|
# Opening a file in write mode{
|
|
file = open("seq.txt", "w+")
|
|
|
|
# Converting the array to a string and writing to the file
|
|
content = str(fPath)
|
|
file.write(content)
|
|
|
|
# Closing the file
|
|
file.close()
|
|
|
|
|
|
# In[279]:
|
|
|
|
|
|
for edge in list(testGraph.edges(data=True))[:1000]:
|
|
print(edge)
|
|
|
|
|
|
# In[161]:
|
|
|
|
|
|
import networkx as nx
|
|
from matplotlib import pyplot as plt
|
|
import math
|
|
|
|
G = nx.grid_graph(dim=(range(-3, 4), range(-3, 4)))
|
|
|
|
def getLabel(x, y):
|
|
num = 1
|
|
den = 1
|
|
if x >= 0:
|
|
num *= math.pow(3, x)
|
|
else:
|
|
den *= math.pow(3, abs(x))
|
|
if y >= 0:
|
|
num *= math.pow(2, y)
|
|
else:
|
|
den *= math.pow(2, abs(y))
|
|
return str(int(num)) + "/" + str(int(den))
|
|
|
|
|
|
|
|
plt.figure(figsize=(10 * math.log2(3), 10 * math.log2(2)))
|
|
#plt.figure(figsize=(10, 10))
|
|
pos = {(x, y):(x * math.log2(3), y * math.log2(2)) for x,y in G.nodes()}
|
|
labels = {(x, y):getLabel(x, y) for x,y in G.nodes()}
|
|
nx.draw_networkx_labels(G, pos, labels=labels)
|
|
nx.draw(G, pos=pos,
|
|
node_color='white',
|
|
with_labels=False,
|
|
node_size=1000)
|
|
|
|
|
|
# In[160]:
|
|
|
|
|
|
import networkx as nx
|
|
from matplotlib import pyplot as plt
|
|
import math
|
|
|
|
G = nx.grid_graph(dim=(range(-2, 3), range(-2, 3)))
|
|
|
|
def collapseLabel(fraction):
|
|
if fraction < 1:
|
|
while fraction < 1:
|
|
fraction *= Fraction(2, 1)
|
|
elif fraction >= 2:
|
|
while fraction >= 2:
|
|
fraction *= Fraction(1, 2)
|
|
return fraction
|
|
|
|
def getLabel(x, y):
|
|
num = 1
|
|
den = 1
|
|
if x >= 0:
|
|
num *= math.pow(5, x)
|
|
else:
|
|
den *= math.pow(5, abs(x))
|
|
if y >= 0:
|
|
num *= math.pow(3, y)
|
|
else:
|
|
den *= math.pow(3, abs(y))
|
|
fraction = collapse(Fraction(int(num), int(den)))
|
|
num = fraction.numerator
|
|
den = fraction.denominator
|
|
return str(int(num)) + "/" + str(int(den))
|
|
|
|
plt.figure(figsize=(5 * math.log2(5), 5 * math.log2(3)))
|
|
#plt.figure(figsize=(10, 10))
|
|
pos = {(x, y):(x, y) for x,y in G.nodes()}
|
|
labels = {(x, y):getLabel(x, y) for x,y in G.nodes()}
|
|
nx.draw_networkx_labels(G, pos, labels=labels)
|
|
nx.draw(G, pos=pos,
|
|
node_color='white',
|
|
with_labels=False,
|
|
node_size=2000)
|
|
|
|
|
|
# In[44]:
|
|
|
|
|
|
for node in list(testGraph.nodes)[2:3]:
|
|
edges = list(testGraph.out_edges(node, data=True))
|
|
for edge in edges:
|
|
if list(edge)[2]['transposition'] != ():
|
|
print(edge)
|
|
|
|
|
|
# In[251]:
|
|
|
|
|
|
import networkx as nx
|
|
import numpy as np
|
|
import matplotlib.pyplot as plt
|
|
from mpl_toolkits.mplot3d import Axes3D
|
|
|
|
# The graph to visualize
|
|
G = nx.grid_graph(dim=(range(-1, 2), range(-1, 2), range(-1, 2)))
|
|
|
|
# 3d spring layout
|
|
#pos = nx.spring_layout(G, dim=3, seed=779)
|
|
pos = {(x, y, z):(math.log2(2) * x, math.log2(3) * y, math.log2(5) * z) for x,y,z in G.nodes()}
|
|
# Extract node and edge positions from the layout
|
|
node_xyz = np.array([pos[v] for v in sorted(G)])
|
|
edge_xyz = np.array([(pos[u], pos[v]) for u, v in G.edges()])
|
|
|
|
# Create the 3D figure
|
|
fig = plt.figure(figsize=(10, 10))
|
|
ax = fig.add_subplot(111, projection="3d")
|
|
|
|
# Plot the nodes - alpha is scaled by "depth" automatically
|
|
ax.scatter(*node_xyz.T, s=100, ec="w")
|
|
|
|
ax.view_init(elev=30, azim=45, roll=15)
|
|
|
|
ax.axis('equal')
|
|
|
|
# Plot the edges
|
|
for vizedge in edge_xyz:
|
|
ax.plot(*vizedge.T, color="tab:gray")
|
|
|
|
|
|
def _format_axes(ax):
|
|
"""Visualization options for the 3D axes."""
|
|
# Turn gridlines off
|
|
ax.grid(False)
|
|
# Suppress tick labels
|
|
for dim in (ax.xaxis, ax.yaxis, ax.zaxis):
|
|
dim.set_ticks([])
|
|
# Set axes labels
|
|
ax.set_xlabel("x")
|
|
ax.set_ylabel("y")
|
|
ax.set_zlabel("z")
|
|
|
|
|
|
_format_axes(ax)
|
|
fig.tight_layout()
|
|
plt.show()
|
|
|
|
|
|
# In[31]:
|
|
|
|
|
|
from tikzpy import TikzPicture
|
|
|
|
def collapseLabel(fraction):
|
|
if fraction < 1:
|
|
while fraction < 1:
|
|
fraction *= Fraction(2, 1)
|
|
elif fraction >= 2:
|
|
while fraction >= 2:
|
|
fraction *= Fraction(1, 2)
|
|
return fraction
|
|
|
|
def getLabel(x, y, z, collapse = False):
|
|
num = 1
|
|
den = 1
|
|
if x >= 0:
|
|
num *= math.pow(3, x)
|
|
else:
|
|
den *= math.pow(3, abs(x))
|
|
|
|
if y >= 0:
|
|
num *= math.pow(5, y)
|
|
else:
|
|
den *= math.pow(5, abs(y))
|
|
|
|
if z >= 0:
|
|
num *= math.pow(2, z)
|
|
else:
|
|
den *= math.pow(2, abs(z))
|
|
if collapse:
|
|
fraction = collapseLabel(Fraction(int(num), int(den)))
|
|
else:
|
|
fraction = Fraction(int(num), int(den))
|
|
num = fraction.numerator
|
|
den = fraction.denominator
|
|
return str(int(num)) + "/" + str(int(den))
|
|
|
|
def chord2Points(chord):
|
|
points = []
|
|
for n in chord:
|
|
counter = Counter(n)
|
|
points.append(tuple([counter[d] - counter[-d] for d in [2, 3, 5]]))
|
|
return tuple(points)
|
|
|
|
def genLattice(chord = None, ranges = None, filename = "tikz", collapse = False, scale = 1):
|
|
|
|
dx = math.log2(3) * scale
|
|
dy = math.log2(5) * scale
|
|
dz = math.log2(2) * scale
|
|
|
|
if chord:
|
|
set = chord2Points(chord)
|
|
|
|
if ranges:
|
|
rz,rx,ry = ranges
|
|
else:
|
|
rz,rx,ry = [[min(t), max(t) + 1] for t in list(zip(*set))]
|
|
|
|
if collapse:
|
|
rz = [0, 1]
|
|
|
|
tikz = TikzPicture(center=True)
|
|
tikz.set_tdplotsetmaincoords(30, -30)
|
|
tikz.options = "tdplot_main_coords"
|
|
|
|
for x in range(*rx):
|
|
for y in range(*ry):
|
|
for z in range(*rz):
|
|
line = tikz.line((x * dx - dx / 2, y * dy, z * dz), (x * dx + dx / 2, y * dy, z * dz), options="thick, black, -")
|
|
line = tikz.line((x * dx, y * dy - dy / 2, z * dz), (x * dx, y * dy + dy / 2, z * dz), options="thick, black, -")
|
|
if not collapse:
|
|
line = tikz.line((x * dx, y * dy, z * dz - dz / 2), (x * dx, y * dy, z * dz + dz / 2), options="thick, black, -")
|
|
node = tikz.node((x * dx, y * dy, z * dz), options="draw, fill=white, scale=0.5", text=getLabel(x,y,z, collapse))
|
|
|
|
if chord:
|
|
for e in set:
|
|
z,x,y = e
|
|
if collapse:
|
|
z = 0
|
|
line = tikz.line((x * dx - dx / 2, y * dy, z * dz), (x * dx + dx / 2, y * dy, z * dz), options="thick, black, -")
|
|
line = tikz.line((x * dx, y * dy - dy / 2, z * dz), (x * dx, y * dy + dy / 2, z * dz), options="thick, black, -")
|
|
if not collapse:
|
|
line = tikz.line((x * dx, y * dy, z * dz - dz / 2), (x * dx, y * dy, z * dz + dz / 2), options="thick, black, -")
|
|
node = tikz.node((x * dx, y * dy, z * dz), options="draw, fill=yellow, scale=0.5", text=getLabel(x,y,z, collapse))
|
|
|
|
tikz.compile(filename + ".pdf", True)
|
|
|
|
texFile = open(filename + ".tex", "w+")
|
|
texFile.write(tikz.code())
|
|
texFile.close()
|
|
|
|
|
|
# In[72]:
|
|
|
|
|
|
edge = (((), (-2, 3), (2, 3, -5), (3, 3, -5)), ((), (2, 2, -3), (-2, 3), (-2, -2, 5)), {'melodic_movement': 813.6862861351653, 'transposition': (2, 3, -5)})
|
|
chord = transposeChord(edge[0], (-2, -3, 5))
|
|
#genLattice(chord, path="figure.pdf", collapse=False)
|
|
genLattice(chord, ranges=[[-2, 2], [-2, 2], [-2, 2]], filename="compact_set_1_transposed_expanded_padded", collapse=False, scale=2)
|
|
|
|
|
|
# In[79]:
|
|
|
|
|
|
edge = (((), (-2, 3), (2, 3, -5), (3, 3, -5)), ((), (2, 2, -3), (-2, 3), (-2, -2, 5)), {'melodic_movement': 813.6862861351653, 'transposition': (2, 3, -5)})
|
|
chord = transposeChord(edge[0], (-2, -3, 5))
|
|
#genLattice(chord, path="figure.pdf", collapse=False)
|
|
genLattice(chord, ranges=[[-2, 2], [-1, 3], [-1, 2]], filename="compact_set_1_transposed_expanded_padded", collapse=False, scale=2)
|
|
|
|
|
|
# In[80]:
|
|
|
|
|
|
edge = (((), (-2, 3), (2, 3, -5), (3, 3, -5)), ((), (2, 2, -3), (-2, 3), (-2, -2, 5)), {'melodic_movement': 813.6862861351653, 'transposition': (2, 3, -5)})
|
|
chord = edge[0]
|
|
#genLattice(chord, path="figure.pdf", collapse=False)
|
|
genLattice(chord, ranges=[[-2, 2], [-1, 3], [-1, 2]], filename="compact_set_1_expanded_padded", collapse=False, scale=2)
|
|
|