Constrained tuning: Difference between revisions
→CTE tuning vs POTE tuning CWE tuning vs CTWE tuning: Missing "vs" |
m Formatting |
||
| Line 166: | Line 166: | ||
</pre> | </pre> | ||
Analytical solutions exist for Euclidean (''L''<sup>2</sup>) tunings, see [[Constrained tuning/Analytical solution to constrained Euclidean tunings]]. It can also be solved in the {{w|Lagrange multiplier|method of Lagrange multiplier}}. The solution is given by | === Analytical solutions === | ||
Analytical solutions exist for Euclidean (''L''<sup>2</sup>) tunings, see [[Constrained tuning/Analytical solution to constrained Euclidean tunings]]. | |||
==== Method of Lagrange multiplier ==== | |||
It can also be solved in the {{w|Lagrange multiplier|method of Lagrange multiplier}}. The solution is given by | |||
<math>\displaystyle | <math>\displaystyle | ||
| Line 187: | Line 191: | ||
which is almost an analytical solution. 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. | which is almost an analytical solution. 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 | === Simple fast closed-form algorithm === | ||
A much simpler way to compute the CTE and CWE tunings, and the CTWE tuning in general, is to use the pseudoinverse. This doesn't require doing any intense nonlinear optimization. | A much simpler way to compute the CTE and CWE tunings, and the CTWE tuning in general, is to use the pseudoinverse. This doesn't require doing any intense nonlinear optimization. | ||
| Line 220: | Line 224: | ||
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 weighting 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| | |||
<syntaxhighlight lang="python"> | |||
import numpy as np | |||
from numpy.linalg import inv, pinv | |||
from scipy.linalg import null_space, sqrtm | |||
def CTWE(limit, M, k): | |||
""" | |||
Computes the CTWE tuning of a *full-limit* temperament given a limit and | |||
mapping matrix M. For k=0, this is CTE, for k=1, this is CWE/KE. | |||
For subgroup temperaments, first compute the tuning of the full-limit | |||
temperament with same kernel, then multiply by the subgroup basis matrix. | |||
""" | |||
# Basics: get the weighting matrix, JIP, etc | |||
W_monzo = np.vstack([np.diag(np.log2(limit)), np.log2(limit) * k]) | |||
W_monzo_gram = sqrtm(W_monzo.T @ W_monzo) | |||
W = inv(W_monzo_gram) # we could call this W_val_gram | |||
j = 1200*np.log2(limit) | |||
# Simple way to get a random generator map with pure octaves: take the pinv | |||
# of the octave mapping column. The corresponding tuning map has an octave | |||
# coordinate of 1 cent, so multiply by the first JIP coord (probably 1200). | |||
octave_col = M[:,0][:,np.newaxis] | |||
x = pinv(M[:,0][:,np.newaxis]) * j[0] | |||
# the left nullspace of octave_col has all generators mapping to 0. | |||
B = null_space(octave_col.T).T | |||
# All pure-octave generator maps are just pure_octave_start + something in | |||
# the above row space. Now we have to solve | |||
# (h@B + x)@M@W ≈ j@W | |||
# which, solving for h and doing the algebra out gives: | |||
h = (j - x@M)@W @ pinv(B@M@W) | |||
g = h@B + x | |||
t = g@M | |||
return g, t | |||
# %% Compute the CTE of septimal meantone temperament | |||
k = 1 | |||
limit = np.array([2, 3, 5, 7]) | |||
M = np.array( # mapping matrix for meantone | |||
[[1, 0, -4, -13], | |||
[0, 1, 4, 10]] | |||
) | |||
G_CTE, T_CTE = CTWE(limit, M, 0) | |||
print("CTE Generator map: " + str(G_CTE)) | |||
print("CTE Tuning map: " + str(T_CTE)) | |||
G_CWE, T_CWE = CTWE(limit, M, 1) | |||
print("CWE Generator map: " + str(G_CWE)) | |||
print("CWE Tuning map: " + str(T_CWE)) | |||
</syntaxhighlight> | |||
}} | |||
=== Interpolating TE/CTE === | === Interpolating TE/CTE === | ||
| Line 276: | Line 284: | ||
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: | ||
{{Databox|Code| | |||
<syntaxhighlight lang="python"> | |||
import numpy as np | |||
def modified_TE(limit, M, H=1000000): | |||
""" | |||
Computes the interpolated TE/CTE tuning of a *full-limit* temperament given | |||
a limit and mapping matrix M. For H = 1 we have TE, and as H -> inf we have | |||
CTE. To compute CTE to arbitrary precision, make H sufficiently large. | |||
For subgroup temperaments, first compute the tuning of the full-limit | |||
temperament with same kernel, then multiply by the subgroup basis matrix. | |||
""" | |||
# Compute adjusted TE weighting matrix and JIP | |||
W = np.diag(1/np.log2(limit)) | |||
W[0,0] = H | |||
J = 1200*np.log2(limit) | |||
# Main calculation: get the generator map g such that g@M@W ≈ J@W. Use pinv | |||
G = (J@W) @ np.linalg.pinv(M@W) | |||
T = G @ M | |||
return G, T | |||
# %% Compute the CTE of septimal meantone temperament | |||
H = 1000000 | |||
limit = np.array([2, 3, 5, 7]) | |||
M = np.array( # mapping matrix for meantone | |||
[[1, 0, -4, -13], | |||
[0, 1, 4, 10]] | |||
) | |||
G, T = modified_TE(limit, M, H) | |||
print("Generator map: " + str(G)) | |||
print("Tuning map: " + str(T)) | |||
</syntaxhighlight> | |||
}} | |||
The output to the above is: | The output to the above is: | ||
<pre> | |||
Generator map: [ 1200.0000 1896.9521] | |||
Tuning map: [ 1200.0000 1896.9521 2787.8086 3369.5214] | |||
</pre> | |||
== CTE tuning vs POTE tuning vs CWE tuning vs CTWE tuning == | == CTE tuning vs POTE tuning vs CWE tuning vs CTWE tuning == | ||