Skip to content

Commit 1800488

Browse files
Dev -> Master (#204)
* Added RelativeDifferenceScore * fixup! Added RelativeDifferenceScore * make judging from features more convenient, this method expects uses to have pre-existing code that performs something like judge generate prediction. Prediction is then just treated as a settable object attribute * Refactored cached prediction logic for tests * Restored feature_judge for backwards compatibility * Drop support for Python 3.5 * Merged Co-authored-by: Russell Jarvis <[email protected]>
1 parent 5ed7579 commit 1800488

File tree

2 files changed

+92
-4
lines changed

2 files changed

+92
-4
lines changed

sciunit/scores/complete.py

Lines changed: 77 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
import numpy as np
1010
import quantities as pq
11-
11+
from typing import Union
1212
from sciunit import errors, utils
1313

1414
from .base import Score
@@ -241,8 +241,83 @@ def norm_score(self) -> float:
241241
return 1 - 2 * math.fabs(0.5 - cdf)
242242

243243
def __str__(self):
244-
return "Ratio = %.2f" % self.score
244+
return 'Ratio = %.2f' % self.score
245+
246+
247+
class RelativeDifferenceScore(Score):
248+
"""A relative difference between prediction and observation.
249+
250+
The absolute value of the difference between the prediction and the
251+
observation is divided by a reference value with the same units. This
252+
reference scale should be chosen for each test such that normalization
253+
produces directly comparable scores across tests. For example, if 5 volts
254+
represents a medium size difference for TestA, and 10 seconds represents a
255+
medium size difference for TestB, then 5 volts and 10 seconds should be
256+
used for this reference scale in TestA and TestB, respectively. The
257+
attribute `scale` can be passed to the compute method or set for the whole
258+
class in advance. Otherwise, a scale of 1 (in the units of the
259+
observation and prediction) will be used.
260+
"""
261+
262+
_allowed_types = (float,)
263+
264+
_description = ('The relative difference between the prediction and the observation')
265+
266+
_best = 0.0 # A RelativeDifferenceScore of 0.0 is best
267+
268+
_worst = np.inf
269+
270+
scale = None
271+
272+
def _check_score(self, score):
273+
if score < 0.0:
274+
raise errors.InvalidScoreError(("RelativeDifferenceScore was initialized with "
275+
"a score of %f, but a RelativeDifferenceScore "
276+
"must be non-negative.") % score)
245277

278+
@classmethod
279+
def compute(cls, observation: Union[dict, float, int, pq.Quantity],
280+
prediction: Union[dict, float, int, pq.Quantity],
281+
key=None,
282+
scale: Union[float, int, pq.Quantity, None] = None) -> 'RelativeDifferenceScore':
283+
"""Compute the relative difference between the observation and a prediction.
284+
285+
Returns:
286+
RelativeDifferenceScore: A relative difference between an observation and a prediction.
287+
"""
288+
assert isinstance(observation, (dict, float, int, pq.Quantity))
289+
assert isinstance(prediction, (dict, float, int, pq.Quantity))
290+
291+
obs, pred = cls.extract_means_or_values(observation, prediction,
292+
key=key)
293+
294+
scale = scale or cls.scale or (obs/float(obs))
295+
assert type(obs) is type(scale)
296+
assert type(obs) is type(pred)
297+
if isinstance(obs, pq.Quantity):
298+
assert obs.units == pred.units, \
299+
"Prediction must have the same units as the observation"
300+
assert obs.units == scale.units, \
301+
"RelativeDifferenceScore.Scale must have the same units as the observation"
302+
assert scale > 0, \
303+
"RelativeDifferenceScore.scale must be positive (not %g)" % scale
304+
value = np.abs(pred - obs) / scale
305+
value = utils.assert_dimensionless(value)
306+
return RelativeDifferenceScore(value)
307+
308+
@property
309+
def norm_score(self) -> float:
310+
"""Return 1.0 for a ratio of 0.0, falling to 0.0 for extremely large values.
311+
312+
Returns:
313+
float: The value of the norm score.
314+
"""
315+
x = self.score
316+
return 1 / (1+x)
317+
318+
def __str__(self):
319+
return 'Relative Difference = %.2f' % self.score
320+
246321

247322
class RelativeDifferenceScore(Score):
248323
"""A relative difference between prediction and observation.

sciunit/tests.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -420,8 +420,21 @@ def _judge(
420420

421421
# 6.
422422
self._bind_score(score, model, self.observation, prediction)
423-
423+
424424
return score
425+
426+
427+
def feature_judge(
428+
self,
429+
model: Model,
430+
skip_incapable: bool = False,
431+
stop_on_error: bool = True,
432+
deep_error: bool = False,
433+
) -> Score:
434+
"""For backwards compatibility"""
435+
return self.judge(model, skip_incapable=skip_incapable, stop_on_error=stop_on_error,
436+
deep_error=deep_error, cached_prediction=True)
437+
425438

426439
def feature_judge(
427440
self,
@@ -445,7 +458,7 @@ def judge(
445458
skip_incapable: bool = False,
446459
stop_on_error: bool = True,
447460
deep_error: bool = False,
448-
cached_prediction: bool = False,
461+
cached_prediction: bool = False
449462
) -> Score:
450463
"""Generate a score for the provided model (public method).
451464

0 commit comments

Comments
 (0)