Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
206 changes: 206 additions & 0 deletions tests/test_homogeneity_additional.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
"""Focused tests for ``iav.interval_analysis_core._homogeneity``."""

from __future__ import annotations

import math

import pytest

from iav.analysis_enums import IntervallicHeadlineMode
from iav.interval_analysis_core._homogeneity import (
cluster_compactness,
dominance,
dominance_weighted,
entropy_evenness,
entropy_evenness_supportbounded,
entropy_evenness_supportbounded_weighted,
homogeneity_score,
intervallic_concentration,
proximity_weighted_distance_counts,
type_concentration,
)


def test_cluster_compactness_single_note_fallback():
notes = [("C", 0.0, 4)]
compactness, span_cents, avg_distance = cluster_compactness(notes, bin_cents=100)
assert compactness == 1.0
assert span_cents == 0
assert avg_distance == 0.0


def test_cluster_compactness_identical_pitches_zero_span():
notes = [("C", 0.0, 4), ("C", 0.0, 4)]
compactness, span_cents, avg_distance = cluster_compactness(notes, bin_cents=100)
assert compactness == 1.0
assert span_cents == 0
assert avg_distance == 0.0


def test_cluster_compactness_nonzero_span():
notes = [("C", 0.0, 4), ("E", 0.0, 4), ("G", 0.0, 4)]
compactness, span_cents, avg_distance = cluster_compactness(notes, bin_cents=100)
assert compactness == pytest.approx(1.0 / 3.0)
assert span_cents == 700
assert avg_distance == pytest.approx(1400.0 / 3.0)


def test_type_concentration_empty_returns_zero():
assert type_concentration({}) == 0.0


def test_type_concentration_total_at_most_one_returns_one():
assert type_concentration({"M3": 1}) == 1.0


def test_type_concentration_single_type_multiple_counts_is_maximal():
assert type_concentration({"P5": 5}) == 1.0


def test_type_concentration_multiple_types_lower_than_maximal():
assert type_concentration({"M3": 2, "P5": 2}) == pytest.approx(2.0 / 3.0)


def test_entropy_evenness_empty_returns_zero():
assert entropy_evenness({}) == 0.0


def test_entropy_evenness_single_category_returns_zero():
assert entropy_evenness({"M3": 4}) == 0.0


def test_entropy_evenness_balanced_higher_than_concentrated():
balanced = entropy_evenness({"M3": 2, "P5": 2})
concentrated = entropy_evenness({"M3": 10, "P5": 1})
assert balanced == pytest.approx(1.0)
assert concentrated < balanced


def test_dominance_empty_returns_zero():
assert dominance({}) == 0.0


def test_dominance_non_empty_share_of_largest_count():
assert dominance({7: 3, 4: 1}) == pytest.approx(0.75)


def test_dominance_zero_total_returns_zero():
assert dominance({7: 0, 4: 0}) == 0.0


def test_dominance_weighted_empty_returns_zero():
assert dominance_weighted({}) == 0.0


def test_dominance_weighted_non_empty_share_of_largest_weight():
assert dominance_weighted({7: 3.0, 4: 1.0}) == pytest.approx(0.75)


def test_dominance_weighted_zero_total_returns_zero():
assert dominance_weighted({7: 0.0, 4: 0.0}) == 0.0


def test_entropy_evenness_supportbounded_empty_returns_zero():
assert entropy_evenness_supportbounded({}, support_bins=5) == 0.0


def test_entropy_evenness_supportbounded_non_positive_total_returns_zero():
assert entropy_evenness_supportbounded({1: 0, 2: 0}, support_bins=5) == 0.0


def test_entropy_evenness_supportbounded_support_bins_at_most_one_returns_zero():
assert entropy_evenness_supportbounded({1: 2, 2: 1}, support_bins=1) == 0.0
assert entropy_evenness_supportbounded({1: 2, 2: 1}, support_bins=0) == 0.0


def test_entropy_evenness_supportbounded_balanced_higher_than_concentrated():
balanced = entropy_evenness_supportbounded({1: 1, 2: 1, 3: 1}, support_bins=3)
concentrated = entropy_evenness_supportbounded({1: 9, 2: 1}, support_bins=3)
assert balanced == pytest.approx(1.0)
assert concentrated < balanced


def test_entropy_evenness_supportbounded_weighted_empty_returns_zero():
assert entropy_evenness_supportbounded_weighted({}, support_bins=5) == 0.0


def test_entropy_evenness_supportbounded_weighted_non_positive_total_returns_zero():
assert entropy_evenness_supportbounded_weighted({1: 0.0}, support_bins=5) == 0.0


def test_entropy_evenness_supportbounded_weighted_support_bins_at_most_one_returns_zero():
assert entropy_evenness_supportbounded_weighted({1: 1.0, 2: 1.0}, support_bins=1) == 0.0


def test_entropy_evenness_supportbounded_weighted_balanced_higher_than_concentrated():
balanced = entropy_evenness_supportbounded_weighted(
{1: 1.0, 2: 1.0, 3: 1.0}, support_bins=3
)
concentrated = entropy_evenness_supportbounded_weighted(
{1: 100.0, 2: 1.0}, support_bins=3
)
assert balanced == pytest.approx(math.log(3) / math.log(3))
assert concentrated < balanced


def test_intervallic_concentration_weighted_dominance_path():
counts = proximity_weighted_distance_counts(
[("C", 0.0, 4), ("E", 0.0, 4), ("G", 0.0, 4)],
bin_cents=100,
inverse_power=1,
)
assert intervallic_concentration(counts, "dominance", support_bins=5) == pytest.approx(
dominance_weighted(counts)
)


def test_intervallic_concentration_weighted_entropy_path():
counts = proximity_weighted_distance_counts(
[("C", 0.0, 4), ("E", 0.0, 4), ("G", 0.0, 4)],
bin_cents=100,
inverse_power=2,
)
expected = 1.0 - entropy_evenness_supportbounded_weighted(counts, 5)
assert intervallic_concentration(counts, "entropy", support_bins=5) == pytest.approx(
expected
)


@pytest.mark.parametrize("alpha", [-0.01, 1.01])
def test_homogeneity_score_rejects_alpha_out_of_range(alpha: float):
notes = [("C", 0.0, 4), ("E", 0.0, 4)]
with pytest.raises(ValueError, match="alpha must be between 0 and 1"):
homogeneity_score(notes, alpha=alpha)


def test_homogeneity_score_rejects_invalid_method():
notes = [("C", 0.0, 4), ("E", 0.0, 4)]
with pytest.raises(ValueError, match="method must be 'dominance' or 'entropy'"):
homogeneity_score(notes, alpha=0.5, method="variance")


def test_homogeneity_score_dominance_method_dyad():
notes = [("C", 0.0, 4), ("G", 0.0, 4)]
h, chain, pair, *_ = homogeneity_score(notes, alpha=0.5, method="dominance")
assert pair == 1.0
assert chain == 1.0
assert h == 1.0


def test_homogeneity_score_entropy_method_dyad():
notes = [("C", 0.0, 4), ("G", 0.0, 4)]
h, chain, pair, *_ = homogeneity_score(notes, alpha=0.5, method="entropy")
assert pair == 1.0
assert chain == 1.0
assert h == 1.0


def test_homogeneity_score_adjacent_headline_mode():
notes = [("C", 0.0, 4), ("C", 1.0, 4), ("D", 0.0, 4)]
h, chain, pair, *_ = homogeneity_score(
notes,
alpha=0.5,
method="entropy",
headline_mode=IntervallicHeadlineMode.ADJACENT,
)
assert h == chain
Loading