This jupyter notebook contains examples of running the various functions available in archeryutils. Where different options exist for the examples they are listed. Users are encouraged to re-run various cells of this notebook with different options to explore the full functionality of the code.
To start off using archeryutils we need to import it. Assuming it has been installed into the local environment according to the repository documentation this can be done as follows:
import archeryutils as au
The basic building blocks of the archeryutils package are the Target
and the Round
classes.
A target is defined with the following attributes:
The following scoring systems are possible:
"5_zone"
,"10_zone"
,"10_zone_compound"
,"10_zone_6_ring"
,"10_zone_5_ring"
,"10_zone_5_ring_compound"
,"WA_field"
,"IFAA_field"
,"IFAA_field_expert"
,"Beiter_hit_miss"
,"Worcester"
,"Worcester_2_ring"
where an "_n_ring"
suffix indicates a reduced scoring area with only the central n rings, and the "_compound"
suffix indicates the system where only the x-ring scores 10 points.
So we could define the target shot on the WA 720 70m round as:
my720target = au.Target("10_zone", 122, 70.0)
and the corresponding target shot by compounds (reduced 6-ring 80cm face at 50m) as:
mycompound720target = au.Target("10_zone_6_ring", 80, 50.0)
The diameter and distance can alternatively be provided as a tuple, where the first element is the magnitude and the second is a string specifying the units of measurement. This provides the ability to specify an imperial distance in yards and face diameter in inches etc.
The target object can also take the optional boolean argument of indoor
(default = False
)
to indicate if the round is to be shot indoors (where rules may be different).
For example, a Worcester target can be defined as:
myWorcesterTarget = au.Target(
"Worcester", diameter=(16, "inches"), distance=(20.0, "yards"), indoor=True
)
and the longest target on an IFAA field round could be defined as follows:
myIFAATarget = au.Target("IFAA_field", diameter=80, distance=(80.0, "yards"))
Sometimes you might want to represent a target that isn't represented by the built in scoring systems.
In this case you can manually supply the target ring sizes and scores as a FaceSpec
, which a mapping (commonly a dict) of ring diameters to scores
and construct a target as so:
# Kings of archery recurve scoring target
face_spec = {8: 10, 12: 8, 16: 7, 20: 6}
myKingsTarget = au.Target.from_face_spec((face_spec, "cm"), 40, 18, indoor=True)
print(myKingsTarget.scoring_system)
Under the hood, all standard scoring systems autogenerate their own FaceSpec
and this is used internally when calculating handicaps and classifications. You can see this stored under the Target.face_spec
property:
print(my720target.face_spec)
Target objects have the ability to return the maximum possible score from the type of face specified through the max_score
method:
for target in [my720target, mycompound720target, myIFAATarget, myWorcesterTarget]:
print(target.max_score())
The natural extension to the targets.Target
class is to shoot a number of arrows at it.
in archeryutils this is called a "pass" and is defined using the rounds.Pass
class which wraps around the targets.Target
class.
This takes a number of arrows followed by all of the arguments to target defined above.
For example, to define the 36 arrow pass that forms the first distance on a WA 1440 70m round, or the first half of the WA 720 70m round we use:
my70mPass = au.Pass(36, my720target)
We can also bypass the Target class and directly construct our Pass using the at_target
constructor
my70mPass = au.Pass.at_target(36, "10_zone", 122, 70.0)
Like the targets.Target
class a rounds.Pass
also has a max_score()
method, but this now returns the maximum possible score for the pass (n_arrows * Target.max_score()
):
print(my70mPass.max_score())
In reality we rarely use the targets.Target
or rounds.Pass
objects by themselves, however, instead preferring to use the rounds.Round
class. This defines multiple passes to form what is commonly known as a round.
A rounds.Round
object is defined with a string name
to provide a popular name for the round and an iterable of rounds.Pass
objects, which will be stored as a list in the passes
attribute.
It may also take the following optional string arguments:
location
- where the round is shot, e.g. 'Indoor', 'Outdoor', 'Field' etc.body
- The governing body the round is defined by, e.g. 'WA', 'IFAA', 'AGB', 'AA' etc.family
- The larger family of rounds to which this round belongs, e.g. 'wa_1440', 'wa_720', 'national' etc.So to define a WA 720 70m round we can re-use our variable my70mPass
from above as follows:
my720Round = au.Round(
"WA 720 (70m)",
[my70mPass, my70mPass],
location="Outdoor Target",
body="WA",
family="WA720",
)
Again we have a method for maximum score:
print(my720Round.max_score())
A number of useful rounds are pre-defined and come preloaded as dictionaries that can be imported:
from archeryutils import load_rounds
agb_outdoor = load_rounds.AGB_outdoor_imperial
for round_i in agb_outdoor.values():
print(round_i.name)
The individial rounds are accessible via 'dot' notation (using the alias listed in agb_outdoor.keys()
) as follows:
agb_outdoor.york.get_info()
agb_outdoor.york.max_score()
Possible options for round collections are:
AGB_outdoor_imperial
- Archery GB outdoor imperial roundsAGB_outdoor_metric
- Archery GB outdoor metric roundsAGB_indoor
- Archery GB indoor roundsWA_outdoor
- World Archery outdoor roundsWA_indoor
- World Archery indoor roundsWA_field
- World Archery field roundsIFAA_field
- IFAA indoor and outdoor roundsAGB_VI
- Archery GB Visually Impaired roundsWA_VI
- World Archery Visually Impaired roundscustom
- custom rounds such as individual distances, 252 awards, frostbites etc.archeryutils provides functionality for calculating various handicaps/skill ratings from scores. These include both the popular Archery GB and Archery Australia schemes.
To use these functionalities import the handicaps module as below.
from archeryutils import handicaps as hc
It is then possible to use the score_for_round
function to calculate score on any rounds.Round
for a given handicap/skill rating.
This requires a round, handicap/skill rating, scheme, and set of handicap parameters.
Possible options for the scheme are:
"AGB"
- The 2023 Archery GB handicap system developed by Jack Atkinson"AGBold"
- The old Archery GB handicap system developed by David Lane"AA2"
- The 2014 Archery Australia Skill rating system developed by Jim Park"AA"
- The old Archery Australia skill rating system developed by Jim ParkFor example, to calculate the score on a York round for a handicap of 38 using the 2023 Archery GB scheme we run:
score_from_hc = hc.score_for_round(
38,
agb_outdoor.york,
"AGB",
)
print(f"A handicap of 38 on a York is a score of {score_from_hc}.")
Note that it is possible to obtain scores for decimal handicaps:
score_from_hc = hc.score_for_round(
38.25,
agb_outdoor.york,
"AGB",
)
print(f"A handicap of 38.25 on a York is a score of {score_from_hc}.")
By default this function returns a round score as would appear in handicap tables and is physically attainable when shooting a round. The rounding mechanism (round/floor/ceil) varies by scheme. However, it is possible to return the mathematically continuous score by setting the rounded_score
optional argument to be False
:
score_from_hc = hc.score_for_round(
38.25,
agb_outdoor.york,
"AGB",
rounded_score=False,
)
print(f"A handicap of 38.25 on a York is a decimal score of {score_from_hc}.")
It is also possible to get the predicted score for each pass in a round using the score_for_passes
function:
pass_scores = hc.score_for_passes(
38,
agb_outdoor.york,
"AGB",
)
print(f"A handicap of 38 on a York gives pass scores of {pass_scores}.")
Mathematically is is easy to define a score for a given handicap, but often the opposite is required, where one wishes to obtain the handicap given a score.
To perform this operation use the handicap_from_score()
function which takes a score, round, and handicap scheme.
By default it returns the decimal handicap corresponding to the provided score exactly.
However, it is possible to return the integer handicap value that the score would correspond to in a handicap table by setting the int_prec
optional argument to True
. Remember that the rounding mechanism (round/floor/ceil) varies by scheme.
For example, to get the 2023 Archery GB handicap given by a score of 950 on a York round:
hc_from_score = hc.handicap_from_score(
950,
agb_outdoor.york,
"AGB",
)
print(f"A score of 950 on a York is a continuous handicap of {hc_from_score}.")
hc_from_score = hc.handicap_from_score(
950,
agb_outdoor.york,
"AGB",
int_prec=True,
)
print(f"A score of 950 on a York is a discrete handicap of {hc_from_score}.")
A further functionality of the code is the ability to generate "Handicap Tables" for arbitrary lists of rounds and handicaps.
To do this use the HandicapTable
object which can be initialised with the name of a handicap scheme, an array of handicaps to display scores for, and a list of Round
s to display scores for.
The HandicapTable
object stores the table, but it can be printed to the output using print()
:
import numpy as np
handicaps = np.arange(0.0, 151.0, 1.0)
rounds = [
agb_outdoor.york,
agb_outdoor.hereford,
agb_outdoor.albion,
agb_outdoor.windsor,
]
# The following would allow handicap tables for an entire group of rounds to be generated:
# rounds = list(load_rounds.AGB_outdoor_imperial.values())
agb_handicap_table = hc.HandicapTable(
"AGB",
handicaps,
rounds,
)
print(agb_handicap_table)
The following optional arguments can be passed to HandicapTable
:
rounded_scores
- if False
, display decimal scores instead of rounding to discrete values as appropriate for the schemeclean_gaps
- if True
, duplicate scores will be displayed for the first occurrence onlyint_prec
- if True
then values will be printed as integers rather than decimalsIt is also possible to pass an array of non-integer handicaps.
The effect of these variables can be examined by changing their values in the following:
handicaps = np.arange(0.0, 51.0, 0.5)
agb_decimal_table = hc.HandicapTable(
"AGB",
handicaps,
rounds,
rounded_scores=False,
clean_gaps=False,
int_prec=False,
)
print(agb_decimal_table)
The HandicapTable
class also contains two methods for saving the table to file:
to_file(<filename>)
- which will save the nicely formatted table as seen from print()
as an ascii file with the provided filenameto_csv(<filename>)
- saves the table in a comma-separated-variable formatagb_handicap_table.to_file("my_saved_table.txt")
agb_handicap_table.to_csv("my_saved_csv_table.csv")
As well as handicap functionalities archeryutils fontains functionalities for calculating Archery GB classifications.
These are accessed by importing the classifications
module:
from archeryutils import classifications as class_func
To get a classification that results from a score use the calculate_X_classification()
function, where X
corresponds to the classification scheme being used (AGB_outdoor
, AGB_indoor
, AGB_field
).
This takes following arguments:
"compound"
, "recurve"
, "longbow"
, "barebow"
, "traditional"
, "flatbow"
)"male"
or "female"
)"50+"
, "adult"
, "under 21"
, "under 18"
, etc.)and returns a string corresponding to the classification it obtains.
These can be investigated in the following code snippet which uses a number of examples:
# AGB Outdoor
class_from_score = class_func.calculate_agb_outdoor_classification(
965,
"hereford",
"recurve",
"male",
"50+",
)
print(
f"A score of 965 on a Hereford is class {class_from_score} for a 50+ male recurve."
)
# AGB Indoor
class_from_score = class_func.calculate_agb_indoor_classification(
562,
"wa18",
"compound",
"female",
"adult",
)
print(
f"A score of 562 on a WA 18 is class {class_from_score} for adult female compound."
)
# AGB Field
class_from_score = class_func.calculate_agb_field_classification(
168,
"wa_field_24_blue_unmarked",
"traditional",
"male",
"under 18",
)
print(
f"A score of 168 on a WA Unmarked 24 is class {class_from_score} for an under 18 male traditional."
)
As well as generating a classification from a score there is also the inverse functionality of obtaining scores required for classifications. This can be done using the X_classification_scores()
functions.
These take a round alias and categories as strings above, and return a list of scores required for each classification in descending order.
Where a classification is not available from a particular round a fill value of -9999 is returned.
class_scores = class_func.agb_outdoor_classification_scores(
"hereford",
"recurve",
"male",
"adult",
)
print(class_scores)
class_scores = class_func.agb_indoor_classification_scores(
"portsmouth",
"compound",
"female",
"adult",
)
print(class_scores)
class_scores = class_func.agb_field_classification_scores(
"wa_field_24_blue_marked",
"flatbow",
"female",
"under 18",
)
print(class_scores)