Skip to content

Commit

Permalink
chore: function to calculate an elo estimate
Browse files Browse the repository at this point in the history
  • Loading branch information
raklaptudirm committed Jan 6, 2025
1 parent 25f06dc commit 17c3245
Showing 1 changed file with 73 additions and 0 deletions.
73 changes: 73 additions & 0 deletions arbiter/src/eve/stats.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.

use statrs::function::erf::erf_inv;

/// f defines the Elo model by providing a relation between elo difference and
/// expected score based on a sigmoid scale. To be precise, f(elo_delta) = E[S].
///
Expand Down Expand Up @@ -71,6 +73,69 @@ impl Model {
}
}

/// Calculates the most probable elo value for the given [Score], alongside
/// an error delta which encodes the possible range for the given acceptable
/// rate of error (p).
pub fn elo(&self, x: Score, p: f64) -> (f64, f64) {
// Calculate the mean and the standard deviation. Assuming that the
// random variable follows a normal distribution, these two properties
// completely specify the particular distribution as N(mu, sigma^2).
let mu = self.mean(x);
let sigma = self.deviation(x, mu);

// Assuming the sample data is distributed in form of N(mu, sigma^2),
// in other words a normal distribution with mean mu and variance
// sigma^2, we can find two points (mu_min and mu_max) in the sample
// space such that the fraction of the probability distribution covered
// between them equals the complement of the desired error rate.
//
// Let the desired error rate be p. Therefore, the fraction of the
// probability distribution covered between mu_min and mu_max should be
// 1 - p. Since mu_min and mu_max are centered about mu, which is to say
// the fraction covered between mu_min and mu, and mu and mu_max is
// equal, which then should equal (1 - p)/2. Considering the case of
// mu_max, it should be situated before p/2 of the probability
// distribution, or after 1 - p/2 of it, which is easily calculated
// using the inverse of the cumulative probability distribution as
// phi_inv(1 - p/2).
//
// It can be shown that mu_min and mu_max can be represented in the form
// mu ± delta for some delta, due to the property phi(x) = 1 - phi(-x)
// of the standard cumulative distribution function. Therefore, instead
// of calculating two different mu_min and mu_max bounds, we calculate
// and return a single delta value. Below, the delta value is calculated
// as a simplified expression of mu_max - mu:
// mu_max - mu = phi_inv(1 - p/2 | mu, sigma) - mu
// = mu + sigma * phi_inv(1 - p/2) - mu = sigma * phi_inv(1 - p/2)
let delta = sigma * phi_inv(1.0 - p / 2.0);

(mu, delta)
}

/// Calculates the empirical mean of the random variable S from the given
/// sample data.
fn mean(&self, x: Score) -> f64 {
match *self {
Self::Pentanomial => 0.0,
Self::Traditional => 1.0 * x.w + 0.5 * x.d + 0.0 * x.l,
}
}

/// Calculates the deviation (square root of the variance) of the given
/// sample data from the given pivot point.
fn deviation(&self, x: Score, mu: f64) -> f64 {
match *self {
Self::Pentanomial => 0.0,
Self::Traditional => {
f64::sqrt(
0.0 + x.w * f64::powi(1.0 - mu, 2)
+ x.d * f64::powi(0.5 - mu, 2)
+ x.l * f64::powi(0.0 - mu, 2),
) / f64::sqrt(x.n * 2.0)
}
}
}

/// llh calculates the log-likelihood of the given sample `x` arising from
/// the probability distribution specified by the parameter `theta`.
///
Expand Down Expand Up @@ -227,6 +292,14 @@ impl Score {
}
}

/// phi_inv is the inverse of the cumulative distribution of the standard
/// normal distribution.
///
/// It's value is calculated using its relation with the the standard erf_inv.
fn phi_inv(x: f64) -> f64 {
f64::sqrt(2.0) * erf_inv(2.0 * x - 1.0)
}

// fn clamp_elo(x: f64) -> f64 {
// if x <= 0.0 || x >= 1.0 {
// 0.0
Expand Down

0 comments on commit 17c3245

Please sign in to comment.