compact_sets/tests/test_compact_sets.py

205 lines
6.3 KiB
Python

import pytest
import sys
sys.path.insert(0, "src")
from src.pitch import Pitch, DIMS_4, DIMS_7
from src.chord import Chord
from src.harmonic_space import HarmonicSpace
class TestPitch:
"""Tests for Pitch class."""
def test_fundamental(self):
p = Pitch((0, 0, 0, 0), DIMS_4)
assert p.to_fraction() == 1
def test_octave(self):
p = Pitch((1, 0, 0, 0), DIMS_4)
assert p.to_fraction() == 2
def test_fifth(self):
p = Pitch((0, 1, 0, 0), DIMS_4)
assert p.to_fraction() == 3
def test_compound(self):
p = Pitch((1, 1, 0, 0), DIMS_4)
assert p.to_fraction() == 6
def test_cents_fundamental(self):
p = Pitch((0, 0, 0, 0), DIMS_4)
assert p.to_cents() == 0.0
def test_cents_octave(self):
p = Pitch((1, 0, 0, 0), DIMS_4)
assert p.to_cents() == 1200.0
def test_cents_fifth(self):
p = Pitch((0, 1, 0, 0), DIMS_4)
assert abs(p.to_cents() - 1901.96) < 0.01
def test_transpose_positive(self):
p = Pitch((0, 1, 0, 0), DIMS_4)
trans = Pitch((1, 0, 0, 0), DIMS_4)
result = p.transpose(trans)
assert result.hs_array == (1, 1, 0, 0)
def test_transpose_negative(self):
p = Pitch((1, 1, 0, 0), DIMS_4)
trans = Pitch((-1, 0, 0, 0), DIMS_4)
result = p.transpose(trans)
assert result.hs_array == (0, 1, 0, 0)
def test_pitch_difference(self):
p1 = Pitch((0, 1, 0, 0), DIMS_4)
p2 = Pitch((0, 0, 0, 0), DIMS_4)
result = p1.pitch_difference(p2)
assert result.hs_array == (0, 1, 0, 0)
def test_collapse(self):
p = Pitch((3, 1, 0, 0), DIMS_4)
collapsed = p.collapse()
# Collapse brings to [1, 2) range: 24 -> 1.5 = 2^-1 * 3
assert collapsed.hs_array == (-1, 1, 0, 0)
def test_collapse_below_one(self):
p = Pitch((-2, 0, 0, 0), DIMS_4)
collapsed = p.collapse()
assert collapsed.hs_array == (0, 0, 0, 0)
def test_hash(self):
p1 = Pitch((0, 1, 0, 0), DIMS_4)
p2 = Pitch((0, 1, 0, 0), DIMS_4)
assert hash(p1) == hash(p2)
def test_equality(self):
p1 = Pitch((0, 1, 0, 0), DIMS_4)
p2 = Pitch((0, 1, 0, 0), DIMS_4)
assert p1 == p2
def test_len(self):
p = Pitch((0, 1, 0, 0), DIMS_4)
assert len(p) == 4
class TestChord:
"""Tests for Chord class."""
def test_create_chord(self):
p1 = Pitch((0, 0, 0, 0), DIMS_4)
p2 = Pitch((0, 1, 0, 0), DIMS_4)
chord = Chord((p1, p2), DIMS_4)
assert len(chord) == 2
def test_transpose(self):
p1 = Pitch((0, 0, 0, 0), DIMS_4)
p2 = Pitch((0, 1, 0, 0), DIMS_4)
chord = Chord((p1, p2), DIMS_4)
trans = Pitch((1, 0, 0, 0), DIMS_4)
transposed = chord.transpose(trans)
assert transposed[0].hs_array == (1, 0, 0, 0)
assert transposed[1].hs_array == (1, 1, 0, 0)
def test_is_connected_single(self):
p = Pitch((0, 0, 0, 0), DIMS_4)
chord = Chord((p,), DIMS_4)
assert chord.is_connected() is True
def test_is_connected_adjacent(self):
p1 = Pitch((0, 0, 0, 0), DIMS_4)
p2 = Pitch((0, 1, 0, 0), DIMS_4)
chord = Chord((p1, p2), DIMS_4)
assert chord.is_connected() is True
def test_is_connected_not_adjacent(self):
p1 = Pitch((0, 0, 0, 0), DIMS_4)
p2 = Pitch((0, 2, 0, 0), DIMS_4)
chord = Chord((p1, p2), DIMS_4)
assert chord.is_connected() is False
def test_sorted_by_frequency(self):
p1 = Pitch((0, 1, 0, 0), DIMS_4) # 3/2
p2 = Pitch((0, 0, 0, 0), DIMS_4) # 1/1
chord = Chord((p1, p2), DIMS_4)
sorted_pitches = chord.sorted_by_frequency()
assert sorted_pitches[0].hs_array == (0, 0, 0, 0)
assert sorted_pitches[1].hs_array == (0, 1, 0, 0)
def test_symmetric_difference_size(self):
p1 = Pitch((0, 0, 0, 0), DIMS_4)
p2 = Pitch((0, 1, 0, 0), DIMS_4)
p3 = Pitch((0, 2, 0, 0), DIMS_4)
chord1 = Chord((p1, p2), DIMS_4)
chord2 = Chord((p1, p3), DIMS_4)
# Both have p1, differ on p2 vs p3: symdiff = 2
assert chord1.symmetric_difference_size(chord2) == 2
def test_hash(self):
p1 = Pitch((0, 0, 0, 0), DIMS_4)
p2 = Pitch((0, 1, 0, 0), DIMS_4)
c1 = Chord((p1, p2), DIMS_4)
c2 = Chord((p1, p2), DIMS_4)
assert hash(c1) == hash(c2)
class TestHarmonicSpace:
"""Tests for HarmonicSpace class."""
def test_create_space(self):
space = HarmonicSpace(DIMS_4)
assert space.dims == DIMS_4
assert space.collapsed is True
def test_root(self):
space = HarmonicSpace(DIMS_4)
root = space.root()
assert root.hs_array == (0, 0, 0, 0)
def test_generate_connected_sets_size_3(self):
space = HarmonicSpace(DIMS_4)
chords = space.generate_connected_sets(3, 3)
assert len(chords) > 0
assert all(len(c) == 3 for c in chords)
def test_generate_connected_sets_all_connected(self):
space = HarmonicSpace(DIMS_4)
chords = space.generate_connected_sets(3, 3)
assert all(c.is_connected() for c in chords)
def test_generate_connected_sets_size_2(self):
space = HarmonicSpace(DIMS_4)
chords = space.generate_connected_sets(2, 2)
assert len(chords) > 0
assert all(len(c) == 2 for c in chords)
def test_build_voice_leading_graph(self):
space = HarmonicSpace(DIMS_4)
chords = space.generate_connected_sets(3, 3)
graph = space.build_voice_leading_graph(chords, 2, 2)
assert graph.number_of_nodes() == len(chords)
assert graph.number_of_edges() > 0
def test_build_graph_symdiff_filter(self):
space = HarmonicSpace(DIMS_4)
chords = space.generate_connected_sets(3, 3)
# Test with symdiff range 1-3
graph = space.build_voice_leading_graph(chords, 1, 3)
assert graph.number_of_edges() > 0
class TestCLI:
"""Tests for CLI functionality."""
def test_args_parsing(self):
import argparse
from src.io import main
# This is a basic test - in practice we'd test the parser more thoroughly
# For now just verify the module can be imported
assert main is not None
if __name__ == "__main__":
pytest.main([__file__, "-v"])