diff --git a/compact_sets_extended_simplified.ipynb b/compact_sets_extended_simplified.ipynb new file mode 100644 index 0000000..26791b0 --- /dev/null +++ b/compact_sets_extended_simplified.ipynb @@ -0,0 +1,248 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 93, + "id": "1b7b9f62-156d-4ac5-876d-0305a25d99e4", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "389" + ] + }, + "execution_count": 93, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from itertools import chain, combinations, permutations, product\n", + "from math import prod, log\n", + "from copy import deepcopy\n", + "\n", + "# this is modified for different chord sizes like original version\n", + "def grow_chords(chord, root, min_chord_size, max_chord_size):\n", + " #this could use the tranpose_pitch function\n", + " branches = [branch for alt in [-1, 1] for d in range(1, len(root)) if (branch:=(*(r:=root)[:d], r[d] + alt, *r[(d + 1):])) not in chord]\n", + " subsets = chain.from_iterable(combinations(branches, r) for r in range(1, max_chord_size - len(chord) + 1))\n", + " for subset in subsets:\n", + " extended_chord = chord + subset\n", + " if(len(extended_chord) < max_chord_size):\n", + " for branch in subset:\n", + " yield from grow_chords(extended_chord, branch, min_chord_size, max_chord_size)\n", + " if(len(extended_chord) >= min_chord_size):\n", + " yield tuple(sorted(extended_chord))\n", + "\n", + "def chords(chord, root, min_chord_size, max_chord_size):\n", + " # this will filter out the 4x dups of paths that are loops, there might be a faster way to test this\n", + " return set(grow_chords(chord, root, min_chord_size, max_chord_size))\n", + "\n", + "root = (0, 0, 0, 0)\n", + "chord = (root,)\n", + "chords = chords(chord, root, 3, 4)\n", + "len(chords)" + ] + }, + { + "cell_type": "code", + "execution_count": 133, + "id": "806f6f69-1e0b-4d34-aac9-695c8531cdb1", + "metadata": {}, + "outputs": [], + "source": [ + "from itertools import chain, combinations, permutations, product\n", + "from math import prod, log\n", + "from copy import deepcopy\n", + "import networkx as nx\n", + "\n", + "def hs_array_to_fr(hs_array):\n", + " return prod([pow(dims[d], hs_array[d]) for d in range(len(dims))])\n", + "\n", + "def hs_array_to_cents(hs_array):\n", + " return (1200 * log(hs_array_to_fr(hs_array), 2))\n", + "\n", + "def expand_pitch(hs_array):\n", + " expanded_pitch = list(hs_array)\n", + " frequency_ratio = hs_array_to_fr(hs_array)\n", + " if frequency_ratio < 1:\n", + " while frequency_ratio < 1:\n", + " frequency_ratio *= 2\n", + " expanded_pitch[0] += 1\n", + " elif frequency_ratio >= 2:\n", + " while frequency_ratio >= 2:\n", + " frequency_ratio *= 1/2\n", + " expanded_pitch[0] += -1\n", + " return tuple(expanded_pitch)\n", + "\n", + "def expand_chord(chord):\n", + " return tuple(expand_pitch(p) for p in chord)\n", + "\n", + "def collapse_pitch(hs_array):\n", + " collapsed_pitch = list(hs_array)\n", + " collapsed_pitch[0] = 0\n", + " return tuple(collapsed_pitch)\n", + "\n", + "def collapse_chord(chord):\n", + " return tuple(collapse_pitch(p) for p in chord)\n", + "\n", + "def transpose_pitch(pitch, trans):\n", + " return tuple(map(lambda x,y:x+y, pitch, trans))\n", + "\n", + "def transpose_chord(chord, trans):\n", + " return tuple(transpose_pitch(p, trans) for p in chord)\n", + "\n", + "def cent_difference(hs_array1, hs_array2):\n", + " return hs_array_to_cents(hs_array2) - hs_array_to_cents(hs_array1)\n", + "\n", + "def pitch_difference(hs_array1, hs_array2):\n", + " return transpose_pitch(hs_array1, [p * -1 for p in hs_array2])\n", + "\n", + "# this is modified for different chord sizes like original version\n", + "def grow_chords(chord, root, min_chord_size, max_chord_size):\n", + " #this could use the tranpose_pitch function\n", + " branches = [branch for alt in [-1, 1] for d in range(1, len(root)) if (branch:=(*(r:=root)[:d], r[d] + alt, *r[(d + 1):])) not in chord]\n", + " subsets = chain.from_iterable(combinations(branches, r) for r in range(1, max_chord_size - len(chord) + 1))\n", + " for subset in subsets:\n", + " extended_chord = chord + subset\n", + " if(len(extended_chord) < max_chord_size):\n", + " for branch in subset:\n", + " yield from grow_chords(extended_chord, branch, min_chord_size, max_chord_size)\n", + " if(len(extended_chord) >= min_chord_size):\n", + " yield tuple(sorted(extended_chord))\n", + "\n", + "def chords(chord, root, min_chord_size, max_chord_size):\n", + " # this will filter out the 4x dups of paths that are loops, there might be a faster way to test this\n", + " return set(grow_chords(chord, root, min_chord_size, max_chord_size))\n", + "\n", + "# this is very slow, I have an idea in mind that my be faster by simply growing the chords to max_chord_size + max_sim_diff\n", + "# technically at that point you have generated both chords and can get the second chord from the first\n", + "def edges(chords, min_symdiff, max_symdiff, max_chord_size): \n", + " def reverse_dict(dict):\n", + " rev_dict = deepcopy(dict)\n", + " rev_trans = tuple(t * -1 for t in rev_dict['transposition'])\n", + " rev_dict['transposition'] = rev_trans\n", + " rev_dict['movements'] = {\n", + " value['destination']:{\n", + " 'destination':key, \n", + " 'cent_difference':value['cent_difference']\n", + " } for key, value in rev_dict['movements'].items()}\n", + " return rev_dict\n", + "\n", + " def is_directly_tunable(intersection, diff):\n", + " return max([len(collapse_pitch(pitch_difference(d, set(list(intersection)[0])))) for d in diff]) == 1\n", + "\n", + " def edge_data(chords):\n", + " [expanded_base, expanded_comp] = [set(expand_chord(chord)) for chord in chords]\n", + " edges = []\n", + " transpositions = set(pitch_difference(pair[0], pair[1]) for pair in set(product(expanded_base, expanded_comp)))\n", + " for trans in transpositions:\n", + " rev_trans = tuple(t * -1 for t in trans)\n", + " expanded_comp_transposed = set(transpose_chord(expanded_comp, trans))\n", + " intersection = expanded_base & expanded_comp_transposed\n", + " [diff1, diff2] = [list(chord - intersection) for chord in [expanded_base, expanded_comp_transposed]]\n", + " base_map = {val: {'destination':transpose_pitch(val, rev_trans), 'cent_difference': 0} for val in intersection}\n", + " symdiff_len = (len(diff1) + len(diff2))\n", + " if (min_symdiff <= symdiff_len <= max_symdiff):\n", + " edge_dict = {\n", + " 'transposition': trans, \n", + " 'symmetric_difference': symdiff_len, \n", + " 'is_directly_tunable': is_directly_tunable(intersection, diff2)\n", + " }\n", + " maps = []\n", + " diff1 += [None] * (max_chord_size - len(diff1) - len(intersection))\n", + " perms = [list(perm) + [None] * (max_chord_size - len(perm) - len(intersection)) for perm in set(permutations(diff2))]\n", + " for p in perms:\n", + " appended_map = {\n", + " diff1[index]:\n", + " {\n", + " 'destination': transpose_pitch(val, rev_trans) if val != None else None, \n", + " 'cent_difference': cent_difference(diff1[index], val) if None not in [diff1[index], val] else None\n", + " } for index, val in enumerate(p)}\n", + " edge_dict['movements'] = base_map | appended_map\n", + " edges.append((tuple(expanded_base), tuple(expanded_comp), edge_dict))\n", + " edges.append((tuple(expanded_comp), tuple(expanded_base), reverse_dict(edge_dict)))\n", + " return edges if edges != [] else None\n", + " \n", + " return list(chain(*[e for c in combinations(chords, 2) if (e := edge_data(c)) is not None]))\n", + "\n", + "def graph_from_edges(edges):\n", + " g = nx.MultiDiGraph()\n", + " g.add_edges_from(edges)\n", + " return g\n", + "\n", + "def generate_graph(chord_set, min_symdiff, max_symdiff, max_chord_size):\n", + " #chord_set = chords(pitch_set, min_chord_size, max_chord_size)\n", + " edge_set = edges(chord_set, min_symdiff, max_symdiff, max_chord_size)\n", + " res_graph = graph_from_edges(edge_set)\n", + " return res_graph\n", + "\n", + "def display_graph(graph):\n", + " show_graph = nx.Graph(graph)\n", + " pos = nx.draw_spring(show_graph, node_size=5, width=0.1)\n", + " plt.figure(1, figsize=(12,12)) \n", + " nx.draw(show_graph, pos, node_size=5, width=0.1)\n", + " plt.show()\n", + " #plt.savefig('compact_sets.png', dpi=150)" + ] + }, + { + "cell_type": "code", + "execution_count": 145, + "id": "3b220e4f-af29-4226-b60d-30078da05663", + "metadata": {}, + "outputs": [], + "source": [ + "dims = (2, 3, 5, 7)\n", + "root = (0, 0, 0, 0)\n", + "chord = (root,)\n", + "chord_set = chords(chord, root, 4, 4)\n", + "#edges(chord_set, 2, 2, 3)\n", + "graph = generate_graph(chord_set, 2, 2, 4)" + ] + }, + { + "cell_type": "code", + "execution_count": 144, + "id": "472e3033-cf7f-43da-9396-df6c6ee426b8", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "344" + ] + }, + "execution_count": 144, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "len(graph.nodes)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +}