Refactor: Rename sustain/last_visited fields for consistency

This commit is contained in:
Michael Winter 2026-03-16 17:35:07 +01:00
parent 861d012a95
commit 482f2b0df5
2 changed files with 33 additions and 31 deletions

View file

@ -23,10 +23,10 @@ class PathStep:
movements: dict[int, int] = field(default_factory=dict) movements: dict[int, int] = field(default_factory=dict)
scores: dict[str, float] = field(default_factory=dict) scores: dict[str, float] = field(default_factory=dict)
weight: float = 0.0 # computed later by _compute_weights weight: float = 0.0 # computed later by _compute_weights
last_visited_count_before: dict | None = None last_visited_counts_before: dict | None = None
last_visited_count_after: dict | None = None last_visited_counts_after: dict | None = None
sustain_count_before: tuple[int, ...] | None = None sustain_counts_before: tuple[int, ...] | None = None
sustain_count_after: tuple[int, ...] | None = None sustain_counts_after: tuple[int, ...] | None = None
class Path: class Path:
@ -60,8 +60,8 @@ class Path:
"""Get last visited counts from the last step, or initialize fresh.""" """Get last visited counts from the last step, or initialize fresh."""
if self.steps: if self.steps:
last_step = self.steps[-1] last_step = self.steps[-1]
if last_step.last_visited_count_after is not None: if last_step.last_visited_counts_after is not None:
return dict(last_step.last_visited_count_after) return dict(last_step.last_visited_counts_after)
# Initialize fresh: all nodes start at 0 (except initial which we set to 0 explicitly) # Initialize fresh: all nodes start at 0 (except initial which we set to 0 explicitly)
return {node: 0 for node in self._graph_nodes} return {node: 0 for node in self._graph_nodes}
@ -70,8 +70,8 @@ class Path:
"""Get sustain counts from the last step, or initialize fresh.""" """Get sustain counts from the last step, or initialize fresh."""
if self.steps: if self.steps:
last_step = self.steps[-1] last_step = self.steps[-1]
if last_step.sustain_count_after is not None: if last_step.sustain_counts_after is not None:
return last_step.sustain_count_after return last_step.sustain_counts_after
# Initialize fresh: all voices start at 0 # Initialize fresh: all voices start at 0
return tuple(0 for _ in range(self._num_voices)) return tuple(0 for _ in range(self._num_voices))
@ -114,10 +114,10 @@ class Path:
sustain_after[voice_idx] = 0 sustain_after[voice_idx] = 0
# Update step with computed state # Update step with computed state
step.last_visited_count_before = last_visited_before step.last_visited_counts_before = last_visited_before
step.last_visited_count_after = last_visited_after step.last_visited_counts_after = last_visited_after
step.sustain_count_before = sustain_before step.sustain_counts_before = sustain_before
step.sustain_count_after = tuple(sustain_after) step.sustain_counts_after = tuple(sustain_after)
self.steps.append(step) self.steps.append(step)
return step return step

View file

@ -57,22 +57,22 @@ class PathFinder:
# Derive state from last step (or initialize fresh for step 0) # Derive state from last step (or initialize fresh for step 0)
if path_obj.steps: if path_obj.steps:
last_step = path_obj.steps[-1] last_step = path_obj.steps[-1]
voice_stay_count = last_step.sustain_count_after sustain_counts = last_step.sustain_counts_after
node_visit_counts = last_step.last_visited_count_after last_visited_counts = last_step.last_visited_counts_after
else: else:
# First step - derive from path object's current state # First step - derive from path object's current state
voice_stay_count = tuple(0 for _ in range(len(path_obj._voice_map))) sustain_counts = tuple(0 for _ in range(len(path_obj._voice_map)))
node_visit_counts = {node: 0 for node in set(self.graph.nodes())} last_visited_counts = {node: 0 for node in set(self.graph.nodes())}
# Build candidates with raw scores # Build candidates with raw scores
candidates = self._build_candidates( candidates = self._build_candidates(
out_edges, out_edges,
path_obj.output_chords, path_obj.output_chords,
weights_config, weights_config,
voice_stay_count, sustain_counts,
path_obj.graph_chords, path_obj.graph_chords,
path_obj._cumulative_trans, path_obj._cumulative_trans,
node_visit_counts, last_visited_counts,
) )
# Compute weights from raw scores # Compute weights from raw scores
@ -105,10 +105,10 @@ class PathFinder:
out_edges: list, out_edges: list,
path: list["Chord"], path: list["Chord"],
config: dict, config: dict,
voice_stay_count: tuple[int, ...] | None, sustain_counts: tuple[int, ...] | None,
graph_path: list["Chord"] | None, graph_path: list["Chord"] | None,
cumulative_trans: "Pitch | None", cumulative_trans: "Pitch | None",
node_visit_counts: dict | None, last_visited_counts: dict | None,
) -> list[PathStep]: ) -> list[PathStep]:
"""Build hypothetical path steps with raw factor scores.""" """Build hypothetical path steps with raw factor scores."""
if not out_edges: if not out_edges:
@ -151,9 +151,11 @@ class PathFinder:
voice_crossing = self._factor_voice_crossing(edge_data, config) voice_crossing = self._factor_voice_crossing(edge_data, config)
melodic = self._factor_melodic_threshold(edge_data, config) melodic = self._factor_melodic_threshold(edge_data, config)
contrary = self._factor_contrary_motion(edge_data, config) contrary = self._factor_contrary_motion(edge_data, config)
hamiltonian = self._factor_dca_hamiltonian(edge, node_visit_counts, config) hamiltonian = self._factor_dca_hamiltonian(
edge, last_visited_counts, config
)
dca_voice = self._factor_dca_voice_movement( dca_voice = self._factor_dca_voice_movement(
edge, path, voice_stay_count, config, cumulative_trans edge, path, sustain_counts, config, cumulative_trans
) )
target = self._factor_target_range(edge, path, config, cumulative_trans) target = self._factor_target_range(edge, path, config, cumulative_trans)
@ -373,7 +375,7 @@ class PathFinder:
return max(0.0, 1.0 - (distance / ideal_up)) return max(0.0, 1.0 - (distance / ideal_up))
def _factor_dca_hamiltonian( def _factor_dca_hamiltonian(
self, edge: tuple, node_visit_counts: dict | None, config: dict self, edge: tuple, last_visited_counts: dict | None, config: dict
) -> float: ) -> float:
"""Returns score based on how long since node was last visited. """Returns score based on how long since node was last visited.
@ -382,11 +384,11 @@ class PathFinder:
if config.get("weight_dca_hamiltonian", 1) == 0: if config.get("weight_dca_hamiltonian", 1) == 0:
return 1.0 return 1.0
if node_visit_counts is None: if last_visited_counts is None:
return 0.0 return 0.0
destination = edge[1] destination = edge[1]
visit_count = node_visit_counts.get(destination, 0) visit_count = last_visited_counts.get(destination, 0)
# Return the visit count - higher is better (more steps since last visit) # Return the visit count - higher is better (more steps since last visit)
return float(visit_count) return float(visit_count)
@ -395,27 +397,27 @@ class PathFinder:
self, self,
edge: tuple, edge: tuple,
path: list, path: list,
voice_stay_count: tuple[int, ...] | None, sustain_counts: tuple[int, ...] | None,
config: dict, config: dict,
cumulative_trans: "Pitch | None", cumulative_trans: "Pitch | None",
) -> float: ) -> float:
"""Returns probability that voices will change. """Returns probability that voices will change.
DCA = Dissonant Counterpoint Algorithm DCA = Dissonant Counterpoint Algorithm
Probability = (sum of stay_counts for changing voices) / (sum of ALL stay_counts) Probability = (sum of sustain_counts for changing voices) / (sum of ALL sustain_counts)
Higher probability = more likely to choose edge where long-staying voices change. Higher probability = more likely to choose edge where long-staying voices change.
""" """
if config.get("weight_dca_voice_movement", 1) == 0: if config.get("weight_dca_voice_movement", 1) == 0:
return 1.0 return 1.0
if voice_stay_count is None or len(path) == 0: if sustain_counts is None or len(path) == 0:
return 1.0 return 1.0
if cumulative_trans is None: if cumulative_trans is None:
return 1.0 return 1.0
num_voices = len(voice_stay_count) num_voices = len(sustain_counts)
if num_voices == 0: if num_voices == 0:
return 1.0 return 1.0
@ -434,14 +436,14 @@ class PathFinder:
candidate_cents = [p.to_cents() for p in candidate_transposed.pitches] candidate_cents = [p.to_cents() for p in candidate_transposed.pitches]
sum_changing = 0 sum_changing = 0
sum_all = sum(voice_stay_count) sum_all = sum(sustain_counts)
if sum_all == 0: if sum_all == 0:
return 1.0 return 1.0
for voice_idx in range(num_voices): for voice_idx in range(num_voices):
if current_cents[voice_idx] != candidate_cents[voice_idx]: if current_cents[voice_idx] != candidate_cents[voice_idx]:
sum_changing += voice_stay_count[voice_idx] sum_changing += sustain_counts[voice_idx]
return sum_changing / sum_all return sum_changing / sum_all