Constrained tuning: Difference between revisions

ArrowHead294 (talk | contribs)
mNo edit summary
m Oopsie
 
(15 intermediate revisions by 3 users not shown)
Line 1: Line 1:
{{texmap}}
{{Texops}}{{texmap}}
'''Constrained tunings''' are tuning [[optimization]] techniques using the constraint of some purely tuned intervals (i.e. [[eigenmonzo|unit eigenmonzos, or unchanged-intervals]]).
'''Constrained tunings''' are tuning [[optimization]] techniques using the constraint of some purely tuned intervals (i.e. [[eigenmonzo|unit eigenmonzos, or unchanged-intervals]]).


Line 11: Line 11:


== Definition ==
== Definition ==
Given a temperament [[mapping]] ''V'' and the [[just tuning map]] ''J'', we specify a weight–skew transformation, represented by transformation matrix ''X'', and a ''q''-norm. Suppose the tuning is constrained by the eigenmonzo list ''M''<sub>''I''</sub>. The goal is to find the generator list ''G'' by
Given a [[temperament mapping matrix]] ''V'' and the [[just tuning map]] ''J'', we specify a weight–skew transformation, represented by transformation matrix ''X'', and a ''q''-norm. Suppose the tuning is constrained by the eigenmonzo list ''M''. Let ''G'' denote the generator tuning map, we want to


Minimize
$$
 
\begin{align}
<math>\displaystyle \left\| GV_X - J_X \right\|_q </math>
& \text{find} && G \\
 
& \text{that minimizes} && \lVert GV_X - J_X \rVert_q \\
subject to
& \text{subject to} && (GV - J)M = O
 
\end{align}
<math>\displaystyle (GV - J)M_I = O </math>
$$


where (·)<sub>''X''</sub> denotes the variable in the weight–skew transformed space, found by
where (·)<sub>''X''</sub> denotes the variable in the weight–skew transformed space, found by


<math>\displaystyle
$$
\begin{align}
\begin{align}
V_X &= VX \\
V_X &= VX \\
J_X &= JX
J_X &= JX
\end{align}
\end{align}
</math>
$$


The problem is feasible if
The problem is feasible if
# rank(''M''<sub>''I''</sub>) ≤ rank(''V''), and
# rank(''M'') ≤ rank(''V''), and
# The subgroups of ''M''<sub>''I''</sub> and N (''V'') are {{w|linear independence|linearly independent}}.
# The subgroups of ''M'' and nullspace(''V'') are {{w|linear independence|linearly independent}}.


== 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/blob/v0.26.4/te_optimizer_legacy.py tuning optimizer] is such an implementation in [https://www.python.org Python]. Note: it uses [https://scipy.org/ Scipy].  
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-2023 Flora Canou | Version 0.27.2
# © 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.30.1


import warnings
import warnings
import numpy as np
import numpy as np
from scipy import optimize, linalg
from scipy import optimize, linalg
np.set_printoptions (suppress = True, linewidth = 256, precision = 4)
np.set_printoptions (suppress = True, linewidth = 256, precision = 3)


PRIME_LIST = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89]
PRIME_LIST = [
    2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37,  
    41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89]


class SCALAR:
class SCALAR:
Line 55: Line 59:
     """Norm profile for the tuning space."""
     """Norm profile for the tuning space."""


     def __init__ (self, wtype = "tenney", wamount = 1, skew = 0, order = 2):
     def __init__ (self, wtype = None, wmode = 1, wstrength = 1, skew = 0, order = 2):
         self.wtype = wtype
         if wtype:
         self.wamount = wamount
            wmode, wstrength = self.__presets (wtype)
        self.wmode = wmode
         self.wstrength = wstrength
         self.skew = skew
         self.skew = skew
         self.order = order
         self.order = order


     def __get_tuning_weight (self, subgroup):
    @staticmethod
         match self.wtype:
     def __presets (wtype):
         match wtype:  
             case "tenney":
             case "tenney":
                 weight_vec = np.reciprocal (np.log2 (subgroup, dtype = float))
                 wmode, wstrength = 1, 1
             case "wilson" | "benedetti":
             case "wilson" | "benedetti":
                 weight_vec = np.reciprocal (np.array (subgroup, dtype = float))
                 wmode, wstrength = 0, 1
             case "equilateral":
             case "equilateral":
                 weight_vec = np.ones (len (subgroup))
                 wmode, wstrength = 0, 0
             case _:
             case _:
                 warnings.warn ("weighter type not supported, using default (\"tenney\")")
                 warnings.warn ("weighter type not supported, using default (\"tenney\")")
                 self.wtype = "tenney"
                 wmode, wstrength = 1, 1
                return self.__get_weight (subgroup)
        return wmode, wstrength
        return np.diag (weight_vec**self.wamount)
 
    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 __get_tuning_skew (self, subgroup):
        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 87: Line 114:
             r*np.ones ((len (subgroup), 1)), axis = 1)
             r*np.ones ((len (subgroup), 1)), axis = 1)


     def tuning_x (self, main, subgroup):
     def val_transform (self, main, subgroup):
         return main @ self.__get_tuning_weight (subgroup) @ self.__get_tuning_skew (subgroup)
         return main @ self.val_weight (subgroup) @ self.val_skew (subgroup)


def __get_subgroup (main, subgroup):
def __get_subgroup (main, subgroup):
Line 95: 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 ("dimension does not match. Casting to the smaller dimension. ")
         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 104: 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 is preferably done in the unit of octaves, but for precision reasons
     # 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.tuning_x (breeds, subgroup)
     breeds_x = norm.val_transform (breeds, subgroup)
     just_tuning_map_x = norm.tuning_x (just_tuning_map, subgroup)
     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)
         gen = res[0]
         gen = res[0]
         print ("Euclidean tuning without constraints, solved using lstsq. ")
         if show:
            print ("Euclidean tuning without constraints, solved using lstsq. ")
     else:
     else:
         gen0 = [SCALAR.CENT]*breeds.shape[0] #initial guess
         gen0 = just_tuning_map[:breeds.shape[0]] #initial guess
         cons = () if cons_monzo_list is None else {
         if cons_monzo_list is None:
             'type': 'eq',
             cons_object = ()
             'fun': lambda gen: (gen @ breeds - just_tuning_map) @ cons_monzo_list
        else:
        }
             cons_object = optimize.LinearConstraint ((breeds @ cons_monzo_list).T,
         res = optimize.minimize (lambda gen: linalg.norm (gen @ breeds_x - just_tuning_map_x, ord = norm.order), gen0,  
                lb = (just_tuning_map @ cons_monzo_list).T,
            method = "SLSQP", options = {'ftol': 1e-9}, constraints = cons)
                ub = (just_tuning_map @ cons_monzo_list).T)
         print (res.message)
         res = optimize.minimize (
            lambda gen: linalg.norm (gen @ breeds_x - just_tuning_map_x, ord = norm.order),  
            gen0, method = "COBYQA", constraints = cons)
         if show:
            print (res.message)
         if res.success:
         if res.success:
             gen = res.x
             gen = res.x
Line 128: Line 163:
             raise ValueError ("infeasible optimization problem. ")
             raise ValueError ("infeasible optimization problem. ")


     if not des_monzo is None:
     if des_monzo is not None:
         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 (tempered_size := gen @ breeds @ des_monzo) == 0:
         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)/tempered_size
             gen *= (just_tuning_map @ des_monzo)/des_tempered_size


     tempered_tuning_map = gen @ breeds
     tempered_tuning_map = gen @ breeds
Line 161: Line 196:


<pre>
<pre>
Optimization terminated successfully.
The lower bound for the trust-region radius has been reached
Generators: [1200.     1896.9521] (¢)
Generators: [1200.   1896.952] (¢)
Tuning map: [1200.     1896.9521 2787.8085 3369.5214] (¢)
Tuning map: [1200.   1896.952 2787.809 3369.521] (¢)
Error map: [ 0.     -5.0029 1.4948 0.6955] (¢)
Error map: [ 0.   -5.003 1.495 0.695] (¢)
</pre>
</pre>


Line 171: Line 206:


==== Method of Lagrange multipliers ====
==== Method of Lagrange multipliers ====
It can also be solved analytically using the {{w|Lagrange multipliers|method of Lagrange multipliers}}. The solution is given by:
It can also be solved analytically using the method of {{w|Lagrange multipliers}}. The solution is given by:


<math>\displaystyle
$$
\begin{bmatrix}
\begin{bmatrix}
G^{\mathsf T}  \\
G^{\mathsf T}  \\
Line 183: Line 218:
(VM)^{\mathsf T} & O
(VM)^{\mathsf T} & O
\end{bmatrix}^{-1}
\end{bmatrix}^{-1}
\begin{bmatrix}
\begin{bmatrix}
V_X J_X^{\mathsf T}\\
V_X J_X^{\mathsf T}\\
(JM)^{\mathsf T}
(JM)^{\mathsf T}
\end{bmatrix}
\end{bmatrix}
</math>
$$


Notice we introduced the vector of lagrange multipliers ''Λ'', with length equal to the number of constraints. The lagrange multipliers have no concrete meaning for the resulting tuning, so they can be discarded.
Notice we introduced the vector of lagrange multipliers ''Λ'', with length equal to the number of constraints. The lagrange multipliers have no concrete meaning for the resulting tuning, so they can be discarded.


=== Simple fast closed-form algorithm ===
==== Simple fast closed-form algorithm ====
Another way to compute the CTE and CWE tunings, and the CTWE tuning in general, is to use the pseudoinverse.
Another way to compute the CTE and CWE tunings, and the CTWE tuning in general, is to use the pseudoinverse.


The basic idea is that the set of all pure-octave tuning maps of some temperament will be the intersection of a linear subspace and a shifted hyperplane, and thus will be a shifted subspace. This means that any pure-octave tuning map can be expressed as the sum of some arbitrary "reference" pure-octave tuning map for the temperament, plus some other one also in the temperament whose octave-coordinate is 0. The set of all such tuning maps of the latter category form a linear subspace.
The basic idea is that the set of all pure-octave tuning maps of some temperament will be the intersection of a linear subspace and a shifted hyperplane, and thus will be a shifted subspace. This means that any pure-octave tuning map can be expressed as the sum of some arbitrary "reference" pure-octave tuning map for the temperament, plus some other one also in the temperament whose octave-coordinate is 0. The set of all such tuning maps of the latter category form a linear subspace.


We have the same thing with generator maps, meaning that any pure-octave generator map <math>g</math> can be expressed as:
We have the same thing with generator maps, meaning that any pure-octave generator map ''G'' can be expressed as:


$$
$$ G = HB + G_0 $$
g = hB + x
$$


where  
where  
* ''G''<sub>0</sub> is any random generator map giving pure octaves;
* ''B'' is an (''r'' - 1)×''r'' matrix whose rows are a basis for the subspace of generator maps with octave coordinate set to 0;
* ''H'' is an (''r'' - 1)-element covector free variable.


* <math>x</math> is any random generator map giving pure octaves
Given that, and assuming ''V'' is our mapping matrix, ''X'' our transformation matrix, and ''J'' our just tuning map, we can solve for the best possible ''G'' in closed form:
* <math>B</math> is a matrix whose rows are a basis for the subspace of generator maps with octave coordinate set to 0
* <math>h</math> is a free variable.
 
Given that, and assuming <math>M</math> is our mapping matrix, <math>W</math> our weighting matrix, and <math>j</math> our JIP, we can solve for the best possible <math>g</math> in closed form:


$$
$$ GV_X \approx J_X $$
gMW ≈ jW
$$


which becomes
which becomes


$$
$$
\left(hB + x\right)MW ≈ jW \\
\begin{align}
h = \left(j - xM\right)W \cdot \left(BMW\right)^\dagger
\left( HB + G_x \right) V_X &\approx J_X \\
H &= \left( J_X - G_0 V_X \right) \cdot \left( BV_X \right)^+
\end{align}
$$
$$


We note that this also works for any weighting matrix, and so we can use this to compute an arbitrary TWE norm very quickly in closed-form. Here is some Python code:
We note that this also works for any transformation matrix, and so we can use this to compute an arbitrary TWE norm very quickly in closed-form. Here is some Python code:


{{Databox|Code|
{{Databox|Code|
Line 255: Line 286:
     # All pure-octave generator maps are just pure_octave_start + something in
     # All pure-octave generator maps are just pure_octave_start + something in
     # the above row space. Now we have to solve
     # the above row space. Now we have to solve
     #  (h@B + x)@M@W ≈ j@W
     #  (h @ B + x) @ M @ W ≈ j @ W
     # which, solving for h and doing the algebra out gives:
     # which, solving for h and doing the algebra out gives:
     h = (j - x@M)@W @ pinv(B@M@W)
     h = (j - x @ M) @ W @ pinv(B @ M @ W)
     g = h@B + x
     g = h @ B + x
     t = g@M
     t = g @ M
     return g, t
     return g, t
      
      
Line 280: Line 311:
}}
}}


=== Interpolating TE/CTE ===
==== Interpolating TE/CTE ====
We can also interpolate between the TE and CTE tunings, if we want. To do this, we modify the TE tuning so that the weighting of the 2's coefficient is very large. As the weighting goes to infinity, we get the CTE tuning. Thus, we can set it to some sufficiently large number, so that we get whatever numerical precision we want, and compute the result in closed-form using the pseudoinverse. Without comments, docstrings, etc, the calculation is only about five lines of python code:
We can also interpolate between the TE and CTE tunings, if we want. To do this, we modify the TE tuning so that the weighting of the 2's coefficient is very large. As the weighting goes to infinity, we get the CTE tuning. Thus, we can set it to some sufficiently large number, so that we get whatever numerical precision we want, and compute the result in closed-form using the pseudoinverse. Without comments, docstrings, etc, the calculation is only about five lines of python code:


Line 301: Line 332:
     J = 1200*np.log2(limit)
     J = 1200*np.log2(limit)


     # Main calculation: get the generator map g such that g@M@W ≈ J@W. Use pinv
     # Main calculation: get the generator map g such that g @ M @ W ≈ J @ W. Use pinv
     G = (J@W) @ np.linalg.pinv(M@W)
     G = (J @ W) @ np.linalg.pinv(M @ W)
     T = G @ M
     T = G @ M


Line 329: Line 360:
</pre>
</pre>


== CTE tuning vs POTE tuning vs CWE tuning vs CTWE tuning ==
== Comparison of tunings ==
{{Todo|inline=1| rework |comment=More properly and concisely summarize each side's POV. }}
 
=== Criticism of CTE ===
=== Criticism of CTE ===
People have long noted, since the early days of the tuning list, that the CTE tuning, despite having very nice qualities on paper, can give surprisingly strange results.{{citation needed}} One good example is blackwood, where the 4:5:6 chord is tuned to 0–386–720 cents, so that the error is not even close to evenly divided between the 5/4, 6/5, and 3/2. The reasons for this are subtle.
People have long noted, since the early days of the [[Yahoo Groups tuning lists]], that the CTE tuning, despite having very nice qualities on paper, can give surprisingly strange results.{{citation needed}} One good example is blackwood, where the 4:5:6 chord is tuned to 0–386–720 cents, so that the error is not even close to evenly divided between the 5/4, 6/5, and 3/2. The reasons for this are subtle.


This sort of thing was important historically when looking at optimal tunings for meantone, and is ultimately the motivation for advanced tuning methods such as TOP, TE, etc. to begin with. Thus, if our goal is to extend this principle in an elegant way to all intervals (and hopefully, triads and large chords), it would seem to defeat the purpose if we use a tuning optimization that doesn't also have this property,  
This sort of thing was important to early [[regular temperament theory]] pioneers when looking at optimal tunings for meantone, and is ultimately the motivation for advanced tuning methods such as TOP, TE, etc. to begin with. Thus, if our goal is to extend this principle in an elegant way to all intervals (and hopefully, triads and large chords), it would seem to defeat the purpose if we use a tuning optimization that doesn't also have this property,  


As a result of this, historically, the POTE tuning was used instead, which tunes it to a result that is approximately [[delta-rational]] 0-400-720 cents. People have also suggested using the Kees-Euclidean or KE tuning, also known as the constrained-Weil-Euclidean or CWE tuning. Here is a summary of the math involved and the historical reasoning behind this.
As a result of this, many use the POTE tuning, which tunes it to a result that is approximately [[delta-rational]]: 0-400-720 cents. People have also suggested using the Kees-Euclidean or KE tuning, also known as the constrained-Weil-Euclidean or CWE tuning. Here is a summary of the math involved and the reasoning behind this:


The CTE tuning can be thought of as a modified TE tuning in which the weighting (in monzo space) on the 2/1 coordinate has been changed to 0, making it a kind of seminorm rather than a norm. As a result, all elements in the same octave-equivalence class are weighted identically: they are all given complexity equal to the ''representative'' in each equivalence class in which all factors of 2 have been removed. Thus 5/4 is given the same complexity as 5/1, 13/8 as 13/1, and so on.
The CTE tuning can be thought of as a modified TE tuning in which the weighting (in monzo space) on the 2/1 coordinate has been changed to 0, making it a kind of seminorm rather than a norm. As a result, all elements in the same octave-equivalence class are weighted identically: they are all given complexity equal to the ''representative'' in each equivalence class in which all factors of 2 have been removed. Thus 5/4 is given the same complexity as 5/1, 13/8 as 13/1, and so on.
Line 357: Line 390:
Another way to think of it is that as POTE destretches the equave, it keeps the angle in the tuning space unchanged, and thus can be thought of as sacrificing multiplicative (typically very large) ratios for divisive (typically very small) ratios, whereas CTE sticks to the original design book of TE-optimality without worrying about that.
Another way to think of it is that as POTE destretches the equave, it keeps the angle in the tuning space unchanged, and thus can be thought of as sacrificing multiplicative (typically very large) ratios for divisive (typically very small) ratios, whereas CTE sticks to the original design book of TE-optimality without worrying about that.


Historically, there was also an observation that the POTE tuning can be thought of as an approximation to the CWE/KE tuning, which we will talk about below.
Observe that POTE tuning can be thought of as an approximation to the CWE/KE tuning, which we will talk about below.


=== Using the Weil norm or Kees expressibility ===
=== Using the Weil norm or Kees expressibility ===
Line 377: Line 410:
So, one simple solution is to interpolate between the two, giving the '''Tenney–Weil–Euclidean norm''': a weighted average of the TE and WE norms, with free weighting parameter k. This can be thought of as adjusting how much we care about the span: {{nowrap|k {{=}} 0}} is the TE norm, {{nowrap|k {{=}} 1}} is the WE norm, and in between we have intermediate norms. This also gives a '''Constrained Tenney–Weil–Euclidean''' or '''CTWE''' tuning as a result, which interpolates between CTE and CKE.
So, one simple solution is to interpolate between the two, giving the '''Tenney–Weil–Euclidean norm''': a weighted average of the TE and WE norms, with free weighting parameter k. This can be thought of as adjusting how much we care about the span: {{nowrap|k {{=}} 0}} is the TE norm, {{nowrap|k {{=}} 1}} is the WE norm, and in between we have intermediate norms. This also gives a '''Constrained Tenney–Weil–Euclidean''' or '''CTWE''' tuning as a result, which interpolates between CTE and CKE.


=== Comparison ===
=== Examples ===
These tunings can be very different from each other.
These tunings can be very different from each other.


Line 383: Line 416:
Take 7-limit meantone as an example. The POTE [[tuning map]] is a little bit flatter than [[quarter-comma meantone]], with all the primes tuned flat:  
Take 7-limit meantone as an example. The POTE [[tuning map]] is a little bit flatter than [[quarter-comma meantone]], with all the primes tuned flat:  


<math>\val{1200.000 & 1896.495 & 2785.980 & 3364.949}</math>
$$ \val{1200.000 & 1896.495 & 2785.980 & 3364.949} $$


The CWE tuning map is a little bit sharper than quarter-comma meantone, with 5 tuned sharp and 3 and 7 flat:  
The CWE tuning map is a little bit sharper than quarter-comma meantone, with 5 tuned sharp and 3 and 7 flat:  


<math>\val{1200.000 & 1896.656 & 2786.625 & 3366.562}</math>
$$ \val{1200.000 & 1896.656 & 2786.625 & 3366.562} $$


The CTE tuning map is even sharper, with 3 tuned flat and 5 and 7 sharp:  
The CTE tuning map is even sharper, with 3 tuned flat and 5 and 7 sharp:  


<math>\val{1200.000 & 1896.952 & 2787.809 & 3369.521}</math>
$$ \val{1200.000 & 1896.952 & 2787.809 & 3369.521} $$


==== Blackwood ====
==== Blackwood ====
Line 398: Line 431:
Note that the POTE tuning still has prime 5 tuned sharp, even though it could be tuned pure:
Note that the POTE tuning still has prime 5 tuned sharp, even though it could be tuned pure:


<math>\langle \begin{matrix} 1200.000 & 1920.000 & 2799.594 \end{matrix} ]</math>
$$ \val{1200.000 & 1920.000 & 2799.594} $$


The CWE gives similar results, although tunes it a few cents flatter:
The CWE gives similar results, although tunes it a few cents flatter:


<math>\val{1200.000 & 1920.000 & 2795.126}</math>
$$ \val{1200.000 & 1920.000 & 2795.126} $$


The CTE tuning, on the other hand, tunes prime 5 pure:
The CTE tuning, on the other hand, tunes prime 5 pure:


<math>\val{1200.000 & 1920.000 & 2786.314}</math>
$$ \val{1200.000 & 1920.000 & 2786.314} $$


Since prime 5 is not involved in the comma to begin with, it is understandable that it is tuned pure as in 5-limit JI. This, as mentioned above, leads to very lopsided behavior for compact chords like 1–5/4–3/2. Note that the tunings for KE and POTE distribute the error between 5/4 and 6/5 relatively evenly; both are very close to the delta-rational 0–397–720. The CTE tuning, on the other hand, has that chord tuned to 0–386–720, so that all of the error is on the 6/5 at about 18 cents sharp.
Since prime 5 is not involved in the comma to begin with, it is understandable that it is tuned pure as in 5-limit JI. This, as mentioned above, leads to very lopsided behavior for compact chords like 1–5/4–3/2. Note that the tunings for KE and POTE distribute the error between 5/4 and 6/5 relatively evenly; both are very close to the delta-rational 0–397–720. The CTE tuning, on the other hand, has that chord tuned to 0–386–720, so that all of the error is on the 6/5 at about 18 cents sharp.


== 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 420: Line 453:
The step size ''g'' can be found by
The step size ''g'' can be found by


<math>\displaystyle g = 1/\operatorname{mean} (V_X)</math>
$$ g = 1/\operatorname{mean} (V_X) $$


The edo number ''n'' can be found by
The edo number ''n'' can be found by


<math>\displaystyle n = 1/g = \operatorname{mean} (V_X)</math>
$$ n = 1/g = \operatorname{mean} (V_X) $$


Unlike TE or TOP, the optimal edo number space in TOC is linear with respect to ''V''. That is, if {{nowrap|''V'' {{=}} ''αV''<sub>1</sub> + ''βV''<sub>2</sub>}}, then
Unlike TE or TOP, the optimal edo number space in TOC is linear with respect to ''V''. That is, if {{nowrap|''V'' {{=}} ''αV''<sub>1</sub> + ''βV''<sub>2</sub>}}, then


<math>\displaystyle
$$
\begin{align}
\begin{align}
n &= \operatorname {mean} (VX) \\
n &= \operatorname {mean} (VX) \\
Line 435: Line 468:
&= \alpha n_1 + \beta n_2
&= \alpha n_1 + \beta n_2
\end{align}
\end{align}
</math>
$$


As a result, the [[Relative interval error #Linearity|relative error space]] is also linear with respect to ''V''.  
As a result, the [[relative interval error #Linearity|relative error space]] is also linear with respect to ''V''.  


For example, the relative errors of 12ettoc5 (12et in 5-limit TOC) is
For example, the relative errors of 12et in 5-limit TOC is


<math>\displaystyle \mathcal{E}_\text {r}(12) = \val{-1.55\% & -4.42\% & +10.08\% }</math>
$$ \mathcal{E}_\text {r}(12) = \val{-1.55\% & -4.42\% & +10.08\% } $$


That of 19ettoc5 is
That of 19et in this tuning is


<math>\displaystyle \mathcal{E}_\text {r}(19) = \val{+4.08\% & -4.97\% & -2.19\% }</math>
$$ \mathcal{E}_\text {r}(19) = \val{+4.08\% & -4.97\% & -2.19\% } $$


As 31 = 12 + 19, the relative errors of 31ettoc5 is
As 31 = 12 + 19, the relative errors of 31et in this tuning is


<math>\displaystyle
$$
\begin{align}
\begin{align}
\mathcal{E}_\text {r}(31) &= \mathcal{E}_\text {r}(12) + \mathcal{E}_\text {r}(19) \\
\mathcal{E}_\text {r}(31) &= \mathcal{E}_\text {r}(12) + \mathcal{E}_\text {r}(19) \\
&= \val{+2.52\% & -9.38\% & +7.88\% }
&= \val{+2.52\% & -9.38\% & +7.88\% }
\end{align}
\end{align}
</math>
$$


== 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 ==