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:
|
||||
- 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
|
||||
edge_data = edge[2]
|
||||
|
||||
# Hard factors (multiplication - eliminate edge if factor = 0)
|
||||
# Direct tuning
|
||||
direct_tuning_factor = self._factor_direct_tuning(edge_data, config)
|
||||
w *= direct_tuning_factor
|
||||
|
||||
# Hard factors
|
||||
w *= self._factor_direct_tuning(edge_data, config)
|
||||
if w == 0:
|
||||
weights.append(0)
|
||||
continue
|
||||
|
||||
# Voice crossing
|
||||
voice_crossing_factor = self._factor_voice_crossing(edge_data, config)
|
||||
w *= voice_crossing_factor
|
||||
|
||||
w *= self._factor_voice_crossing(edge_data, config)
|
||||
if w == 0:
|
||||
weights.append(0)
|
||||
continue
|
||||
|
||||
# Soft factors (weighted sum)
|
||||
w += self._factor_melodic_threshold(edge_data, config) * config.get(
|
||||
"weight_melodic", 1
|
||||
)
|
||||
w += self._factor_contrary_motion(edge_data, config) * config.get(
|
||||
"weight_contrary_motion", 0
|
||||
)
|
||||
w += self._factor_hamiltonian(edge, graph_path, config) * config.get(
|
||||
"weight_hamiltonian", 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)
|
||||
# Soft factors (sum normalized, then weighted)
|
||||
if melodic_norm:
|
||||
w += melodic_norm[i] * config.get("weight_melodic", 1)
|
||||
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)
|
||||
if dca_norm:
|
||||
w += dca_norm[i] * config.get("weight_dca", 1)
|
||||
if target_norm:
|
||||
w += target_norm[i] * config.get("weight_target_range", 1)
|
||||
|
||||
weights.append(w)
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue