Constrained tuning: Difference between revisions
m Oopsie |
|||
| (2 intermediate revisions by the same user not shown) | |||
| Line 35: | Line 35: | ||
== Computation == | == Computation == | ||
As a standard optimization problem, numerous algorithms exist to solve it, such as {{w|sequential quadratic programming}}, to name one. [[Flora Canou]]'s [https://github.com/FloraCanou/temperament_evaluator | As a standard optimization problem, numerous algorithms exist to solve it, such as {{w|sequential quadratic programming}}, to name one. [[Flora Canou]]'s [https://github.com/FloraCanou/temperament_evaluator Temperament Evaluator] solves constrained tuning problems in [https://www.python.org Python], using [https://scipy.org/ Scipy]'s [https://www.cobyqa.com/stable/ COBYQA] algorithm. Here is an abridged version of it: | ||
{{Todo|rework|comment=Make an absolutely minimal version of it. }} | |||
{{Databox| Code | | {{Databox| Code | | ||
<syntaxhighlight lang="python"> | <syntaxhighlight lang="python"> | ||
# © 2020-2025 Flora Canou | # © 2020-2025 Flora Canou | ||
# This work is licensed under the GNU General Public License version 3. | # This work is licensed under the GNU General Public License version 3. | ||
# Version 0. | # Version 0.30.1 | ||
import warnings | import warnings | ||
| Line 50: | Line 51: | ||
PRIME_LIST = [ | PRIME_LIST = [ | ||
2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, | 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, | ||
41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89 | 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89] | ||
] | |||
class SCALAR: | class SCALAR: | ||
| Line 59: | Line 59: | ||
"""Norm profile for the tuning space.""" | """Norm profile for the tuning space.""" | ||
def __init__ (self, wtype = | def __init__ (self, wtype = None, wmode = 1, wstrength = 1, skew = 0, order = 2): | ||
self.wtype = | if wtype: | ||
self. | wmode, wstrength = self.__presets (wtype) | ||
self.wmode = wmode | |||
self.wstrength = wstrength | |||
self.skew = skew | self.skew = skew | ||
self.order = order | self.order = order | ||
def | @staticmethod | ||
match | def __presets (wtype): | ||
match wtype: | |||
case "tenney": | case "tenney": | ||
wmode, wstrength = 1, 1 | |||
case "wilson" | "benedetti": | case "wilson" | "benedetti": | ||
wmode, wstrength = 0, 1 | |||
case "equilateral": | case "equilateral": | ||
wmode, wstrength = 0, 0 | |||
case _: | case _: | ||
warnings.warn ("weighter type not supported, using default (\"tenney\")") | warnings.warn ("weighter type not supported, using default (\"tenney\")") | ||
wmode, wstrength = 1, 1 | |||
return wmode, wstrength | |||
return | |||
def | def __weight_vec (self, primes): | ||
"""Returns the interval weight vector for a list of formal primes. """ | |||
if not isinstance (self.wmode, (int, np.integer)): | |||
raise TypeError ("non-integer modes not supported. ") | |||
def modal_weighter (primes, m): | |||
if m == 0: | |||
return primes | |||
elif m > 0: | |||
return modal_weighter (2*np.log2 (primes), m - 1) | |||
else: | |||
return modal_weighter (np.exp2 (primes/2), m + 1) | |||
return (modal_weighter (np.asarray (primes), self.wmode)/2)**self.wstrength | |||
def val_weight (self, primes): | |||
"""Returns the val weight matrix for a list of formal primes. """ | |||
return np.diag (1/self.__weight_vec (primes)) | |||
def val_skew (self, subgroup): | |||
"""Returns the val skew matrix for a list of formal primes. """ | |||
if self.skew == 0: | if self.skew == 0: | ||
return np.eye (len (subgroup)) | return np.eye (len (subgroup)) | ||
| Line 91: | Line 114: | ||
r*np.ones ((len (subgroup), 1)), axis = 1) | r*np.ones ((len (subgroup), 1)), axis = 1) | ||
def | def val_transform (self, main, subgroup): | ||
return main @ self. | return main @ self.val_weight (subgroup) @ self.val_skew (subgroup) | ||
def __get_subgroup (main, subgroup): | def __get_subgroup (main, subgroup): | ||
| Line 99: | Line 122: | ||
subgroup = PRIME_LIST[:main.shape[1]] | subgroup = PRIME_LIST[:main.shape[1]] | ||
elif main.shape[1] != len (subgroup): | elif main.shape[1] != len (subgroup): | ||
warnings.warn (" | warnings.warn ("dimensionalities do not match. Casting to the smaller dimensionality. ") | ||
dim = min (main.shape[1], len (subgroup)) | dim = min (main.shape[1], len (subgroup)) | ||
main = main[:, :dim] | main = main[:, :dim] | ||
| Line 108: | Line 131: | ||
cons_monzo_list = None, des_monzo = None, show = True): | cons_monzo_list = None, des_monzo = None, show = True): | ||
# NOTE: "map" is a reserved word | # NOTE: "map" is a reserved word | ||
# optimization | # optimization would ideally be performed in the unit of octaves | ||
# unfortunately, that often results in insufficient accuracy | |||
# the cent is a practical choice of unit, and test shows that further scaling | |||
# doesn't improve accuracy for most main-sequence temperaments | |||
breeds, subgroup = __get_subgroup (breeds, subgroup) | breeds, subgroup = __get_subgroup (breeds, subgroup) | ||
just_tuning_map = SCALAR.CENT*np.log2 (subgroup) | just_tuning_map = SCALAR.CENT*np.log2 (subgroup) | ||
breeds_x = norm. | breeds_x = norm.val_transform (breeds, subgroup) | ||
just_tuning_map_x = norm. | just_tuning_map_x = norm.val_transform (just_tuning_map, subgroup) | ||
if norm.order == 2 and cons_monzo_list is None: #simply using lstsq for better performance | if norm.order == 2 and cons_monzo_list is None: #simply using lstsq for better performance | ||
res = linalg.lstsq (breeds_x.T, just_tuning_map_x) | res = linalg.lstsq (breeds_x.T, just_tuning_map_x) | ||
| Line 122: | Line 148: | ||
gen0 = just_tuning_map[:breeds.shape[0]] #initial guess | gen0 = just_tuning_map[:breeds.shape[0]] #initial guess | ||
if cons_monzo_list is None: | if cons_monzo_list is None: | ||
cons_object = () | |||
else: | else: | ||
cons_object = optimize.LinearConstraint ((breeds @ cons_monzo_list).T, | |||
lb = (just_tuning_map @ cons_monzo_list).T, | lb = (just_tuning_map @ cons_monzo_list).T, | ||
ub = (just_tuning_map @ cons_monzo_list).T) | ub = (just_tuning_map @ cons_monzo_list).T) | ||
| Line 140: | Line 166: | ||
if np.asarray (des_monzo).ndim > 1 and np.asarray (des_monzo).shape[1] != 1: | if np.asarray (des_monzo).ndim > 1 and np.asarray (des_monzo).shape[1] != 1: | ||
raise IndexError ("only one destretch target is allowed. ") | raise IndexError ("only one destretch target is allowed. ") | ||
elif ( | elif (des_tempered_size := gen @ breeds @ des_monzo) == 0: | ||
raise ZeroDivisionError ("destretch target is in the nullspace. ") | raise ZeroDivisionError ("destretch target is in the nullspace. ") | ||
else: | else: | ||
gen *= (just_tuning_map @ des_monzo)/ | gen *= (just_tuning_map @ des_monzo)/des_tempered_size | ||
tempered_tuning_map = gen @ breeds | tempered_tuning_map = gen @ breeds | ||
| Line 418: | Line 444: | ||
== Special constraint == | == Special constraint == | ||
The special eigenmonzo ''X'''''j''', where '''j''' is the all-ones monzo, has the effect of removing the weighted–skewed tuning bias. This eigenmonzo is actually proportional to the monzo of the extra dimension introduced by the skew. In other words, it forces the extra dimension to be pure, and therefore, the skew will have no effect with this constrained tuning. | The special eigenmonzo ''X''⋅'''j''', where '''j''' is the all-ones monzo, has the effect of removing the weighted–skewed tuning bias. This eigenmonzo is actually proportional to the monzo of the extra dimension introduced by the skew. In other words, it forces the extra dimension to be pure, and therefore, the skew will have no effect with this constrained tuning. | ||
It can be regarded as a distinct optimum. In the case of Tenney weighting, it is the '''TOCTE tuning''' ('''Tenney ones constrained Tenney–Euclidean tuning'''). | It can be regarded as a distinct optimum. In the case of Tenney weighting, it is the '''TOCTE tuning''' ('''Tenney ones constrained Tenney–Euclidean tuning'''). | ||
| Line 444: | Line 470: | ||
$$ | $$ | ||
As a result, the [[ | As a result, the [[relative interval error #Linearity|relative error space]] is also linear with respect to ''V''. | ||
For example, the relative errors of | For example, the relative errors of 12et in 5-limit TOC is | ||
$$ \mathcal{E}_\text {r}(12) = \val{-1.55\% & -4.42\% & +10.08\% } $$ | $$ \mathcal{E}_\text {r}(12) = \val{-1.55\% & -4.42\% & +10.08\% } $$ | ||
That of | That of 19et in this tuning is | ||
$$ \mathcal{E}_\text {r}(19) = \val{+4.08\% & -4.97\% & -2.19\% } $$ | $$ \mathcal{E}_\text {r}(19) = \val{+4.08\% & -4.97\% & -2.19\% } $$ | ||
As 31 = 12 + 19, the relative errors of | As 31 = 12 + 19, the relative errors of 31et in this tuning is | ||
$$ | $$ | ||
| Line 464: | Line 490: | ||
== Systematic name == | == Systematic name == | ||
In [[D&D's guide|D&D's guide to RTT]], the [[Dave Keenan & Douglas Blumeyer's guide to RTT/Alternative complexities#Naming|systematic name]] for the CTE tuning scheme is ''[[Dave Keenan %26 Douglas Blumeyer%27s guide to RTT/All-interval tuning schemes #Held-octave minimax-.28E.29S|held-octave minimax-ES]]'', and the systematic name for the CTWE tuning scheme is ''[[Dave Keenan %26 Douglas Blumeyer%27s guide to RTT/Tuning fundamentals #Held-intervals|held-octave]] [[Dave Keenan %26 Douglas Blumeyer%27s guide to RTT/Alternative complexities #Tunings used in 7|minimax-E-lils-S]]''. | In [[D&D's guide|D&D's guide to RTT]], the [[Dave Keenan & Douglas Blumeyer's guide to RTT/Alternative complexities #Naming|systematic name]] for the CTE tuning scheme is ''[[Dave Keenan %26 Douglas Blumeyer%27s guide to RTT/All-interval tuning schemes #Held-octave minimax-.28E.29S|held-octave minimax-ES]]'', and the systematic name for the CTWE tuning scheme is ''[[Dave Keenan %26 Douglas Blumeyer%27s guide to RTT/Tuning fundamentals #Held-intervals|held-octave]] [[Dave Keenan %26 Douglas Blumeyer%27s guide to RTT/Alternative complexities #Tunings used in 7|minimax-E-lils-S]]''. | ||
== Open problems == | == Open problems == | ||