mirror of
https://github.com/ppy/osu.git
synced 2026-06-03 03:20:16 +08:00
Refactor and re-comment osu! standard deviation calculations (#33218)
* Refactor * Fix typo * Prevent double.PositiveInfinity from occuring * Fix leftover code branch * Fix some idiot putting Math.Max instead of Math.Min * Address NaN values --------- Co-authored-by: James Wilson <tsunyoku@gmail.com>
This commit is contained in:
committed by
GitHub
Unverified
parent
6ae8a68389
commit
c4b07413b1
@@ -5,7 +5,6 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Difficulty.Utils;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
@@ -398,7 +397,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
double relevantCountOk = Math.Min(countOk, speedNoteCount - relevantCountMiss - relevantCountMeh);
|
||||
double relevantCountGreat = Math.Max(0, speedNoteCount - relevantCountMiss - relevantCountMeh - relevantCountOk);
|
||||
|
||||
return calculateDeviation(attributes, relevantCountGreat, relevantCountOk, relevantCountMeh, relevantCountMiss);
|
||||
return calculateDeviation(relevantCountGreat, relevantCountOk, relevantCountMeh);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -407,45 +406,45 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
/// will always return the same deviation. Misses are ignored because they are usually due to misaiming.
|
||||
/// Greats and oks are assumed to follow a normal distribution, whereas mehs are assumed to follow a uniform distribution.
|
||||
/// </summary>
|
||||
private double? calculateDeviation(OsuDifficultyAttributes attributes, double relevantCountGreat, double relevantCountOk, double relevantCountMeh, double relevantCountMiss)
|
||||
private double? calculateDeviation(double relevantCountGreat, double relevantCountOk, double relevantCountMeh)
|
||||
{
|
||||
if (relevantCountGreat + relevantCountOk + relevantCountMeh <= 0)
|
||||
return null;
|
||||
|
||||
double objectCount = relevantCountGreat + relevantCountOk + relevantCountMeh + relevantCountMiss;
|
||||
|
||||
// The probability that a player hits a circle is unknown, but we can estimate it to be
|
||||
// the number of greats on circles divided by the number of circles, and then add one
|
||||
// to the number of circles as a bias correction.
|
||||
double n = Math.Max(1, objectCount - relevantCountMiss - relevantCountMeh);
|
||||
const double z = 2.32634787404; // 99% critical value for the normal distribution (one-tailed).
|
||||
|
||||
// Proportion of greats hit on circles, ignoring misses and 50s.
|
||||
// The sample proportion of successful hits.
|
||||
double n = Math.Max(1, relevantCountGreat + relevantCountOk);
|
||||
double p = relevantCountGreat / n;
|
||||
|
||||
// We can be 99% confident that p is at least this value.
|
||||
double pLowerBound = (n * p + z * z / 2) / (n + z * z) - z / (n + z * z) * Math.Sqrt(n * p * (1 - p) + z * z / 4);
|
||||
// 99% critical value for the normal distribution (one-tailed).
|
||||
const double z = 2.32634787404;
|
||||
|
||||
// Compute the deviation assuming greats and oks are normally distributed, and mehs are uniformly distributed.
|
||||
// Begin with greats and oks first. Ignoring mehs, we can be 99% confident that the deviation is not higher than:
|
||||
double deviation = greatHitWindow / (Math.Sqrt(2) * DifficultyCalculationUtils.ErfInv(pLowerBound));
|
||||
// We can be 99% confident that the population proportion is at least this value.
|
||||
double pLowerBound = Math.Min(p, (n * p + z * z / 2) / (n + z * z) - z / (n + z * z) * Math.Sqrt(n * p * (1 - p) + z * z / 4));
|
||||
|
||||
double randomValue = Math.Sqrt(2 / Math.PI) * okHitWindow * Math.Exp(-0.5 * Math.Pow(okHitWindow / deviation, 2))
|
||||
/ (deviation * DifficultyCalculationUtils.Erf(okHitWindow / (Math.Sqrt(2) * deviation)));
|
||||
double deviation;
|
||||
|
||||
deviation *= Math.Sqrt(1 - randomValue);
|
||||
// Tested max precision for the deviation calculation.
|
||||
if (pLowerBound > 1e-06)
|
||||
{
|
||||
// Compute deviation assuming greats and oks are normally distributed.
|
||||
deviation = greatHitWindow / (Math.Sqrt(2) * DifficultyCalculationUtils.ErfInv(pLowerBound));
|
||||
|
||||
// Value deviation approach as greatCount approaches 0
|
||||
double limitValue = okHitWindow / Math.Sqrt(3);
|
||||
// Subtract the deviation provided by tails that land outside the ok hit window from the deviation computed above.
|
||||
// This is equivalent to calculating the deviation of a normal distribution truncated at +-okHitWindow.
|
||||
double okHitWindowTailAmount = Math.Sqrt(2 / Math.PI) * okHitWindow * Math.Exp(-0.5 * Math.Pow(okHitWindow / deviation, 2))
|
||||
/ (deviation * DifficultyCalculationUtils.Erf(okHitWindow / (Math.Sqrt(2) * deviation)));
|
||||
|
||||
// If precision is not enough to compute true deviation - use limit value
|
||||
if (Precision.AlmostEquals(pLowerBound, 0.0) || randomValue >= 1 || deviation > limitValue)
|
||||
deviation = limitValue;
|
||||
deviation *= Math.Sqrt(1 - okHitWindowTailAmount);
|
||||
}
|
||||
else
|
||||
{
|
||||
// A tested limit value for the case of a score only containing oks.
|
||||
deviation = okHitWindow / Math.Sqrt(3);
|
||||
}
|
||||
|
||||
// Then compute the variance for mehs.
|
||||
// Compute and add the variance for mehs, assuming that they are uniformly distributed.
|
||||
double mehVariance = (mehHitWindow * mehHitWindow + okHitWindow * mehHitWindow + okHitWindow * okHitWindow) / 3;
|
||||
|
||||
// Find the total deviation.
|
||||
deviation = Math.Sqrt(((relevantCountGreat + relevantCountOk) * Math.Pow(deviation, 2) + relevantCountMeh * mehVariance) / (relevantCountGreat + relevantCountOk + relevantCountMeh));
|
||||
|
||||
return deviation;
|
||||
|
||||
Reference in New Issue
Block a user