new faster version of generating compact sets

dev
mwinter 8 months ago
parent 29da52a98f
commit 99b9cd705b

@ -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
}
Loading…
Cancel
Save