Closed zepumph closed 4 years ago
I was able to shape the tolerance by normalizing on the size of the target ratio. This worked very well for the denominator, solving the 1/10 case as described above. The issue that we keep coming back to is that this reciprocally effects the left hand also, so when we shrink the range of fitness for the right value, it does the same on the left value. Again we are constrained by everything discussed in #14, where because we support bimodal interaction, we have to base the fitness calculation on the ratio, and not the "other value".
Here is a patch working for the second screen that demonstrated how shrinking the right value's fitness also shrinks the left value:
After discussing this with @samreid, we were able to classify the problem into trying to solve a function based on each value and the target ratio: f( x1, x2, t)
. This helped us note that there were two ways to solve this, each treating a different value as the "optimal" or "target" value, and then calculating how "off" the other value was, like this:
const f1 = ( x1, x2opt, t ) => 1 - 0.5 * Math.abs( x1 - t * x2opt );
const f2 = ( x1opt, x2, t ) => 1 - 0.5 * Math.abs( x2 - x1opt / t );
We then used this to note the paradox in how each state can yield two different values. There wasn't an obviously "best way" to solve this, but we experimented with always using the max
fitness (like giving the benefit of the doubt for the algorithm). This yielded the same issues as to why this issue was created. In which smaller target ratios, like 1/10, yielded a much larger fitness range.
Then we looked at using the min fitness from each of them. This got us as far as the above patch did, in which the right value always has 40% of the range giving fitness feedback, but the left value could be quite a bit smaller than that. The aforementioned cases are only for ratios smaller than 1 (otherwise the hands' behavior is reversed).
Still this was nice enough that I decided to commit using the min fitness between the two.
I think this could be improved by potentially experimenting with using this as the default behavior, and then having a single interaction drag give priority to one of the fitness functions. That would likely yield some cases where the fitness unexpectedly jumped a large amount, but I think it is worth exploring.
Thanks @samreid for all your help today!
Ideas from using deltas:
@samreid and @jonathanolson helped further with this, and we got very far with an approach that uses deltas. I ended up committing because it is generally working well, although there are a few strange pieces that still need to be worked out. I also committed tests. @samreid a couple tests are commented out, because there seems to be an edge case where fitness can get greater than 1. I don't think this should be able to happen, and we should catch these cases and try to solve it. This is all I can work on this tonight, but I'll go ahead again tomorrow.
I also just noted that we are not yet supporting when the target ratio changes. We should link and recalculate fitness accordingly there. This may also fix the reset case too.
After sleeping on this for a night. I'm pretty sure that solving tolerance by deltas won't work for two reasons. Both of thesehas the paradox of potentially having two different fitness values depending on what ratio value you consider the "ground truth"
With this in mind. I am going to revert "by deltas" commit, along with the tests. and instead work towards a solution that has trade-offs. Likely something based on https://github.com/phetsims/ratio-and-proportion/commit/1fb0ae747e8d824b93363279d7eb56434275fdba
I reverted the delta-related algorithm above. Now the algorithm is back to the description explained in https://github.com/phetsims/ratio-and-proportion/issues/91#issuecomment-661268791. We have successfully shrunk the range of fitness to always be 40% of the screen whe moving the right value. This yields quite small ranges for the fitness when moving the left value for small ratios like 1/10. @emily-phet @BLFiedler please review on master.
Some notes from another possibility:
// Going from 2/4 to 1/4 is very different than going from 2/4 to 1/3
const fitness = ( x1, x2, targetFraction ) => {
const actualFraction = x1 / x2;
// equal footing for being too high or too low compared to target???
const fitness = Math.min( actualFraction / targetFraction, targetFraction / actualFraction );
// Sharpen so it's not so diffuse for 1:10
return Math.pow( fitness, 3 );
}
We also discussed making a 3d plot of the function (for fixed targetFraction
) to better visualize the behavior over all ranges.
@zepumph I think it looks good to me. I think by the time someone is deeply exploring 1:10, they are most likely to be in a place where they are confirming their understanding, and likely will need less feedback.
@BLFiedler any additional thoughts?
It would be cool to see a 3D plot of the function though...
@zepumph I think it looks good to me. I think by the time someone is deeply exploring 1:10, they are most likely to be in a place where they are confirming their understanding, and likely will need less feedback.
Agreed. I mostly stuck to talking about 1:10 because it was the extreme case, but I think it makes the transition between 1:2 and 1:3 on screen 1 more cohesive as well. Thanks for putting in so much effort to tackle this @zepumph and @samreid!
Nothing more on this particular take on tolerance from me.
@zepumph, It's a little unclear from Sam's last comment if there was any more to do here. Assigning to you to close if not.
@samreid and I spent a bit more time plotting some of the functions for different fitness algorithms. We used Mathematica.
How to read these: the "flat" coordinates are the left/right values, and the height is the fitness, from 0 to 1. Note that all of these are plotted with the target ratio as a constant: 1/2. Please ignore the spikey ridge tops, this is just due to an incomplete sampling size.
Here is the what master looks like. This has been signed off on as design. Key notes here are how larger manifestations of the same ratio (i.e. 4/8) have the same slope on either side of the ridge as does smaller ones (i.e. 1/2):
Here is what https://github.com/phetsims/ratio-and-proportion/issues/91#issuecomment-662545967 looks like. First without the smoothing of taking the cube:
Notice how asymmetric it is for larger values vs higher. This is the behavior of how moving the right value when finding the target ratio of 1/10 can take up the entire value range. Next here is the same function with the "cubed smoothing" applied to it, still you have inconsistencies, but the slope on either side of the mountain ridge is steeper.
We finally discussed the downside of the current function (implemented on master in the first graphs). The issue is that the fitness range is not symmetric when altering each value. You can see this visually by looking at a given point on the ridge top, and counting the number of lines in an east/west direction to 0 fitness and comparing it to the north/south version. It is easiest to see in the second picture of this comment. When the target ratio is 1, the distance along axis are the same:
So for this function, it seemed dependent on the ratio, and we can't change the tolerance in a way that would alter this feedback.
From this exploration, we were not able to rule out the possibility of a perfect algorithm out there in the world, but we were not able to have any breakthroughs beyond visualizing the same work we have done earlier in this issue. With this in mind, coupled with the fact that master has already gotten the go-ahead, I think the best path forward is to call it a day, and begin to clean up what is currently in place.
Please speak your mind if you disagree! I'm happy to continue working on this, though I am under the impression that this is quickly sinking in my priority list.
Keeping open until clean up can occur.
One way to compare the slopes in each direction is to take derivatives in each dimension. For the function in master:
df/dleft = -toleranceFactor OR toleranceFactor/targetRatio (depending on which side takes the min)
df/dright = toleranceFactor*targetRatio OR -toleranceFactor (depending on which side takes the min)
This shows why putting targetRatio=1
yielded equal slopes in each direction.
If the design in master had not been approved, then we might use the idea of analytical derivatives to help design a function that had equal slopes in each dimension, but it's unclear if/how we could do that and also achieve the other constraints of the fitness function.
I'm not recommending any further work by this comment, just wanted to jot down the analytical derivatives of the solution in master to help with our understanding of the function, and in case we come back to it in the future.
Thanks @samreid, I agree with you. So far designers seem to like the current behavior. I'm going to close this issue, and we can reopen if there are problems found with the current approach.
From design discussion over in https://github.com/phetsims/ratio-and-proportion/issues/83, we don't like that for some custom ratios, you get too much range of feedback. For example with a ratio of 1/10, moving the right hand yields feedback for 100% of the range. Designers would most like feedback to range 40% of the screen. Perhaps normalizing on the numerator and denominator would make the tolerance not change so drastically based on the ratio.