Add interval vector calculations, tests

This commit is contained in:
Jonathan Harker 2024-09-23 15:23:39 +12:00
parent 77bbc71214
commit 0b968d8e48
2 changed files with 63 additions and 6 deletions

View file

@ -48,7 +48,7 @@ class SetClass(list):
def __repr__(self) -> str:
"""Return the Python instance string representation."""
return f"SetClass{self.pitch_classes}".replace(' ', '')
return f"SetClass{set(self.pitch_classes) or '{}'}".replace(' ', '')
def __hash__(self) -> int:
return sum([hash(i) for i in self.pitch_classes])
@ -89,6 +89,38 @@ class SetClass(list):
intervals.append(self.tonality - prev)
return intervals
@cache_property
def interval_vector(self):
"""
An ordered tuple containing the multiplicities of each interval class in the set class.
Denoted in angle-brackets, e.g.
The interval vector of {0,2,4,5,7,9,11} is 2,5,4,3,6,1
Rahn (1987), p. 100
"""
from itertools import combinations
iv = [0 for i in range(1, int(self.tonality / 2) + 1)]
for (a, b) in combinations(self.pitch_classes, 2):
ic = SetClass.unordered_interval(a, b)
iv[ic - 1] += 1
return iv
def ordered_interval(a: int, b: int) -> int:
"""
The ordered interval or "directed interval" (Babbitt) of two pitch classes is determined by
the difference of the pitch class values, modulo 12:
ia,b = b-a mod 12 Rahn (1987), p. 25
"""
return (b % 12 - a % 12) % 12
def unordered_interval(a: int, b: int) -> int:
"""
The unordered interval (also "interval distance", "interval class", "ic", or "undirected
interval") of two pitch classes is the smaller of the two possible ordered intervals
(differences in pitch class value):
i(a,b) = min: ia,b, ib,a Rahn (1987), p. 28
"""
return min(SetClass.ordered_interval(a, b), SetClass.ordered_interval(b, a))
@cache_property
def versions(self):
"""

View file

@ -38,6 +38,31 @@ def test_repeat_intervals():
assert a == SetClass(12, 1, -11, 15, -9)
def test_interval_vector():
test = [
# pairs of set class, expected interval vector
(SetClass(0, 3, 4, 7, 8, 11), [3, 0, 3, 6, 3, 0]),
(SetClass(0, 1, 3, 5, 6, 8, 10), [2, 5, 4, 3, 6, 1]),
]
for (sc, iv) in test:
assert sc.interval_vector == iv
def test_interval_vector_invarance():
test = [
SetClass(0, 1, 3, 5, 6, 8, 10),
SetClass(0, 1, 3, 5, 7, 8, 10),
SetClass(0, 2, 3, 5, 7, 8, 10),
SetClass(0, 2, 3, 5, 7, 9, 10),
SetClass(0, 2, 4, 5, 7, 9, 10),
SetClass(0, 2, 4, 5, 7, 9, 11),
SetClass(0, 2, 4, 6, 7, 9, 11),
]
iv = [2, 5, 4, 3, 6, 1]
for sc in test:
assert sc.interval_vector == iv
# ----------------------------------------------------------------------------
# Example Forte 5-20 set class
f520 = SetClass(0, 1, 5, 6, 8)
@ -180,11 +205,11 @@ def test_empty_bright_dark_forms_equal():
def test_empty_duodecimal_notation():
assert empty.duodecimal_notation == 'SetClass[]'
assert empty.duodecimal_notation == 'SetClass{}'
def test_empty_dozenal_notation():
assert empty.dozenal_notation == 'SetClass[]'
assert empty.dozenal_notation == 'SetClass{}'
def test_empty_leonard_notation():
@ -192,7 +217,7 @@ def test_empty_leonard_notation():
# ----------------------------------------------------------------------------
# Forte 12-1, the complement of empty (full set class)
# Forte 12-1, the complement of empty is the "aggregate" set class
f121 = empty.complement
@ -239,11 +264,11 @@ def test_empty_complement_bright_dark_forms_equal():
def test_empty_complement_duodecimal_notation():
assert f121.duodecimal_notation == 'SetClass[0,1,2,3,4,5,6,7,8,9,T,E]'
assert f121.duodecimal_notation == 'SetClass{0,1,2,3,4,5,6,7,8,9,T,E}'
def test_empty_complement_dozenal_notation():
assert f121.dozenal_notation == 'SetClass[0,1,2,3,4,5,6,7,8,9,↊,↋]'
assert f121.dozenal_notation == 'SetClass{0,1,2,3,4,5,6,7,8,9,↊,↋}'
def test_empty_complement_leonard_notation():