Source code for smartcer.gym.metrics.fit_score

"""
Fit Score ([M23] eq. 11, docs/FORMULATION.md section 13) between daily welfare series.
"""

import numpy as np
from numpy.typing import ArrayLike
import pandas as pd


[docs] def daily_welfare(community: pd.DataFrame) -> pd.Series: """ Welfare per calendar day of a community table. Args: community: Community table with the columns `datetime` and `welfare`. Returns: pd.Series: Sum of W_t per day (€), indexed by date. Raises: ValueError: If a column is missing. """ missing = [c for c in ('datetime', 'welfare') if c not in community.columns] if missing: raise ValueError(f'community table: missing columns {missing}') days = pd.to_datetime(community['datetime']).dt.date return community['welfare'].groupby(days.to_numpy()).sum()
[docs] def fit_score(reference: ArrayLike, candidate: ArrayLike) -> float: """ Fit Score F = (1 - Σ_d (T2_d - T1_d)² / Σ_d (T1_d - mean(T1))²) · 100. 100 when the candidate equals the reference; 0 when it is as far from the reference as the reference's mean; negative when it is farther. Args: reference: Daily series T1 (e.g. the welfare of the MILP oracle). candidate: Daily series T2 of the same days (e.g. the welfare of a controller). Returns: float: F (percentage). Raises: ValueError: If the series are empty, of different lengths or not finite, or the reference is constant (F undefined). """ t1 = np.asarray(reference, dtype=np.float64) t2 = np.asarray(candidate, dtype=np.float64) if t1.ndim != 1 or t1.shape != t2.shape or t1.size == 0: raise ValueError(f'reference and candidate must be non-empty 1-D series of the same ' f'length, got shapes {t1.shape} and {t2.shape}') if not (np.all(np.isfinite(t1)) and np.all(np.isfinite(t2))): raise ValueError('reference and candidate must be finite') spread = float(np.sum((t1 - t1.mean()) ** 2)) if spread == 0.0: raise ValueError('the Fit Score is undefined for a constant reference series') return (1.0 - float(np.sum((t2 - t1) ** 2)) / spread) * 100.0