diff --git a/compact_sets.py b/compact_sets.py new file mode 100644 index 0000000..25138d3 --- /dev/null +++ b/compact_sets.py @@ -0,0 +1,577 @@ +#!/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) +