11"""A module for Figure of Merits"""
22
33from abc import ABC , abstractmethod
4+ from enum import Enum , auto
45
56import numpy as np
67
78from MDMC .common .decorators import repr_decorator
89from MDMC .trajectory_analysis .observables .obs import Observable
910
1011
12+ class AutoScale (Enum ):
13+ """
14+ Autoscaling methods.
15+
16+ Notes
17+ -----
18+
19+ - ``CONSTANT`` applies a constant scaling factor to the MD data
20+ to align with the epxerimental data.
21+
22+ - ``MINIMISE_FOM`` uses the algorithm described in
23+ :ref:`explanation/figure-of-merit:rescaling` to best minimise
24+ the figure of merit.
25+
26+ - ``MATCH_MAXIMUM`` rescales the MD data such that the maximum is
27+ the same as that of the experimental data.
28+
29+ - ``MATCH_ABS_MAXIMUM`` rescales the MD data such that the absolute maximum is
30+ the same as that of the experimental data.
31+
32+ - ``MATCH_SUM`` is a naïve approximation to rescale the MD data such that
33+ the integral area under the curve is the same under the assumumption
34+ that the samples are evenly spaced.
35+
36+ - ``MATCH_ABS_SUM`` is like ``MATCH_SUM`` except that the data's
37+ absolute values are used instead.
38+ """
39+ #: Constant valued scaling.
40+ CONSTANT = auto ()
41+ #: Minimise the main FoM factor e.g. χ²
42+ MINIMISE_FOM = auto ()
43+ #: Match the maxima of the values.
44+ MATCH_MAXIMUM = auto ()
45+ #: Match the absolute maxima of the values.
46+ MATCH_ABS_MAXIMUM = auto ()
47+ #: Match the integral area (approximated by a sum) of the values.
48+ MATCH_SUM = auto ()
49+ #: Match the absolute integral area (approximated by an abs->sum) of the values.
50+ MATCH_ABS_SUM = auto ()
51+
52+ #: No scaling to be applied (CONSTANT w/ 1.).
53+ NONE = CONSTANT
54+ MINIMIZE_FOM = MINIMISE_FOM
55+
56+
1157@repr_decorator ('weight' , 'exp_obs' , 'MD_obs' , 'rescale_factor' , 'auto_scale' )
1258class ObservablePair :
1359
@@ -35,7 +81,7 @@ class ObservablePair:
3581 """
3682
3783 def __init__ (self , exp_obs : Observable , MD_obs : Observable , weight : float ,
38- rescale_factor : float = 1. , auto_scale : bool = False ):
84+ rescale_factor : float = 1. , auto_scale : AutoScale = AutoScale . NONE ):
3985
4086 self .exp_obs = exp_obs
4187 self .MD_obs = MD_obs
@@ -326,7 +372,6 @@ def calculate_exp_errors(self) -> np.ndarray:
326372
327373 return np .array (* self .exp_obs .errors .values ()) * self .rescale_factor
328374
329-
330375@repr_decorator ('value' , 'obs_pairs' )
331376class FigureOfMerit (ABC ):
332377
@@ -421,8 +466,7 @@ def data_norm_factor(self, obs_pair: ObservablePair) -> int:
421466 """
422467
423468 if self .norm :
424- norm_factor = np .size (
425- * obs_pair .MD_obs .dependent_variables .values ())
469+ norm_factor = np .size (* obs_pair .MD_obs .dependent_variables .values ())
426470 norm_factor -= self .n_parameters
427471 else :
428472 norm_factor = 1
@@ -446,3 +490,69 @@ def calculate_single_FoM(self, obs_pair: ObservablePair) -> float:
446490 """
447491
448492 raise NotImplementedError
493+
494+ @abstractmethod
495+ def _compute_unreduced (self , obs_pair : ObservablePair ) -> float :
496+ """
497+ Compute the unreduced FoM value for the given observable pair.
498+
499+ Parameters
500+ ----------
501+ obs_pair : ObservablePair
502+ An ``ObservablePair`` for which the FoM is calculated.
503+
504+ Returns
505+ -------
506+ float
507+ Unreduced FoM value.
508+ """
509+
510+ @abstractmethod
511+ def _minimise_factor (self , obs_pair : ObservablePair ) -> float :
512+ """
513+ Minimise the FoM factor for the given FoM type.
514+
515+ Parameters
516+ ----------
517+ obs_pair : ObservablePair
518+ An ``ObservablePair`` for which the FoM is calculated
519+
520+ Returns
521+ -------
522+ float
523+ Computed auto_scale factor to minimise the FoM.
524+ """
525+
526+ def compute_rescale_factor (self , obs_pair : ObservablePair ) -> float :
527+ """
528+ Compute rescale factor for calculated observable to match experimental data.
529+
530+ Parameters
531+ ----------
532+ obs_pair : ObservablePair
533+ An ``ObservablePair`` for which the FoM is calculated
534+
535+ Returns
536+ -------
537+ float
538+ Computed rescale factor.
539+ """
540+ dep_vars = np .array (* obs_pair .exp_obs .dependent_variables .values ())
541+
542+ match obs_pair .auto_scale :
543+ case AutoScale .CONSTANT :
544+ fac = obs_pair .rescale_factor
545+ case AutoScale .MINIMISE_FOM :
546+ fac = self ._minimise_factor (obs_pair )
547+ case AutoScale .MATCH_MAXIMUM :
548+ fac = max (obs .max () for obs in dep_vars )
549+ case AutoScale .MATCH_ABS_MAXIMUM :
550+ fac = max (np .abs (obs ).max () for obs in dep_vars )
551+ case AutoScale .MATCH_SUM :
552+ fac = sum (obs .sum () for obs in dep_vars )
553+ case AutoScale .MATCH_ABS_SUM :
554+ fac = sum (np .abs (obs .sum ()) for obs in dep_vars )
555+ case _:
556+ fac = 1.
557+
558+ return fac
0 commit comments