From dba0a1e874b357903683d4a56d74c32927027b40 Mon Sep 17 00:00:00 2001 From: Tom Hall Date: Tue, 17 Dec 2024 16:18:27 +0000 Subject: [PATCH] Replace inner loops on classification data generation with vectorised operations draft 1 replace inner loop on agb_outdoor classification data generation draft 2 replace internal loops on agb 2023 outdoors incomplete, old implementation still present draft 3 complete replacement of inner loops draft 4 vectorise handicap caluclation for field and indoor agb2023 classifications draft 5 refactor distance calculations on agb_field to inside dedicate distance function --- .../agb_field_classifications.py | 39 ++++++----- .../agb_indoor_classifications.py | 16 ++--- .../agb_outdoor_classifications.py | 64 +++++++------------ 3 files changed, 47 insertions(+), 72 deletions(-) diff --git a/archeryutils/classifications/agb_field_classifications.py b/archeryutils/classifications/agb_field_classifications.py index e7706cc..c633464 100644 --- a/archeryutils/classifications/agb_field_classifications.py +++ b/archeryutils/classifications/agb_field_classifications.py @@ -77,7 +77,6 @@ def _make_agb_field_classification_dict() -> dict[str, GroupData]: agb_classes_field = agb_classes_info_field["classes"] agb_classes_field_long = agb_classes_info_field["classes_long"] - # Generate dict of classifications # loop over all bowstyles, genders, ages classification_dict = {} @@ -90,7 +89,7 @@ def _make_agb_field_classification_dict() -> dict[str, GroupData]: # Get max dists for category from json file data # Use metres as corresponding yards >= metric - dists = _assign_dists(bowstyle["bowstyle"], age) + min_dists, max_distance = _assign_dists(bowstyle["bowstyle"], age) # set step from datum based on age and gender steps required delta_hc_age_gender = cls_funcs.get_age_gender_step( @@ -100,27 +99,18 @@ def _make_agb_field_classification_dict() -> dict[str, GroupData]: bowstyle["genderStep_field"], ) - classifications_count = len(agb_classes_field) - - class_hc = np.empty(classifications_count) - - min_dists = np.empty(classifications_count) - min_dists[0:6] = dists[0] - min_dists[6:9] = [max(dists[0] - 10 * i, 30) for i in range(1, 4)] - - for i in range(classifications_count): - # Assign handicap for this classification - class_hc[i] = ( - bowstyle["datum_field"] - + delta_hc_age_gender - + (i - 2) * bowstyle["classStep_field"] - ) + # set handicap threshold values for all classifications in the category + class_hc = ( + bowstyle["datum_field"] + + delta_hc_age_gender + + (np.arange(len(agb_classes_field)) - 2) * bowstyle["classStep_field"] + ) groupdata: GroupData = { "classes": agb_classes_field, "classes_long": agb_classes_field_long, "class_HC": class_hc, - "max_distance": dists[1], + "max_distance": max_distance, "min_dists": min_dists, } @@ -166,8 +156,17 @@ def _assign_dists( # U15 All Blue, R/C Red, Others White # U12 R/C/CL Red, All Blue, All White, if bowstyle.lower().replace(" ", "") in ("compound", "recurve", "compoundlimited"): - return age["red"] - return age["blue"] + min_d, max_d = age["red"] + else: + min_d, max_d = age["blue"] + + n_classes: int = 9 # [EMB, GMB, MB, B1, B2, B3, A1, A2, A3] + + min_dists = np.empty(n_classes) + min_dists[0:6] = min_d + min_dists[6:9] = np.maximum(min_d - 10 * np.arange(1, 4), 30) + + return min_dists, max_d agb_field_classifications = _make_agb_field_classification_dict() diff --git a/archeryutils/classifications/agb_indoor_classifications.py b/archeryutils/classifications/agb_indoor_classifications.py index 3abd7a7..db247da 100644 --- a/archeryutils/classifications/agb_indoor_classifications.py +++ b/archeryutils/classifications/agb_indoor_classifications.py @@ -93,16 +93,12 @@ def _make_agb_indoor_classification_dict() -> dict[str, GroupData]: bowstyle["genderStep_in"], ) - classifications_count = len(agb_classes_in) - - class_hc = np.empty(classifications_count) - for i in range(classifications_count): - # Assign handicap for this classification - class_hc[i] = ( - bowstyle["datum_in"] - + delta_hc_age_gender - + (i - 1) * bowstyle["classStep_in"] - ) + # set handicap threshold values for all classifications in the category + class_hc = ( + bowstyle["datum_in"] + + delta_hc_age_gender + + (np.arange(len(agb_classes_in)) - 1) * bowstyle["classStep_in"] + ) groupdata: GroupData = { "classes": agb_classes_in, diff --git a/archeryutils/classifications/agb_outdoor_classifications.py b/archeryutils/classifications/agb_outdoor_classifications.py index 423d907..41d38a3 100644 --- a/archeryutils/classifications/agb_outdoor_classifications.py +++ b/archeryutils/classifications/agb_outdoor_classifications.py @@ -97,26 +97,18 @@ def _make_agb_outdoor_classification_dict() -> dict[str, GroupData]: bowstyle["ageStep_out"], bowstyle["genderStep_out"], ) - classifications_count = len(agb_classes_out) - - class_hc = np.empty(classifications_count) - min_dists = np.empty(classifications_count) - - for i in range(classifications_count): - # Assign handicap for this classification - class_hc[i] = ( - bowstyle["datum_out"] - + delta_hc_age_gender - + (i - 2) * bowstyle["classStep_out"] - ) - - # Get minimum distance that must be shot for this classification - min_dists[i] = _assign_min_dist( - n_class=i, - gender=gender, - age_group=age["age_group"], - max_dists=max_dist, - ) + + # set handicap threshold values for all classifications in the category + class_hc = ( + bowstyle["datum_out"] + + delta_hc_age_gender + + (np.arange(len(agb_classes_out)) - 2) * bowstyle["classStep_out"] + ) + + # get minimum distances to be shot for all classifications in the category + min_dists = _assign_min_dist( + gender=gender, age_group=age["age_group"], max_dists=max_dist + ) # Assign prestige rounds for the category prestige_rounds = _assign_outdoor_prestige( @@ -141,11 +133,10 @@ def _make_agb_outdoor_classification_dict() -> dict[str, GroupData]: def _assign_min_dist( - n_class: int, gender: str, age_group: str, max_dists: list[int], -) -> int: +) -> npt.NDArray[int]: """ Assign appropriate minimum distance required for a category and classification. @@ -153,8 +144,6 @@ def _assign_min_dist( Parameters ---------- - n_class : int - integer corresponding to classification [0=EMB, 8=A3] gender : str string defining gender age_group : str, @@ -164,8 +153,8 @@ def _assign_min_dist( Returns ------- - min_dist : int - minimum distance [m] required for this classification + min_dists : array of int + minimum distance [m] required for this category References ---------- @@ -181,20 +170,12 @@ def _assign_min_dist( # List of maximum distances for use in assigning maximum distance [metres] # Use metres because corresponding yards distances are >= metric ones dists = [90, 70, 60, 50, 40, 30, 20, 15] - - # Number of MB categories (distance restrictions superceded by prestige rounds.) - n_mb: int = 3 + n_classes: int = 9 # [EMB, GMB, MB, B1, B2, B3, A1, A2, A3] max_dist_index = dists.index(np.min(max_dists)) - # B1 and above - if n_class <= n_mb: - # All MB and B1 require max distance for everyone: - return dists[max_dist_index] - - # Below B1 # Age group trickery: - # U16 males and above step down for B2 and beyond + # U16 males and above step down for B2 and less if gender.lower() in ("male") and age_group.lower().replace(" ", "") in ( "adult", "50+", @@ -202,14 +183,13 @@ def _assign_min_dist( "under18", "under16", ): - return dists[max_dist_index + (n_class - n_mb)] + idxs = np.array([0, 0, 0, 0, 1, 2, 3, 4, 5]) # All other categories require max dist for B1 and B2 then step down - try: - return dists[max_dist_index + (n_class - n_mb) - 1] - except IndexError: - # Distances stack at the bottom end as we can't go below 15m - return dists[-1] + else: + idxs = np.array([0, 0, 0, 0, 0, 1, 2, 3, 4]) + + return np.take(dists, idxs + max_dist_index, mode="clip") def _assign_outdoor_prestige(