Generator embedding optimization: Difference between revisions
Cmloegcmluin (talk | contribs) Exact solutions with advanced tie-breaking |
Cmloegcmluin (talk | contribs) →Tie-breaking: misc issues |
||
| Line 8,263: | Line 8,263: | ||
Now maybe this doesn't fully test the system, since we only convexly combined two tunings, but this is probably sound for general use. At least, the test suite of the RTT Library in Wolfram Language included several examples that should have failed upon switching to this way of computing true optimum tunings if this were a problem, but they did not. | Now maybe this doesn't fully test the system, since we only convexly combined two tunings, but this is probably sound for general use. At least, the test suite of the RTT Library in Wolfram Language included several examples that should have failed upon switching to this way of computing true optimum tunings if this were a problem, but they did not. | ||
===Misc. issues: polytope=== | |||
Keenan Pepper named the file where we wrote the coinciding-damage method "tiptop.py", and along with that coined the tuning scheme name "TIP-TOP", for "Tiebreaker-in-polytope TOP".<ref>https://yahootuninggroupsultimatebackup.github.io/tuning-math/topicId_20405.html#20412</ref> So what's up with this "polytope"? | |||
Polytopes — they're nothing too scary, actually. The name seems imposing, but it's really just a name for a shape which is as generic as possible: | |||
* The "poly-" part means generic to how many ''vertices/edges/faces/etc.'' the shape has. This prefix generalizes prefixes you may be familiar with like "tetra-", "penta-", "hexa-", etc., which are used for shapes where we ''do'' know exactly how many of the given type of feature a shape has, like a "penta-gon" (5 edges) or a "tetra-hedron" (4 faces). | |||
* The "-tope" part means generic to how many ''dimensions'' these features occupy. This suffix generalizes suffixes you may be familiar with like "-gon", "-hedron", "-choron"/"-cell"/"-hedroid", etc., which are used for shapes where we ''do'' know exactly how many dimensions a shape occupies, again, like a "hexa-gon" (2 dimensions) or an "octa-hedron" (3 dimensions). | |||
We note that the polytope Keenan refers to is ''not'' the full coinciding-damage point set. | |||
It's not even the faceted bowl we see formed from the aerial view by the combination of all the individual target-intervals' tuning damage graph hyper-V's; that's an in-between sized point set we would call the "maximum damage graph vertices". | |||
No, Keenan's polytope refers to an even smaller set of points. It contains either only a single point for the case of an immediately unique optimum, or more than one point which together bound the region (such as a line segment, triangle, etc.) within which the true optimum may be found in the next iteration of the algorithm, using blends. This is what we could call the ''minimax polytope''. | |||
===Misc. issues: major modification to Keenan's original method=== | |||
In the beginning of our discussion of the coinciding-damage method, we mentioned that Douglas Blumeyer had modified Keenan Pepper's algorithm in a way that "simplifies it conceptually and allows it to identify optimum tunings more quickly". Here is an explanation of that change. | |||
Keenan's original design was to only include the zero-damage points once tuning damage space had been reduced to 2D. This design still does eventually finds the same true optimum tunings, but the problem is that it requires advanced tie-breaking to accomplish this, where basic tie-breaking could have worked had those points been included. Consider the case of {2/1,3/1,5/1,6/5} minimax-U blackwood we gave as our basic tie-breaking example [[Generator_embedding_optimization#Basic_tie-breaking_example:_setup|here]]: with Keenan's design, the intersection between <math>\frac51</math> and <math>\frac65</math>'s creases would not have been included; it would have been as if we were doing the primes minimax-U blackwood example we look at in the following section where basic tie-breaking must fail. | |||
Since advanced tie-breaking requires an entire new iteration of the algorithm, gathering a whole new coinciding-damage point set, it is more computationally expensive than handling a tie-break with the basic technique by simply including some more points in the current iteration. | |||
Also, because always including zero-damage points is a conceptually purer way of presenting the concepts (it doesn't require an entire 7-page separate section to this article explaining the single-free-generator 2D tuning damage space exception, which as you might guess, I did write before having the necessary insight to simplify things), it is preferable for human understanding as well. | |||
I also considered adding the unison to the target-interval set in order to capture the zero-damage points, but that was terribly inefficient and confusing. I also tried generalizing Keenan's code in a different way. Keenan's code includes <math>K</math> which make a target-interval itself unchanged, but it only does that when the <math>K</math> have only one column (meaning we're searching 2D tuning damage space). What if we think of it like this: our point set always includes <math>K</math> which have one column for enforcing a target-interval itself, among any other unchanged-intervals, and we do this not only when <math>K</math> is one column. That turned out to be an improvement, but it still resulted in redundant points, because we don't need direction permutations for the non-unison target-intervals when their errors are 0 either (e.g. If <math>\frac21</math> is pure, and <math>\frac61</math> is pure, that implies that <math>\frac31</math> is pure. But if <math>\frac21</math> is pure, and <math>\frac32</math> is pure, that just as well implies that <math>\frac31</math> is pure. | |||
===Misc. issues: why we abbreviate=== | |||
For some readers, it may seem pointless, or wasteful, to abbreviate DSLODs like this. Especially in those cases where you're tantalizingly close… you can see that you ''could'' break a tie, if only you were allowed to include one more entry in each ADSLOD. Or perhaps you could at least reduce the count of tunings that are tied. | |||
Well, here's another way to think about the reason for this abbreviation, which may help you respect the purpose for the abbreviation. If you falsely eliminate tunings that rightly should still have been tied at this stage, then you will be eliminating tunings that should have been helping to define the boundary of the region to check in the next iteration of the method. | |||
So, if you erroneously reduced the search space down to two tuning points defining a line segment region, you should have been searching an entire triangle-shaped region instead. You might miss the true optimum somewhere in the middle of the area of that triangle, not merely along one of its three sides. | |||
===Misc. issues: importance of deduplication=== | |||
Note that a very good reason to perform the type of deduplication within the target-interval set discussed earlier in this article ([[#Within_target-interval_set|here]]) is to prevent unnecessary failing of the basic tie-breaking mechanism. Suppose we have a case like our basic tie-breakable blackwood example, where two damage graphs' crease is parallel to the floor and forms the minimum of the max damage graph, but we can still look one more position down the ADSLODs to tie-break at some point in the middle of this line segment range which minimizes the third damage position. Well, now imagine that instead we ''clogged'' our ADSLODs with a duplicate target-interval, i.e. one whose damage graph is identical to one or the other of these two forming this tied minimax crease. Now we unnecessarily find ourselves with ''three'' coinciding damages up top instead of just ''two'', and will be forced to dip into advanced tie-breaking. But if we had only de-duped the target-intervals which map to the same mapped interval up front, we wouldn't have had to do that. | |||
===Misc. issues: held-intervals with respect to advanced tie-breaking=== | |||
In [[Generator_embedding_optimization#With_held-intervals_3|this section]] we discuss how to handle held-intervals with the coinciding-damage method. We note here that the extra steps involved — allocating columns of the constraint matrices to the held-intervals — are ''only necessary'' in the first iteration of the method. | |||
Think of it this way: whichever generators were locked into the appropriate proportion in order to achieve the held-intervals at the top-level, main tuning damage space, those will ''remain'' locked in the blends of tunings at lower levels. In other words, whatever cross-section we take to capture the minimax polytope will already be within the held-interval subregion of tuning damage space. | |||
===Misc. issues: a major flaw with the method=== | |||
Keenan himself considers his algorithm to be pretty dumb. (It seems sort of fantastically powerful and genius to me, overall, but I can sort of see what he means a bit, now). | |||
One core problem it has causes it to be potentially majorly inefficient, and also somewhat conceptually misleading. That's what we'll discuss here. | |||
When we [[Generator_embedding_optimization#Blends:_abstract_concept|introduced the concept of blends in an earlier section]], we noted how any point within the bounded region can be specified as a blend where the blending variables are positive sum to 1. That's the key thing that keeps us within the region; if we can specify huge and/or negative blending variables, then the exercise is moot, and we can specify points anywhere. Well, if we've got some 1D line segment embedded in a 2D plane, without the sum-to-1 rule, we can use <math>\mathbf{A}</math> and <math>\mathbf{B}</math> to describe any point within that 2D plane, anyway. | |||
So, it turns out that this is essentially the same as how it works in advanced tie-breaking. When we take a cross-section of tuning damage space which contains the line segment of our tied basic minimax region, and gather a new coinciding-damage point set in terms of blending variables, we don't know if a point is going to fall within the line segment we care about or not ''until after we've already computed its blend variables''. (Remember, the blend variable for the anchor tuning is always assumed to be whatever is required to get the sum exactly to 1, so all we care about is the other variables summing to something between 0 and 1.) | |||
For example, consider the diagram we showed in [[Generator_embedding_optimization#Apply_formula_to_example|this section]]. Note how the damage graphs for <math>\frac52</math> and <math>\frac{16}{5}</math> intersect (within this cross-section) but ''just outside the range'' where <math>0 < b_1 < 1</math>. Well, when we gather our coinciding-damage points, convert their ReDPOTICs and STICs to constraint matrices, and convert those to tunings, it's not until then that we'll realize this tuning is outside of bounds. We could filter it out at this point — it will never qualify as the true optimum tuning, because if you look straight up in the diagram, you can see that the damage to <math>\frac95</math> is greater than the basic minimax could potentially be. But we already wasted a lot of resources finding it. | |||
Essentially we search the whole cross-section, not just the minimax polytope we've identified. | |||
And there's no particularly obvious way to rework this method to only find coinciding-damage points for <math>K</math> where every entry of <math>𝒃</math> is non-negative and <math>\llzigzag 𝒃 \rrzigzag_1 = 1</math>. To improve this part of the algorithm would require basically rethinking it from the inside out. | |||
===Misc. issues: derivation of extracted anchor=== | |||
We can derive this formula from the one we had before, like so. Start with: | |||
<math> | |||
\mathbf{P} = x\mathbf{A} + y\mathbf{B} + z\mathbf{C} | |||
</math> | |||
Add <math>\mathbf{A} - \mathbf{A}</math> to this, which changes nothing. | |||
<math> | |||
\mathbf{P} = \mathbf{A} - \mathbf{A} + x\mathbf{A} + y\mathbf{B} + z\mathbf{C} | |||
</math> | |||
Recognize a coefficient of <math>1</math> on the subtracted <math>\mathbf{A}</math>. | |||
<math> | |||
\mathbf{P} = \mathbf{A} - 1\mathbf{A} + x\mathbf{A} + y\mathbf{B} + z\mathbf{C} | |||
</math> | |||
We know <math>x + y + z = 1</math>, so we can substitute that in for this <math>1</math>. | |||
<math> | |||
\mathbf{P} = \mathbf{A} - (x + y + z)\mathbf{A} + x\mathbf{A} + y\mathbf{B} + z\mathbf{C} | |||
</math> | |||
Distribute. | |||
<math> | |||
\mathbf{P} = \mathbf{A} - x\mathbf{A} - y\mathbf{A} - z\mathbf{A} + x\mathbf{A} + y\mathbf{B} + z\mathbf{C} | |||
</math> | |||
Regroup by <math>x</math> and <math>y</math>. | |||
<math> | |||
\mathbf{P} = \mathbf{A} + x(\mathbf{A} - \mathbf{A}) + y(\mathbf{B} - \mathbf{A}) + z(\mathbf{C} - \mathbf{A}) | |||
</math> | |||
Cancel out these <math>\mathbf{A}</math>'s and thus <math>x</math>. | |||
<math> | |||
\mathbf{P} = \mathbf{A} + x(\cancel{\mathbf{A}} - \cancel{\mathbf{A}}) + y(\mathbf{B} - \mathbf{A}) + z(\mathbf{C} - \mathbf{A}) | |||
\mathbf{P} = \mathbf{A} + x(0) + y(\mathbf{B} - \mathbf{A}) + z(\mathbf{C} - \mathbf{A}) | |||
</math> | |||
And so our final formula: | |||
<math> | |||
\mathbf{P} = \mathbf{A} + y(\mathbf{B} - \mathbf{A}) + z(\mathbf{C} - \mathbf{A}) | |||
</math> | |||
===Misc. issues: equivalence to power-limit approach=== | |||
In Keenan's original Yahoo groups post, he claims that his method (the core idea of which is explained in a modified form here as the coinciding-damage method) is equivalent to the [[Dave_Keenan_%26_Douglas_Blumeyer%27s_guide_to_RTT:_tuning_computation#Tie_breaking:_power_limit_method|power-limit method]] for finding true optimums for minimax tunings: "This is equivalent to taking the limit of the Lp norm minimizer as p tends to infinity (exercise for the reader!)"<ref>https://yahootuninggroupsultimatebackup.github.io/tuning-math/topicId_20405.html#20412</ref>. Douglas Blumeyer has attempted this exercise, but failed. He pestered Keenan himself for the solution, but it had been so long (about 10 years) since Keenan wrote this, he himself could not reproduce. So at this time, this remains an open problem — an exercise for ''you readers'', now. | |||
===Misc. issues: normalization required to handle exact tunings=== | |||
One complication arose with the advanced tie-breaking part of the code (in Dave Keenan & Douglas Blumeyer's RTT Library in Wolfram Language which was adapted from Keenan Pepper's original Python code) upon the switch from Keenan's original technique of computing approximate generator tuning maps <math>𝒈</math> by solving linear systems of equations to Dave Keenan's technique of computing exact generator embeddings <math>G</math> by doing matrix inverses. In some cases where Keenan's technique used to work fine, Dave's method would fall on its face. Here's what happened. | |||
Essentially, the basic tie-breaking step would come back with a set of tied tunings such as this: | |||
* <math>𝒈_0</math> = {{rbra|240.000 2786.314}} | |||
* <math>𝒈_1</math> = {{rbra|240.000 2795.336}} | |||
* <math>𝒈_2</math> = {{rbra|240.000 2804.359}} | |||
The problem is that when we have ''three'' points defining a convex hull, it's supposed to be a triangle! This is a degenerate case where all three points ''fall along the same line''. Not only is this wasteful, but it also screws stuff up, because now there's essentially more than one way to blend <math>\mathbf{A}</math>, <math>\mathbf{B}</math>, and <math>\mathbf{C}</math> together to get <math>\mathbf{P}</math>, because <math>\mathbf{B}</math> and <math>\mathbf{C}</math> pull us away from <math>\mathbf{A}</math> in the exact same direction. | |||
Note that the only thing that matters is the ''direction'' that the tied tunings are from each other, not the distance; the values in the blend map <math>𝒃</math> are continuous and can be anything they need to be to reach a desired point. In other words, all that matters are the proportions of the entries of the deltas to each other. In still other words, different tunings on the same line are redundant. | |||
It happens to be the case here that the <math>𝜹_i</math> are not only on the same line, but simple multiples of each other: | |||
* <math>𝜹_2 = 𝒈_1 - 𝒈_0</math> = {{rbra|240.000 2795.336}} - {{rbra|240.000 2786.314}} = {{rbra|0 9.0225}} | |||
* <math>𝜹_1 = 𝒈_2 - 𝒈_0</math> = {{rbra|240.000 2804.359}} - {{rbra|240.000 2786.314}} = {{rbra|0 18.045}} | |||
which is to say that <math>𝒈_1</math> happens to be ''smack-dab halfway'' between <math>𝒈_0</math> and <math>𝒈_1</math>. But that's just a distraction; that's not important. It could have been <math>ϕ</math> of the way between them instead and the problem would have been the same. | |||
Remember that these <math>𝜹_i</math> get combined into one big <math>D</math> matrix. In this case, that's | |||
<math> | |||
\left[ \begin{array} {r} | |||
0 & 9.0225 \\ | |||
0 & 18.045 \\ | |||
\end{array} \right] | |||
</math> | |||
Using this for <math>D</math>, however, causes every <math>B</math> we try to find via | |||
<math> | |||
B = \mathrm{T}WK(DM\mathrm{T}WK)^{-1} | |||
</math> | |||
to fail, because the <math>DM\mathrm{T}WK</math> matrix we try to invert is singular. (And for one reason or another, Keenan's way, using a <code>LinearSolve[]</code>, handled this degenerate case without complaining.) | |||
One might think the solution would be simply to canonicalize this <math>D</math> matrix: [[HNF]] it, and delete the all-zero rows. But here's the thing: it's not an integer matrix. It's not even rational. Even though it's seems obvious that since <math>18.045 = 9.0225 × 2</math> we should be able to reduce this thing to: | |||
<math> | |||
\left[ \begin{array} {r} | |||
0 & 1 \\ | |||
0 & 2 \\ | |||
\end{array} \right] | |||
</math> | |||
actually what we want to do here is different and maybe slightly simpler. At least, it's a different breed of normalization. | |||
What the RTT Library in Wolfram Language does now is <code>Normalize[]</code> every <math>𝜹_i</math> to a unit vector and then dedupe them according to if they equal each other or their negation. Given the design of the algorithm, namely, how it doesn't actually restrict itself to searching the convex combination but instead searches the whole affine plane or whatever. And that works. | |||
As a result, however, it doesn't always work directly in blend variables, but in scaled blend variables, scaled by the factor between the normalized and non-normalized deltas. For example, normalizing the above example would create a normalizing size factor of 9.0225. So now the tied minimax range wouldn't be from <math>0 < b_1 < 1</math>, but from <math>0 < b_1 < 9.0225</math>. | |||
==For all-interval tuning schemes== | ==For all-interval tuning schemes== | ||