Constrained tuning: Difference between revisions

Add KE back per discussion. Now weight and skewed are collectively denoted by X. p-norm -> q-norm since it's the dual norm (p is reserved for interval space norm)
Computation: update code
Line 30: Line 30:


== Computation ==
== Computation ==
As a standard optimization problem, numerous algorithms exist to solve it, such as [[Wikipedia: Sequential quadratic programming|sequential quadratic programming]], to name one. [[Flora Canou]]'s [https://github.com/FloraCanou/temperament_evaluator/blob/32a2198f1ebd9038fc64e02b10b547e011d4eaa4/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 [[Wikipedia: Sequential quadratic programming|sequential quadratic programming]], to name one. [[Flora Canou]]'s [https://github.com/FloraCanou/temperament_evaluator/blob/85b847a41ab0f6273ea43394147b1b8f8a13b8ea/te_optimizer_legacy.py tuning optimizer] is such an implementation in [https://www.python.org Python]. Note: it uses [https://scipy.org/ Scipy].  


{{Databox| Code |
{{Databox| Code |
<syntaxhighlight lang="python">
<syntaxhighlight lang="python">
# © 2020-2023 Flora Canou | Version 0.26.1
# © 2020-2023 Flora Canou | Version 0.26.2
# 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 70: Line 70:
             return np.eye (len (subgroup))
             return np.eye (len (subgroup))
         elif self.order == 2:
         elif self.order == 2:
             if self.skew != np.inf:
             r = 1/(len (subgroup)*self.skew + 1/self.skew)
                r = self.skew/(len (subgroup)*self.skew**2 + 1)
             kr = 1/(len (subgroup) + 1/self.skew**2)
                kr = self.skew*r
             else:
                r = 0
                kr = 1/len (subgroup)
         else:
         else:
             raise NotImplementedError ("Weil skew only works with Euclidean norm as of now.")
             raise NotImplementedError ("Weil skew only works with Euclidean norm as of now.")
Line 96: Line 92:
     return main, subgroup
     return main, subgroup


def __error (gen, vals, jip, order):
def __error (gen, vals, just_tuning_map, order):
     return linalg.norm (gen @ vals - jip, ord = order)
     return linalg.norm (gen @ vals - just_tuning_map, ord = order)


def optimizer_main (vals, subgroup = None, norm = Norm (), #"map" is a reserved word
def optimizer_main (vals, subgroup = None, norm = Norm (), #"map" is a reserved word
Line 103: Line 99:
     vals, subgroup = __get_subgroup (vals, subgroup)
     vals, subgroup = __get_subgroup (vals, subgroup)


     jip = np.log2 (subgroup)*SCALAR
     just_tuning_map = np.log2 (subgroup)*SCALAR
     vals_wx = norm.weightskewed (vals, subgroup)
     vals_x = norm.weightskewed (vals, subgroup)
     jip_wx = norm.weightskewed (jip, subgroup)
     just_tuning_map_x = norm.weightskewed (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 (vals_wx.T, jip_wx)
         res = linalg.lstsq (vals_x.T, just_tuning_map_x)
         gen = res[0]
         gen = res[0]
         print ("Euclidean tuning without constraints, solved using lstsq. ")
         print ("Euclidean tuning without constraints, solved using lstsq. ")
     else:
     else:
         gen0 = [SCALAR]*vals.shape[0] #initial guess
         gen0 = [SCALAR]*vals.shape[0] #initial guess
         cons = () if cons_monzo_list is None else {'type': 'eq', 'fun': lambda gen: (gen @ vals - jip) @ cons_monzo_list}
         cons = () if cons_monzo_list is None else {'type': 'eq', 'fun': lambda gen: (gen @ vals - just_tuning_map) @ cons_monzo_list}
         res = optimize.minimize (__error, gen0, args = (vals_wx, jip_wx, norm.order), method = "SLSQP",
         res = optimize.minimize (__error, gen0, args = (vals_x, just_tuning_map_x, norm.order), method = "SLSQP",
             options = {'ftol': 1e-9}, constraints = cons)
             options = {'ftol': 1e-9}, constraints = cons)
         print (res.message)
         print (res.message)
Line 127: Line 123:
             raise ZeroDivisionError ("destretch target is in the nullspace. ")
             raise ZeroDivisionError ("destretch target is in the nullspace. ")
         else:
         else:
             gen *= (jip @ des_monzo)/tempered_size
             gen *= (just_tuning_map @ des_monzo)/tempered_size


     tuning_map = gen @ vals
     tempered_tuning_map = gen @ vals
     mistuning_map = tuning_map - jip
     mistuning_map = tempered_tuning_map - just_tuning_map


     if show:
     if show:
         print (f"Generators: {gen} (¢)",
         print (f"Generators: {gen} (¢)",
             f"Tuning map: {tuning_map} (¢)",
             f"Tuning map: {tempered_tuning_map} (¢)",
             f"Mistuning map: {mistuning_map} (¢)", sep = "\n")
             f"Mistuning map: {mistuning_map} (¢)", sep = "\n")


     return gen, tuning_map, mistuning_map
     return gen, tempered_tuning_map, mistuning_map


optimiser_main = optimizer_main
optimiser_main = optimizer_main