{ "cells": [ { "cell_type": "markdown", "id": "2c6b63a3", "metadata": {}, "source": [ "# Examples\n", "\n", "This jupyter notebook contains examples of running the various functions available in _archeryutils_.\n", "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.\n", "\n", "## 0. Getting set up\n", "\n", "To start off using archeryutils we need to import it.\n", "Assuming it has been installed into the local environment according to the repository documentation this can be done as follows:" ] }, { "cell_type": "code", "execution_count": null, "id": "785be58b", "metadata": { "scrolled": true }, "outputs": [], "source": [ "import archeryutils as au" ] }, { "cell_type": "markdown", "id": "2dfde093", "metadata": {}, "source": [ "## 1. Basic Building Blocks\n", "The basic building blocks of the archeryutils package are the `Target` and the `Round` classes.\n", "\n", "### Target\n", "\n", "A target is defined with the following attributes:\n", "- Scoring system\n", "- Face diameter [default cm]\n", "- Distance [default m]\n", "\n", "The following scoring systems are possible:\n", "- `\"5_zone\"`,\n", "- `\"10_zone\"`,\n", "- `\"10_zone_compound\"`,\n", "- `\"10_zone_6_ring\"`,\n", "- `\"10_zone_5_ring\"`,\n", "- `\"10_zone_5_ring_compound\"`,\n", "- `\"WA_field\"`,\n", "- `\"IFAA_field\"`,\n", "- `\"IFAA_field_expert\"`,\n", "- `\"Beiter_hit_miss\"`,\n", "- `\"Worcester\"`,\n", "- `\"Worcester_2_ring\"`\n", "\n", "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.\n", "\n", "So we could define the target shot on the WA 720 70m round as:" ] }, { "cell_type": "code", "execution_count": null, "id": "061d6aee", "metadata": {}, "outputs": [], "source": [ "my720target = au.Target(\"10_zone\", 122, 70.0)" ] }, { "cell_type": "markdown", "id": "5e0711be", "metadata": {}, "source": [ "and the corresponding target shot by compounds (reduced 6-ring 80cm face at 50m) as:" ] }, { "cell_type": "code", "execution_count": null, "id": "473c3b7d", "metadata": {}, "outputs": [], "source": [ "mycompound720target = au.Target(\"10_zone_6_ring\", 80, 50.0)" ] }, { "cell_type": "markdown", "id": "24650a96-08a0-4510-8bb8-1be49d10f56e", "metadata": {}, "source": [ "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.\n", "This provides the ability to specify an imperial distance in yards and face diameter in inches etc.\n", "\n", "The target object can also take the optional boolean argument of `indoor` (default = `False`)\n", "to indicate if the round is to be shot indoors (where rules may be different).\n", "\n", "For example, a Worcester target can be defined as:" ] }, { "cell_type": "code", "execution_count": null, "id": "5aae9256-6341-44b4-ac12-5cfe0786f3fb", "metadata": {}, "outputs": [], "source": [ "myWorcesterTarget = au.Target(\n", " \"Worcester\", diameter=(16, \"inches\"), distance=(20.0, \"yards\"), indoor=True\n", ")" ] }, { "cell_type": "markdown", "id": "1125e7c9", "metadata": {}, "source": [ "and the longest target on an IFAA field round could be defined as follows:" ] }, { "cell_type": "code", "execution_count": null, "id": "d72fc06d", "metadata": {}, "outputs": [], "source": [ "myIFAATarget = au.Target(\"IFAA_field\", diameter=80, distance=(80.0, \"yards\"))" ] }, { "cell_type": "markdown", "id": "5227ae9b", "metadata": {}, "source": [ "Sometimes you might want to represent a target that isn't represented by the built in scoring systems.\n", "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\n", "and construct a target as so:" ] }, { "cell_type": "code", "execution_count": null, "id": "b64c8ced", "metadata": {}, "outputs": [], "source": [ "# Kings of archery recurve scoring target\n", "face_spec = {8: 10, 12: 8, 16: 7, 20: 6}\n", "myKingsTarget = au.Target.from_face_spec((face_spec, \"cm\"), 40, 18, indoor=True)\n", "print(myKingsTarget.scoring_system)" ] }, { "cell_type": "markdown", "id": "f44592a2", "metadata": {}, "source": [ "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:" ] }, { "cell_type": "code", "execution_count": null, "id": "c113e587", "metadata": {}, "outputs": [], "source": [ "print(my720target.face_spec)" ] }, { "cell_type": "markdown", "id": "c46e1fda", "metadata": {}, "source": [ "Target objects have the ability to return the maximum possible score from the type of face specified through the `max_score` method:" ] }, { "cell_type": "code", "execution_count": null, "id": "c1fd3bbc", "metadata": {}, "outputs": [], "source": [ "for target in [my720target, mycompound720target, myIFAATarget, myWorcesterTarget]:\n", " print(target.max_score())" ] }, { "cell_type": "markdown", "id": "fcbcd67d", "metadata": {}, "source": [ "### Pass\n", "The natural extension to the `targets.Target` class is to shoot a number of arrows at it.\n", "in _archeryutils_ this is called a \"pass\" and is defined using the `rounds.Pass` class which wraps around the `targets.Target` class.\n", "\n", "This takes a number of arrows followed by all of the arguments to target defined above.\n", "\n", "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:" ] }, { "cell_type": "code", "execution_count": null, "id": "a877c384", "metadata": {}, "outputs": [], "source": [ "my70mPass = au.Pass(36, my720target)" ] }, { "cell_type": "markdown", "id": "15415889", "metadata": {}, "source": [ "We can also bypass the Target class and directly construct our Pass using the `at_target` constructor" ] }, { "cell_type": "code", "execution_count": null, "id": "8035f27c", "metadata": {}, "outputs": [], "source": [ "my70mPass = au.Pass.at_target(36, \"10_zone\", 122, 70.0)" ] }, { "cell_type": "markdown", "id": "37b3d5a3", "metadata": {}, "source": [ "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()`):" ] }, { "cell_type": "code", "execution_count": null, "id": "dc431f7a", "metadata": { "scrolled": true }, "outputs": [], "source": [ "print(my70mPass.max_score())" ] }, { "cell_type": "markdown", "id": "31c781a2", "metadata": {}, "source": [ "### Round\n", "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.\n", "\n", "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.\n", "\n", "It may also take the following optional string arguments:\n", "- `location` - where the round is shot, e.g. 'Indoor', 'Outdoor', 'Field' etc.\n", "- `body` - The governing body the round is defined by, e.g. 'WA', 'IFAA', 'AGB', 'AA' etc.\n", "- `family` - The larger family of rounds to which this round belongs, e.g. 'wa_1440', 'wa_720', 'national' etc.\n", "\n", "So to define a WA 720 70m round we can re-use our variable `my70mPass` from above as follows:" ] }, { "cell_type": "code", "execution_count": null, "id": "56587b23", "metadata": {}, "outputs": [], "source": [ "my720Round = au.Round(\n", " \"WA 720 (70m)\",\n", " [my70mPass, my70mPass],\n", " location=\"Outdoor Target\",\n", " body=\"WA\",\n", " family=\"WA720\",\n", ")" ] }, { "cell_type": "markdown", "id": "7b9db58b", "metadata": {}, "source": [ "Again we have a method for maximum score:" ] }, { "cell_type": "code", "execution_count": null, "id": "43894dd3", "metadata": {}, "outputs": [], "source": [ "print(my720Round.max_score())" ] }, { "cell_type": "markdown", "id": "9d1e43bf", "metadata": {}, "source": [ "### Default Rounds\n", "\n", "A number of useful rounds are pre-defined and come preloaded as dictionaries that can be imported:" ] }, { "cell_type": "code", "execution_count": null, "id": "3e5974f9", "metadata": {}, "outputs": [], "source": [ "from archeryutils import load_rounds\n", "\n", "agb_outdoor = load_rounds.AGB_outdoor_imperial\n", "\n", "for round_i in agb_outdoor.values():\n", " print(round_i.name)" ] }, { "cell_type": "markdown", "id": "941fdddc", "metadata": {}, "source": [ "The individial rounds are accessible via 'dot' notation (using the alias listed in `agb_outdoor.keys()`) as follows:" ] }, { "cell_type": "code", "execution_count": null, "id": "cb287016", "metadata": {}, "outputs": [], "source": [ "agb_outdoor.york.get_info()\n", "\n", "agb_outdoor.york.max_score()" ] }, { "cell_type": "markdown", "id": "fc1e0c97", "metadata": {}, "source": [ "Possible options for round collections are:\n", "- `AGB_outdoor_imperial` - Archery GB outdoor imperial rounds\n", "- `AGB_outdoor_metric` - Archery GB outdoor metric rounds\n", "- `AGB_indoor` - Archery GB indoor rounds\n", "- `WA_outdoor` - World Archery outdoor rounds\n", "- `WA_indoor` - World Archery indoor rounds\n", "- `WA_field` - World Archery field rounds\n", "- `IFAA_field` - IFAA indoor and outdoor rounds\n", "- `AGB_VI` - Archery GB Visually Impaired rounds\n", "- `WA_VI` - World Archery Visually Impaired rounds\n", "- `custom` - custom rounds such as individual distances, 252 awards, frostbites etc." ] }, { "cell_type": "markdown", "id": "39d6d6a3", "metadata": {}, "source": [ "## 2. Handicap Schemes\n", "_archeryutils_ provides functionality for calculating various [handicaps/skill ratings](https://jackatkinson.net/post/archery_handicap/) from scores. These include both the popular Archery GB and Archery Australia schemes.\n", "\n", "To use these functionalities import the handicaps module as below." ] }, { "cell_type": "code", "execution_count": null, "id": "de89712b", "metadata": {}, "outputs": [], "source": [ "from archeryutils import handicaps as hc" ] }, { "cell_type": "markdown", "id": "0ab115fe", "metadata": {}, "source": [ "### Score from handicap\n", "\n", "It is then possible to use the `score_for_round` function to calculate score on any `rounds.Round` for a given handicap/skill rating.\n", "\n", "This requires a round, handicap/skill rating, scheme, and set of handicap parameters.\n", "\n", "Possible options for the scheme are:\n", "- `\"AGB\"` - The 2023 Archery GB handicap system developed by Jack Atkinson\n", "- `\"AGBold\"` - The old Archery GB handicap system developed by David Lane\n", "- `\"AA2\"` - The 2014 Archery Australia Skill rating system developed by Jim Park\n", "- `\"AA\"` - The old Archery Australia skill rating system developed by Jim Park\n", "\n", "For example, to calculate the score on a York round for a handicap of 38 using the 2023 Archery GB scheme we run:" ] }, { "cell_type": "code", "execution_count": null, "id": "c627dccc", "metadata": {}, "outputs": [], "source": [ "score_from_hc = hc.score_for_round(\n", " 38,\n", " agb_outdoor.york,\n", " \"AGB\",\n", ")\n", "\n", "print(f\"A handicap of 38 on a York is a score of {score_from_hc}.\")" ] }, { "cell_type": "markdown", "id": "77a766f1", "metadata": {}, "source": [ "Note that it is possible to obtain scores for decimal handicaps:" ] }, { "cell_type": "code", "execution_count": null, "id": "6d4de971", "metadata": {}, "outputs": [], "source": [ "score_from_hc = hc.score_for_round(\n", " 38.25,\n", " agb_outdoor.york,\n", " \"AGB\",\n", ")\n", "\n", "print(f\"A handicap of 38.25 on a York is a score of {score_from_hc}.\")" ] }, { "cell_type": "markdown", "id": "ee060506", "metadata": {}, "source": [ "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`:" ] }, { "cell_type": "code", "execution_count": null, "id": "dbde138c", "metadata": {}, "outputs": [], "source": [ "score_from_hc = hc.score_for_round(\n", " 38.25,\n", " agb_outdoor.york,\n", " \"AGB\",\n", " rounded_score=False,\n", ")\n", "\n", "print(f\"A handicap of 38.25 on a York is a decimal score of {score_from_hc}.\")" ] }, { "cell_type": "markdown", "id": "ee860e8e-a23b-4a06-88a7-04ca209aa092", "metadata": {}, "source": [ "It is also possible to get the predicted score for each pass in a round using the `score_for_passes` function:" ] }, { "cell_type": "code", "execution_count": null, "id": "b8f743c8-9199-48ea-a0a5-bf309f1a887b", "metadata": {}, "outputs": [], "source": [ "pass_scores = hc.score_for_passes(\n", " 38,\n", " agb_outdoor.york,\n", " \"AGB\",\n", ")\n", "\n", "print(f\"A handicap of 38 on a York gives pass scores of {pass_scores}.\")" ] }, { "cell_type": "markdown", "id": "c43a0f46", "metadata": {}, "source": [ "### Handicap from Score\n", "\n", "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.\n", "\n", "To perform this operation use the `handicap_from_score()` function which takes a score, round, and handicap scheme.\n", "By default it returns the decimal handicap corresponding to the provided score exactly.\n", "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.\n", "\n", "\n", "For example, to get the 2023 Archery GB handicap given by a score of 950 on a York round:" ] }, { "cell_type": "code", "execution_count": null, "id": "47e4ee24", "metadata": {}, "outputs": [], "source": [ "hc_from_score = hc.handicap_from_score(\n", " 950,\n", " agb_outdoor.york,\n", " \"AGB\",\n", ")\n", "print(f\"A score of 950 on a York is a continuous handicap of {hc_from_score}.\")\n", "\n", "hc_from_score = hc.handicap_from_score(\n", " 950,\n", " agb_outdoor.york,\n", " \"AGB\",\n", " int_prec=True,\n", ")\n", "print(f\"A score of 950 on a York is a discrete handicap of {hc_from_score}.\")" ] }, { "cell_type": "markdown", "id": "5107fa30", "metadata": {}, "source": [ "### Handicap Tables\n", "\n", "A further functionality of the code is the ability to generate \"Handicap Tables\" for arbitrary lists of rounds and handicaps.\n", "\n", "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.\n", "The `HandicapTable` object stores the table, but it can be printed to the output using `print()`:" ] }, { "cell_type": "code", "execution_count": null, "id": "bce21c56", "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "\n", "handicaps = np.arange(0.0, 151.0, 1.0)\n", "rounds = [\n", " agb_outdoor.york,\n", " agb_outdoor.hereford,\n", " agb_outdoor.albion,\n", " agb_outdoor.windsor,\n", "]\n", "# The following would allow handicap tables for an entire group of rounds to be generated:\n", "# rounds = list(load_rounds.AGB_outdoor_imperial.values())\n", "\n", "agb_handicap_table = hc.HandicapTable(\n", " \"AGB\",\n", " handicaps,\n", " rounds,\n", ")\n", "\n", "print(agb_handicap_table)" ] }, { "cell_type": "markdown", "id": "59749871", "metadata": {}, "source": [ "The following optional arguments can be passed to `HandicapTable`:\n", "- `rounded_scores` - if `False`, display decimal scores instead of rounding to discrete values as appropriate for the scheme\n", "- `clean_gaps` - if `True`, duplicate scores will be displayed for the first occurrence only\n", "- `int_prec` - if `True` then values will be printed as integers rather than decimals\n", "\n", "It is also possible to pass an array of non-integer handicaps.\n", "\n", "The effect of these variables can be examined by changing their values in the following:" ] }, { "cell_type": "code", "execution_count": null, "id": "03084231", "metadata": {}, "outputs": [], "source": [ "handicaps = np.arange(0.0, 51.0, 0.5)\n", "\n", "agb_decimal_table = hc.HandicapTable(\n", " \"AGB\",\n", " handicaps,\n", " rounds,\n", " rounded_scores=False,\n", " clean_gaps=False,\n", " int_prec=False,\n", ")\n", "\n", "print(agb_decimal_table)" ] }, { "cell_type": "markdown", "id": "99076c03-9b22-43c1-8445-ad0e2b55f689", "metadata": {}, "source": [ "The `HandicapTable` class also contains two methods for saving the table to file:\n", "- `to_file()` - which will save the nicely formatted table as seen from `print()` as an ascii file with the provided filename\n", "- `to_csv()` - saves the table in a comma-separated-variable format\n" ] }, { "cell_type": "code", "execution_count": null, "id": "caa3a235-6a2c-425d-bbcc-91cdb6345b94", "metadata": {}, "outputs": [], "source": [ "agb_handicap_table.to_file(\"my_saved_table.txt\")\n", "\n", "agb_handicap_table.to_csv(\"my_saved_csv_table.csv\")" ] }, { "cell_type": "markdown", "id": "d63355b1", "metadata": {}, "source": [ "## 3. Classifications\n", "\n", "As well as handicap functionalities _archeryutils_ fontains functionalities for calculating Archery GB classifications.\n", "These are accessed by importing the `classifications` module:" ] }, { "cell_type": "code", "execution_count": null, "id": "3492598b", "metadata": {}, "outputs": [], "source": [ "from archeryutils import classifications as class_func" ] }, { "cell_type": "markdown", "id": "86494fa6", "metadata": {}, "source": [ "### Classification from score\n", "\n", "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`).\n", "\n", "This takes following arguments:\n", "- a score\n", "- a string of a round alias (see [Default Rounds](#Default-Rounds)).\n", "- a string of bowstyle (`\"compound\"`, `\"recurve\"`, `\"longbow\"`, `\"barebow\"`, `\"traditional\"`, `\"flatbow\"`)\n", "- a string of gender under Archery GB (`\"male\"` or `\"female\"`)\n", "- an Archery GB age group (`\"50+\"`, `\"adult\"`, `\"under 21\"`, `\"under 18\"`, etc.)\n", "\n", "and returns a string corresponding to the classification it obtains.\n", "\n", "These can be investigated in the following code snippet which uses a number of examples:" ] }, { "cell_type": "code", "execution_count": null, "id": "24eee19c", "metadata": {}, "outputs": [], "source": [ "# AGB Outdoor\n", "class_from_score = class_func.calculate_agb_outdoor_classification(\n", " 965,\n", " \"hereford\",\n", " \"recurve\",\n", " \"male\",\n", " \"50+\",\n", ")\n", "print(\n", " f\"A score of 965 on a Hereford is class {class_from_score} for a 50+ male recurve.\"\n", ")" ] }, { "cell_type": "code", "execution_count": null, "id": "c3b7d25c", "metadata": {}, "outputs": [], "source": [ "# AGB Indoor\n", "class_from_score = class_func.calculate_agb_indoor_classification(\n", " 562,\n", " \"wa18\",\n", " \"compound\",\n", " \"female\",\n", " \"adult\",\n", ")\n", "print(\n", " f\"A score of 562 on a WA 18 is class {class_from_score} for adult female compound.\"\n", ")" ] }, { "cell_type": "code", "execution_count": null, "id": "d221a62c", "metadata": {}, "outputs": [], "source": [ "# AGB Field\n", "class_from_score = class_func.calculate_agb_field_classification(\n", " 168,\n", " \"wa_field_24_blue_unmarked\",\n", " \"traditional\",\n", " \"male\",\n", " \"under 18\",\n", ")\n", "print(\n", " f\"A score of 168 on a WA Unmarked 24 is class {class_from_score} for an under 18 male traditional.\"\n", ")" ] }, { "cell_type": "markdown", "id": "f6c9f122", "metadata": {}, "source": [ "### Classification scores\n", "\n", "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.\n", "\n", "These take a round alias and categories as strings above, and return a list of scores required for each classification in descending order.\n", "\n", "Where a classification is not available from a particular round a fill value of -9999 is returned." ] }, { "cell_type": "code", "execution_count": null, "id": "da99fc4b", "metadata": {}, "outputs": [], "source": [ "class_scores = class_func.agb_outdoor_classification_scores(\n", " \"hereford\",\n", " \"recurve\",\n", " \"male\",\n", " \"adult\",\n", ")\n", "print(class_scores)" ] }, { "cell_type": "code", "execution_count": null, "id": "9e1bad8e", "metadata": {}, "outputs": [], "source": [ "class_scores = class_func.agb_indoor_classification_scores(\n", " \"portsmouth\",\n", " \"compound\",\n", " \"female\",\n", " \"adult\",\n", ")\n", "print(class_scores)" ] }, { "cell_type": "code", "execution_count": null, "id": "5220a9b0", "metadata": {}, "outputs": [], "source": [ "class_scores = class_func.agb_field_classification_scores(\n", " \"wa_field_24_blue_marked\",\n", " \"flatbow\",\n", " \"female\",\n", " \"under 18\",\n", ")\n", "print(class_scores)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.12.2" } }, "nbformat": 4, "nbformat_minor": 5 }