Normalize edge weights with sum normalization
- Each soft factor is sum-normalized across all edge candidates - Ensures factors compete equally regardless of native value ranges - Skip factors with no variance (all candidates same value) - Base weight of 1.0 ensures minimum probability
This commit is contained in:
parent
ebbb288844
commit
8cdbe90501
100
src/graph.py
100
src/graph.py
|
|
@ -169,48 +169,90 @@ class PathFinder:
|
||||||
|
|
||||||
Uses hybrid approach:
|
Uses hybrid approach:
|
||||||
- Hard factors (direct tuning, voice crossing): multiplication (eliminate if factor fails)
|
- Hard factors (direct tuning, voice crossing): multiplication (eliminate if factor fails)
|
||||||
- Soft factors (melodic, contrary, hamiltonian, dca, target range): weighted sum
|
- Soft factors: sum normalized per factor, then weighted sum
|
||||||
"""
|
"""
|
||||||
weights = []
|
if not out_edges:
|
||||||
|
return []
|
||||||
|
|
||||||
for edge_idx, edge in enumerate(out_edges):
|
# First pass: collect raw factor values for all edges
|
||||||
|
melodic_values = []
|
||||||
|
contrary_values = []
|
||||||
|
hamiltonian_values = []
|
||||||
|
dca_values = []
|
||||||
|
target_values = []
|
||||||
|
|
||||||
|
for edge in out_edges:
|
||||||
|
edge_data = edge[2]
|
||||||
|
|
||||||
|
# Hard factors first (to filter invalid edges)
|
||||||
|
direct_tuning = self._factor_direct_tuning(edge_data, config)
|
||||||
|
voice_crossing = self._factor_voice_crossing(edge_data, config)
|
||||||
|
|
||||||
|
# Skip if hard factors eliminate this edge
|
||||||
|
if direct_tuning == 0 or voice_crossing == 0:
|
||||||
|
melodic_values.append(0)
|
||||||
|
contrary_values.append(0)
|
||||||
|
hamiltonian_values.append(0)
|
||||||
|
dca_values.append(0)
|
||||||
|
target_values.append(0)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Soft factors
|
||||||
|
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)
|
||||||
|
)
|
||||||
|
dca_values.append(
|
||||||
|
self._factor_dca(edge, path, voice_stay_count, config, cumulative_trans)
|
||||||
|
)
|
||||||
|
target_values.append(
|
||||||
|
self._factor_target_range(edge, path, config, cumulative_trans)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Helper function for sum normalization
|
||||||
|
def sum_normalize(values: list) -> list | None:
|
||||||
|
"""Normalize values to sum to 1. Returns None if no discrimination."""
|
||||||
|
total = sum(values)
|
||||||
|
if total == 0 or len(set(values)) <= 1:
|
||||||
|
return None # no discrimination
|
||||||
|
return [v / total for v in values]
|
||||||
|
|
||||||
|
# Sum normalize each factor
|
||||||
|
melodic_norm = sum_normalize(melodic_values)
|
||||||
|
contrary_norm = sum_normalize(contrary_values)
|
||||||
|
hamiltonian_norm = sum_normalize(hamiltonian_values)
|
||||||
|
dca_norm = sum_normalize(dca_values)
|
||||||
|
target_norm = sum_normalize(target_values)
|
||||||
|
|
||||||
|
# Second pass: calculate final weights
|
||||||
|
weights = []
|
||||||
|
for i, edge in enumerate(out_edges):
|
||||||
w = 1.0 # base weight
|
w = 1.0 # base weight
|
||||||
edge_data = edge[2]
|
edge_data = edge[2]
|
||||||
|
|
||||||
# Hard factors (multiplication - eliminate edge if factor = 0)
|
# Hard factors
|
||||||
# Direct tuning
|
w *= self._factor_direct_tuning(edge_data, config)
|
||||||
direct_tuning_factor = self._factor_direct_tuning(edge_data, config)
|
|
||||||
w *= direct_tuning_factor
|
|
||||||
|
|
||||||
if w == 0:
|
if w == 0:
|
||||||
weights.append(0)
|
weights.append(0)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Voice crossing
|
w *= self._factor_voice_crossing(edge_data, config)
|
||||||
voice_crossing_factor = self._factor_voice_crossing(edge_data, config)
|
|
||||||
w *= voice_crossing_factor
|
|
||||||
|
|
||||||
if w == 0:
|
if w == 0:
|
||||||
weights.append(0)
|
weights.append(0)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Soft factors (weighted sum)
|
# Soft factors (sum normalized, then weighted)
|
||||||
w += self._factor_melodic_threshold(edge_data, config) * config.get(
|
if melodic_norm:
|
||||||
"weight_melodic", 1
|
w += melodic_norm[i] * config.get("weight_melodic", 1)
|
||||||
)
|
if contrary_norm:
|
||||||
w += self._factor_contrary_motion(edge_data, config) * config.get(
|
w += contrary_norm[i] * config.get("weight_contrary_motion", 0)
|
||||||
"weight_contrary_motion", 0
|
if hamiltonian_norm:
|
||||||
)
|
w += hamiltonian_norm[i] * config.get("weight_hamiltonian", 1)
|
||||||
w += self._factor_hamiltonian(edge, graph_path, config) * config.get(
|
if dca_norm:
|
||||||
"weight_hamiltonian", 1
|
w += dca_norm[i] * config.get("weight_dca", 1)
|
||||||
)
|
if target_norm:
|
||||||
|
w += target_norm[i] * config.get("weight_target_range", 1)
|
||||||
w += self._factor_dca(
|
|
||||||
edge, path, voice_stay_count, config, cumulative_trans
|
|
||||||
) * config.get("weight_dca", 1)
|
|
||||||
w += self._factor_target_range(
|
|
||||||
edge, path, config, cumulative_trans
|
|
||||||
) * config.get("weight_target_range", 1)
|
|
||||||
|
|
||||||
weights.append(w)
|
weights.append(w)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue