Constrained tuning: Difference between revisions

Expansion
Update the script and style of the tuning maps
Line 44: Line 44:
Otherwise, as a standard optimization problem, numerous algorithms exist to solve it, such as [[Wikipedia: Sequential quadratic programming|sequential quadratic programming]], to name one.  
Otherwise, as a standard optimization problem, numerous algorithms exist to solve it, such as [[Wikipedia: Sequential quadratic programming|sequential quadratic programming]], to name one.  


The following [https://www.python.org Python] code is an excerpt from [[Flora Canou]]'s [https://github.com/FloraCanou/te_temperament_measures/blob/54c9ec58acf0ad5adc7c1d96f2deaf1d2a503702/tuning_optimizer.py tuning optimizer]. Note: it depends on [https://scipy.org/ Scipy].  
The following [https://www.python.org Python] code is an excerpt from [[Flora Canou]]'s [https://github.com/FloraCanou/te_temperament_measures/blob/545cc301418253cf46785c6c59a0cc6754986b08/tuning_optimizer.py tuning optimizer]. Note: it depends on [https://scipy.org/ Scipy].  


{{Databox| Code |
{{Databox| Code |
<syntaxhighlight lang="python">
<syntaxhighlight lang="python">
# © 2020-2021 Flora Canou | Version 0.8
# © 2020-2021 Flora Canou | Version 0.10
# This work is licensed under the GNU General Public License version 3.
# This work is licensed under the GNU General Public License version 3.


Line 58: Line 58:
SCALAR = 1200 #could be in octave, but for precision reason
SCALAR = 1200 #could be in octave, but for precision reason


def weighted (matrix, subgroup, type = "tenney"):
def weighted (matrix, subgroup, wtype = "tenney"):
     if not type in {"tenney", "frobenius"}:
     if not wtype in {"tenney", "frobenius", "partch"}:
         type = "tenney"
         wtype = "tenney"
        raise Warning ("unknown weighter type, using default (\"tenney\")")


     if type == "tenney":
     if wtype == "tenney":
         weighter = np.diag (1/np.log2 (subgroup))
         weighter = np.diag (1/np.log2 (subgroup))
     elif type == "frobenius":
     elif wtype == "frobenius":
         weighter = np.eye (len (subgroup))
         weighter = np.eye (len (subgroup))
    elif wtype == "partch":
        weighter = np.diag (np.log2 (subgroup))
     return matrix @ weighter
     return matrix @ weighter


Line 71: Line 74:
     return linalg.norm (gen @ map - jip, ord = order)
     return linalg.norm (gen @ map - jip, ord = order)


def optimizer_main (map, subgroup = [], order = 2, weighter = "tenney", cons_monzo_list = np.array ([]), stretch_monzo = np.array ([]), show = True):
def optimizer_main (map, subgroup = None, wtype = "tenney", order = 2, cons_monzo_list = None, stretch_monzo = None, show = True):
     if len (subgroup) == 0:
     if subgroup is None:
         subgroup = PRIME_LIST[:map.shape[1]]
         subgroup = PRIME_LIST[:map.shape[1]]


     jip = np.log2 (subgroup)*SCALAR
     jip = np.log2 (subgroup)*SCALAR
     map_w = weighted (map, subgroup, type = weighter)
     map_w = weighted (map, subgroup, wtype = wtype)
     jip_w = weighted (jip, subgroup, type = weighter)
     jip_w = weighted (jip, subgroup, wtype = wtype)
     if order == 2 and not cons_monzo_list.size: #te with no constraints, simply use lstsq for better performance
     if order == 2 and cons_monzo_list is None: #te with no constraints, simply use lstsq for better performance
         res = linalg.lstsq (map_w.T, jip_w)
         res = linalg.lstsq (map_w.T, jip_w)
         gen = res[0]
         gen = res[0]
Line 84: Line 87:
     else:
     else:
         gen0 = [SCALAR]*map.shape[0] #initial guess
         gen0 = [SCALAR]*map.shape[0] #initial guess
         cons = {'type': 'eq', 'fun': lambda gen: (gen @ map - jip) @ cons_monzo_list} if cons_monzo_list.size else ()
         cons = () if cons_monzo_list is None else {'type': 'eq', 'fun': lambda gen: (gen @ map - jip) @ cons_monzo_list}
         res = optimize.minimize (error, gen0, args = (map_w, jip_w, order), method = "SLSQP", constraints = cons)
         res = optimize.minimize (error, gen0, args = (map_w, jip_w, order), method = "SLSQP", constraints = cons)
         print (res.message)
         print (res.message)
Line 90: Line 93:
             gen = res.x
             gen = res.x


     if stretch_monzo.size:
     if not stretch_monzo is None:
         gen *= (jip @ stretch_monzo)/(gen @ map @ stretch_monzo)
         gen *= (jip @ stretch_monzo)/(gen @ map @ stretch_monzo)


Line 97: Line 100:


     return gen
     return gen
optimiser_main = optimizer_main


</syntaxhighlight>
</syntaxhighlight>
Line 106: Line 111:
The pure-octave CTE tuning can be very different from [[POTE tuning]]. Take 7-limit meantone as an example, the POTE [[tuning map]]:  
The pure-octave CTE tuning can be very different from [[POTE tuning]]. Take 7-limit meantone as an example, the POTE [[tuning map]]:  


{{val| 1200.000 1896.495 2785.980 3364.949 }}
<math>\langle \begin{matrix} 1200.000 & 1896.495 & 2785.980 & 3364.949 \end{matrix} ]</math>


This is a little bit flatter than [[quarter-comma meantone]], with all the primes tuned flat.  
This is a little bit flatter than [[quarter-comma meantone]], with all the primes tuned flat.  
Line 112: Line 117:
The pure-octave CTE tuning map:  
The pure-octave CTE tuning map:  


{{val| 1200.000 1896.952 2787.809 3369.521}}
<math>\langle \begin{matrix} 1200.000 & 1896.952 & 2787.809 & 3369.521 \end{matrix} ]</math>


This is a little bit sharper than quarter-comma meantone, with prime 3 tuned flat and 5 and 7 sharp.  
This is a little bit sharper than quarter-comma meantone, with prime 3 tuned flat and 5 and 7 sharp.