"""Independent exhaustive oracle for small grip/hand placement examples.""" import itertools def regions(shape): return tuple(h for h in range(1, 13) if all(h <= p.fret <= h + 3 for p in shape)) if shape else tuple([0]) def movement(a, b, ha, hb, acoustic=False): if not a or not b: return 0, 0 if len(a) == len(b) == 1: pairs, unmatched = [(a[0], b[0])], 0 else: def voices(shape): return {('bass' if acoustic and p.string_number in (5, 6) else p.string_number): p for p in shape} x, y = voices(a), voices(b) pairs = [(x[s], y[s]) for s in x.keys() & y.keys()] unmatched = len(x.keys() ^ y.keys()) return abs(ha - hb), unmatched + sum(abs((p.fret - ha) - (q.fret - hb)) + abs(p.string_number - q.string_number) for p, q in pairs) def cost(path, costs, indices, *, acoustic=False, left=(), right=(), close=False, hands=None, left_hand=None, right_hand=None): travel, peak, effort = indices base = [sum(costs[i][v][j] for i, v in enumerate(path)) for j in range(len(costs[0][path[0]]))] results = [] for positions in [hands] if hands is not None else itertools.product(*(regions(v) for v in path)): for lh in [left_hand] if left_hand is not None else regions(left): for rh in [positions[0]] if close else [right_hand] if right_hand is not None else regions(right): points = [(left, lh), *zip(path, positions), (path[0] if close else right, rh)] total = base.copy() total[peak] = max(h for _, h in points) for (a, ha), (b, hb) in zip(points, points[1:]): shift, ergonomics = movement(a, b, ha, hb, acoustic) total[travel] += shift total[effort] += ergonomics results.append(tuple(total)) return min(results)