Refactor DCA factor: rename to DCA Voice Movement and DCA Hamiltonian
- Rename _factor_dca to _factor_dca_voice_movement (tracks voice pitch changes) - Rename _factor_hamiltonian to _factor_dca_hamiltonian (tracks node visit counts) - Update CLI: --weight-dca -> --weight-dca-voice-movement - Update CLI: --weight-hamiltonian -> --weight-dca-hamiltonian - Remove hamiltonian coverage from analysis (no longer tracking)
This commit is contained in:
parent
8cdbe90501
commit
218d7a55ff
|
|
@ -195,19 +195,9 @@ def format_analysis(metrics: dict) -> str:
|
|||
f"Percentage: {metrics['contrary_motion_percent']:.1f}%",
|
||||
f"Avg score: {metrics['contrary_motion_score']:.2f}",
|
||||
"",
|
||||
"--- DCA (Voice Stay) ---",
|
||||
"--- DCA Voice Movement ---",
|
||||
f"Avg stay count: {metrics['dca_avg_voice_stay']:.2f} steps",
|
||||
f"Max stay count: {metrics['dca_max_voice_stay']} steps",
|
||||
"",
|
||||
"--- Hamiltonian ---",
|
||||
f"Unique nodes: {metrics['hamiltonian_unique_nodes']}",
|
||||
]
|
||||
|
||||
if metrics["hamiltonian_coverage"] is not None:
|
||||
lines.append(f"Coverage: {metrics['hamiltonian_coverage']:.1f}%")
|
||||
|
||||
lines.extend(
|
||||
[
|
||||
"",
|
||||
"--- Target Range ---",
|
||||
f"Target: {metrics['target_octaves']} octaves ({metrics['target_cents']:.0f} cents)",
|
||||
|
|
@ -215,7 +205,6 @@ def format_analysis(metrics: dict) -> str:
|
|||
f"End: {metrics['target_end_cents']:.0f} cents",
|
||||
f"Achieved: {metrics['target_actual_cents']:.0f} cents ({metrics['target_percent']:.1f}%)",
|
||||
]
|
||||
)
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
|
|
|
|||
55
src/graph.py
55
src/graph.py
|
|
@ -44,6 +44,11 @@ class PathFinder:
|
|||
|
||||
graph_path = [graph_node]
|
||||
|
||||
# Track how long since each node was last visited (for DCA Hamiltonian)
|
||||
node_visit_counts = {node: 0 for node in self.graph.nodes()}
|
||||
# Mark start node as just visited
|
||||
node_visit_counts[graph_node] = 0
|
||||
|
||||
from .pitch import Pitch
|
||||
|
||||
dims = output_chord.dims
|
||||
|
|
@ -55,6 +60,10 @@ class PathFinder:
|
|||
voice_stay_count = [0] * num_voices
|
||||
|
||||
for _ in range(max_length):
|
||||
# Increment all node visit counts
|
||||
for node in node_visit_counts:
|
||||
node_visit_counts[node] += 1
|
||||
|
||||
out_edges = list(self.graph.out_edges(graph_node, data=True))
|
||||
|
||||
if not out_edges:
|
||||
|
|
@ -68,6 +77,7 @@ class PathFinder:
|
|||
tuple(voice_stay_count),
|
||||
graph_path,
|
||||
cumulative_trans,
|
||||
node_visit_counts,
|
||||
)
|
||||
|
||||
edge = choices(out_edges, weights=weights)[0]
|
||||
|
|
@ -103,6 +113,10 @@ class PathFinder:
|
|||
graph_node = next_graph_node
|
||||
graph_path.append(graph_node)
|
||||
|
||||
# Reset visit count for visited node
|
||||
if next_graph_node in node_visit_counts:
|
||||
node_visit_counts[next_graph_node] = 0
|
||||
|
||||
path.append(output_chord)
|
||||
last_graph_nodes = last_graph_nodes + (graph_node,)
|
||||
if len(last_graph_nodes) > 2:
|
||||
|
|
@ -164,6 +178,7 @@ class PathFinder:
|
|||
voice_stay_count: tuple[int, ...] | None = None,
|
||||
graph_path: list["Chord"] | None = None,
|
||||
cumulative_trans: "Pitch | None" = None,
|
||||
node_visit_counts: dict | None = None,
|
||||
) -> list[float]:
|
||||
"""Calculate weights for edges based on configuration.
|
||||
|
||||
|
|
@ -201,10 +216,12 @@ class PathFinder:
|
|||
melodic_values.append(self._factor_melodic_threshold(edge_data, config))
|
||||
contrary_values.append(self._factor_contrary_motion(edge_data, config))
|
||||
hamiltonian_values.append(
|
||||
self._factor_hamiltonian(edge, graph_path, config)
|
||||
self._factor_dca_hamiltonian(edge, node_visit_counts, config)
|
||||
)
|
||||
dca_values.append(
|
||||
self._factor_dca(edge, path, voice_stay_count, config, cumulative_trans)
|
||||
self._factor_dca_voice_movement(
|
||||
edge, path, voice_stay_count, config, cumulative_trans
|
||||
)
|
||||
)
|
||||
target_values.append(
|
||||
self._factor_target_range(edge, path, config, cumulative_trans)
|
||||
|
|
@ -248,9 +265,9 @@ class PathFinder:
|
|||
if contrary_norm:
|
||||
w += contrary_norm[i] * config.get("weight_contrary_motion", 0)
|
||||
if hamiltonian_norm:
|
||||
w += hamiltonian_norm[i] * config.get("weight_hamiltonian", 1)
|
||||
w += hamiltonian_norm[i] * config.get("weight_dca_hamiltonian", 1)
|
||||
if dca_norm:
|
||||
w += dca_norm[i] * config.get("weight_dca", 1)
|
||||
w += dca_norm[i] * config.get("weight_dca_voice_movement", 1)
|
||||
if target_norm:
|
||||
w += target_norm[i] * config.get("weight_target_range", 1)
|
||||
|
||||
|
|
@ -321,23 +338,33 @@ class PathFinder:
|
|||
distance = abs(num_up - ideal_up)
|
||||
return max(0.0, 1.0 - (distance / ideal_up))
|
||||
|
||||
def _factor_hamiltonian(
|
||||
self, edge: tuple, graph_path: list | None, config: dict
|
||||
def _factor_dca_hamiltonian(
|
||||
self, edge: tuple, node_visit_counts: dict | None, config: dict
|
||||
) -> float:
|
||||
"""Returns 1.0 if destination not visited, lower if already visited."""
|
||||
# Check weight - if 0, return 1.0 (neutral)
|
||||
if config.get("weight_hamiltonian", 1) == 0:
|
||||
"""Returns probability based on how long since node was last visited.
|
||||
|
||||
DCA Hamiltonian: longer since visited = higher probability.
|
||||
Similar to DCA voice movement but for graph nodes.
|
||||
"""
|
||||
if config.get("weight_dca_hamiltonian", 1) == 0:
|
||||
return 1.0
|
||||
|
||||
if not config.get("hamiltonian", False):
|
||||
if node_visit_counts is None:
|
||||
return 1.0
|
||||
|
||||
destination = edge[1]
|
||||
if graph_path and destination in graph_path:
|
||||
return 0.1 # penalize revisiting
|
||||
if destination not in node_visit_counts:
|
||||
return 1.0
|
||||
|
||||
def _factor_dca(
|
||||
visit_count = node_visit_counts[destination]
|
||||
total_counts = sum(node_visit_counts.values())
|
||||
|
||||
if total_counts == 0:
|
||||
return 1.0
|
||||
|
||||
return visit_count / total_counts
|
||||
|
||||
def _factor_dca_voice_movement(
|
||||
self,
|
||||
edge: tuple,
|
||||
path: list,
|
||||
|
|
@ -352,7 +379,7 @@ class PathFinder:
|
|||
|
||||
Higher probability = more likely to choose edge where long-staying voices change.
|
||||
"""
|
||||
if config.get("weight_dca", 1) == 0:
|
||||
if config.get("weight_dca_voice_movement", 1) == 0:
|
||||
return 1.0
|
||||
|
||||
if voice_stay_count is None or len(path) == 0:
|
||||
|
|
|
|||
12
src/io.py
12
src/io.py
|
|
@ -303,16 +303,16 @@ def main():
|
|||
help="Weight for contrary motion factor (0=disabled, default: 0)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--weight-hamiltonian",
|
||||
"--weight-dca-hamiltonian",
|
||||
type=float,
|
||||
default=1,
|
||||
help="Weight for Hamiltonian factor (0=disabled, default: 1)",
|
||||
help="Weight for DCA Hamiltonian factor - favors long-unvisited nodes (0=disabled, default: 1)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--weight-dca",
|
||||
"--weight-dca-voice-movement",
|
||||
type=float,
|
||||
default=1,
|
||||
help="Weight for DCA factor - favors edges where voices stay (0=disabled, default: 1)",
|
||||
help="Weight for DCA voice movement factor - favors voices that stay long to change (0=disabled, default: 1)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--weight-target-range",
|
||||
|
|
@ -435,8 +435,8 @@ def main():
|
|||
# Soft factor weights
|
||||
weights_config["weight_melodic"] = args.weight_melodic
|
||||
weights_config["weight_contrary_motion"] = args.weight_contrary_motion
|
||||
weights_config["weight_hamiltonian"] = args.weight_hamiltonian
|
||||
weights_config["weight_dca"] = args.weight_dca
|
||||
weights_config["weight_dca_hamiltonian"] = args.weight_dca_hamiltonian
|
||||
weights_config["weight_dca_voice_movement"] = args.weight_dca_voice_movement
|
||||
|
||||
# Target range
|
||||
if args.target_range > 0:
|
||||
|
|
|
|||
Loading…
Reference in a new issue