concertina_helper.finger_finder
1from typing import Iterable 2from dataclasses import dataclass 3 4from astar import AStar # type: ignore 5 6from .layouts.bisonoric import AnnotatedBisonoricFingering 7from .penalties import PenaltyFunction 8 9 10def find_best_fingerings( 11 all_fingerings: Iterable[set[AnnotatedBisonoricFingering]], 12 penalty_functions: Iterable[PenaltyFunction] 13) -> Iterable[AnnotatedBisonoricFingering]: 14 ''' 15 Given a list of sets of possible fingerings, 16 returns a list representing the best fingerings. 17 See `concertina_helper.notes_on_layout.NotesOnLayout.get_best_fingerings` 18 for a convenience method that wraps this. 19 ''' 20 finder = _FingerFinder(all_fingerings, penalty_functions) 21 return finder.find() 22 23 24@dataclass(frozen=True) 25class _Node: 26 position: int 27 annotated_fingering: AnnotatedBisonoricFingering | None 28 29 30class _FingerFinder(AStar): 31 def __init__( 32 self, 33 fingerings: Iterable[set[AnnotatedBisonoricFingering]], 34 penalty_functions: Iterable[PenaltyFunction]): 35 self.penalty_functions = penalty_functions 36 self.index: dict[int, set[_Node]] = { 37 i: {_Node(i, f) for f in f_set} 38 for i, f_set in enumerate(fingerings) 39 } 40 41 def find(self) -> Iterable[AnnotatedBisonoricFingering]: 42 start = _Node(-1, None) 43 max_index = max(self.index.keys()) 44 goal = list(self.index[max_index])[0] 45 # is_goal_reached() only checks position, 46 # so I think we can use any final node. 47 # ... but then why is the goal parameter needed on astar(start, goal)? 48 49 return [ 50 node.annotated_fingering for node in self.astar(start, goal) 51 if node.annotated_fingering is not None 52 ] 53 54 def heuristic_cost_estimate(self, current: _Node, goal: _Node) -> float: 55 return goal.position - current.position 56 57 def distance_between(self, n1: _Node, n2: _Node) -> float: 58 # TODO: Make the weightings here configurable. 59 distance = float(abs(n1.position - n2.position)) 60 assert distance == 1.0 # Should only be used with immediate neighbors 61 62 if n1.annotated_fingering is not None and n2.annotated_fingering is not None: 63 # If either is an end node, thers is no additional transition cost. 64 f1 = n1.annotated_fingering 65 f2 = n2.annotated_fingering 66 for function in self.penalty_functions: 67 distance += function(f1, f2) 68 return distance 69 70 def neighbors(self, node: _Node) -> Iterable[_Node]: 71 return self.index[node.position + 1] 72 73 def is_goal_reached(self, current: _Node, goal: _Node) -> bool: 74 return current.position == goal.position
def
find_best_fingerings( all_fingerings: Iterable[set[concertina_helper.layouts.bisonoric.AnnotatedBisonoricFingering]], penalty_functions: Iterable[collections.abc.Callable[[concertina_helper.layouts.bisonoric.AnnotatedBisonoricFingering, concertina_helper.layouts.bisonoric.AnnotatedBisonoricFingering], float]]) -> Iterable[concertina_helper.layouts.bisonoric.AnnotatedBisonoricFingering]:
11def find_best_fingerings( 12 all_fingerings: Iterable[set[AnnotatedBisonoricFingering]], 13 penalty_functions: Iterable[PenaltyFunction] 14) -> Iterable[AnnotatedBisonoricFingering]: 15 ''' 16 Given a list of sets of possible fingerings, 17 returns a list representing the best fingerings. 18 See `concertina_helper.notes_on_layout.NotesOnLayout.get_best_fingerings` 19 for a convenience method that wraps this. 20 ''' 21 finder = _FingerFinder(all_fingerings, penalty_functions) 22 return finder.find()
Given a list of sets of possible fingerings,
returns a list representing the best fingerings.
See concertina_helper.notes_on_layout.NotesOnLayout.get_best_fingerings
for a convenience method that wraps this.