"""Code for handicap calculations using Archery Australia handicap schemes.
Extended Summary
----------------
Code to calculate information using a number of schemes:
- Original Archery Australia (J Park)
- Updated Archery Australia (J Park)
Routine Listings
----------------
- HandicapAA Class
- HandicapAA2 Class
References
----------
- Modelling archers’ scores at different distances to quantify
score loss due to equipment selection and technique errors
Park, J (2014)
https://doi.org/10.1177%2F1754337114539308
"""
import numpy as np
import numpy.typing as npt
from .handicap_scheme import HandicapScheme, _cast_float_array
[docs]
class HandicapAA(HandicapScheme):
"""
Class to represent the original Archery Australia scheme by J. Park.
Parameters
----------
ang_0 : float
datum angle used in scheme, default = 1.0e-3,
k0 : float
handicap offset to set scratch point, default= 2.37,
ks : float
change with each handicap step as a geometric progression, default= 0.027,
kd : float
distance scaling factor, default= 0.004,
Attributes
----------
name : str="AA"
The name of the handicap scheme.
arw_d_out : float
diameter of an outdoor arrow: 5.0e-3 [metres]
arw_d_in: float
diameter of an indoor arrow 9.3e-3 [metres]
Warnings
--------
Using non-default values for the kwargs may produce results that
do not match the official values for this scheme.
Only change if you are experimenting and know what you are doing!
See Also
--------
HandicapScheme : The base class for a handicap scheme from which this is subclassed
containing details of additional methods.
"""
def __init__(
self,
ang_0: float = 1.0e-3,
k0: float = 2.37,
ks: float = 0.027,
kd: float = 0.004,
):
super().__init__()
self.name: str = "AA"
self.arw_d_out: float = 5.0e-3
self.arw_d_in: float = 9.3e-3
# AA Uses an ascending scale with round. All numbers typically in [-250, 175]
self.desc_scale: bool = False
self.scale_bounds: npt.NDArray[np.float64] = np.array([-250, 175])
self.max_score_rounding_lim: float = 0.5
self.params = {
"ang_0": ang_0, # Baseline angle used for group size 1.0 [millirad].
"k0": k0, # Offset required to set handicap 100 at desired score.
"ks": ks, # Change with each step of geometric progression.
"kd": kd, # Distance scaling factor [1/metres].
}
[docs]
def sigma_t(self, handicap: npt.ArrayLike, dist: float) -> npt.NDArray[np.float64]:
"""Calculate angular deviation for given handicap and distance.
Parameters
----------
handicap : ArrayLike
handicap(s) to calculate sigma_t at
dist : float
distance to target [metres]
Returns
-------
sig_t : NDArray[np.float64]
angular deviation [rad]
Notes
-----
This is the key part of this scheme.
The values are taken from Park (2014) [2]_.
References
----------
.. [2] Park, J. L. (2014). "Modelling archers’ scores at different distances
to quantify score loss due to equipment selection and technique errors."
Proceedings of the Institution of Mechanical Engineers,
Part P: Journal of Sports Engineering and Technology, 228(4), 250-258.
DOI: 10.1177/175433711453930
Examples
--------
Angular deviation at a distance of 25m, using the AGBold handicap system at a
handicap of 10 can be calculated with:
>>> import archeryutils.handicaps as hc
>>> aa_scheme = hc.handicap_scheme("AA")
>>> aa_scheme.sigma_t(10.0, 25.0)
0.012763296491500004
It can also be passed an array of handicaps:
>>> import numpy as np
>>> aa_scheme.sigma_t(np.array([10.0, 50.0, 100.0]), 25.0)
array([0.0127633 , 0.00433436, 0.00112364])
"""
handicap = _cast_float_array(handicap) # Cast for calculation
# Factor of sqrt(2) to deal with factor of 2 in differing definitions of sigma
# between AGB and AA
# Required so code elsewhere is unchanged
# Factor of 1.0e-3 due to AA algorithm specifying sigma t in milliradians, so
# convert to rad
return (
np.sqrt(2.0)
* self.params["ang_0"]
* np.exp(
self.params["k0"]
- self.params["ks"] * handicap
+ self.params["kd"] * dist,
)
)
[docs]
class HandicapAA2(HandicapScheme):
"""
Class to represent the updated (2014) Archery Australia scheme by J. Park.
Parameters
----------
ang_0 : float
datum angle used in scheme, default = 1.0e-3,
k0 : float
handicap offset to set scratch point, default= 2.57,
ks : float
change with each handicap step as a geometric progression, default= 0.027,
f1 : float
'linear' scaling factor, default= 0.815,
f2 : float
'quadratic' scaling factor, default= 0.185,
d0 : float
normalisation distance, default = 50.0,
Attributes
----------
name : str="AA2"
The name of the handicap scheme.
arw_d_out : float
diameter of an outdoor arrow: 5.0e-3 [metres]
arw_d_in: float
diameter of an indoor arrow 9.3e-3 [metres]
Warnings
--------
Using non-default values for the kwargs may produce results that
do not match the official values for this scheme.
Only change if you are experimenting and know what you are doing!
See Also
--------
HandicapScheme : The base class for a handicap scheme from which this is subclassed
containing details of additional methods.
"""
def __init__(
self,
ang_0: float = 1.0e-3,
k0: float = 2.57,
ks: float = 0.027,
f1: float = 0.815,
f2: float = 0.185,
d0: float = 50.0,
):
super().__init__()
self.name: str = "AA2"
self.arw_d_out: float = 5.0e-3
self.arw_d_in: float = 9.3e-3
# AA Uses an ascending scale with round. All numbers typically in [-250, 175]
self.desc_scale: bool = False
self.scale_bounds: npt.NDArray[np.float64] = np.array([-250, 175])
self.max_score_rounding_lim: float = 0.5
self.params = {
"ang_0": ang_0,
"k0": k0, # Offset required to set handicap 100 at desired score.
"ks": ks, # Change with each step of geometric progression.
"f1": f1, # 'Linear' scaling factor.
"f2": f2, # 'Quadratic' scaling factor.
"d0": d0, # Normalisation distance [metres].
}
[docs]
def sigma_t(self, handicap: npt.ArrayLike, dist: float) -> npt.NDArray[np.float64]:
"""Calculate angular deviation for given handicap and distance.
Parameters
----------
handicap : ArrayLike
handicap(s) to calculate sigma_t at
dist : float
distance to target [metres]
Returns
-------
sig_t : NDArray[np.float64]
angular deviation [rad]
Notes
-----
This is the key part of this scheme.
The values are taken from Park (2014) [3]_.
References
----------
.. [3] Park, J. L. (2014). "Modelling archers’ scores at different distances
to quantify score loss due to equipment selection and technique errors."
Proceedings of the Institution of Mechanical Engineers,
Part P: Journal of Sports Engineering and Technology, 228(4), 250-258.
DOI: 10.1177/175433711453930
Examples
--------
Angular deviation at a distance of 25m, using the AGBold handicap system at a
handicap of 10 can be calculated with:
>>> import archeryutils.handicaps as hc
>>> aa2_scheme = hc.handicap_scheme("AA2")
>>> aa2_scheme.sigma_t(10.0, 25.0)
0.012800853871823342
It can also be passed an array of handicaps:
>>> import numpy as np
>>> aa2_scheme.sigma_t(np.array([10.0, 50.0, 100.0]), 25.0)
array([0.01280085, 0.00434711, 0.00112695])
"""
handicap = _cast_float_array(handicap) # Cast for calculation
# Factor of sqrt(2) to deal with factor of 2 in differing definitions of sigma
# between AGB and AA
# Required so code elsewhere is unchanged
# Factor of 1.0e-3 due to AA algorithm specifying sigma t in milliradians, so
# convert to rad
return (
np.sqrt(2.0)
* self.params["ang_0"]
* np.exp(self.params["k0"] - self.params["ks"] * handicap)
* (self.params["f1"] + self.params["f2"] * dist / self.params["d0"])
)