Quick-Start Guide
This page contains a few quick examples of how you can use archeryutils. For full details and functionalities you should consult the rest of the documentation.
Examples Notebook
The source repository contains a notebook of examples exercises.ipynb which is very similar to the examples on this page. This is the perfect place to learn the basic functionalities of archeryutils and experiment yourself with what the code can do. We advise starting by running this.
To do so online you can access the Binder instance we have set up. This will spin up an interactive online session in which you can run the code.
Alternatively you can run it locally in your browser by downloading the notebook and running:
pip install jupyter
jupyter notebook examples.ipynb
Importing
To import the archeryutils package use:
In [1]: import archeryutils as au
Target
The most basic building block of archeryutils is the archeryutils.Target
class which represents a target with a particular target face at a certain distance.
It can be invoked by specifying a scoring system, face size (cm) and distance (m):
In [2]: my720target = au.Target("10_zone", 122, 70.0)
In [3]: mycompound720target = au.Target("10_zone_6_ring", 80, 50.0)
In more complicated cases specific units can be passed in with the diameter and distance
as a plain tuple, or (recommeneded for clarity) as a Quantity:
In [4]: myWorcesterTarget = au.Target(
...: "Worcester", diameter=(16, "inches"), distance=(20.0, "yards"), indoor=True
...: )
...:
In [5]: myIFAATarget = au.Target(
...: "IFAA_field", diameter=80, distance=au.Quantity(80.0, "yards")
...: )
...:
If the target you want is not supported, you can manually supply the target ring sizes and scores as a FaceSpec and construct a target as so.
# Kings of archery recurve scoring target
In [6]: face_spec = {8: 10, 12: 8, 16: 7, 20: 6}
In [7]: myKingsTarget = au.Target.from_face_spec((face_spec, "cm"), 40, 18, indoor=True)
In [8]: print(myKingsTarget.scoring_system)
Custom
Note
Although we can provide the face_spec in any unit listed under _supported_diameter_units,
the sizes in the specification are converted to metres and stored in this form.
Therefore unlike the target diameter paramater, the default unit for specifications is [metres]
The only limitations to the target faces you can represent in this way are:
Targets must be formed of concentric rings
The score must monotonically decrease as the rings get larger
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
face_spec property:
In [9]: print(my720target.face_spec)
{0.122: 10, 0.244: 9, 0.366: 8, 0.488: 7, 0.61: 6, 0.732: 5, 0.854: 4, 0.976: 3, 1.098: 2, 1.22: 1}
The target features max_score() and min_score() methods:
In [10]: for target in [
....: my720target,
....: mycompound720target,
....: myIFAATarget,
....: myWorcesterTarget,
....: myKingsTarget,
....: ]:
....: print(
....: f"{target.scoring_system} has max score {target.max_score()} ",
....: f"and min score {target.min_score()}.",
....: )
....:
10_zone has max score 10 and min score 1.
10_zone_6_ring has max score 10 and min score 5.
IFAA_field has max score 5 and min score 3.
Worcester has max score 5 and min score 1.
Custom has max score 10 and min score 6.
Pass
The next unit up is the archeryutils.Pass - a number of arrows shot at
a target:
In [11]: my70mPass = au.Pass(36, my720target)
In [12]: print(my70mPass.max_score())
360
We can also bypass the Target class and directly construct our Pass using the at_target constructor
In [13]: my70mPass = au.Pass.at_target(36, "10_zone", 122, 70.0)
Round
Finally we have the archeryutils.Round class made up of a number of Passes.
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. ‘WA1440’, ‘WA720’, ‘Nationals’ etc.
In [14]: my720Round = au.Round(
....: "WA 720 (70m)",
....: [my70mPass, my70mPass],
....: location="Outdoor Target",
....: body="WA",
....: family="WA720",
....: )
....:
Default Rounds
A number of useful rounds are pre-defined and come preloaded as dictionaries that can be imported:
In [15]: from archeryutils import load_rounds
In [16]: agb_outdoor = load_rounds.AGB_outdoor_imperial
In [17]: for round_i in agb_outdoor.values():
....: print(round_i.name)
....:
York
Hereford
Bristol I
Bristol II
Bristol III
Bristol IV
Bristol V
St. George
Albion
Windsor
Windsor 50
Windsor 40
Windsor 30
New Western
Long Western
Western
Western 50
Western 40
Western 30
American
St Nicholas
New National
Long National
National
National 50
National 40
National 30
New Warwick
Long Warwick
Warwick
Warwick 50
Warwick 40
Warwick 30
Individial rounds are accessible via ‘dot’ notation (using the alias listed in agb_outdoor.keys()) as follows:
In [18]: agb_outdoor.york.get_info()
A York consists of 3 passes:
- 72 arrows at a 122.0 cm target at 100.0 yards.
- 48 arrows at a 122.0 cm target at 80.0 yards.
- 24 arrows at a 122.0 cm target at 60.0 yards.
In [19]: agb_outdoor.york.max_score()
Out[19]: 1296
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 roundsWA_experimental- Experimental World Archery 660 rounds used at Antalya WC 2025IFAA_field- IFAA indoor and outdoor roundsAA_outdoor_metric- Archery Australia outdoor metric roundsAA_indoor- Archery Australia indoor roundsAA_field- Archery Australia field roundsAGB_VI- Archery GB Visually Impaired roundsWA_VI- World Archery Visually Impaired roundsmisc- Miscellaneous rounds such as individual distances, 252 awards, frostbites etc.
Handicap Schemes
archeryutils features support for calculations using a number of different handicap schemes for accuracy measurement, including those of Archery GB (Atkinson (2023), Lane (1978)) and Archery Australia (Park (2014)).
In [20]: from archeryutils import handicaps as hc
Given a handicap and a round we can calculate the score that would be achieved:
In [21]: score_from_hc = hc.score_for_round(
....: 38,
....: agb_outdoor.york,
....: "AGB",
....: )
....:
In [22]: print(f"A handicap of 38 on a York is a score of {score_from_hc}.")
A handicap of 38 on a York is a score of 940.0.
In [23]: pass_scores = hc.score_for_passes(
....: 38,
....: agb_outdoor.york,
....: "AGB",
....: )
....:
In [24]: print(f"A handicap of 38 on a York gives pass scores of {pass_scores}.")
A handicap of 38 on a York gives pass scores of [421. 331. 188.].
Perhaps more interestingly we can take a score on a particular round and convert it to a handicap:
In [25]: hc_from_score = hc.handicap_from_score(
....: 950,
....: agb_outdoor.york,
....: "AGB",
....: )
....:
In [26]: print(f"A score of 950 on a York is a continuous handicap of {hc_from_score}.")
A score of 950 on a York is a continuous handicap of 37.374134403932665.
In [27]: hc_from_score = hc.handicap_from_score(
....: 950,
....: agb_outdoor.york,
....: "AGB",
....: int_prec=True,
....: )
....:
In [28]: print(f"A score of 950 on a York is a discrete handicap of {hc_from_score}.")
A score of 950 on a York is a discrete handicap of 38.0.
There is also the HandicapTable class for generating handicap tables:
In [29]: handicaps = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
In [30]: rounds = [
....: agb_outdoor.york,
....: agb_outdoor.hereford,
....: agb_outdoor.st_george,
....: agb_outdoor.albion,
....: ]
....:
# The following would allow printing of handicap tables for an entire group of rounds:
# rounds = list(load_rounds.AGB_outdoor_imperial.values())
In [31]: my_agb_table = hc.HandicapTable(
....: "AGB",
....: handicaps,
....: rounds,
....: )
....:
In [32]: my_agb_table.print()
Handicap York Hereford St. George Albion
0 1284 1295 966 972
1 1281 965
2 1279 1294 963
3 1276 1293 961 971
4 1272 1292 959 970
5 1268 1290 957
6 1264 1289 955 969
7 1260 1287 953 968
8 1255 1285 950 967
9 1250 1283 947 966
Classifications
Finally there is support for the various Archery GB classification schemes
For full details see the summary on archerycalculator.com, the Archery GB website here and here, and the Shooting Administrative Procedures.
The classification routines make use of built in types to specify the age, bowstyle, and gender for the category for which data is being collected. This allows the code to be robust, less prone to unexpected errors and misuse, and to provide useful feedback when given invalid options.
The options available are as follows:
In [33]: from archeryutils.classifications import AGB_ages, AGB_bowstyles, AGB_genders
In [34]: for age in AGB_ages:
....: print(age.name)
....:
OVER_50
ADULT
UNDER_21
UNDER_18
UNDER_16
UNDER_15
UNDER_14
UNDER_12
In [35]: for bowstyle in AGB_bowstyles:
....: print(bowstyle.name)
....:
COMPOUND
RECURVE
BAREBOW
LONGBOW
TRADITIONAL
FLATBOW
COMPOUNDLIMITED
COMPOUNDBAREBOW
In [36]: for gender in AGB_genders:
....: print(gender.name)
....:
OPEN
FEMALE
They can be specified as the input to a function using dot notation as the following examples illustrate:
Given a score we can calculate the classification it achieves:
In [37]: from archeryutils import classifications as cf
In [38]: from archeryutils.classifications import AGB_ages, AGB_bowstyles, AGB_genders
In [39]: from archeryutils import load_rounds
# AGB Outdoor
In [40]: agb_outdoor_imperial = load_rounds.AGB_outdoor_imperial
In [41]: class_from_score = cf.calculate_agb_outdoor_classification(
....: 965,
....: agb_outdoor_imperial.hereford,
....: AGB_bowstyles.RECURVE,
....: AGB_genders.OPEN,
....: AGB_ages.OVER_50,
....: )
....:
In [42]: print(
....: f"A score of 965 on a Hereford is class {class_from_score} for a 50+ open recurve."
....: )
....:
A score of 965 on a Hereford is class B2 for a 50+ open recurve.
# AGB Indoor
In [43]: wa_indoor = load_rounds.WA_indoor
In [44]: class_from_score = cf.calculate_agb_indoor_classification(
....: 562,
....: wa_indoor.wa18,
....: AGB_bowstyles.COMPOUND,
....: AGB_genders.FEMALE,
....: AGB_ages.ADULT,
....: )
....:
In [45]: print(
....: f"A score of 562 on a WA 18 is class {class_from_score} for adult female compound."
....: )
....:
A score of 562 on a WA 18 is class I-B1 for adult female compound.
# AGB Field
In [46]: wa_field = load_rounds.WA_field
In [47]: class_from_score = cf.calculate_agb_field_classification(
....: 168,
....: wa_field.wa_field_24_blue_unmarked,
....: AGB_bowstyles.TRADITIONAL,
....: AGB_genders.FEMALE,
....: AGB_ages.ADULT,
....: )
....:
In [48]: print(
....: f"A score of 168 on a WA Unmarked 24 is class {class_from_score} for an under adult female traditional."
....: )
....:
A score of 168 on a WA Unmarked 24 is class B2 for an under adult female traditional.
Or, given a round we can output the scores required for each classification band:
In [49]: agb_outdoor_imperial = load_rounds.AGB_outdoor_imperial
In [50]: class_scores = cf.agb_outdoor_classification_scores(
....: agb_outdoor_imperial.hereford,
....: AGB_bowstyles.RECURVE,
....: AGB_genders.OPEN,
....: AGB_ages.ADULT,
....: )
....:
In [51]: print(class_scores)
[-9999, -9999, -9999, -9999, 985, 863, 721, 571, 427]
In [52]: agb_indoor = load_rounds.AGB_indoor
In [53]: class_scores = cf.agb_indoor_classification_scores(
....: agb_indoor.portsmouth,
....: AGB_bowstyles.COMPOUND,
....: AGB_genders.FEMALE,
....: AGB_ages.ADULT,
....: )
....:
In [54]: print(class_scores)
[589, 577, 566, 555, 541, 521, 491, 449]
In [55]: wa_field = load_rounds.WA_field
In [56]: class_scores = cf.agb_field_classification_scores(
....: wa_field.wa_field_24_blue_unmarked,
....: AGB_bowstyles.FLATBOW,
....: AGB_genders.FEMALE,
....: AGB_ages.UNDER_18,
....: )
....:
In [57]: print(class_scores)
[164, 132, 104, 80, 60, 44, 32, 23, 16]