diff --git a/README.md b/README.md index 6cc92d1..a3971e4 100644 --- a/README.md +++ b/README.md @@ -36,8 +36,8 @@ These factors weigh edges without eliminating them. Set weight to 0 to disable. - `--weight-melodic` - Weight for melodic threshold (default: 1) - `--weight-contrary-motion` - Weight for contrary motion (default: 0) -- `--weight-hamiltonian` - Weight for Hamiltonian path (default: 1) -- `--weight-dca` - Weight for DCA +- `--weight-dca-hamiltonian` - Weight for DCA Hamiltonian (favors unvisited nodes, default: 1) +- `--weight-dca-voice-movement` - Weight for DCA voice movement (favors voice changes, default: 1) - `--weight-target-range` - Weight for target register range (default: 1) ### Target Range @@ -54,8 +54,8 @@ These factors weigh edges without eliminating them. Set weight to 0 to disable. ### Soft Factors (Weigh edges) - **Melodic threshold**: Penalizes edges with voice movements outside the melodic range - **Contrary motion**: Boosts edges where some voices move up and others move down -- **Hamiltonian**: Penalizes edges that revisit nodes already in the path -- **DCA**: Boosts edges where voices change/move (rather than staying) +- **DCA Hamiltonian**: Boosts edges to nodes that haven't been visited recently (favors covering new nodes) +- **DCA Voice Movement**: Boosts edges where voices change/move rather than staying on same pitch - **Target range**: Boosts edges that move toward the target register ## Examples @@ -67,11 +67,11 @@ python -m src.io # Rising register to 2 octaves python -m src.io --target-range 2 -# Heavy target range, no DCA -python -m src.io --target-range 2 --weight-target-range 10 --weight-dca 0 +# Heavy target range, no DCA voice movement +python -m src.io --target-range 2 --weight-target-range 10 --weight-dca-voice-movement 0 -# Disable Hamiltonian (allow revisiting nodes) -python -m src.io --weight-hamiltonian 0 +# Disable DCA Hamiltonian (allow revisiting nodes freely) +python -m src.io --weight-dca-hamiltonian 0 # Enable voice crossing python -m src.io --voice-crossing @@ -87,3 +87,4 @@ Generated files go to `output/`: - `output_chords.json` - Chord data - `output_chords.txt` - Human-readable chords - `output_frequencies.txt` - Frequencies in Hz +- `graph_path.json` - Hashes of graph nodes visited (for DCA Hamiltonian analysis) diff --git a/src/analyze.py b/src/analyze.py index 91d0c04..518718c 100644 --- a/src/analyze.py +++ b/src/analyze.py @@ -199,13 +199,24 @@ def format_analysis(metrics: dict) -> str: f"Avg stay count: {metrics['dca_avg_voice_stay']:.2f} steps", f"Max stay count: {metrics['dca_max_voice_stay']} steps", "", - "--- Target Range ---", - f"Target: {metrics['target_octaves']} octaves ({metrics['target_cents']:.0f} cents)", - f"Start: {metrics['target_start_cents']:.0f} cents", - f"End: {metrics['target_end_cents']:.0f} cents", - f"Achieved: {metrics['target_actual_cents']:.0f} cents ({metrics['target_percent']:.1f}%)", + "--- DCA 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)", + f"Start: {metrics['target_start_cents']:.0f} cents", + f"End: {metrics['target_end_cents']:.0f} cents", + f"Achieved: {metrics['target_actual_cents']:.0f} cents ({metrics['target_percent']:.1f}%)", + ] + ) + return "\n".join(lines) diff --git a/src/graph.py b/src/graph.py index 8eaa492..7ffcc34 100644 --- a/src/graph.py +++ b/src/graph.py @@ -357,12 +357,13 @@ class PathFinder: return 1.0 visit_count = node_visit_counts[destination] - total_counts = sum(node_visit_counts.values()) + max_count = max(node_visit_counts.values()) if node_visit_counts else 0 - if total_counts == 0: + if max_count == 0: return 1.0 - return visit_count / total_counts + # Normalize by max squared - gives stronger discrimination + return visit_count / (max_count**2) def _factor_dca_voice_movement( self,